Skip to content

Commit

Permalink
Allow install/uninstall plugins via admin
Browse files Browse the repository at this point in the history
  • Loading branch information
huacnlee committed Feb 3, 2020
1 parent 8507c00 commit 17fec5d
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 68 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ spec/examples.txt
.ruby-version
/storage/*
/plugins/*
!/plugins/test/
app.local.env
*.tmp
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ kbd {
font-size: 18px;
line-height: 100%;
display: flex;
align-items: center;

.card-subtitle {
margin-top: 10px;
Expand Down
38 changes: 38 additions & 0 deletions app/controllers/admin/plugins_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module Admin
class PluginsController < Admin::ApplicationController
before_action :set_plugin, only: %i[show destroy]
def index
end

def show
end

def create
tmp = params[:file].tempfile
FileUtils.move tmp.path, Rails.root.join("plugins")
basename = File.basename(tmp.path)
zip_filename = Rails.root.join("plugins", basename)
`cd plugins; unzip -o #{zip_filename}`
Homeland.reboot
redirect_to admin_plugins_path, notice: "插件安装成功,如列表没有更新,请再次刷新页面。"
ensure
FileUtils.rm_f(zip_filename)
end

def destroy
if @plugin.deleteable?
FileUtils.rm_rf(@plugin.source_path)
end
Homeland.reboot
redirect_to admin_plugins_path, notice: "卸载成功,如列表没有更新,请再次刷新页面"
end

private

def set_plugin
@plugin = Homeland.find_plugin(params[:id])
end
end
end
1 change: 1 addition & 0 deletions app/views/admin/dashboards/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<div class="toolbar">
<div class="btn-group">
<a href="/admin/site_configs" class="btn btn-default"><i class="fa fa-cogs"></i> <%= t("admin.site_configs.settings") %></a>
<a href="/admin/plugins" class="btn btn-default"><i class="fa fa-plug"></i> 插件</a>
<a href="/admin/applications" class="btn btn-default"><i class="fa fa-cubes"></i> 三方应用</a>
<a href="/sidekiq" class="btn btn-default" target="_blank"><i class="fa fa-tasks"></i> 异步任务...</a>
<a href="/pghero" class="btn btn-default" target="_blank"><i class="fa fa-database"></i> 数据库状态...</a>
Expand Down
17 changes: 17 additions & 0 deletions app/views/admin/plugins/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="card-title">
已启用插件
<div class="actions">
<%= link_to "安装插件...", new_admin_plugin_path, class: "btn btn-primary btn-sm" %>
<%= link_to "查找插件", "https://github.com/topics/homeland-plugin", class: "btn btn-default btn-sm", target: "_blank" %>
</div>
</div>
<ul class="list-group">
<% Homeland.plugins.each do |plugin| %>
<li class="list-group-item clearfix">
<%= icon_tag("plug") %><strong><%= link_to plugin.display_name, admin_plugin_path(plugin.name) %></strong> <span class="text-gray"><%= plugin.version %></span>
<% if plugin.deleteable? %>
<%= link_to "卸载", admin_plugin_path(plugin.name), data: { method: :delete, confirm: "确定要卸载这个插件吗?", confirm_with: "卸载中..." }, class: "pull-right" %>
<% end %>
</li>
<% end %>
</ul>
11 changes: 11 additions & 0 deletions app/views/admin/plugins/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="card-title">安装插件</div>
<%= form_for User.new, url: admin_plugins_path, method: :post, html: { enctype: "multipart/form-data" } do |f| %>
<div class="form-group">
<label class="control-label">选择插件包</label>
<input name="file" type="file" accept="*.zip" class="form-control" />
<div class="form-text">请选择 Homeland 的插件 zip 包,并安装。请勿安装非 Homeland 插件,否则会导致无法预期的问题!</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">安装</button>
</div>
<% end %>
17 changes: 17 additions & 0 deletions app/views/admin/plugins/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="card-title">
<div><%= icon_tag("plug") %> <%= @plugin.display_name %></div>
<div class="counter"><%= @plugin.version %></div>
<div class="actions">
<%= link_to "返回", admin_plugins_path, class: "btn btn-default btn-sm" %>
</div>
</div>
<div class="form">
<% if @plugin.url %>
<div class="form-group">
<%= link_to @plugin.url %>
</div>
<% end %>
<p>
<%= @plugin.description %>
</p>
</div>
127 changes: 61 additions & 66 deletions app/views/doorkeeper/applications/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,101 +1,96 @@
<div class="row">
<%= render "/settings/menu" %>

<div class="col-md-10">
<div class="card">
<div class="card-body">
<div class="card-title clearfix">
管理的应用列表
<%= link_to "注册新应用", new_oauth_application_path, class: 'btn btn-primary float-right' %>
</div>

<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th><%= t('.name') %></th>
<th>Client Id</th>
<th>Level</th>
<th>用户量</th>
<th></th>
</tr>
</thead>
<tbody>
<% @applications.each do |app| %>
<tr>
<td><%= link_to app.name, oauth_application_path(app) %></td>
<td><%= app.uid %></td>
<td><%= app.level %></td>
<td><%= app.access_tokens.count %></td>
<td class="opts">
<%= link_to icon_tag('pencil'), edit_oauth_application_path(app)%>
<%= link_to icon_tag('trash'), oauth_application_path(app), data: { method: :delete, confirm: "确定要删除么?" }%>
</td>
<th><%= t('.name') %></th>
<th>Client Id</th>
<th>Level</th>
<th>用户量</th>
<th></th>
</tr>
<% end %>
</thead>
<tbody>
<% @applications.each do |app| %>
<tr>
<td><%= link_to app.name, oauth_application_path(app) %></td>
<td><%= app.uid %></td>
<td><%= app.level %></td>
<td><%= app.access_tokens.count %></td>
<td class="opts">
<%= link_to icon_tag('pencil'), edit_oauth_application_path(app)%>
<%= link_to icon_tag('trash'), oauth_application_path(app), data: { method: :delete, confirm: "确定要删除么?" }%>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>

<div class="card">
<div class="card-body">
<div class="card-title clearfix">
<div>已授权的应用</div>
<div class="card-subtitle">下面列表是已经认证的应用,它们可以访问你的帐号。</div>
</div>
<p>下面列表是已经认证的应用,它们可以访问你的帐号。</p>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th><%= t('doorkeeper.authorized_applications.index.application') %></th>
<th><%= t('doorkeeper.authorized_applications.index.created_at') %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @authorized_applications.each do |app| %>
<tr>
<td><%= app.name %></td>
<td><%= app.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %></td>
<td>
<%= link_to "注销", oauth_authorized_application_path(app), class: "btn btn-default btn-sm", data: { confirm: "确定要注销么?", method: :delete } %>
</td>
<th><%= t('doorkeeper.authorized_applications.index.application') %></th>
<th><%= t('doorkeeper.authorized_applications.index.created_at') %></th>
<th></th>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>

<% if @devices.length > 0 %>
<div class="card">
<div class="card-header clearfix">
我的设备信息
</div>

<div class="card-body">
<p>下面列表是已经连接上的设备,它们将会收到 Push 通知。</p>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th><%= t('activerecord.attributes.device.platform') %></th>
<th><%= t('activerecord.attributes.device.token') %></th>
<th><%= t('activerecord.attributes.device.alive') %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @devices.each do |device| %>
<tr>
<td><%= device.platform_name %></td>
<td><%= device.token %></td>
<td><%= device.alive? %></td>
<td><%= link_to '删除', device, class: "btn btn-default btn-sm", data: { confirm: '确定要删除么?', method: :delete } %></td>
</tr>
<% end %>
<% @authorized_applications.each do |app| %>
<tr>
<td><%= app.name %></td>
<td><%= app.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %></td>
<td>
<%= link_to "注销", oauth_authorized_application_path(app), class: "btn btn-default btn-sm", data: { confirm: "确定要注销么?", method: :delete } %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<% if @devices.length > 0 %>
<div class="card">
<div class="card-header clearfix">
我的设备信息
</div>
<div class="card-body">
<p>下面列表是已经连接上的设备,它们将会收到 Push 通知。</p>
<table class="table table-bordered table-striped table-condensed">
<thead>
<tr>
<th><%= t('activerecord.attributes.device.platform') %></th>
<th><%= t('activerecord.attributes.device.token') %></th>
<th><%= t('activerecord.attributes.device.alive') %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @devices.each do |device| %>
<tr>
<td><%= device.platform_name %></td>
<td><%= device.token %></td>
<td><%= device.alive? %></td>
<td><%= link_to '删除', device, class: "btn btn-default btn-sm", data: { confirm: '确定要删除么?', method: :delete } %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<% end %>
</div>
</div>
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
resources :locations
resources :applications
resources :stats
resources :plugins
end

get "api", to: "home#api", as: "api"
Expand Down
5 changes: 5 additions & 0 deletions lib/homeland.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ def register_plugin
yield plugin
@plugins << plugin
@sorted_plugins = nil
plugin.version ||= "0.0.0"
plugin
end

def find_plugin(name)
self.plugins.find { |p| p.name == name.strip }
end

def boot
ActiveSupport.on_load(:after_initialize) do
puts "=> Booting Homeland" unless Rails.env.test?
Expand Down
11 changes: 11 additions & 0 deletions lib/homeland/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Plugin
# Display name of plugin
attr_accessor :display_name

# Project url
attr_accessor :url

# set true if plugin link wants list in top navbar
attr_accessor :navbar_link

Expand All @@ -32,6 +35,14 @@ class Plugin
# add RSpec test path
attr_accessor :spec_path

def source_path
@source_path ||= Rails.root.join("plugins", self.name)
end

def deleteable?
File.exists?(source_path)
end

class << self
# Booting Homeland plugins
def boot
Expand Down
8 changes: 8 additions & 0 deletions plugins/test/boot.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

Homeland.register_plugin do |s|
s.name = "test"
s.display_name = "Test plugin"
s.description = "Plugin for Homeland development test."
s.url = "https://github.com/ruby-china/homeland/tree/master/plugins/test"
end
3 changes: 3 additions & 0 deletions plugins/test/locales/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"zh-CN":
test:
this_is_plugin_test_key: "This is Plugin i18n test key"
8 changes: 7 additions & 1 deletion test/lib/homeland_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@ class HomelandTest < ActiveSupport::TestCase
test "boot_at" do
assert_kind_of Time, Homeland.boot_at
end
end

test "find_plugin" do
plugin = Homeland.find_plugin("test")
assert_kind_of Homeland::Plugin, plugin
assert_equal "test", plugin.name
end
end
1 change: 1 addition & 0 deletions test/plugins/plugin_load_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class PluginLoadTest < ActiveSupport::TestCase
assert_kind_of Homeland::Plugin, plugin
assert_equal "Test plugin", plugin.display_name
assert_equal "Plugin for Homeland development test.", plugin.description
assert_equal "0.0.0", plugin.version
end

test "I18n load paths" do
Expand Down
2 changes: 1 addition & 1 deletion test/plugins/plugin_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Homeland::PluginTest < ActiveSupport::TestCase
assert_equal true, @plugin.user_menu_link
assert_equal true, @plugin.admin_navbar_link

assert_nil @plugin1.version
assert_equal "0.0.0", @plugin1.version
assert_equal "dar", @plugin1.name
assert_equal "Dar bar", @plugin1.display_name
assert_equal false, @plugin1.navbar_link
Expand Down

0 comments on commit 17fec5d

Please sign in to comment.