panda-core 0.2.4 → 0.4.1
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 +104 -7
- data/app/components/panda/core/UI/badge.rb +107 -0
- data/app/components/panda/core/UI/button.rb +89 -0
- data/app/components/panda/core/UI/card.rb +88 -0
- data/app/components/panda/core/admin/button_component.rb +46 -28
- data/app/components/panda/core/admin/container_component.rb +52 -4
- data/app/components/panda/core/admin/flash_message_component.rb +74 -9
- 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 +52 -24
- data/app/components/panda/core/admin/panel_component.rb +33 -4
- data/app/components/panda/core/admin/slideover_component.rb +8 -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 +90 -9
- data/app/components/panda/core/admin/tag_component.rb +21 -16
- data/app/components/panda/core/admin/user_activity_component.rb +43 -0
- data/app/components/panda/core/admin/user_display_component.rb +78 -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 +3 -3
- data/app/controllers/panda/core/admin/sessions_controller.rb +9 -6
- data/app/helpers/panda/core/sessions_helper.rb +1 -1
- 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 +17 -13
- data/app/views/layouts/panda/core/admin.html.erb +40 -3
- data/app/views/layouts/panda/core/admin_simple.html.erb +5 -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 +13 -27
- data/app/views/panda/core/admin/sessions/new.html.erb +7 -7
- 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 +36 -20
- data/app/views/panda/core/shared/_header.html.erb +13 -5
- data/config/importmap.rb +11 -6
- data/config/routes.rb +2 -4
- data/db/migrate/20250810120000_add_current_theme_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 +43 -6
- data/lib/panda/core/version.rb +1 -1
- data/lib/panda/core.rb +1 -0
- metadata +93 -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,49 +1,35 @@
|
|
|
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
|
-
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-md">
|
|
11
|
-
<div class="text-sm text-red-600">
|
|
12
|
-
<% user.errors.full_messages.each do |message| %>
|
|
13
|
-
<p><%= message %></p>
|
|
14
|
-
<% end %>
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
<% end %>
|
|
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) %>
|
|
18
10
|
|
|
19
11
|
<div class="space-y-4">
|
|
20
12
|
<div class="field">
|
|
21
|
-
<%= f.label :
|
|
22
|
-
<%= f.text_field :
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
<div class="field">
|
|
26
|
-
<%= f.label :lastname, "Last Name", class: "block text-sm font-medium text-gray-700" %>
|
|
27
|
-
<%= f.text_field :lastname, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" %>
|
|
13
|
+
<%= f.label :name %>
|
|
14
|
+
<%= f.text_field :name %>
|
|
28
15
|
</div>
|
|
29
16
|
|
|
30
17
|
<div class="field">
|
|
31
|
-
<%= f.label :email
|
|
32
|
-
<%= f.email_field :email
|
|
18
|
+
<%= f.label :email %>
|
|
19
|
+
<%= f.email_field :email %>
|
|
33
20
|
</div>
|
|
34
21
|
|
|
35
22
|
<div class="field">
|
|
36
|
-
<%= f.label :current_theme, "Theme"
|
|
23
|
+
<%= f.label :current_theme, "Theme" %>
|
|
37
24
|
<%= f.select :current_theme,
|
|
38
|
-
options_for_select(Panda::Core.
|
|
25
|
+
options_for_select(Panda::Core.config.available_themes || [["Default", "default"], ["Sky", "sky"]], user.current_theme),
|
|
39
26
|
{},
|
|
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
27
|
data: { action: "change->theme-form#updateTheme" } %>
|
|
42
28
|
</div>
|
|
43
29
|
</div>
|
|
44
30
|
|
|
45
31
|
<%= f.submit "Update Profile",
|
|
46
|
-
class: "btn btn-primary mt-
|
|
32
|
+
class: "btn btn-primary mt-4",
|
|
47
33
|
data: { disable_with: "Saving..." } %>
|
|
48
34
|
<% end %>
|
|
49
35
|
<% end %>
|
|
@@ -1,20 +1,20 @@
|
|
|
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.
|
|
13
|
+
<% provider_config = Panda::Core.config.authentication_providers[provider] %>
|
|
14
14
|
<% provider_name = provider_config&.dig(:name) || provider.to_s.humanize %>
|
|
15
15
|
<% provider_path = provider_config&.dig(:path_name) || provider %>
|
|
16
16
|
<div class="mt-4 text-center sm:mx-auto sm:w-full sm:max-w-sm">
|
|
17
|
-
<%= form_tag "#{Panda::Core.
|
|
17
|
+
<%= form_tag "#{Panda::Core.config.admin_path}/auth/#{provider_path}", method: "post", data: {turbo: false} do %>
|
|
18
18
|
<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
19
|
<i class="fa-brands fa-<%= oauth_provider_icon(provider) %> text-xl mr-1"></i>
|
|
20
20
|
Sign in with <%= provider_name %>
|
|
@@ -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"></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 fa-regular fa-chevron-right font-light <%= breadcrumb_styles %> py-5 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" data-controller="toggle">
|
|
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,43 @@
|
|
|
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
|
+
<% Panda::Core.config.admin_navigation_items&.call(current_user)&.each do |item| %>
|
|
5
|
+
<li>
|
|
6
|
+
<%
|
|
7
|
+
# Exact match for dashboard, starts_with for others
|
|
8
|
+
# Check if current path matches this nav item
|
|
9
|
+
is_active = if request.path == item[:path]
|
|
10
|
+
true
|
|
11
|
+
elsif request.path.starts_with?(item[:path] + "/")
|
|
12
|
+
true
|
|
13
|
+
else
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
%>
|
|
17
|
+
<%= 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 %>
|
|
18
|
+
<span class="text-center w-6"><i class="<%= item[:icon] %> text-xl fa-fw"></i></span>
|
|
19
|
+
<span><%= item[:label] %></span>
|
|
12
20
|
<% end %>
|
|
13
|
-
</
|
|
21
|
+
</li>
|
|
22
|
+
<% end %>
|
|
23
|
+
<li>
|
|
24
|
+
<%= 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 %>
|
|
25
|
+
<span class="text-center w-6"><i class="text-xl fa-solid fa-door-open fa-fw"></i></span>
|
|
26
|
+
<span>Logout</span>
|
|
27
|
+
<% end %>
|
|
14
28
|
</li>
|
|
15
|
-
|
|
16
29
|
<li class="mt-auto">
|
|
17
|
-
|
|
18
|
-
<% if current_user %>
|
|
19
|
-
<span class="
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<% end %>
|
|
30
|
+
<%= 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 %>
|
|
31
|
+
<% if !current_user.image_url.to_s.empty? %>
|
|
32
|
+
<span class="text-center w-6"><img src="<%= current_user.image_url %>" class="w-auto h-7 rounded-full"></span>
|
|
33
|
+
<% else %>
|
|
34
|
+
<span class="text-center w-6"><i class="text-xl fa-regular fa-circle-user fa-fw"></i></span>
|
|
23
35
|
<% end %>
|
|
24
|
-
|
|
36
|
+
<span><%= current_user.name %></span>
|
|
37
|
+
<% end %>
|
|
38
|
+
</li>
|
|
39
|
+
<li class="px-2 py-3">
|
|
40
|
+
<span class="text-xs text-white">Panda Core v<%= Panda::Core::VERSION %></span>
|
|
25
41
|
</li>
|
|
26
42
|
</ul>
|
|
27
|
-
</nav>
|
|
43
|
+
</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,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
pin "
|
|
5
|
-
pin "
|
|
6
|
-
|
|
7
|
-
pin "tailwindcss-stimulus-components" # @6.1.3
|
|
3
|
+
# Panda Core application and controllers
|
|
4
|
+
pin "panda/core/application", to: "panda/core/application.js"
|
|
5
|
+
pin "panda/core/controllers/index", to: "panda/core/controllers/index.js"
|
|
6
|
+
pin_all_from Panda::Core::Engine.root.join("app/javascript/panda/core/controllers"), under: "panda/core/controllers"
|
|
8
7
|
|
|
9
|
-
#
|
|
8
|
+
# Base JavaScript dependencies for Panda Core (vendored for reliability)
|
|
9
|
+
pin "@hotwired/stimulus", to: "panda/core/vendor/@hotwired--stimulus.js", preload: true # @3.2.2
|
|
10
|
+
pin "@hotwired/turbo", to: "panda/core/vendor/@hotwired--turbo.js", preload: true # @8.0.18
|
|
11
|
+
pin "@rails/actioncable/src", to: "panda/core/vendor/@rails--actioncable--src.js", preload: true # @8.0.201
|
|
12
|
+
pin "tailwindcss-stimulus-components", to: "panda/core/tailwindcss-stimulus-components.js" # @6.1.3
|
|
13
|
+
|
|
14
|
+
# Font Awesome icons (from CDN)
|
|
10
15
|
pin "@fortawesome/fontawesome-free", to: "https://ga.jspm.io/npm:@fortawesome/fontawesome-free@7.1.0/js/all.js"
|
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,6 @@ Panda::Core::Engine.routes.draw do
|
|
|
17
17
|
get "/", to: "admin/dashboard#show", as: :root
|
|
18
18
|
|
|
19
19
|
# Profile management
|
|
20
|
-
|
|
21
|
-
resource :my_profile, only: %i[edit update], controller: "admin/my_profile", path: "my_profile"
|
|
22
|
-
end
|
|
20
|
+
resource :my_profile, only: %i[edit update], controller: "admin/my_profile", path: "my_profile"
|
|
23
21
|
end
|
|
24
22
|
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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Debug
|
|
6
|
+
class << self
|
|
7
|
+
# Check if debug mode is enabled via PANDA_DEBUG environment variable
|
|
8
|
+
def enabled?
|
|
9
|
+
ENV["PANDA_DEBUG"].to_s.downcase == "true" || ENV["PANDA_DEBUG"] == "1"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Log a debug message if debug mode is enabled
|
|
13
|
+
def log(message, prefix: "PANDA")
|
|
14
|
+
return unless enabled?
|
|
15
|
+
|
|
16
|
+
timestamp = Time.current.strftime("%Y-%m-%d %H:%M:%S")
|
|
17
|
+
puts "[#{prefix} DEBUG #{timestamp}] #{message}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Log an object with pretty printing (using awesome_print if available)
|
|
21
|
+
def inspect(object, label: nil, prefix: "PANDA")
|
|
22
|
+
return unless enabled?
|
|
23
|
+
|
|
24
|
+
timestamp = Time.current.strftime("%Y-%m-%d %H:%M:%S")
|
|
25
|
+
header = label ? "#{label}: " : ""
|
|
26
|
+
|
|
27
|
+
puts "\n[#{prefix} DEBUG #{timestamp}] #{header}"
|
|
28
|
+
if defined?(AwesomePrint)
|
|
29
|
+
ap object
|
|
30
|
+
else
|
|
31
|
+
pp object
|
|
32
|
+
end
|
|
33
|
+
puts
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Enable HTTP debugging for Net::HTTP requests
|
|
37
|
+
def enable_http_debug!
|
|
38
|
+
return unless enabled? || ENV["DEBUG_HTTP"].to_s.downcase == "true"
|
|
39
|
+
|
|
40
|
+
require "net/http"
|
|
41
|
+
Net::HTTP.set_debug_output($stdout)
|
|
42
|
+
log("HTTP debugging enabled - all HTTP requests will be logged")
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/panda/core/engine.rb
CHANGED
|
@@ -13,6 +13,8 @@ module Panda
|
|
|
13
13
|
config.eager_load_namespaces << Panda::Core::Engine
|
|
14
14
|
|
|
15
15
|
# Add engine's app directories to autoload paths
|
|
16
|
+
# Note: Only add the root directories, not nested subdirectories
|
|
17
|
+
# Zeitwerk will automatically discover nested modules from these roots
|
|
16
18
|
config.autoload_paths += Dir[root.join("app", "models")]
|
|
17
19
|
config.autoload_paths += Dir[root.join("app", "controllers")]
|
|
18
20
|
config.autoload_paths += Dir[root.join("app", "builders")]
|
|
@@ -43,7 +45,7 @@ module Panda
|
|
|
43
45
|
end
|
|
44
46
|
end
|
|
45
47
|
|
|
46
|
-
initializer "panda_core.
|
|
48
|
+
initializer "panda_core.config" do |app|
|
|
47
49
|
# Configuration is already initialized with defaults in Configuration class
|
|
48
50
|
end
|
|
49
51
|
|
|
@@ -64,18 +66,18 @@ module Panda
|
|
|
64
66
|
end
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
|
|
68
69
|
initializer "panda_core.omniauth" do |app|
|
|
69
70
|
# Mount OmniAuth at configurable admin path
|
|
70
71
|
app.middleware.use OmniAuth::Builder do
|
|
71
72
|
# Configure OmniAuth to use the configured admin path
|
|
72
73
|
configure do |config|
|
|
73
|
-
config.path_prefix = "#{Panda::Core.
|
|
74
|
-
#
|
|
75
|
-
|
|
74
|
+
config.path_prefix = "#{Panda::Core.config.admin_path}/auth"
|
|
75
|
+
# POST-only for CSRF protection (CVE-2015-9284)
|
|
76
|
+
# All login forms use POST via form_tag method: "post"
|
|
77
|
+
config.allowed_request_methods = [:post]
|
|
76
78
|
end
|
|
77
79
|
|
|
78
|
-
Panda::Core.
|
|
80
|
+
Panda::Core.config.authentication_providers.each do |provider_name, settings|
|
|
79
81
|
# Build provider options, allowing custom path name override
|
|
80
82
|
provider_options = settings[:options] || {}
|
|
81
83
|
|
|
@@ -97,6 +99,41 @@ module Panda
|
|
|
97
99
|
end
|
|
98
100
|
end
|
|
99
101
|
end
|
|
102
|
+
|
|
103
|
+
# Load Phlex base component after Rails application is initialized
|
|
104
|
+
# This ensures Rails.application.routes is available
|
|
105
|
+
initializer "panda_core.phlex_base", after: :load_config_initializers do
|
|
106
|
+
require "phlex"
|
|
107
|
+
require "phlex-rails"
|
|
108
|
+
require "literal"
|
|
109
|
+
require "tailwind_merge"
|
|
110
|
+
|
|
111
|
+
# Load the base component
|
|
112
|
+
require root.join("app/components/panda/core/base")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Set up ViewComponent and Lookbook previews
|
|
116
|
+
initializer "panda_core.view_component" do |app|
|
|
117
|
+
app.config.view_component.preview_paths ||= []
|
|
118
|
+
app.config.view_component.preview_paths << root.join("spec/components/previews")
|
|
119
|
+
|
|
120
|
+
# Add preview directories to autoload paths in development
|
|
121
|
+
if Rails.env.development?
|
|
122
|
+
# Handle frozen autoload_paths array
|
|
123
|
+
if app.config.autoload_paths.frozen?
|
|
124
|
+
app.config.autoload_paths = app.config.autoload_paths.dup
|
|
125
|
+
end
|
|
126
|
+
app.config.autoload_paths << root.join("spec/components/previews")
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Create AdminController alias after controllers are loaded
|
|
131
|
+
# This allows other gems to inherit from Panda::Core::AdminController
|
|
132
|
+
initializer "panda_core.admin_controller_alias", after: :load_config_initializers do
|
|
133
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
134
|
+
Panda::Core.const_set(:AdminController, Panda::Core::Admin::BaseController) unless Panda::Core.const_defined?(:AdminController)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
100
137
|
end
|
|
101
138
|
end
|
|
102
139
|
end
|
data/lib/panda/core/version.rb
CHANGED