panda-core 0.1.16 → 0.2.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/README.md +9 -16
- data/Rakefile +3 -0
- data/app/builders/panda/core/form_builder.rb +225 -0
- data/app/components/panda/core/admin/button_component.rb +70 -0
- data/app/components/panda/core/admin/container_component.html.erb +12 -0
- data/app/components/panda/core/admin/container_component.rb +13 -0
- data/app/components/panda/core/admin/flash_message_component.html.erb +31 -0
- data/app/components/panda/core/admin/flash_message_component.rb +47 -0
- data/app/components/panda/core/admin/heading_component.rb +46 -0
- data/app/components/panda/core/admin/panel_component.html.erb +7 -0
- data/app/components/panda/core/admin/panel_component.rb +13 -0
- data/app/components/panda/core/admin/slideover_component.html.erb +9 -0
- data/app/components/panda/core/admin/slideover_component.rb +15 -0
- data/app/components/panda/core/admin/table_component.html.erb +29 -0
- data/app/components/panda/core/admin/table_component.rb +46 -0
- data/app/components/panda/core/admin/tag_component.rb +35 -0
- data/app/constraints/panda/core/admin_constraint.rb +14 -0
- data/app/controllers/panda/core/admin/dashboard_controller.rb +22 -0
- data/app/controllers/panda/core/admin/my_profile_controller.rb +49 -0
- data/app/controllers/panda/core/admin/sessions_controller.rb +69 -0
- data/app/controllers/panda/core/admin_controller.rb +28 -0
- data/app/controllers/panda/core/application_controller.rb +59 -0
- data/app/helpers/panda/core/asset_helper.rb +32 -0
- data/app/javascript/panda/core/application.js +9 -0
- data/app/javascript/panda/core/controllers/index.js +20 -0
- data/app/javascript/panda/core/controllers/theme_form_controller.js +25 -0
- data/app/javascript/panda/core/tailwindcss-stimulus-components.js +3 -0
- data/app/models/panda/core/application_record.rb +9 -0
- data/app/models/panda/core/breadcrumb.rb +17 -0
- data/app/models/panda/core/current.rb +16 -0
- data/app/models/panda/core/user.rb +51 -0
- data/app/views/layouts/panda/core/admin.html.erb +59 -0
- data/app/views/panda/core/admin/dashboard/show.html.erb +27 -0
- data/app/views/panda/core/admin/my_profile/edit.html.erb +49 -0
- data/app/views/panda/core/admin/sessions/new.html.erb +38 -0
- data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +35 -0
- data/app/views/panda/core/admin/shared/_flash.html.erb +31 -0
- data/app/views/panda/core/admin/shared/_sidebar.html.erb +27 -0
- data/app/views/panda/core/admin/shared/_slideover.html.erb +33 -0
- data/config/routes.rb +22 -0
- data/db/migrate/20241210000003_add_current_theme_to_panda_core_users.rb +7 -0
- data/db/migrate/20250809000001_create_panda_core_users.rb +16 -0
- data/lib/generators/panda/core/dev_tools/USAGE +24 -0
- data/lib/generators/panda/core/dev_tools/templates/lefthook.yml +13 -0
- data/lib/generators/panda/core/dev_tools/templates/spec_support_panda_core_helpers.rb +18 -0
- data/lib/generators/panda/core/dev_tools_generator.rb +143 -0
- data/lib/panda/core/asset_loader.rb +221 -0
- data/lib/panda/core/authentication.rb +36 -0
- data/lib/panda/core/component_registry.rb +37 -0
- data/lib/panda/core/configuration.rb +31 -1
- data/lib/panda/core/engine.rb +43 -7
- data/lib/panda/core/notifications.rb +40 -0
- data/lib/panda/core/rake_tasks.rb +16 -0
- data/lib/panda/core/subscribers/authentication_subscriber.rb +61 -0
- data/lib/panda/core/testing/capybara_config.rb +70 -0
- data/lib/panda/core/testing/omniauth_helpers.rb +52 -0
- data/lib/panda/core/testing/rspec_config.rb +72 -0
- data/lib/panda/core/version.rb +1 -1
- data/lib/panda/core.rb +2 -8
- data/lib/tasks/assets.rake +423 -0
- data/lib/tasks/panda/core/migrations.rake +13 -0
- data/lib/tasks/panda_core.rake +52 -0
- metadata +320 -11
- data/db/migrate/20250121012333_logidze_install.rb +0 -577
- data/db/migrate/20250121012334_enable_hstore.rb +0 -5
@@ -0,0 +1,35 @@
|
|
1
|
+
<% if defined?(@breadcrumbs) && @breadcrumbs.any? %>
|
2
|
+
<div class="flex">
|
3
|
+
<nav class="flex-1 px-4 py-3 text-gray-700 border-b border-gray-200 bg-gray-50" aria-label="Breadcrumb" id="panda-breadcrumbs">
|
4
|
+
<ol class="inline-flex items-center space-x-1 md:space-x-3">
|
5
|
+
<% @breadcrumbs.each_with_index do |breadcrumb, index| %>
|
6
|
+
<li class="inline-flex items-center">
|
7
|
+
<% if index > 0 %>
|
8
|
+
<svg class="w-3 h-3 text-gray-400 mx-1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
9
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
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" %>
|
31
|
+
</a>
|
32
|
+
</div>
|
33
|
+
<% end %>
|
34
|
+
</div>
|
35
|
+
<% end %>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<% if flash.any? %>
|
2
|
+
<div class="fixed top-4 right-4 z-50 space-y-2">
|
3
|
+
<% flash.each do |type, message| %>
|
4
|
+
<div class="bg-white rounded-lg shadow-lg p-4 max-w-sm" data-controller="flash" data-flash-delay-value="5000">
|
5
|
+
<div class="flex items-start">
|
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 %>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<nav class="flex flex-col flex-1">
|
2
|
+
<ul role="list" class="flex flex-col flex-1 gap-y-7">
|
3
|
+
<li>
|
4
|
+
<ul role="list" class="-mx-2 space-y-1">
|
5
|
+
<% Panda::Core.configuration.admin_navigation_items&.call(current_user)&.each do |item| %>
|
6
|
+
<li>
|
7
|
+
<%= link_to item[:path], class: "group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold text-gray-200 hover:text-white hover:bg-gray-700" do %>
|
8
|
+
<i class="<%= item[:icon] %> text-gray-200 group-hover:text-white"></i>
|
9
|
+
<%= item[:label] %>
|
10
|
+
<% end %>
|
11
|
+
</li>
|
12
|
+
<% end %>
|
13
|
+
</ul>
|
14
|
+
</li>
|
15
|
+
|
16
|
+
<li class="mt-auto">
|
17
|
+
<div class="flex items-center gap-x-4 px-6 py-3 text-sm font-semibold leading-6 text-gray-200">
|
18
|
+
<% if current_user %>
|
19
|
+
<span class="flex-1"><%= "#{current_user.firstname} #{current_user.lastname}" %></span>
|
20
|
+
<%= link_to panda_core.admin_logout_path, method: :delete, class: "text-gray-200 hover:text-white" do %>
|
21
|
+
<i class="fa-regular fa-sign-out"></i>
|
22
|
+
<% end %>
|
23
|
+
<% end %>
|
24
|
+
</div>
|
25
|
+
</li>
|
26
|
+
</ul>
|
27
|
+
</nav>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<div class="fixed inset-0 z-50 lg:hidden" role="dialog" aria-modal="true" id="mobile-slideover">
|
2
|
+
<div class="fixed inset-0 bg-gray-600 bg-opacity-75"></div>
|
3
|
+
<div class="fixed inset-0 flex">
|
4
|
+
<div class="relative flex w-full max-w-xs flex-1 flex-col bg-gray-800">
|
5
|
+
<div class="absolute top-0 right-0 -mr-12 pt-2">
|
6
|
+
<button type="button" class="ml-1 flex h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
7
|
+
<span class="sr-only">Close sidebar</span>
|
8
|
+
<svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
9
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
10
|
+
</svg>
|
11
|
+
</button>
|
12
|
+
</div>
|
13
|
+
<%= yield %>
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
|
18
|
+
<div class="hidden lg:flex lg:flex-shrink-0" id="slideover" data-toggle-target="content">
|
19
|
+
<div class="flex w-96">
|
20
|
+
<div class="flex min-h-0 flex-1 flex-col bg-gray-50 border-l border-gray-200">
|
21
|
+
<% if content_for?(:sidebar_title) %>
|
22
|
+
<div class="flex h-16 flex-shrink-0 items-center border-b border-gray-200 px-6">
|
23
|
+
<h2 class="text-lg font-medium text-gray-900"><%= yield :sidebar_title %></h2>
|
24
|
+
</div>
|
25
|
+
<% end %>
|
26
|
+
<div class="flex flex-1 flex-col overflow-y-auto">
|
27
|
+
<div class="px-6 py-4">
|
28
|
+
<%= yield %>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Panda::Core::Engine.routes.draw do
|
2
|
+
# Use the configured admin path (defaults to "/admin")
|
3
|
+
admin_path = Panda::Core.configuration.admin_path.delete_prefix("/")
|
4
|
+
|
5
|
+
scope path: admin_path, as: "admin" do
|
6
|
+
get "/login", to: "admin/sessions#new", as: :login
|
7
|
+
|
8
|
+
# OmniAuth routes - middleware handles /admin/auth/:provider automatically
|
9
|
+
# We just need to define the callback routes
|
10
|
+
get "/auth/:provider/callback", to: "admin/sessions#create", as: :auth_callback
|
11
|
+
post "/auth/:provider/callback", to: "admin/sessions#create"
|
12
|
+
get "/auth/failure", to: "admin/sessions#failure", as: :auth_failure
|
13
|
+
delete "/logout", to: "admin/sessions#destroy", as: :logout
|
14
|
+
|
15
|
+
constraints Panda::Core::AdminConstraint.new do
|
16
|
+
get "/", to: "admin/dashboard#show", as: :root
|
17
|
+
|
18
|
+
# Profile management
|
19
|
+
resource :my_profile, only: %i[edit update], controller: "admin/my_profile", path: "my_profile"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CreatePandaCoreUsers < ActiveRecord::Migration[7.1]
|
4
|
+
def change
|
5
|
+
create_table :panda_core_users, id: :uuid do |t|
|
6
|
+
t.string :firstname, null: false
|
7
|
+
t.string :lastname, null: false
|
8
|
+
t.string :email, null: false
|
9
|
+
t.string :image_url
|
10
|
+
t.boolean :admin, default: false, null: false
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
|
14
|
+
add_index :panda_core_users, :email, unique: true
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Description:
|
2
|
+
Sets up or updates Panda Core development tools in your gem or Rails application.
|
3
|
+
This includes linting configurations, testing helpers, and CI/CD workflows.
|
4
|
+
|
5
|
+
Example:
|
6
|
+
# Initial setup
|
7
|
+
rails generate panda:core:dev_tools
|
8
|
+
|
9
|
+
# Force update (overwrites existing files)
|
10
|
+
rails generate panda:core:dev_tools --force
|
11
|
+
|
12
|
+
# Skip adding dependencies to Gemfile
|
13
|
+
rails generate panda:core:dev_tools --skip-dependencies
|
14
|
+
|
15
|
+
This will:
|
16
|
+
- Copy .standard.yml, .yamllint, and .rspec configurations
|
17
|
+
- Set up GitHub Actions workflows for CI and releases
|
18
|
+
- Add development dependencies to your Gemfile
|
19
|
+
- Create testing helper configuration
|
20
|
+
- Add rake tasks for linting and security checks
|
21
|
+
- Track version for future updates
|
22
|
+
|
23
|
+
To check for updates later:
|
24
|
+
bundle exec rake panda:check_updates
|
@@ -0,0 +1,13 @@
|
|
1
|
+
---
|
2
|
+
assert_lefthook_installed: true
|
3
|
+
colors: true
|
4
|
+
pre-commit:
|
5
|
+
parallel: true
|
6
|
+
jobs:
|
7
|
+
- name: bundle-audit
|
8
|
+
run: bundle exec bundle-audit --update
|
9
|
+
- name: bundle-outdated
|
10
|
+
run: bundle outdated --strict
|
11
|
+
- name: standardrb
|
12
|
+
run: bundle exec standardrb --fix {all_files}
|
13
|
+
glob: "**/*.rb"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "panda/core/testing/rspec_config"
|
4
|
+
require "panda/core/testing/omniauth_helpers"
|
5
|
+
require "panda/core/testing/capybara_config"
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
# Apply Panda Core RSpec configuration
|
9
|
+
Panda::Core::Testing::RSpecConfig.configure(config)
|
10
|
+
Panda::Core::Testing::RSpecConfig.setup_matchers
|
11
|
+
|
12
|
+
# Configure Capybara
|
13
|
+
Panda::Core::Testing::CapybaraConfig.configure
|
14
|
+
|
15
|
+
# Include helpers
|
16
|
+
config.include Panda::Core::Testing::OmniAuthHelpers, type: :system
|
17
|
+
config.include Panda::Core::Testing::CapybaraConfig::Helpers, type: :system
|
18
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
|
5
|
+
module Panda
|
6
|
+
module Core
|
7
|
+
module Generators
|
8
|
+
class DevToolsGenerator < Rails::Generators::Base
|
9
|
+
source_root File.expand_path("dev_tools/templates", __dir__)
|
10
|
+
|
11
|
+
desc "Set up or update Panda Core development tools in your gem/application"
|
12
|
+
|
13
|
+
class_option :force, type: :boolean, default: false,
|
14
|
+
desc: "Overwrite existing files"
|
15
|
+
class_option :skip_dependencies, type: :boolean, default: false,
|
16
|
+
desc: "Skip adding dependencies to Gemfile"
|
17
|
+
|
18
|
+
VERSION_FILE = ".panda-dev-tools-version"
|
19
|
+
CURRENT_VERSION = "1.0.0"
|
20
|
+
|
21
|
+
def check_for_updates
|
22
|
+
if File.exist?(VERSION_FILE)
|
23
|
+
installed_version = File.read(VERSION_FILE).strip
|
24
|
+
if installed_version != CURRENT_VERSION
|
25
|
+
say "Updating Panda Core dev tools from #{installed_version} to #{CURRENT_VERSION}", :yellow
|
26
|
+
@updating = true
|
27
|
+
else
|
28
|
+
say "Panda Core dev tools are up to date (#{CURRENT_VERSION})", :green
|
29
|
+
unless options[:force]
|
30
|
+
say "Use --force to reinstall anyway"
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
say "Installing Panda Core dev tools #{CURRENT_VERSION}", :green
|
36
|
+
@updating = false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def copy_linting_configs
|
41
|
+
say "Copying linting configurations..."
|
42
|
+
copy_file ".standard.yml", force: options[:force] || @updating
|
43
|
+
copy_file ".yamllint", force: options[:force] || @updating
|
44
|
+
copy_file ".rspec", force: options[:force] || @updating
|
45
|
+
copy_file "lefthook.yml", force: options[:force] || @updating
|
46
|
+
end
|
47
|
+
|
48
|
+
def copy_github_workflows
|
49
|
+
say "Copying GitHub Actions workflows..."
|
50
|
+
directory ".github", ".github", force: options[:force] || @updating
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_version_file
|
54
|
+
create_file VERSION_FILE, CURRENT_VERSION, force: true
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_development_dependencies
|
58
|
+
say "Adding development dependencies to gemspec..."
|
59
|
+
|
60
|
+
if File.exist?("Gemfile")
|
61
|
+
append_to_file "Gemfile" do
|
62
|
+
<<~RUBY
|
63
|
+
|
64
|
+
group :development, :test do
|
65
|
+
# Panda Core development tools
|
66
|
+
gem "standard"
|
67
|
+
gem "brakeman"
|
68
|
+
gem "bundler-audit"
|
69
|
+
gem "yamllint"
|
70
|
+
end
|
71
|
+
RUBY
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_spec_helper
|
77
|
+
say "Creating spec helper with Panda Core testing configuration..."
|
78
|
+
|
79
|
+
create_file "spec/support/panda_core_helpers.rb" do
|
80
|
+
<<~RUBY
|
81
|
+
# frozen_string_literal: true
|
82
|
+
|
83
|
+
require 'panda/core/testing/rspec_config'
|
84
|
+
require 'panda/core/testing/omniauth_helpers'
|
85
|
+
require 'panda/core/testing/capybara_config'
|
86
|
+
|
87
|
+
RSpec.configure do |config|
|
88
|
+
# Apply Panda Core RSpec configuration
|
89
|
+
Panda::Core::Testing::RSpecConfig.configure(config)
|
90
|
+
Panda::Core::Testing::RSpecConfig.setup_matchers
|
91
|
+
|
92
|
+
# Configure Capybara
|
93
|
+
Panda::Core::Testing::CapybaraConfig.configure
|
94
|
+
|
95
|
+
# Include helpers
|
96
|
+
config.include Panda::Core::Testing::OmniAuthHelpers, type: :system
|
97
|
+
config.include Panda::Core::Testing::CapybaraConfig::Helpers, type: :system
|
98
|
+
end
|
99
|
+
RUBY
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_rake_tasks
|
104
|
+
say "Adding Panda Core rake tasks..."
|
105
|
+
|
106
|
+
append_to_file "Rakefile" do
|
107
|
+
<<~RUBY
|
108
|
+
|
109
|
+
# Panda Core development tasks
|
110
|
+
namespace :panda do
|
111
|
+
desc "Run all linters"
|
112
|
+
task :lint do
|
113
|
+
sh "bundle exec standardrb"
|
114
|
+
sh "yamllint -c .yamllint ."
|
115
|
+
end
|
116
|
+
|
117
|
+
desc "Run security checks"
|
118
|
+
task :security do
|
119
|
+
sh "bundle exec brakeman --quiet"
|
120
|
+
sh "bundle exec bundle-audit --update"
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "Run all quality checks"
|
124
|
+
task quality: [:lint, :security]
|
125
|
+
end
|
126
|
+
RUBY
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def display_instructions
|
131
|
+
say "\n✅ Panda Core development tools have been set up!", :green
|
132
|
+
say "\nNext steps:"
|
133
|
+
say " 1. Run 'bundle install' to install new dependencies"
|
134
|
+
say " 2. Run 'bundle exec rake panda:quality' to check code quality"
|
135
|
+
say " 3. Customize .github/workflows for your gem's needs"
|
136
|
+
say " 4. Add 'require' statements to your spec_helper.rb or rails_helper.rb:"
|
137
|
+
say " require 'support/panda_core_helpers'"
|
138
|
+
say "\nFor more information, see: docs/development_tools.md"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Panda
|
8
|
+
module Core
|
9
|
+
# AssetLoader handles loading compiled assets from GitHub releases
|
10
|
+
# Falls back to local development assets when GitHub assets unavailable
|
11
|
+
class AssetLoader
|
12
|
+
class << self
|
13
|
+
# Generate HTML tags for loading Panda Core assets
|
14
|
+
def asset_tags(options = {})
|
15
|
+
if use_github_assets?
|
16
|
+
github_asset_tags(options)
|
17
|
+
else
|
18
|
+
development_asset_tags(options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get the JavaScript asset URL
|
23
|
+
def javascript_url
|
24
|
+
if use_github_assets?
|
25
|
+
github_javascript_url
|
26
|
+
else
|
27
|
+
development_javascript_url
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the CSS asset URL (if exists)
|
32
|
+
def css_url
|
33
|
+
if use_github_assets?
|
34
|
+
github_css_url
|
35
|
+
else
|
36
|
+
development_css_url
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Check if GitHub-hosted assets should be used
|
41
|
+
def use_github_assets?
|
42
|
+
# Use GitHub assets in production or when explicitly enabled
|
43
|
+
Rails.env.production? ||
|
44
|
+
ENV['PANDA_CORE_USE_GITHUB_ASSETS'] == 'true' ||
|
45
|
+
!development_assets_available? ||
|
46
|
+
((Rails.env.test? || in_test_environment?) && compiled_assets_available?)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def github_asset_tags(_options = {})
|
52
|
+
version = asset_version
|
53
|
+
base_url = github_base_url(version)
|
54
|
+
|
55
|
+
tags = []
|
56
|
+
|
57
|
+
# JavaScript tag with integrity check
|
58
|
+
js_url = "#{base_url}panda-core-#{version}.js"
|
59
|
+
|
60
|
+
js_attrs = {
|
61
|
+
src: js_url
|
62
|
+
}
|
63
|
+
# In CI environment, don't use defer to ensure immediate execution
|
64
|
+
js_attrs[:defer] = true unless ENV['GITHUB_ACTIONS'] == 'true'
|
65
|
+
|
66
|
+
tags << content_tag(:script, '', js_attrs)
|
67
|
+
|
68
|
+
# CSS tag if CSS bundle exists
|
69
|
+
css_url = "#{base_url}panda-core-#{version}.css"
|
70
|
+
if github_asset_exists?(version, "panda-core-#{version}.css")
|
71
|
+
css_attrs = {
|
72
|
+
rel: 'stylesheet',
|
73
|
+
href: css_url
|
74
|
+
}
|
75
|
+
tags << tag(:link, css_attrs)
|
76
|
+
end
|
77
|
+
|
78
|
+
tags.join("\n").html_safe
|
79
|
+
end
|
80
|
+
|
81
|
+
def development_asset_tags(_options = {})
|
82
|
+
# In test environment with CI, always use compiled assets
|
83
|
+
if (Rails.env.test? || ENV['CI'].present?) && compiled_assets_available?
|
84
|
+
# Use the same logic as GitHub assets but with local paths
|
85
|
+
version = asset_version
|
86
|
+
js_url = "/panda-core-assets/panda-core-#{version}.js"
|
87
|
+
css_url = "/panda-core-assets/panda-core-#{version}.css"
|
88
|
+
|
89
|
+
tags = []
|
90
|
+
|
91
|
+
# JavaScript tag
|
92
|
+
js_attrs = {
|
93
|
+
src: js_url
|
94
|
+
}
|
95
|
+
js_attrs[:defer] = true unless ENV['GITHUB_ACTIONS'] == 'true'
|
96
|
+
tags << content_tag(:script, '', js_attrs)
|
97
|
+
|
98
|
+
# CSS tag if exists
|
99
|
+
if File.exist?(Rails.root.join('public', 'panda-core-assets', "panda-core-#{version}.css"))
|
100
|
+
css_attrs = {
|
101
|
+
rel: 'stylesheet',
|
102
|
+
href: css_url
|
103
|
+
}
|
104
|
+
tags << tag(:link, css_attrs)
|
105
|
+
end
|
106
|
+
|
107
|
+
else
|
108
|
+
# Development mode - use importmap
|
109
|
+
tags = []
|
110
|
+
tags << javascript_include_tag('panda/core/application', type: 'module')
|
111
|
+
end
|
112
|
+
tags.join("\n").html_safe
|
113
|
+
end
|
114
|
+
|
115
|
+
def github_javascript_url
|
116
|
+
version = asset_version
|
117
|
+
# In test environment with local compiled assets, use local URL
|
118
|
+
if Rails.env.test? && compiled_assets_available?
|
119
|
+
"/panda-core-assets/panda-core-#{version}.js"
|
120
|
+
else
|
121
|
+
"#{github_base_url(version)}panda-core-#{version}.js"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def github_css_url
|
126
|
+
version = asset_version
|
127
|
+
# In test environment with local compiled assets, use local URL
|
128
|
+
if Rails.env.test? && compiled_assets_available?
|
129
|
+
css_file = "/panda-core-assets/panda-core-#{version}.css"
|
130
|
+
File.exist?(Rails.root.join("public#{css_file}")) ? css_file : nil
|
131
|
+
else
|
132
|
+
"#{github_base_url(version)}panda-core-#{version}.css"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def development_javascript_url
|
137
|
+
if compiled_assets_available?
|
138
|
+
version = asset_version
|
139
|
+
"/panda-core-assets/panda-core-#{version}.js"
|
140
|
+
else
|
141
|
+
# Return importmap path
|
142
|
+
'/assets/panda/core/application.js'
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def development_css_url
|
147
|
+
return unless compiled_assets_available?
|
148
|
+
|
149
|
+
version = asset_version
|
150
|
+
css_file = "/panda-core-assets/panda-core-#{version}.css"
|
151
|
+
File.exist?(Rails.root.join("public#{css_file}")) ? css_file : nil
|
152
|
+
end
|
153
|
+
|
154
|
+
def asset_version
|
155
|
+
Panda::Core::VERSION
|
156
|
+
end
|
157
|
+
|
158
|
+
def github_base_url(version)
|
159
|
+
"https://github.com/tastybamboo/panda-core/releases/download/v#{version}/"
|
160
|
+
end
|
161
|
+
|
162
|
+
def github_asset_exists?(_version, _filename)
|
163
|
+
# For now, assume assets exist if we're using GitHub mode
|
164
|
+
# Could implement actual checking via GitHub API
|
165
|
+
true
|
166
|
+
end
|
167
|
+
|
168
|
+
def development_assets_available?
|
169
|
+
# Check if we're in a development environment with importmap available
|
170
|
+
Rails.env.development? && defined?(Importmap)
|
171
|
+
end
|
172
|
+
|
173
|
+
def compiled_assets_available?
|
174
|
+
# Check if compiled assets exist in test location
|
175
|
+
version = asset_version
|
176
|
+
js_file = Rails.public_path.join('panda-core-assets', "panda-core-#{version}.js")
|
177
|
+
js_file.exist?
|
178
|
+
end
|
179
|
+
|
180
|
+
def in_test_environment?
|
181
|
+
# Additional check for test environment indicators
|
182
|
+
ENV['CI'].present? || ENV['GITHUB_ACTIONS'].present? || ENV['RAILS_ENV'] == 'test'
|
183
|
+
end
|
184
|
+
|
185
|
+
# Helper methods to match ActionView helpers
|
186
|
+
def content_tag(name, content = nil, options = {})
|
187
|
+
attrs = options.map do |k, v|
|
188
|
+
if v == true
|
189
|
+
k.to_s
|
190
|
+
elsif v
|
191
|
+
"#{k}=\"#{v}\""
|
192
|
+
end
|
193
|
+
end.compact.join(' ')
|
194
|
+
|
195
|
+
if content || block_given?
|
196
|
+
"<#{name}#{attrs.present? ? " #{attrs}" : ''}>#{content || (block_given? ? yield : '')}</#{name}>"
|
197
|
+
else
|
198
|
+
"<#{name}#{attrs.present? ? " #{attrs}" : ''}><#{name}>"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def tag(name, options = {})
|
203
|
+
attrs = options.map do |k, v|
|
204
|
+
if v == true
|
205
|
+
k.to_s
|
206
|
+
elsif v
|
207
|
+
"#{k}=\"#{v}\""
|
208
|
+
end
|
209
|
+
end.compact.join(' ')
|
210
|
+
|
211
|
+
"<#{name}#{attrs.present? ? " #{attrs}" : ''} />"
|
212
|
+
end
|
213
|
+
|
214
|
+
def javascript_include_tag(source, options = {})
|
215
|
+
options[:src] = source.start_with?('/') ? source : "/assets/#{source}"
|
216
|
+
content_tag(:script, '', options)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Panda
|
4
|
+
module Core
|
5
|
+
module Authentication
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
helper_method :current_user if respond_to?(:helper_method)
|
10
|
+
end
|
11
|
+
|
12
|
+
def current_user
|
13
|
+
return @current_user if defined?(@current_user)
|
14
|
+
@current_user = User.find_by(id: session[:user_id]) if session[:user_id]
|
15
|
+
end
|
16
|
+
|
17
|
+
def require_authentication
|
18
|
+
redirect_to admin_login_path unless current_user
|
19
|
+
end
|
20
|
+
|
21
|
+
def require_admin
|
22
|
+
redirect_to admin_login_path unless current_user&.admin?
|
23
|
+
end
|
24
|
+
|
25
|
+
def sign_in(user)
|
26
|
+
session[:user_id] = user.id
|
27
|
+
@current_user = user
|
28
|
+
end
|
29
|
+
|
30
|
+
def sign_out
|
31
|
+
session.delete(:user_id)
|
32
|
+
@current_user = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Panda
|
4
|
+
module Core
|
5
|
+
class ComponentRegistry
|
6
|
+
class << self
|
7
|
+
def components
|
8
|
+
@components ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register(name, component_class)
|
12
|
+
components[name.to_sym] = component_class
|
13
|
+
end
|
14
|
+
|
15
|
+
def unregister(name)
|
16
|
+
components.delete(name.to_sym)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(name)
|
20
|
+
components[name.to_sym]
|
21
|
+
end
|
22
|
+
|
23
|
+
def all
|
24
|
+
components
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear!
|
28
|
+
@components = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def registered?(name)
|
32
|
+
components.key?(name.to_sym)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|