panda-core 0.2.4 → 0.6.0
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 +4 -4
- data/app/assets/tailwind/application.css +199 -7
- data/app/assets/tailwind/tailwind.config.js +8 -0
- data/app/components/panda/core/UI/badge.rb +107 -0
- data/app/components/panda/core/UI/button.rb +110 -0
- data/app/components/panda/core/UI/card.rb +88 -0
- data/app/components/panda/core/admin/breadcrumb_component.rb +133 -0
- data/app/components/panda/core/admin/button_component.rb +46 -28
- data/app/components/panda/core/admin/container_component.rb +75 -4
- data/app/components/panda/core/admin/file_gallery_component.rb +157 -0
- data/app/components/panda/core/admin/flash_message_component.rb +98 -15
- data/app/components/panda/core/admin/form_error_component.rb +48 -0
- data/app/components/panda/core/admin/form_input_component.rb +50 -0
- data/app/components/panda/core/admin/form_select_component.rb +68 -0
- data/app/components/panda/core/admin/heading_component.rb +53 -24
- data/app/components/panda/core/admin/page_header_component.rb +107 -0
- data/app/components/panda/core/admin/panel_component.rb +33 -4
- data/app/components/panda/core/admin/slideover_component.rb +66 -4
- data/app/components/panda/core/admin/statistics_component.rb +19 -0
- data/app/components/panda/core/admin/tab_bar_component.rb +101 -0
- data/app/components/panda/core/admin/table_component.rb +92 -11
- data/app/components/panda/core/admin/tag_component.rb +58 -16
- data/app/components/panda/core/admin/user_activity_component.rb +43 -0
- data/app/components/panda/core/admin/user_display_component.rb +77 -0
- data/app/components/panda/core/base.rb +122 -0
- data/app/controllers/panda/core/admin/base_controller.rb +68 -0
- data/app/controllers/panda/core/admin/dashboard_controller.rb +5 -3
- data/app/controllers/panda/core/admin/my_profile_controller.rb +4 -4
- data/app/controllers/panda/core/admin/sessions_controller.rb +15 -8
- data/app/controllers/panda/core/admin/test_sessions_controller.rb +60 -0
- data/app/helpers/panda/core/asset_helper.rb +31 -5
- data/app/helpers/panda/core/sessions_helper.rb +27 -2
- data/app/javascript/panda/core/application.js +8 -1
- data/app/javascript/panda/core/controllers/alert_controller.js +38 -0
- data/app/javascript/panda/core/controllers/index.js +3 -3
- data/app/javascript/panda/core/controllers/toggle_controller.js +41 -0
- data/app/javascript/panda/core/tailwindplus-elements.js +31 -0
- data/app/javascript/panda/core/vendor/@hotwired--stimulus.js +4 -0
- data/app/javascript/panda/core/vendor/@hotwired--turbo.js +160 -0
- data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +4 -0
- data/app/models/panda/core/user.rb +61 -14
- data/app/services/panda/core/attach_avatar_service.rb +67 -0
- data/app/views/layouts/panda/core/admin.html.erb +40 -3
- data/app/views/layouts/panda/core/admin_simple.html.erb +6 -0
- data/app/views/panda/core/admin/dashboard/_default_content.html.erb +4 -4
- data/app/views/panda/core/admin/dashboard/show.html.erb +2 -2
- data/app/views/panda/core/admin/my_profile/edit.html.erb +36 -25
- data/app/views/panda/core/admin/sessions/new.html.erb +9 -10
- data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +27 -34
- data/app/views/panda/core/admin/shared/_flash.html.erb +4 -30
- data/app/views/panda/core/admin/shared/_sidebar.html.erb +41 -20
- data/app/views/panda/core/shared/_header.html.erb +13 -5
- data/config/importmap.rb +19 -6
- data/config/routes.rb +10 -3
- data/db/migrate/20250810120000_add_current_theme_to_panda_core_users.rb +7 -0
- data/db/migrate/20250811120000_add_oauth_avatar_url_to_panda_core_users.rb +7 -0
- data/lib/panda/core/asset_loader.rb +23 -8
- data/lib/panda/core/configuration.rb +12 -9
- data/lib/panda/core/debug.rb +47 -0
- data/lib/panda/core/engine.rb +55 -9
- data/lib/panda/core/services/base_service.rb +19 -4
- data/lib/panda/core/version.rb +1 -1
- data/lib/panda/core.rb +2 -0
- data/lib/tasks/panda_core_users.rake +158 -0
- metadata +103 -14
- data/app/components/panda/core/admin/container_component.html.erb +0 -12
- data/app/components/panda/core/admin/flash_message_component.html.erb +0 -31
- data/app/components/panda/core/admin/panel_component.html.erb +0 -7
- data/app/components/panda/core/admin/slideover_component.html.erb +0 -9
- data/app/components/panda/core/admin/table_component.html.erb +0 -29
- data/app/controllers/panda/core/admin_controller.rb +0 -30
|
@@ -1,5 +1,42 @@
|
|
|
1
|
-
<%= render "panda/core/shared/header", html_class: "h-full
|
|
2
|
-
<div class="flex
|
|
3
|
-
|
|
1
|
+
<%= render "panda/core/shared/header", html_class: "h-full bg-white" %>
|
|
2
|
+
<div class="flex h-full" id="panda-container">
|
|
3
|
+
<div class="absolute top-0 w-full lg:flex lg:fixed lg:inset-y-0 lg:z-50 lg:flex-col lg:w-72">
|
|
4
|
+
<div class="flex overflow-y-auto flex-col gap-y-5 px-4 pb-4 max-h-16 bg-gradient-admin lg:max-h-full grow" data-transition-enter="transition-all ease-in-out duration-300" data-transition-enter-from="m-h-16" data-transition-enter-to="max-h-full" data-transition-leave="transition-all ease-in-out duration-300" data-transition-leave-from="max-h-full" data-transition-leave-to="max-h-16">
|
|
5
|
+
<%= render "panda/core/admin/shared/sidebar" %>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="flex flex-col flex-1 mt-16 ml-0 lg:mt-0 lg:ml-72" id="panda-inner-container" <% if content_for :sidebar %> data-controller="toggle" data-action="keydown.esc->modal#close" tabindex="-1"<% end %>>
|
|
9
|
+
<section id="panda-main" class="flex flex-row h-full">
|
|
10
|
+
<div class="flex-1 h-full" id="panda-primary-content">
|
|
11
|
+
<%= render "panda/core/admin/shared/breadcrumbs" %>
|
|
12
|
+
<%= render "panda/core/admin/shared/flash" %>
|
|
13
|
+
<%= yield %>
|
|
14
|
+
</div>
|
|
15
|
+
<% if content_for :sidebar %>
|
|
16
|
+
<div data-toggle-target="toggleable" class="hidden flex absolute right-0 flex-col h-full bg-white divide-y divide-gray-200 shadow-xl basis-3/12"
|
|
17
|
+
data-transition-enter="transform transition ease-in-out duration-500"
|
|
18
|
+
data-transition-enter-from="translate-x-full"
|
|
19
|
+
data-transition-enter-to="translate-x-0"
|
|
20
|
+
data-transition-leave="transform transition ease-in-out duration-500"
|
|
21
|
+
data-transition-leave-from="translate-x-full"
|
|
22
|
+
data-transition-leave-to="translate-x-0"
|
|
23
|
+
id="slideover">
|
|
24
|
+
<div class="overflow-y-auto flex-1 h-0">
|
|
25
|
+
<div class="py-3 px-4 mb-4 bg-black">
|
|
26
|
+
<div class="flex justify-between items-center">
|
|
27
|
+
<h2 class="text-base font-semibold leading-6 text-white" id="slideover-title"><i class="mr-2 fa-solid fa-gear"></i> <%= yield :sidebar_title %> </h2>
|
|
28
|
+
<button type="button" data-action="click->toggle#toggle touch->toggle#toggle"><i class="font-bold text-white fa-solid fa-xmark right"></i></button>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="flex flex-col flex-1 justify-between">
|
|
32
|
+
<div class="px-4 space-y-6">
|
|
33
|
+
<%= yield :sidebar %>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
</section>
|
|
40
|
+
</div>
|
|
4
41
|
</div>
|
|
5
42
|
<%= render "panda/core/shared/footer" %>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<%= render "panda/core/shared/header", html_class: "h-full", body_class: "bg-gradient-admin" %>
|
|
2
|
+
<%= render "panda/core/admin/shared/flash" %>
|
|
3
|
+
<div class="flex flex-col items-center justify-center min-h-screen px-4">
|
|
4
|
+
<%= yield %>
|
|
5
|
+
</div>
|
|
6
|
+
<%= render "panda/core/shared/footer" %>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<div class="px-4 py-5 sm:p-6">
|
|
11
11
|
<div class="flex items-center">
|
|
12
12
|
<div class="flex-shrink-0">
|
|
13
|
-
<i class="fa-
|
|
13
|
+
<i class="fa-solid fa-file-lines text-3xl text-gray-400"></i>
|
|
14
14
|
</div>
|
|
15
15
|
<div class="ml-5 w-0 flex-1">
|
|
16
16
|
<dt class="text-sm font-medium text-gray-500 truncate">Content Management</dt>
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
|
|
48
48
|
<% # Hook for additional dashboard cards %>
|
|
49
|
-
<% if Panda::Core.
|
|
50
|
-
<% cards = Panda::Core.
|
|
49
|
+
<% if Panda::Core.config.respond_to?(:admin_dashboard_cards) %>
|
|
50
|
+
<% cards = Panda::Core.config.admin_dashboard_cards&.call(current_user) %>
|
|
51
51
|
<% cards&.each do |card| %>
|
|
52
52
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
|
53
53
|
<div class="px-4 py-5 sm:p-6">
|
|
@@ -70,4 +70,4 @@
|
|
|
70
70
|
<% end %>
|
|
71
71
|
<% end %>
|
|
72
72
|
</div>
|
|
73
|
-
</div>
|
|
73
|
+
</div>
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
<% container.with_heading(text: "Dashboard", level: 1) %>
|
|
4
4
|
|
|
5
5
|
<% # Hook for dashboard widgets %>
|
|
6
|
-
<% if Panda::Core.
|
|
7
|
-
<% widgets = Panda::Core.
|
|
6
|
+
<% if Panda::Core.config.admin_dashboard_widgets %>
|
|
7
|
+
<% widgets = Panda::Core.config.admin_dashboard_widgets.call(current_user) %>
|
|
8
8
|
<% if widgets && widgets.any? %>
|
|
9
9
|
<div class="grid grid-cols-1 gap-5 mt-5 sm:grid-cols-3">
|
|
10
10
|
<% widgets.each do |widget| %>
|
|
@@ -1,49 +1,60 @@
|
|
|
1
1
|
<%= render Panda::Core::Admin::ContainerComponent.new do |component| %>
|
|
2
|
-
<% component.
|
|
2
|
+
<% component.heading(text: "My Profile", level: 1) %>
|
|
3
3
|
|
|
4
4
|
<%= form_with model: user,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
url: admin_my_profile_path,
|
|
6
|
+
method: :patch,
|
|
7
|
+
local: true,
|
|
8
|
+
data: { controller: "theme-form" } do |f| %>
|
|
9
|
+
<%= render Panda::Core::Admin::FormErrorComponent.new(model: user) %>
|
|
10
|
+
|
|
11
|
+
<div class="space-y-6">
|
|
12
|
+
<!-- Avatar Section -->
|
|
13
|
+
<div class="col-span-full">
|
|
14
|
+
<%= f.label :avatar, "Profile Picture", class: "block text-sm/6 font-medium text-gray-900 dark:text-white" %>
|
|
15
|
+
<div class="mt-2 flex items-center gap-x-3">
|
|
16
|
+
<% if user.avatar.attached? %>
|
|
17
|
+
<%= image_tag main_app.url_for(user.avatar), alt: user.name, class: "w-48 h-48 max-w-48 rounded-full object-cover" %>
|
|
18
|
+
<% else %>
|
|
19
|
+
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" class="w-48 h-48 text-gray-300 dark:text-gray-500">
|
|
20
|
+
<path d="M18.685 19.097A9.723 9.723 0 0 0 21.75 12c0-5.385-4.365-9.75-9.75-9.75S2.25 6.615 2.25 12a9.723 9.723 0 0 0 3.065 7.097A9.716 9.716 0 0 0 12 21.75a9.716 9.716 0 0 0 6.685-2.653Zm-12.54-1.285A7.486 7.486 0 0 1 12 15a7.486 7.486 0 0 1 5.855 2.812A8.224 8.224 0 0 1 12 20.25a8.224 8.224 0 0 1-5.855-2.438ZM15.75 9a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" clip-rule="evenodd" fill-rule="evenodd" />
|
|
21
|
+
</svg>
|
|
14
22
|
<% end %>
|
|
23
|
+
<label for="<%= f.field_id(:avatar) %>" class="rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs inset-ring inset-ring-gray-300 hover:bg-gray-50 cursor-pointer dark:bg-white/10 dark:text-white dark:shadow-none dark:inset-ring-white/5 dark:hover:bg-white/20">
|
|
24
|
+
Change
|
|
25
|
+
</label>
|
|
26
|
+
<%= f.file_field :avatar,
|
|
27
|
+
accept: "image/png,image/jpeg,image/jpg,image/gif,image/webp",
|
|
28
|
+
class: "sr-only" %>
|
|
15
29
|
</div>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<%= f.label :firstname, "First Name", class: "block text-sm font-medium text-gray-700" %>
|
|
22
|
-
<%= f.text_field :firstname, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" %>
|
|
30
|
+
<% if user.avatar.attached? %>
|
|
31
|
+
<p class="mt-2 text-xs text-gray-600 dark:text-gray-400">
|
|
32
|
+
Current: <%= user.avatar.filename %> (<%= number_to_human_size(user.avatar.byte_size) %>)
|
|
33
|
+
</p>
|
|
34
|
+
<% end %>
|
|
23
35
|
</div>
|
|
24
36
|
|
|
25
37
|
<div class="field">
|
|
26
|
-
<%= f.label :
|
|
27
|
-
<%= f.text_field :
|
|
38
|
+
<%= f.label :name %>
|
|
39
|
+
<%= f.text_field :name %>
|
|
28
40
|
</div>
|
|
29
41
|
|
|
30
42
|
<div class="field">
|
|
31
|
-
<%= f.label :email
|
|
32
|
-
<%= f.email_field :email
|
|
43
|
+
<%= f.label :email %>
|
|
44
|
+
<%= f.email_field :email %>
|
|
33
45
|
</div>
|
|
34
46
|
|
|
35
47
|
<div class="field">
|
|
36
|
-
<%= f.label :current_theme, "Theme"
|
|
48
|
+
<%= f.label :current_theme, "Theme" %>
|
|
37
49
|
<%= f.select :current_theme,
|
|
38
|
-
options_for_select(Panda::Core.
|
|
50
|
+
options_for_select(Panda::Core.config.available_themes || [["Default", "default"], ["Sky", "sky"]], user.current_theme),
|
|
39
51
|
{},
|
|
40
|
-
class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm",
|
|
41
52
|
data: { action: "change->theme-form#updateTheme" } %>
|
|
42
53
|
</div>
|
|
43
54
|
</div>
|
|
44
55
|
|
|
45
56
|
<%= f.submit "Update Profile",
|
|
46
|
-
class: "btn btn-primary mt-
|
|
57
|
+
class: "btn btn-primary mt-4",
|
|
47
58
|
data: { disable_with: "Saving..." } %>
|
|
48
59
|
<% end %>
|
|
49
60
|
<% end %>
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
<div class="flex flex-col justify-center py-12 px-6 min-h-full text-center lg:px-8">
|
|
2
2
|
<div class="text-center sm:mx-auto sm:w-full sm:max-w-sm">
|
|
3
|
-
<% if Panda::Core.
|
|
4
|
-
<img src="<%= Panda::Core.
|
|
3
|
+
<% if Panda::Core.config.login_logo_path %>
|
|
4
|
+
<img src="<%= Panda::Core.config.login_logo_path %>" class="py-2 mx-auto w-auto h-32">
|
|
5
5
|
<% end %>
|
|
6
6
|
<h2 class="mt-10 mb-6 text-2xl font-bold text-center text-white">
|
|
7
|
-
<%= Panda::Core.
|
|
7
|
+
<%= Panda::Core.config.login_page_title || "Sign in to your account" %>
|
|
8
8
|
</h2>
|
|
9
9
|
</div>
|
|
10
|
-
<% if @providers&.any? || Panda::Core.
|
|
11
|
-
<% providers = @providers || Panda::Core.
|
|
10
|
+
<% if @providers&.any? || Panda::Core.config.authentication_providers.any? %>
|
|
11
|
+
<% providers = @providers || Panda::Core.config.authentication_providers.keys %>
|
|
12
12
|
<% providers.each do |provider| %>
|
|
13
|
-
<% provider_config = Panda::Core.
|
|
14
|
-
<% provider_name = provider_config&.dig(:name) || provider.to_s.humanize %>
|
|
13
|
+
<% provider_config = Panda::Core.config.authentication_providers[provider] %>
|
|
15
14
|
<% provider_path = provider_config&.dig(:path_name) || provider %>
|
|
16
15
|
<div class="mt-4 text-center sm:mx-auto sm:w-full sm:max-w-sm">
|
|
17
|
-
<%= form_tag "#{Panda::Core.
|
|
16
|
+
<%= form_tag "#{Panda::Core.config.admin_path}/auth/#{provider_path}", method: "post", data: {turbo: false} do %>
|
|
18
17
|
<button type="submit" id="button-sign-in-<%= provider_path %>" class="inline-flex gap-x-2 items-center py-2.5 px-3.5 mx-auto mb-4 bg-white text-gray-900 rounded-md border min-w-56 border-neutral-400 hover:bg-gray-50">
|
|
19
|
-
<i class="fa-brands fa-<%= oauth_provider_icon(provider) %> text-xl mr-1"></i>
|
|
20
|
-
Sign in with <%=
|
|
18
|
+
<i class="<%= oauth_provider_non_brand?(provider) ? 'fa-solid' : 'fa-brands' %> fa-<%= oauth_provider_icon(provider) %> text-xl mr-1"></i>
|
|
19
|
+
Sign in with <%= oauth_provider_name(provider, provider_config) %>
|
|
21
20
|
</button>
|
|
22
21
|
<% end %>
|
|
23
22
|
</div>
|
|
@@ -1,35 +1,28 @@
|
|
|
1
|
-
<%
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
</svg>
|
|
11
|
-
<% end %>
|
|
12
|
-
|
|
13
|
-
<% if breadcrumb.path && index < @breadcrumbs.length - 1 %>
|
|
14
|
-
<%= link_to breadcrumb.label, breadcrumb.path, class: "inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600" %>
|
|
15
|
-
<% else %>
|
|
16
|
-
<span class="text-sm font-medium text-gray-500"><%= breadcrumb.label %></span>
|
|
17
|
-
<% end %>
|
|
18
|
-
</li>
|
|
19
|
-
<% end %>
|
|
20
|
-
</ol>
|
|
21
|
-
</nav>
|
|
22
|
-
|
|
23
|
-
<% if content_for?(:sidebar) %>
|
|
24
|
-
<div class="px-4 py-3 border-b border-gray-200 bg-gray-50 text-gray-700" tabindex="-1" data-controller="toggle">
|
|
25
|
-
<a href="#" id="slideover-toggle" data-action="click->toggle#toggle touch->toggle#toggle" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600">
|
|
26
|
-
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
27
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
|
28
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
29
|
-
</svg>
|
|
30
|
-
<%= yield(:sidebar_title) || "Settings" %>
|
|
1
|
+
<% breadcrumb_styles = "text-black/60 hover:text-black/80" %>
|
|
2
|
+
|
|
3
|
+
<div class="flex mt-1 -mb-1">
|
|
4
|
+
<nav aria-label="Breadcrumb" id="panda-breadcrumbs" class="grow">
|
|
5
|
+
<ol role="list" class="px-4 w-full sm:px-6">
|
|
6
|
+
<li class="inline-block">
|
|
7
|
+
<a href="<%= Panda::Core.config.admin_path %>" class="<%= breadcrumb_styles %>">
|
|
8
|
+
<i class="fa fa-solid fa-house fa-fw inline-block w-4 text-center"></i>
|
|
9
|
+
<span class="sr-only">Home</span>
|
|
31
10
|
</a>
|
|
32
|
-
</
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
11
|
+
</li>
|
|
12
|
+
<% breadcrumbs.each do |crumb| %>
|
|
13
|
+
<li class="inline-block">
|
|
14
|
+
<i class="fa-solid fa-chevron-right fa-fw inline-block w-4 text-center font-light <%= breadcrumb_styles %> px-2"></i>
|
|
15
|
+
<a href="<%= crumb.path %>" class="text-sm font-normal <%= breadcrumb_styles %>"><%= crumb.name %></a>
|
|
16
|
+
</li>
|
|
17
|
+
<% end %>
|
|
18
|
+
</ol>
|
|
19
|
+
</nav>
|
|
20
|
+
|
|
21
|
+
<% if content_for :sidebar %>
|
|
22
|
+
<div class="pt-4 pr-8 text-black/80" tabindex="-1">
|
|
23
|
+
<button type="button" id="slideover-toggle" data-action="click->toggle#toggle touch->toggle#toggle" class="text-sm font-light">
|
|
24
|
+
<i class="mr-1 fa-light fa-gear"></i> <%= yield :sidebar_title %>
|
|
25
|
+
</button>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
28
|
+
</div>
|
|
@@ -1,31 +1,5 @@
|
|
|
1
1
|
<% if flash.any? %>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<div class="flex-shrink-0">
|
|
7
|
-
<% if type == "success" %>
|
|
8
|
-
<i class="fa-regular fa-check-circle text-green-500"></i>
|
|
9
|
-
<% elsif type == "error" %>
|
|
10
|
-
<i class="fa-regular fa-times-circle text-red-500"></i>
|
|
11
|
-
<% elsif type == "warning" %>
|
|
12
|
-
<i class="fa-regular fa-exclamation-triangle text-yellow-500"></i>
|
|
13
|
-
<% else %>
|
|
14
|
-
<i class="fa-regular fa-info-circle text-blue-500"></i>
|
|
15
|
-
<% end %>
|
|
16
|
-
</div>
|
|
17
|
-
<div class="ml-3 w-0 flex-1">
|
|
18
|
-
<p class="text-sm font-medium text-gray-900">
|
|
19
|
-
<%= message %>
|
|
20
|
-
</p>
|
|
21
|
-
</div>
|
|
22
|
-
<div class="ml-4 flex-shrink-0 flex">
|
|
23
|
-
<button type="button" class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500" data-action="click->flash#close">
|
|
24
|
-
<i class="fa-regular fa-times"></i>
|
|
25
|
-
</button>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
<% end %>
|
|
30
|
-
</div>
|
|
31
|
-
<% end %>
|
|
2
|
+
<% flash.each do |kind, message| %>
|
|
3
|
+
<%= render Panda::Core::Admin::FlashMessageComponent.new(kind: kind.to_sym, message: message) %>
|
|
4
|
+
<% end %>
|
|
5
|
+
<% end %>
|
|
@@ -1,27 +1,48 @@
|
|
|
1
1
|
<nav class="flex flex-col flex-1">
|
|
2
|
-
<ul role="list" class="flex flex-col flex-1
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
<ul role="list" class="flex flex-col flex-1">
|
|
3
|
+
<a class="block p-0 mt-4 mb-4 ml-2 text-xl font-medium text-white"><%= Panda::Core.config.admin_title || "Panda Admin" %></a>
|
|
4
|
+
<%
|
|
5
|
+
# Get navigation items in original order for display
|
|
6
|
+
nav_items = Panda::Core.config.admin_navigation_items&.call(current_user) || []
|
|
7
|
+
|
|
8
|
+
# Find the most specific matching path by checking longest paths first
|
|
9
|
+
active_path = nav_items
|
|
10
|
+
.sort_by { |item| -item[:path].length }
|
|
11
|
+
.find { |item| request.path == item[:path] || request.path.starts_with?(item[:path] + "/") }
|
|
12
|
+
&.dig(:path)
|
|
13
|
+
%>
|
|
14
|
+
<% nav_items.each do |item| %>
|
|
15
|
+
<li>
|
|
16
|
+
<%
|
|
17
|
+
# Check if this item is the active one
|
|
18
|
+
is_active = item[:path] == active_path
|
|
19
|
+
%>
|
|
20
|
+
<%= link_to item[:path], class: "#{is_active ? "bg-mid text-white relative flex items-center transition-all py-3 px-2 mb-2 rounded-md group gap-x-3 text-base leading-6 font-normal" : "text-white hover:bg-mid/60 transition-all group flex items-center gap-x-3 py-3 px-2 mb-2 rounded-md text-base leading-6 font-normal"}" do %>
|
|
21
|
+
<span class="text-center w-6"><i class="<%= item[:icon] %> text-xl fa-fw"></i></span>
|
|
22
|
+
<span><%= item[:label] %></span>
|
|
12
23
|
<% end %>
|
|
13
|
-
</
|
|
24
|
+
</li>
|
|
25
|
+
<% end %>
|
|
26
|
+
<li>
|
|
27
|
+
<%= button_to panda_core.admin_logout_path, method: :delete, id: "logout-link", data: { turbo: false }, class: "text-white hover:bg-mid/60 transition-all group flex items-center gap-x-3 py-3 px-2 mb-2 rounded-md text-base leading-6 font-normal w-full" do %>
|
|
28
|
+
<span class="text-center w-6"><i class="text-xl fa-solid fa-door-open fa-fw"></i></span>
|
|
29
|
+
<span>Logout</span>
|
|
30
|
+
<% end %>
|
|
14
31
|
</li>
|
|
15
|
-
|
|
16
32
|
<li class="mt-auto">
|
|
17
|
-
|
|
18
|
-
<% if current_user %>
|
|
19
|
-
<span class="
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
<%= link_to panda_core.edit_admin_my_profile_path, class: "text-white hover:bg-mid/60 transition-all group flex items-center gap-x-3 py-3 px-2 mb-2 rounded-md text-base leading-6 font-normal w-full", title: "Edit my Profile" do %>
|
|
34
|
+
<% if current_user.avatar.attached? %>
|
|
35
|
+
<span class="text-center w-6"><%= image_tag main_app.url_for(current_user.avatar), alt: current_user.name, class: "w-auto h-7 rounded-full object-cover" %></span>
|
|
36
|
+
<% elsif !current_user.image_url.to_s.empty? %>
|
|
37
|
+
<span class="text-center w-6"><img src="<%= current_user.image_url %>" class="w-auto h-7 rounded-full"></span>
|
|
38
|
+
<% else %>
|
|
39
|
+
<span class="text-center w-6"><i class="text-xl fa-solid fa-circle-user fa-fw"></i></span>
|
|
23
40
|
<% end %>
|
|
24
|
-
|
|
41
|
+
<span><%= current_user.name %></span>
|
|
42
|
+
<% end %>
|
|
43
|
+
</li>
|
|
44
|
+
<li class="px-2 py-3">
|
|
45
|
+
<span class="text-xs text-white">Panda Core v<%= Panda::Core::VERSION %></span>
|
|
25
46
|
</li>
|
|
26
47
|
</ul>
|
|
27
|
-
</nav>
|
|
48
|
+
</nav>
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html data-theme="<%= Panda::Core::Current&.user&.current_theme || Panda::Core.
|
|
2
|
+
<html data-theme="<%= Panda::Core::Current&.user&.current_theme || Panda::Core.config.default_theme %>" class="<%= local_assigns[:html_class] || "" %>">
|
|
3
3
|
<head>
|
|
4
|
-
<title><%= content_for?(:title) ? yield(:title) : (Panda::Core.
|
|
4
|
+
<title><%= content_for?(:title) ? yield(:title) : (Panda::Core.config.admin_title || "Panda Admin") %></title>
|
|
5
5
|
<%= csrf_meta_tags %>
|
|
6
6
|
<%= csp_meta_tag %>
|
|
7
|
-
<
|
|
8
|
-
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@7.1.0/css/all.min.css">
|
|
8
|
+
<%= panda_core_stylesheet %>
|
|
9
|
+
<%= panda_core_javascript %>
|
|
10
|
+
|
|
11
|
+
<% if defined?(Panda::CMS) && controller.class.name.start_with?("Panda::CMS") %>
|
|
12
|
+
<!-- CMS Assets -->
|
|
13
|
+
<%= panda_cms_javascript %>
|
|
14
|
+
<%= render "panda/cms/shared/favicons" %>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
9
17
|
<%= yield :head %>
|
|
10
18
|
</head>
|
|
11
|
-
<body class="h-full <%= local_assigns[:body_class] || "" %>" data-environment="<%= Rails.env %>">
|
|
19
|
+
<body class="overflow-hidden h-full <%= local_assigns[:body_class] || "" %>" data-environment="<%= Rails.env %>">
|
data/config/importmap.rb
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
|
|
5
|
-
pin "
|
|
6
|
-
pin "
|
|
7
|
-
pin "
|
|
3
|
+
# Panda Core application and controllers
|
|
4
|
+
# Served via Rack::Static middleware from app/javascript
|
|
5
|
+
pin "panda/core/application", to: "/panda/core/application.js"
|
|
6
|
+
pin "panda/core/controllers/index", to: "/panda/core/controllers/index.js"
|
|
7
|
+
pin "panda/core/controllers/toggle_controller", to: "/panda/core/controllers/toggle_controller.js"
|
|
8
|
+
pin "panda/core/controllers/theme_form_controller", to: "/panda/core/controllers/theme_form_controller.js"
|
|
9
|
+
pin "panda/core/tailwindplus-elements", to: "/panda/core/tailwindplus-elements.js"
|
|
8
10
|
|
|
9
|
-
#
|
|
11
|
+
# Base JavaScript dependencies for Panda Core (vendored for reliability)
|
|
12
|
+
pin "@hotwired/stimulus", to: "/panda/core/vendor/@hotwired--stimulus.js", preload: true # @3.2.2
|
|
13
|
+
pin "@hotwired/turbo", to: "/panda/core/vendor/@hotwired--turbo.js", preload: true # @8.0.18
|
|
14
|
+
pin "@rails/actioncable/src", to: "/panda/core/vendor/@rails--actioncable--src.js", preload: true # @8.0.201
|
|
15
|
+
pin "tailwindcss-stimulus-components", to: "/panda/core/tailwindcss-stimulus-components.js" # @6.1.3
|
|
16
|
+
|
|
17
|
+
# Font Awesome icons (from CDN)
|
|
10
18
|
pin "@fortawesome/fontawesome-free", to: "https://ga.jspm.io/npm:@fortawesome/fontawesome-free@7.1.0/js/all.js"
|
|
19
|
+
|
|
20
|
+
# Tailwind Plus Elements - Vanilla JS interactive components (from CDN)
|
|
21
|
+
# Provides: Autocomplete, Command palette, Dialog, Disclosure, Dropdown menu, Popover, Select, Tabs
|
|
22
|
+
# Note: Using esm.sh instead of jsdelivr for better ES module support
|
|
23
|
+
pin "@tailwindplus/elements", to: "https://esm.sh/@tailwindplus/elements@1", preload: false
|
data/config/routes.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Panda::Core::Engine.routes.draw do
|
|
2
2
|
# Get admin_path from configuration
|
|
3
3
|
# Default to "/admin" if not yet configured
|
|
4
|
-
admin_path = (Panda::Core.
|
|
4
|
+
admin_path = (Panda::Core.config.admin_path || "/admin").delete_prefix("/")
|
|
5
5
|
|
|
6
6
|
scope path: admin_path, as: "admin" do
|
|
7
7
|
get "/login", to: "admin/sessions#new", as: :login
|
|
@@ -17,8 +17,15 @@ Panda::Core::Engine.routes.draw do
|
|
|
17
17
|
get "/", to: "admin/dashboard#show", as: :root
|
|
18
18
|
|
|
19
19
|
# Profile management
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
resource :my_profile, only: %i[edit update], controller: "admin/my_profile", path: "my_profile"
|
|
21
|
+
|
|
22
|
+
# Test-only authentication endpoint (available in development and test environments)
|
|
23
|
+
# This bypasses OAuth for faster, more reliable test execution
|
|
24
|
+
# Development: Used by Capybara system tests which run Rails server in development mode
|
|
25
|
+
# Test: Used by controller/request tests
|
|
26
|
+
unless Rails.env.production?
|
|
27
|
+
get "/test_login/:user_id", to: "admin/test_sessions#create", as: :test_login
|
|
28
|
+
post "/test_sessions", to: "admin/test_sessions#create", as: :test_sessions
|
|
22
29
|
end
|
|
23
30
|
end
|
|
24
31
|
end
|
|
@@ -105,9 +105,19 @@ module Panda
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
else
|
|
108
|
-
# Development mode - use importmap
|
|
108
|
+
# Development mode - use importmap for JS and static CSS
|
|
109
109
|
tags = []
|
|
110
110
|
tags << javascript_include_tag("panda/core/application", type: "module")
|
|
111
|
+
|
|
112
|
+
# Add CSS if available
|
|
113
|
+
css_path = development_css_url
|
|
114
|
+
if css_path
|
|
115
|
+
css_attrs = {
|
|
116
|
+
rel: "stylesheet",
|
|
117
|
+
href: css_path
|
|
118
|
+
}
|
|
119
|
+
tags << tag(:link, css_attrs)
|
|
120
|
+
end
|
|
111
121
|
end
|
|
112
122
|
tags.join("\n").html_safe
|
|
113
123
|
end
|
|
@@ -144,11 +154,16 @@ module Panda
|
|
|
144
154
|
end
|
|
145
155
|
|
|
146
156
|
def development_css_url
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
# Try versioned file first
|
|
149
158
|
version = asset_version
|
|
150
|
-
|
|
151
|
-
File.exist?(Rails.
|
|
159
|
+
versioned_file = "/panda-core-assets/panda-core-#{version}.css"
|
|
160
|
+
return versioned_file if File.exist?(Rails.public_path.join("panda-core-assets", "panda-core-#{version}.css"))
|
|
161
|
+
|
|
162
|
+
# Fall back to unversioned file (always available from engine's public directory)
|
|
163
|
+
unversioned_file = "/panda-core-assets/panda-core.css"
|
|
164
|
+
return unversioned_file if File.exist?(Panda::Core::Engine.root.join("public", "panda-core-assets", "panda-core.css"))
|
|
165
|
+
|
|
166
|
+
nil
|
|
152
167
|
end
|
|
153
168
|
|
|
154
169
|
def asset_version
|
|
@@ -193,9 +208,9 @@ module Panda
|
|
|
193
208
|
end.compact.join(" ")
|
|
194
209
|
|
|
195
210
|
if content || block_given?
|
|
196
|
-
"<#{name}#{
|
|
211
|
+
"<#{name}#{" #{attrs}" if attrs.present?}>#{content || (block_given? ? yield : "")}</#{name}>"
|
|
197
212
|
else
|
|
198
|
-
"<#{name}#{
|
|
213
|
+
"<#{name}#{" #{attrs}" if attrs.present?}><#{name}>"
|
|
199
214
|
end
|
|
200
215
|
end
|
|
201
216
|
|
|
@@ -208,7 +223,7 @@ module Panda
|
|
|
208
223
|
end
|
|
209
224
|
end.compact.join(" ")
|
|
210
225
|
|
|
211
|
-
"<#{name}#{
|
|
226
|
+
"<#{name}#{" #{attrs}" if attrs.present?} />"
|
|
212
227
|
end
|
|
213
228
|
|
|
214
229
|
def javascript_include_tag(source, options = {})
|
|
@@ -46,7 +46,7 @@ module Panda
|
|
|
46
46
|
{
|
|
47
47
|
label: "Dashboard",
|
|
48
48
|
path: @admin_path,
|
|
49
|
-
icon: "fa-
|
|
49
|
+
icon: "fa-solid fa-house"
|
|
50
50
|
}
|
|
51
51
|
]
|
|
52
52
|
|
|
@@ -55,14 +55,14 @@ module Panda
|
|
|
55
55
|
items << {
|
|
56
56
|
label: "Content",
|
|
57
57
|
path: "#{@admin_path}/cms",
|
|
58
|
-
icon: "fa-
|
|
58
|
+
icon: "fa-solid fa-file-lines"
|
|
59
59
|
}
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
items << {
|
|
63
63
|
label: "My Profile",
|
|
64
64
|
path: "#{@admin_path}/my_profile/edit",
|
|
65
|
-
icon: "fa-
|
|
65
|
+
icon: "fa-solid fa-user"
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
items
|
|
@@ -84,19 +84,22 @@ module Panda
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
class << self
|
|
87
|
-
attr_writer :
|
|
87
|
+
attr_writer :config
|
|
88
88
|
|
|
89
|
-
def
|
|
90
|
-
@
|
|
89
|
+
def config
|
|
90
|
+
@config ||= Configuration.new
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
def configure
|
|
94
|
-
yield(
|
|
94
|
+
yield(config)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
-
def
|
|
98
|
-
@
|
|
97
|
+
def reset_config!
|
|
98
|
+
@config = Configuration.new
|
|
99
99
|
end
|
|
100
|
+
|
|
101
|
+
# Alias for backward compatibility with test expectations
|
|
102
|
+
alias_method :reset_configuration!, :reset_config!
|
|
100
103
|
end
|
|
101
104
|
end
|
|
102
105
|
end
|