zen_admin 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +76 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets/zen_admin/application.css +15 -0
- data/app/controllers/zen_admin/application_controller.rb +27 -0
- data/app/controllers/zen_admin/dashboard_controller.rb +9 -0
- data/app/controllers/zen_admin/sessions_controller.rb +40 -0
- data/app/controllers/zen_admin/ui/resources_controller.rb +331 -0
- data/app/helpers/zen_admin/application_helper.rb +80 -0
- data/app/javascript/controllers/application.js +9 -0
- data/app/javascript/controllers/bulk_actions_controller.js +55 -0
- data/app/javascript/controllers/clipboard_controller.js +27 -0
- data/app/javascript/controllers/index.js +5 -0
- data/app/javascript/controllers/layout_controller.js +48 -0
- data/app/javascript/controllers/menu_group_controller.js +40 -0
- data/app/javascript/controllers/modal_controller.js +31 -0
- data/app/javascript/controllers/toast_controller.js +14 -0
- data/app/javascript/controllers/tom_select_controller.js +35 -0
- data/app/javascript/zen_admin/application.js +12 -0
- data/app/jobs/zen_admin/application_job.rb +4 -0
- data/app/mailers/zen_admin/application_mailer.rb +6 -0
- data/app/models/zen_admin/application_record.rb +5 -0
- data/app/models/zen_admin/asset.rb +43 -0
- data/app/models/zen_admin/audit_log.rb +116 -0
- data/app/models/zen_admin/permission.rb +73 -0
- data/app/models/zen_admin/role.rb +45 -0
- data/app/models/zen_admin/trash_item.rb +98 -0
- data/app/models/zen_admin/user.rb +37 -0
- data/app/policies/zen_admin/application_policy.rb +53 -0
- data/app/policies/zen_admin/resource_policy.rb +48 -0
- data/app/views/layouts/zen_admin/_flash.html.erb +9 -0
- data/app/views/layouts/zen_admin/_sidebar.html.erb +115 -0
- data/app/views/layouts/zen_admin/application.html.erb +98 -0
- data/app/views/zen_admin/builtin/_copy_button.html.erb +9 -0
- data/app/views/zen_admin/builtin/_file_link.html.erb +22 -0
- data/app/views/zen_admin/dashboard/index.html.erb +27 -0
- data/app/views/zen_admin/sessions/new.html.erb +47 -0
- data/app/views/zen_admin/ui/resources/_form.html.erb +226 -0
- data/app/views/zen_admin/ui/resources/_row.html.erb +170 -0
- data/app/views/zen_admin/ui/resources/create.turbo_stream.erb +2 -0
- data/app/views/zen_admin/ui/resources/destroy.turbo_stream.erb +2 -0
- data/app/views/zen_admin/ui/resources/edit.html.erb +11 -0
- data/app/views/zen_admin/ui/resources/index.html.erb +285 -0
- data/app/views/zen_admin/ui/resources/new.html.erb +11 -0
- data/app/views/zen_admin/ui/resources/show.html.erb +133 -0
- data/app/views/zen_admin/ui/resources/update.turbo_stream.erb +2 -0
- data/config/importmap.rb +10 -0
- data/config/locales/en.yml +107 -0
- data/config/locales/zh-CN.yml +110 -0
- data/config/routes.rb +24 -0
- data/lib/generators/zen_admin/admin_user/admin_user_generator.rb +55 -0
- data/lib/generators/zen_admin/install/install_generator.rb +50 -0
- data/lib/generators/zen_admin/install/templates/asset.rb +46 -0
- data/lib/generators/zen_admin/install/templates/create_zen_admin_assets.rb.erb +9 -0
- data/lib/generators/zen_admin/install/templates/create_zen_admin_audit_logs.rb.erb +18 -0
- data/lib/generators/zen_admin/install/templates/create_zen_admin_rbac.rb.erb +57 -0
- data/lib/generators/zen_admin/install/templates/create_zen_admin_trash_items.rb.erb +13 -0
- data/lib/generators/zen_admin/install/templates/zen_admin.rb +17 -0
- data/lib/generators/zen_admin/install/templates/zh-CN.yml +15 -0
- data/lib/generators/zen_admin/model/model_generator.rb +65 -0
- data/lib/generators/zen_admin/model/templates/zen_admin_config.rb.erb +80 -0
- data/lib/generators/zen_admin/rbac_install/rbac_install_generator.rb +52 -0
- data/lib/generators/zen_admin/rbac_install/templates/create_zen_admin_rbac.rb.erb +42 -0
- data/lib/generators/zen_admin/rbac_install/templates/seeds.rb.erb +13 -0
- data/lib/tasks/zen_admin_tasks.rake +4 -0
- data/lib/zen_admin/authenticatable.rb +44 -0
- data/lib/zen_admin/builtin.rb +77 -0
- data/lib/zen_admin/configuration.rb +17 -0
- data/lib/zen_admin/core/field.rb +34 -0
- data/lib/zen_admin/core/filter.rb +16 -0
- data/lib/zen_admin/core/resource.rb +175 -0
- data/lib/zen_admin/core.rb +3 -0
- data/lib/zen_admin/engine.rb +67 -0
- data/lib/zen_admin/link_registerable.rb +11 -0
- data/lib/zen_admin/registerable.rb +17 -0
- data/lib/zen_admin/registry.rb +83 -0
- data/lib/zen_admin/schema/serializer.rb +15 -0
- data/lib/zen_admin/schema.rb +1 -0
- data/lib/zen_admin/version.rb +3 -0
- data/lib/zen_admin.rb +51 -0
- metadata +233 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module ZenAdmin
|
|
2
|
+
class ApplicationPolicy
|
|
3
|
+
attr_reader :user, :record
|
|
4
|
+
|
|
5
|
+
def initialize(user, record)
|
|
6
|
+
@user = user
|
|
7
|
+
@record = record
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def index?
|
|
11
|
+
false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def show?
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create?
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def new?
|
|
23
|
+
create?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update?
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def edit?
|
|
31
|
+
update?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def destroy?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Scope
|
|
39
|
+
def initialize(user, scope)
|
|
40
|
+
@user = user
|
|
41
|
+
@scope = scope
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def resolve
|
|
45
|
+
scope.all
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :user, :scope
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module ZenAdmin
|
|
2
|
+
class ResourcePolicy < ApplicationPolicy
|
|
3
|
+
def index?
|
|
4
|
+
check_permission("index")
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def show?
|
|
8
|
+
check_permission("show")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create?
|
|
12
|
+
check_permission("create")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def new?
|
|
16
|
+
create?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def update?
|
|
20
|
+
check_permission("update")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def edit?
|
|
24
|
+
update?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def destroy?
|
|
28
|
+
check_permission("destroy")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def check_permission(action)
|
|
34
|
+
return true if user.respond_to?(:super_admin?) && user.super_admin?
|
|
35
|
+
|
|
36
|
+
# Handle both class (index) and instance (member)
|
|
37
|
+
klass = record.is_a?(Class) ? record : record.class
|
|
38
|
+
resource_name = klass.name.underscore.pluralize.gsub('/', '_')
|
|
39
|
+
|
|
40
|
+
permission = "#{resource_name}:#{action}"
|
|
41
|
+
result = user.can?(permission)
|
|
42
|
+
|
|
43
|
+
Rails.logger.debug "[ZenAdmin] Auth Check: User=#{user.username} | Permission=#{permission} | Result=#{result}"
|
|
44
|
+
|
|
45
|
+
result
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% flash.each do |key, value| %>
|
|
2
|
+
<% alert_class = key.to_s == 'notice' ? 'success' : 'danger' %>
|
|
3
|
+
<% icon_class = key.to_s == 'notice' ? 'check' : 'ban' %>
|
|
4
|
+
<div class="alert alert-<%= alert_class %> alert-dismissible" data-controller="toast" data-toast-target="element">
|
|
5
|
+
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
|
6
|
+
<h5><i class="icon fas fa-<%= icon_class %>"></i> <%= key.to_s.humanize %>!</h5>
|
|
7
|
+
<%= value %>
|
|
8
|
+
</div>
|
|
9
|
+
<% end %>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<aside class="main-sidebar sidebar-dark-primary elevation-4">
|
|
2
|
+
<!-- Brand Logo -->
|
|
3
|
+
<a href="<%= zen_admin.root_path %>" class="brand-link">
|
|
4
|
+
<span class="brand-text font-weight-light px-3">ZenAdmin</span>
|
|
5
|
+
</a>
|
|
6
|
+
|
|
7
|
+
<!-- Sidebar -->
|
|
8
|
+
<div class="sidebar">
|
|
9
|
+
<!-- Sidebar user panel -->
|
|
10
|
+
<div class="user-panel mt-3 pb-3 mb-3 d-flex align-items-center">
|
|
11
|
+
<div class="image">
|
|
12
|
+
<img src="https://ui-avatars.com/api/?name=<%= current_admin_user.username %>&background=random" class="img-circle elevation-2" alt="User Image">
|
|
13
|
+
</div>
|
|
14
|
+
<div class="info flex-grow-1">
|
|
15
|
+
<a href="#" class="d-block text-truncate"><%= current_admin_user.username %></a>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="logout-btn pr-3">
|
|
18
|
+
<%= link_to zen_admin.logout_path, data: { turbo_method: :delete }, class: "text-danger", title: "退出登录" do %>
|
|
19
|
+
<i class="fas fa-sign-out-alt"></i>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Sidebar Menu -->
|
|
25
|
+
<nav class="mt-2">
|
|
26
|
+
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
|
27
|
+
|
|
28
|
+
<li class="nav-item">
|
|
29
|
+
<a href="<%= zen_admin.root_path %>" class="nav-link <%= 'active' if request.path == zen_admin.root_path %>">
|
|
30
|
+
<i class="nav-icon fas fa-tachometer-alt"></i>
|
|
31
|
+
<p>Dashboard</p>
|
|
32
|
+
</a>
|
|
33
|
+
</li>
|
|
34
|
+
|
|
35
|
+
<li class="nav-header">MENU</li>
|
|
36
|
+
|
|
37
|
+
<% ZenAdmin::Registry.instance.menu_items.each do |item| %>
|
|
38
|
+
<% next unless menu_item_visible?(item, current_admin_user) %>
|
|
39
|
+
|
|
40
|
+
<% if item[:type] == :group %>
|
|
41
|
+
<li class="nav-item" data-controller="menu-group">
|
|
42
|
+
<a href="#" class="nav-link" data-action="click->menu-group#toggle">
|
|
43
|
+
<i class="nav-icon fas fa-folder"></i>
|
|
44
|
+
<p>
|
|
45
|
+
<%= item[:label] %>
|
|
46
|
+
<i class="right fas fa-angle-left"></i>
|
|
47
|
+
</p>
|
|
48
|
+
</a>
|
|
49
|
+
<ul class="nav nav-treeview" data-menu-group-target="treeview" style="display: none;">
|
|
50
|
+
<% item[:items].each do |sub_item| %>
|
|
51
|
+
<% next unless menu_item_visible?(sub_item, current_admin_user) %>
|
|
52
|
+
|
|
53
|
+
<% if sub_item[:type] == :resource %>
|
|
54
|
+
<% resource = sub_item[:object] %>
|
|
55
|
+
<li class="nav-item">
|
|
56
|
+
<a href="<%= zen_admin.ui_resource_index_path(resource.name) %>" class="nav-link <%= 'active' if params[:resource_name] == resource.name %>">
|
|
57
|
+
<i class="nav-icon <%= resource.menu_options[:icon] || 'far fa-circle' %>"></i>
|
|
58
|
+
<p>
|
|
59
|
+
<%
|
|
60
|
+
menu_label = resource.menu_options[:label]
|
|
61
|
+
label = if menu_label.present?
|
|
62
|
+
menu_label.is_a?(Symbol) ? t(menu_label) : menu_label
|
|
63
|
+
else
|
|
64
|
+
resource.label_plural
|
|
65
|
+
end
|
|
66
|
+
%>
|
|
67
|
+
<%= label %>
|
|
68
|
+
</p>
|
|
69
|
+
</a>
|
|
70
|
+
</li>
|
|
71
|
+
<% elsif sub_item[:type] == :link %>
|
|
72
|
+
<% link = sub_item[:object] %>
|
|
73
|
+
<li class="nav-item">
|
|
74
|
+
<a href="<%= link[:path] %>" class="nav-link">
|
|
75
|
+
<i class="nav-icon <%= link[:icon] %>"></i>
|
|
76
|
+
<p><%= link[:label] %></p>
|
|
77
|
+
</a>
|
|
78
|
+
</li>
|
|
79
|
+
<% end %>
|
|
80
|
+
<% end %>
|
|
81
|
+
</ul>
|
|
82
|
+
</li>
|
|
83
|
+
<% elsif item[:type] == :resource %>
|
|
84
|
+
<% resource = item[:object] %>
|
|
85
|
+
<li class="nav-item">
|
|
86
|
+
<a href="<%= zen_admin.ui_resource_index_path(resource.name) %>" class="nav-link <%= 'active' if params[:resource_name] == resource.name %>">
|
|
87
|
+
<i class="nav-icon <%= resource.menu_options[:icon] || 'fas fa-circle' %>"></i>
|
|
88
|
+
<p>
|
|
89
|
+
<%
|
|
90
|
+
menu_label = resource.menu_options[:label]
|
|
91
|
+
label = if menu_label.present?
|
|
92
|
+
menu_label.is_a?(Symbol) ? t(menu_label) : menu_label
|
|
93
|
+
else
|
|
94
|
+
resource.label_plural
|
|
95
|
+
end
|
|
96
|
+
%>
|
|
97
|
+
<%= label %>
|
|
98
|
+
</p>
|
|
99
|
+
</a>
|
|
100
|
+
</li>
|
|
101
|
+
<% else %>
|
|
102
|
+
<% link = item[:object] %>
|
|
103
|
+
<li class="nav-item">
|
|
104
|
+
<a href="<%= link[:path] %>" class="nav-link">
|
|
105
|
+
<i class="nav-icon <%= link[:icon] %>"></i>
|
|
106
|
+
<p><%= link[:label] %></p>
|
|
107
|
+
</a>
|
|
108
|
+
</li>
|
|
109
|
+
<% end %>
|
|
110
|
+
<% end %>
|
|
111
|
+
|
|
112
|
+
</ul>
|
|
113
|
+
</nav>
|
|
114
|
+
</div>
|
|
115
|
+
</aside>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= I18n.locale %>">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ZenAdmin</title>
|
|
7
|
+
|
|
8
|
+
<!-- Google Font & Icons -->
|
|
9
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
|
|
10
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
11
|
+
|
|
12
|
+
<!-- Theme style (AdminLTE) -->
|
|
13
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
|
14
|
+
|
|
15
|
+
<!-- Tom Select CSS -->
|
|
16
|
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/tom-select/2.2.2/css/tom-select.bootstrap5.min.css" rel="stylesheet">
|
|
17
|
+
|
|
18
|
+
<%= stylesheet_link_tag "actiontext", "data-turbo-track": "reload" %>
|
|
19
|
+
|
|
20
|
+
<% # 核心:必须使用 Rails 标准标签来加载环境,确保 CSRF 和 Turbo 正常工作 %>
|
|
21
|
+
<%= csrf_meta_tags %>
|
|
22
|
+
<%= csp_meta_tag %>
|
|
23
|
+
|
|
24
|
+
<%= javascript_importmap_tags "zen_admin/application" %>
|
|
25
|
+
<%= yield :head %>
|
|
26
|
+
|
|
27
|
+
<style>
|
|
28
|
+
:root {
|
|
29
|
+
--primary-color: #165DFF;
|
|
30
|
+
--text-color: #1D2129;
|
|
31
|
+
--text-secondary: #4E5969;
|
|
32
|
+
--border-color: #E5E6EB;
|
|
33
|
+
--bg-color: #F2F3F5;
|
|
34
|
+
}
|
|
35
|
+
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif; color: var(--text-color); background-color: var(--bg-color); }
|
|
36
|
+
.content-wrapper { background-color: var(--bg-color); }
|
|
37
|
+
.card { border: none; border-radius: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05); margin-bottom: 24px; border-top: 3px solid var(--primary-color); }
|
|
38
|
+
.btn-action { padding: 4px 8px; font-size: 12px; border-radius: 4px; }
|
|
39
|
+
|
|
40
|
+
/* Hide logout button when sidebar is collapsed (but not hovered) */
|
|
41
|
+
body.sidebar-mini.sidebar-collapse .main-sidebar:not(:hover) .user-panel > .logout-btn {
|
|
42
|
+
display: none !important;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body class="hold-transition sidebar-mini layout-fixed" data-controller="layout">
|
|
47
|
+
<div class="wrapper">
|
|
48
|
+
|
|
49
|
+
<div id="flash" style="position: fixed; top: 1rem; right: 1rem; z-index: 1050; width: 350px;">
|
|
50
|
+
<%= render "layouts/zen_admin/flash" %>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Navbar -->
|
|
54
|
+
<nav class="main-header navbar navbar-expand navbar-white navbar-light border-bottom-0 shadow-sm">
|
|
55
|
+
<ul class="navbar-nav">
|
|
56
|
+
<li class="nav-item">
|
|
57
|
+
<a class="nav-link" href="#" role="button" data-action="click->layout#toggleSidebar">
|
|
58
|
+
<i class="fas fa-bars text-muted"></i>
|
|
59
|
+
</a>
|
|
60
|
+
</li>
|
|
61
|
+
</ul>
|
|
62
|
+
<ul class="navbar-nav ml-auto">
|
|
63
|
+
<li class="nav-item">
|
|
64
|
+
<a class="nav-link" href="#" role="button" data-action="click->layout#toggleFullscreen">
|
|
65
|
+
<i class="fas fa-expand-arrows-alt text-muted"></i>
|
|
66
|
+
</a>
|
|
67
|
+
</li>
|
|
68
|
+
</ul>
|
|
69
|
+
</nav>
|
|
70
|
+
|
|
71
|
+
<%= render "layouts/zen_admin/sidebar" %>
|
|
72
|
+
|
|
73
|
+
<div class="content-wrapper pt-3">
|
|
74
|
+
<div class="content">
|
|
75
|
+
<div class="container-fluid">
|
|
76
|
+
<div class="d-flex align-items-center justify-content-between mb-4">
|
|
77
|
+
<h2 class="fw-bold mb-0"><%= content_for?(:title) ? yield(:title) : "Dashboard" %></h2>
|
|
78
|
+
<div class="page-actions">
|
|
79
|
+
<%= yield :page_actions %>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<%= yield %>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<footer class="main-footer border-top-0 small text-muted">
|
|
88
|
+
<strong>Copyright © <%= Time.now.year %> <a href="#" class="text-decoration-none">ZenAdmin</a>.</strong>
|
|
89
|
+
</footer>
|
|
90
|
+
|
|
91
|
+
<%= turbo_frame_tag "modal" %>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/tom-select/2.2.2/js/tom-select.complete.min.js"></script>
|
|
95
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" data-turbo-track="reload"></script>
|
|
96
|
+
|
|
97
|
+
</body>
|
|
98
|
+
</html>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<div data-controller="clipboard" class="d-inline-block">
|
|
2
|
+
<button class="btn btn-outline-primary py-1 px-2"
|
|
3
|
+
type="button"
|
|
4
|
+
data-action="clipboard#copy"
|
|
5
|
+
data-clipboard-text="<%= url %>"
|
|
6
|
+
title="<%= t('zen_admin.actions.copy') rescue '复制' %>">
|
|
7
|
+
<i class="fas fa-copy"></i>
|
|
8
|
+
</button>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<div class="input-group" data-controller="clipboard">
|
|
2
|
+
<input type="text" class="form-control" value="<%= url %>" readonly data-clipboard-target="input">
|
|
3
|
+
<div class="input-group-append">
|
|
4
|
+
<button class="btn btn-outline-primary" type="button" data-action="clipboard#copy">
|
|
5
|
+
<i class="fas fa-copy"></i> 复制链接
|
|
6
|
+
</button>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="mt-3 text-center">
|
|
10
|
+
<% # 兼容 Attachment, Blob 和 Asset %>
|
|
11
|
+
<% blob = if @record.is_a?(ActiveStorage::Attachment)
|
|
12
|
+
@record.blob
|
|
13
|
+
elsif @record.respond_to?(:file) && @record.file.attached?
|
|
14
|
+
@record.file.blob
|
|
15
|
+
else
|
|
16
|
+
@record
|
|
17
|
+
end %>
|
|
18
|
+
<% if blob.is_a?(ActiveStorage::Blob) && blob.representable? %>
|
|
19
|
+
<% # 使用原图预览,避免产生变体记录 %>
|
|
20
|
+
<img src="<%= main_app.url_for(blob) %>" class="img-fluid rounded shadow-sm border" style="max-height: 600px;">
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<div style="font-family: sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; border: 1px solid #eee; border-radius: 8px; text-align: center; box-shadow: 0 2px 10px rgba(0,0,0,0.05);">
|
|
2
|
+
<h1 style="color: #2c3e50;">ZenAdmin 挂载成功!</h1>
|
|
3
|
+
<p style="color: #7f8c8d; font-size: 1.1em;">
|
|
4
|
+
您现在看到的页面是由 <strong>ZenAdmin Engine</strong> 渲染的。
|
|
5
|
+
</p>
|
|
6
|
+
<hr style="border: 0; border-top: 1px solid #eee; margin: 20px 0;">
|
|
7
|
+
<div style="text-align: left; background: #f8f9fa; padding: 15px; border-radius: 4px;">
|
|
8
|
+
<p><strong>当前配置:</strong></p>
|
|
9
|
+
<ul style="list-style: none; padding: 0;">
|
|
10
|
+
<li>✅ UI 状态: <code><%= ZenAdmin.configuration.enable_ui %></code></li>
|
|
11
|
+
<li>✅ 管理路径: <code><%= ZenAdmin.configuration.admin_path %></code></li>
|
|
12
|
+
</ul>
|
|
13
|
+
|
|
14
|
+
<p class="mt-3"><strong>已注册资源:</strong></p>
|
|
15
|
+
<ul style="padding-left: 20px;">
|
|
16
|
+
<% ZenAdmin::Registry.instance.all.each do |resource| %>
|
|
17
|
+
<li style="margin-bottom: 5px;">
|
|
18
|
+
<%= link_to resource.model.name, ui_resource_index_path(resource.name), style: "color: #3498db; text-decoration: none; font-weight: bold;" %>
|
|
19
|
+
<span style="color: #999; font-size: 0.9em;">(<%= resource.name %>)</span>
|
|
20
|
+
</li>
|
|
21
|
+
<% end %>
|
|
22
|
+
</ul>
|
|
23
|
+
<% if ZenAdmin::Registry.instance.all.empty? %>
|
|
24
|
+
<p style="color: #999; font-style: italic;">暂无注册资源。</p>
|
|
25
|
+
<% end %>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>ZenAdmin | 登录</title>
|
|
7
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
|
|
8
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body class="hold-transition login-page">
|
|
12
|
+
<div class="login-box">
|
|
13
|
+
<div class="card card-outline card-primary">
|
|
14
|
+
<div class="card-header text-center">
|
|
15
|
+
<a href="#" class="h1"><b>Zen</b>Admin</a>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="card-body">
|
|
18
|
+
<p class="login-box-msg">请登录以开启管理后台</p>
|
|
19
|
+
|
|
20
|
+
<% if flash[:alert] %>
|
|
21
|
+
<div class="alert alert-danger"><%= flash[:alert] %></div>
|
|
22
|
+
<% end %>
|
|
23
|
+
|
|
24
|
+
<%= form_with url: login_path, method: :post, local: true do |f| %>
|
|
25
|
+
<div class="input-group mb-3">
|
|
26
|
+
<%= f.text_field :username, class: "form-control", placeholder: "用户名" %>
|
|
27
|
+
<div class="input-group-append">
|
|
28
|
+
<div class="input-group-text"><span class="fas fa-user"></span></div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="input-group mb-3">
|
|
32
|
+
<%= f.password_field :password, class: "form-control", placeholder: "密码" %>
|
|
33
|
+
<div class="input-group-append">
|
|
34
|
+
<div class="input-group-text"><span class="fas fa-lock"></span></div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="row">
|
|
38
|
+
<div class="col-12">
|
|
39
|
+
<%= f.submit "登录", class: "btn btn-primary btn-block" %>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<% end %>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|