panda-core 0.12.5 → 1.1.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.
Files changed (251) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +53 -0
  4. data/app/assets/builds/panda_core_admin.css +2 -0
  5. data/app/assets/tailwind/application.css +236 -36
  6. data/app/builders/panda/core/form_builder.rb +120 -18
  7. data/app/components/panda/core/UI/badge.html.erb +10 -0
  8. data/app/components/panda/core/UI/badge.rb +14 -30
  9. data/app/components/panda/core/UI/button.html.erb +5 -0
  10. data/app/components/panda/core/UI/button.rb +11 -15
  11. data/app/components/panda/core/UI/card.html.erb +3 -0
  12. data/app/components/panda/core/UI/card.rb +7 -6
  13. data/app/components/panda/core/admin/attribute_diff_component.html.erb +21 -0
  14. data/app/components/panda/core/admin/attribute_diff_component.rb +29 -0
  15. data/app/components/panda/core/admin/breadcrumb_component.html.erb +36 -0
  16. data/app/components/panda/core/admin/breadcrumb_component.rb +12 -64
  17. data/app/components/panda/core/admin/breadcrumbs_component.html.erb +16 -0
  18. data/app/components/panda/core/admin/breadcrumbs_component.rb +26 -0
  19. data/app/components/panda/core/admin/button_component.html.erb +9 -0
  20. data/app/components/panda/core/admin/button_component.rb +36 -31
  21. data/app/components/panda/core/admin/callout_component.html.erb +17 -0
  22. data/app/components/panda/core/admin/callout_component.rb +57 -0
  23. data/app/components/panda/core/admin/code_block_component.html.erb +9 -0
  24. data/app/components/panda/core/admin/code_block_component.rb +23 -0
  25. data/app/components/panda/core/admin/container_component.html.erb +27 -0
  26. data/app/components/panda/core/admin/container_component.rb +11 -77
  27. data/app/components/panda/core/admin/contributors_summary_component.html.erb +15 -0
  28. data/app/components/panda/core/admin/contributors_summary_component.rb +23 -0
  29. data/app/components/panda/core/admin/dashboard_default_content_component.html.erb +70 -0
  30. data/app/components/panda/core/admin/dashboard_default_content_component.rb +32 -0
  31. data/app/components/panda/core/admin/delete_button_component.html.erb +4 -0
  32. data/app/components/panda/core/admin/delete_button_component.rb +41 -0
  33. data/app/components/panda/core/admin/dropdown_button_component.html.erb +28 -0
  34. data/app/components/panda/core/admin/dropdown_button_component.rb +100 -0
  35. data/app/components/panda/core/admin/dropdown_component.html.erb +32 -0
  36. data/app/components/panda/core/admin/dropdown_component.rb +65 -0
  37. data/app/components/panda/core/admin/empty_state_component.html.erb +16 -0
  38. data/app/components/panda/core/admin/empty_state_component.rb +25 -0
  39. data/app/components/panda/core/admin/file_gallery_component.html.erb +58 -0
  40. data/app/components/panda/core/admin/file_gallery_component.rb +26 -98
  41. data/app/components/panda/core/admin/filter_pills_component.html.erb +6 -0
  42. data/app/components/panda/core/admin/filter_pills_component.rb +29 -0
  43. data/app/components/panda/core/admin/flash_message_component.html.erb +11 -0
  44. data/app/components/panda/core/admin/flash_message_component.rb +33 -60
  45. data/app/components/panda/core/admin/form_error_component.html.erb +7 -0
  46. data/app/components/panda/core/admin/form_error_component.rb +8 -16
  47. data/app/components/panda/core/admin/form_footer_component.html.erb +16 -0
  48. data/app/components/panda/core/admin/form_footer_component.rb +65 -0
  49. data/app/components/panda/core/admin/form_input_component.html.erb +1 -0
  50. data/app/components/panda/core/admin/form_input_component.rb +21 -13
  51. data/app/components/panda/core/admin/form_section_component.html.erb +16 -0
  52. data/app/components/panda/core/admin/form_section_component.rb +64 -0
  53. data/app/components/panda/core/admin/form_select_component.html.erb +5 -0
  54. data/app/components/panda/core/admin/form_select_component.rb +25 -26
  55. data/app/components/panda/core/admin/heading_component.html.erb +26 -0
  56. data/app/components/panda/core/admin/heading_component.rb +23 -60
  57. data/app/components/panda/core/admin/login_form_component.html.erb +38 -0
  58. data/app/components/panda/core/admin/login_form_component.rb +28 -0
  59. data/app/components/panda/core/admin/main_layout_component.html.erb +88 -0
  60. data/app/components/panda/core/admin/main_layout_component.rb +19 -0
  61. data/app/components/panda/core/admin/my_profile/connected_account_component.html.erb +41 -0
  62. data/app/components/panda/core/admin/my_profile/connected_account_component.rb +27 -0
  63. data/app/components/panda/core/admin/navigation/item_component.html.erb +34 -0
  64. data/app/components/panda/core/admin/navigation/item_component.rb +69 -0
  65. data/app/components/panda/core/admin/navigation/sub_item_component.html.erb +9 -0
  66. data/app/components/panda/core/admin/navigation/sub_item_component.rb +44 -0
  67. data/app/components/panda/core/admin/navigation/user_menu_component.html.erb +30 -0
  68. data/app/components/panda/core/admin/navigation/user_menu_component.rb +36 -0
  69. data/app/components/panda/core/admin/page_header_component.html.erb +28 -0
  70. data/app/components/panda/core/admin/page_header_component.rb +19 -35
  71. data/app/components/panda/core/admin/pagination_component.html.erb +33 -0
  72. data/app/components/panda/core/admin/pagination_component.rb +56 -0
  73. data/app/components/panda/core/admin/panel_component.html.erb +19 -0
  74. data/app/components/panda/core/admin/panel_component.rb +4 -29
  75. data/app/components/panda/core/admin/prose_panel_component.html.erb +6 -0
  76. data/app/components/panda/core/admin/prose_panel_component.rb +43 -0
  77. data/app/components/panda/core/admin/search_bar_component.html.erb +19 -0
  78. data/app/components/panda/core/admin/search_bar_component.rb +31 -0
  79. data/app/components/panda/core/admin/search_filter_bar_component.html.erb +22 -0
  80. data/app/components/panda/core/admin/search_filter_bar_component.rb +27 -0
  81. data/app/components/panda/core/admin/secret_field_component.html.erb +24 -0
  82. data/app/components/panda/core/admin/secret_field_component.rb +31 -0
  83. data/app/components/panda/core/admin/sidebar_component.html.erb +160 -0
  84. data/app/components/panda/core/admin/sidebar_component.rb +55 -0
  85. data/app/components/panda/core/admin/slideover_component.html.erb +67 -0
  86. data/app/components/panda/core/admin/slideover_component.rb +31 -70
  87. data/app/components/panda/core/admin/statistics_component.html.erb +4 -0
  88. data/app/components/panda/core/admin/statistics_component.rb +6 -8
  89. data/app/components/panda/core/admin/tab_bar_component.html.erb +48 -0
  90. data/app/components/panda/core/admin/tab_bar_component.rb +32 -67
  91. data/app/components/panda/core/admin/table_component.html.erb +82 -0
  92. data/app/components/panda/core/admin/table_component.rb +54 -87
  93. data/app/components/panda/core/admin/tag_badge_component.html.erb +7 -0
  94. data/app/components/panda/core/admin/tag_badge_component.rb +21 -0
  95. data/app/components/panda/core/admin/tag_component.html.erb +1 -0
  96. data/app/components/panda/core/admin/tag_component.rb +51 -20
  97. data/app/components/panda/core/admin/tag_input_component.html.erb +37 -0
  98. data/app/components/panda/core/admin/tag_input_component.rb +22 -0
  99. data/app/components/panda/core/admin/tags_panel_component.html.erb +8 -0
  100. data/app/components/panda/core/admin/tags_panel_component.rb +33 -0
  101. data/app/components/panda/core/admin/user_activity_component.html.erb +15 -0
  102. data/app/components/panda/core/admin/user_activity_component.rb +9 -22
  103. data/app/components/panda/core/admin/user_display_component.html.erb +8 -0
  104. data/app/components/panda/core/admin/user_display_component.rb +19 -22
  105. data/app/components/panda/core/base.rb +35 -42
  106. data/app/components/panda/core/shared/footer_component.html.erb +2 -0
  107. data/app/components/panda/core/shared/footer_component.rb +11 -0
  108. data/app/components/panda/core/shared/header_component.html.erb +32 -0
  109. data/app/components/panda/core/shared/header_component.rb +19 -0
  110. data/app/constraints/panda/core/admin_constraint.rb +2 -2
  111. data/app/controllers/concerns/panda/core/admin_authorization.rb +58 -0
  112. data/app/controllers/concerns/panda/core/authorizable.rb +107 -0
  113. data/app/controllers/panda/core/admin/base_controller.rb +62 -3
  114. data/app/controllers/panda/core/admin/editor/search_controller.rb +17 -0
  115. data/app/controllers/panda/core/admin/feature_flags_controller.rb +35 -0
  116. data/app/controllers/panda/core/admin/file_categories_controller.rb +84 -0
  117. data/app/controllers/panda/core/admin/files_controller.rb +230 -0
  118. data/app/controllers/panda/core/admin/import_sessions_controller.rb +120 -0
  119. data/app/controllers/panda/core/admin/my_profile/logins_controller.rb +24 -0
  120. data/app/controllers/panda/core/admin/my_profile_controller.rb +31 -0
  121. data/app/controllers/panda/core/admin/search_controller.rb +15 -0
  122. data/app/controllers/panda/core/admin/sessions_controller.rb +84 -10
  123. data/app/controllers/panda/core/admin/tags_controller.rb +77 -0
  124. data/app/controllers/panda/core/admin/test_sessions_controller.rb +5 -6
  125. data/app/controllers/panda/core/admin/users_controller.rb +184 -0
  126. data/app/controllers/panda/core/application_controller.rb +13 -1
  127. data/app/helpers/panda/core/active_storage_helper.rb +24 -0
  128. data/app/helpers/panda/core/feature_flag_helper.rb +11 -0
  129. data/app/helpers/panda/core/form_helper.rb +15 -0
  130. data/app/javascript/panda/core/controllers/avatar_upload_controller.js +73 -0
  131. data/app/javascript/panda/core/controllers/clipboard_controller.js +119 -0
  132. data/app/javascript/panda/core/controllers/collapsible_item_controller.js +45 -0
  133. data/app/javascript/panda/core/controllers/custom_select_controller.js +256 -0
  134. data/app/javascript/panda/core/controllers/datepicker_controller.js +121 -0
  135. data/app/javascript/panda/core/controllers/daterange_controller.js +193 -0
  136. data/app/javascript/panda/core/controllers/file_gallery_controller.js +134 -0
  137. data/app/javascript/panda/core/controllers/file_upload_controller.js +165 -0
  138. data/app/javascript/panda/core/controllers/global_search_controller.js +184 -0
  139. data/app/javascript/panda/core/controllers/index.js +36 -0
  140. data/app/javascript/panda/core/controllers/mobile_sidebar_controller.js +58 -0
  141. data/app/javascript/panda/core/controllers/navigation_toggle_controller.js +49 -24
  142. data/app/javascript/panda/core/controllers/row_link_controller.js +27 -0
  143. data/app/javascript/panda/core/controllers/tag_input_controller.js +183 -0
  144. data/app/javascript/panda/core/tailwindcss-stimulus-components.js +2 -3
  145. data/app/javascript/panda/core/vendor/@fortawesome--fontawesome-free@7.2.0.js +8129 -0
  146. data/app/javascript/panda/core/vendor/@hotwired--turbo.js +7197 -100
  147. data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +511 -2
  148. data/app/javascript/panda/core/vendor/@tailwindplus--elements@1.0.22.js +65 -0
  149. data/app/javascript/panda/core/vendor/cropperjs@2.1.0.js +3 -0
  150. data/app/javascript/panda/core/vendor/vanilla-calendar-pro.js +2 -0
  151. data/app/models/concerns/panda/core/has_metadata.rb +164 -0
  152. data/app/models/concerns/panda/core/importable.rb +79 -0
  153. data/app/models/concerns/panda/core/searchable.rb +66 -0
  154. data/app/models/concerns/panda/core/taggable.rb +37 -0
  155. data/app/models/panda/core/feature_flag.rb +56 -0
  156. data/app/models/panda/core/file_categorization.rb +14 -0
  157. data/app/models/panda/core/file_category.rb +45 -0
  158. data/app/models/panda/core/import_session.rb +79 -0
  159. data/app/models/panda/core/presence.rb +24 -0
  160. data/app/models/panda/core/tag.rb +33 -0
  161. data/app/models/panda/core/tagging.rb +14 -0
  162. data/app/models/panda/core/user.rb +94 -16
  163. data/app/models/panda/core/user_activity.rb +42 -0
  164. data/app/models/panda/core/user_session.rb +35 -0
  165. data/app/services/panda/core/attach_avatar_service.rb +15 -3
  166. data/app/services/panda/core/csv_import_service.rb +65 -0
  167. data/app/services/panda/core/file_categorizer.rb +52 -0
  168. data/app/services/panda/core/file_parser.rb +76 -0
  169. data/app/services/panda/core/invite_user_service.rb +44 -0
  170. data/app/services/panda/core/presence_service.rb +49 -0
  171. data/app/views/layouts/panda/core/admin.html.erb +6 -76
  172. data/app/views/layouts/panda/core/admin_simple.html.erb +2 -2
  173. data/app/views/panda/core/admin/dashboard/_default_content.html.erb +1 -73
  174. data/app/views/panda/core/admin/dashboard/show.html.erb +12 -15
  175. data/app/views/panda/core/admin/feature_flags/index.html.erb +56 -0
  176. data/app/views/panda/core/admin/file_categories/_form.html.erb +20 -0
  177. data/app/views/panda/core/admin/file_categories/edit.html.erb +5 -0
  178. data/app/views/panda/core/admin/file_categories/index.html.erb +55 -0
  179. data/app/views/panda/core/admin/file_categories/new.html.erb +5 -0
  180. data/app/views/panda/core/admin/files/_file_details.html.erb +156 -0
  181. data/app/views/panda/core/admin/files/index.html.erb +61 -0
  182. data/app/views/panda/core/admin/import_sessions/column_map.html.erb +43 -0
  183. data/app/views/panda/core/admin/import_sessions/index.html.erb +48 -0
  184. data/app/views/panda/core/admin/import_sessions/new.html.erb +37 -0
  185. data/app/views/panda/core/admin/import_sessions/preview.html.erb +51 -0
  186. data/app/views/panda/core/admin/import_sessions/show.html.erb +87 -0
  187. data/app/views/panda/core/admin/my_profile/edit.html.erb +51 -39
  188. data/app/views/panda/core/admin/my_profile/logins/show.html.erb +72 -0
  189. data/app/views/panda/core/admin/sessions/new.html.erb +1 -37
  190. data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +1 -18
  191. data/app/views/panda/core/admin/shared/_sidebar.html.erb +1 -99
  192. data/app/views/panda/core/admin/shared/_slideover.html.erb +3 -3
  193. data/app/views/panda/core/admin/tags/_form.html.erb +21 -0
  194. data/app/views/panda/core/admin/tags/edit.html.erb +4 -0
  195. data/app/views/panda/core/admin/tags/index.html.erb +41 -0
  196. data/app/views/panda/core/admin/tags/new.html.erb +4 -0
  197. data/app/views/panda/core/admin/users/activity.html.erb +90 -0
  198. data/app/views/panda/core/admin/users/edit.html.erb +48 -0
  199. data/app/views/panda/core/admin/users/index.html.erb +161 -0
  200. data/app/views/panda/core/admin/users/sessions.html.erb +103 -0
  201. data/app/views/panda/core/admin/users/show.html.erb +224 -0
  202. data/app/views/panda/core/shared/_footer.html.erb +1 -2
  203. data/app/views/panda/core/shared/_header.html.erb +4 -22
  204. data/config/brakeman.ignore +68 -31
  205. data/config/importmap.rb +13 -11
  206. data/config/routes.rb +49 -2
  207. data/db/migrate/20260202171614_create_panda_core_file_categories.rb +30 -0
  208. data/db/migrate/20260203000001_add_user_management_fields_to_panda_core_users.rb +21 -0
  209. data/db/migrate/20260203000002_create_panda_core_user_activities.rb +24 -0
  210. data/db/migrate/20260203000003_create_panda_core_user_sessions.rb +25 -0
  211. data/db/migrate/20260207100000_create_panda_core_presences.rb +21 -0
  212. data/db/migrate/20260213000001_fix_active_storage_attachments_record_id_type.rb +44 -0
  213. data/db/migrate/20260217000001_create_panda_core_feature_flags.rb +14 -0
  214. data/db/migrate/20260303000001_add_metadata_to_panda_core_users.rb +11 -0
  215. data/db/migrate/20260310000001_create_panda_core_tags.rb +27 -0
  216. data/db/migrate/20260310000002_create_panda_core_import_sessions.rb +29 -0
  217. data/lib/generators/panda/core/install_generator.rb +32 -0
  218. data/lib/generators/panda/core/templates/panda.rb +63 -0
  219. data/lib/panda/core/asset_loader.rb +5 -2
  220. data/lib/panda/core/authentication.rb +3 -3
  221. data/lib/panda/core/configuration.rb +195 -1
  222. data/lib/panda/core/engine/admin_controller_config.rb +3 -6
  223. data/lib/panda/core/engine/autoload_config.rb +21 -9
  224. data/lib/panda/core/engine/omniauth_config.rb +108 -38
  225. data/lib/panda/core/engine/pending_migrations_check.rb +46 -0
  226. data/lib/panda/core/engine/sqlite_schema_compatibility.rb +36 -0
  227. data/lib/panda/core/engine/{phlex_config.rb → view_component_config.rb} +5 -7
  228. data/lib/panda/core/engine.rb +90 -25
  229. data/lib/panda/core/middleware.rb +35 -0
  230. data/lib/panda/core/navigation_registry.rb +455 -0
  231. data/lib/panda/core/oauth_providers.rb +27 -0
  232. data/lib/panda/core/permission_registry.rb +89 -0
  233. data/lib/panda/core/search_registry.rb +69 -0
  234. data/lib/panda/core/seeds/file_categories.rb +30 -0
  235. data/lib/panda/core/shared/inflections_config.rb +1 -5
  236. data/lib/panda/core/testing/rails_helper.rb +87 -0
  237. data/lib/panda/core/testing/support/authentication_test_helpers.rb +22 -18
  238. data/lib/panda/core/testing/support/system/cuprite_helpers.rb +4 -4
  239. data/lib/panda/core/testing/view_component_test_controller.rb +19 -0
  240. data/lib/panda/core/version.rb +1 -1
  241. data/lib/panda/core/widget_registry.rb +50 -0
  242. data/lib/panda/core.rb +17 -8
  243. data/lib/tasks/panda/core/file_categories.rake +41 -0
  244. data/public/panda-core-assets/panda-core-0.12.5.css +2 -0
  245. data/public/panda-core-assets/panda-core-0.13.0.css +2 -0
  246. data/public/panda-core-assets/panda-core-0.14.0.css +2 -0
  247. data/public/panda-core-assets/panda-core-0.14.4.css +2 -0
  248. data/public/panda-core-assets/panda-core.css +2 -2
  249. data/public/panda-core-assets/vanilla-calendar.css +47 -0
  250. metadata +203 -26
  251. data/lib/panda/core/engine/inflections_config.rb +0 -23
@@ -1,22 +1,4 @@
1
- <!DOCTYPE html>
2
- <html data-theme="<%= Panda::Core::Current&.user&.current_theme || Panda::Core.config.default_theme %>" class="<%= local_assigns[:html_class] || "" %>">
3
- <head>
4
- <title><%= content_for?(:title) ? yield(:title) : (Panda::Core.config.admin_title || "Panda Admin") %></title>
5
- <%= csrf_meta_tags %>
6
- <%= csp_meta_tag %>
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
-
10
- <!-- ES Module Shims polyfill - must load BEFORE importmap -->
11
- <script async src="https://ga.jspm.io/npm:es-module-shims@2.6.2/dist/es-module-shims.js"></script>
12
-
13
- <!-- Panda JavaScript (Core + all registered modules via ModuleRegistry) -->
14
- <%= panda_core_javascript %>
15
-
16
- <% if defined?(Panda::CMS) && controller.class.name.start_with?("Panda::CMS") %>
17
- <%= render "panda/cms/shared/favicons" %>
18
- <% end %>
19
-
20
- <%= yield :head %>
21
- </head>
22
- <body class="overflow-hidden h-full <%= local_assigns[:body_class] || "" %>" data-environment="<%= Rails.env %>">
1
+ <%= render Panda::Core::Shared::HeaderComponent.new(
2
+ html_class: local_assigns[:html_class] || "",
3
+ body_class: local_assigns[:body_class] || ""
4
+ ) %>
@@ -1,51 +1,88 @@
1
1
  {
2
2
  "ignored_warnings": [
3
3
  {
4
- "warning_type": "Command Injection",
5
- "warning_code": 14,
6
- "fingerprint": "29d5b60b583fc98e293f6ac47f153bbf5db48a03963f8c9a7711bd251ecf2d81",
7
- "check_name": "Execute",
8
- "message": "Possible command injection",
9
- "file": "lib/panda/core/testing/support/system/cuprite_helpers.rb",
10
- "line": 78,
11
- "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
12
- "code": "system(\"ffmpeg -y -framerate 8 -pattern_type glob -i '#{File.join(dir, \"frames\")}/*.png' -c:v libx264 -pix_fmt yuv420p '#{capybara_artifacts_dir.join(\"#{example.metadata[:full_description].parameterize}.mp4\")}'\\n\")",
4
+ "warning_type": "Cross-Site Request Forgery",
5
+ "warning_code": 86,
6
+ "fingerprint": "fdf51189b80e32937b18aea1d088b9de2397d9a958f4a961030b37ebeae4e53d",
7
+ "check_name": "ForgerySetting",
8
+ "message": "`protect_from_forgery` should be configured with `with: :exception`",
9
+ "file": "lib/panda/core/testing/view_component_test_controller.rb",
10
+ "line": 7,
11
+ "link": "https://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
12
+ "code": "protect_from_forgery(:with => :null_session)",
13
13
  "render_path": null,
14
14
  "location": {
15
- "type": "method",
16
- "class": "Panda::Core::Testing::CupriteHelpers",
17
- "method": "record_video!"
15
+ "type": "controller",
16
+ "controller": "ViewComponentTestController"
18
17
  },
19
- "user_input": "File.join(dir, \"frames\")",
18
+ "user_input": null,
20
19
  "confidence": "Medium",
21
20
  "cwe_id": [
22
- 77
21
+ 352
23
22
  ],
24
- "note": "This does not use user input and is based on environment variables and the current folder"
23
+ "note": ""
25
24
  },
26
25
  {
27
- "warning_type": "Command Injection",
28
- "warning_code": 14,
29
- "fingerprint": "d3c339f054cbd511956e04eaf8763bc722bd2e4559a1c1fec58749abef9792cd",
30
- "check_name": "Execute",
31
- "message": "Possible command injection",
32
- "file": "lib/panda/core/testing/support/system/chrome_verification.rb",
33
- "line": 43,
34
- "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
35
- "code": "Process.spawn(*[BrowserPath.resolve, v.nil? ? (\"--#{k}\") : (\"--#{k}=#{v}\"), \"about:blank\"], :out => (File::NULL), :err => (File::NULL))",
26
+ "warning_type": "Mass Assignment",
27
+ "warning_code": 105,
28
+ "fingerprint": "7b32da2eebb702da28834b0ac77f1a3801b9abb4b09527ce7726070b5d91ee7a",
29
+ "check_name": "PermitAttributes",
30
+ "message": "Potentially dangerous key allowed for mass assignment",
31
+ "file": "app/controllers/panda/core/admin/users_controller.rb",
32
+ "line": 169,
33
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
34
+ "code": "params.require(:user).permit(:name, :email, :admin, :internal)",
36
35
  "render_path": null,
37
36
  "location": {
38
37
  "type": "method",
39
- "class": "Panda::Core::Testing::Support::System::ChromeVerification",
40
- "method": "s(:self).verify!"
38
+ "class": "Panda::Core::Admin::UsersController",
39
+ "method": "user_params"
41
40
  },
42
- "user_input": "k",
43
- "confidence": "Medium",
41
+ "user_input": ":admin",
42
+ "confidence": "High",
43
+ "cwe_id": [
44
+ 915
45
+ ],
46
+ "note": "Admin-only controller behind authenticate_admin_user! - only admins can modify user roles and metadata"
47
+ },
48
+ {
49
+ "warning_type": "Cross-Site Scripting",
50
+ "warning_code": 2,
51
+ "fingerprint": "46a409fd77a42e7e6db3d8c22a494fa102be16bb04ca613494baf0e3f75710e8",
52
+ "check_name": "CrossSiteScripting",
53
+ "message": "Unescaped parameter value",
54
+ "file": "app/views/panda/core/admin/users/edit.html.erb",
55
+ "line": 41,
56
+ "link": "https://brakemanscanner.org/docs/warning_types/cross-site_scripting/",
57
+ "code": "Panda::Core.config.admin_user_edit_content.call(User.find(params[:id]), f, self)",
58
+ "render_path": null,
59
+ "location": null,
60
+ "user_input": "params[:id]",
61
+ "confidence": "Weak",
62
+ "cwe_id": [
63
+ 79
64
+ ],
65
+ "note": "Hook content is controlled by the host application developer, not user input. The raw call is intentional to render host-provided HTML."
66
+ },
67
+ {
68
+ "warning_type": "Cross-Site Scripting",
69
+ "warning_code": 2,
70
+ "fingerprint": "f60426d4ca96ba191b9a49da3b27193b2dc00fdcb93574037165d98b3dbab0d4",
71
+ "check_name": "CrossSiteScripting",
72
+ "message": "Unescaped parameter value",
73
+ "file": "app/views/panda/core/admin/users/show.html.erb",
74
+ "line": 36,
75
+ "link": "https://brakemanscanner.org/docs/warning_types/cross-site_scripting/",
76
+ "code": "Panda::Core.config.admin_user_show_content.call(User.find(params[:id]), self)",
77
+ "render_path": null,
78
+ "location": null,
79
+ "user_input": "params[:id]",
80
+ "confidence": "Weak",
44
81
  "cwe_id": [
45
- 77
82
+ 79
46
83
  ],
47
- "note": "Chrome verification uses a fixed set of browser flags from CHROME_FLAGS constant; no user input is accepted"
84
+ "note": "Hook content is controlled by the host application developer, not user input. The raw call is intentional to render host-provided HTML."
48
85
  }
49
86
  ],
50
- "brakeman_version": "7.1.1"
87
+ "brakeman_version": "8.0.4"
51
88
  }
data/config/importmap.rb CHANGED
@@ -9,20 +9,22 @@ pin "panda/core/controllers/theme_form_controller", to: "/panda/core/controllers
9
9
  pin "panda/core/controllers/image_cropper_controller", to: "/panda/core/controllers/image_cropper_controller.js"
10
10
  pin "panda/core/tailwindplus-elements", to: "/panda/core/tailwindplus-elements.js"
11
11
 
12
- # Base JavaScript dependencies for Panda Core (vendored for reliability)
12
+ # Base JavaScript dependencies for Panda Core (all vendored for reliability)
13
13
  pin "@hotwired/stimulus", to: "/panda/core/vendor/@hotwired--stimulus.js", preload: true # @3.2.2
14
- pin "@hotwired/turbo", to: "/panda/core/vendor/@hotwired--turbo.js", preload: true # @8.0.18
15
- pin "@rails/actioncable/src", to: "/panda/core/vendor/@rails--actioncable--src.js", preload: true # @8.0.201
14
+ pin "@hotwired/turbo", to: "/panda/core/vendor/@hotwired--turbo.js", preload: true # @8.0.23
15
+ pin "@rails/actioncable/src", to: "/panda/core/vendor/@rails--actioncable--src.js", preload: true # @8.1.300
16
16
  pin "tailwindcss-stimulus-components", to: "/panda/core/tailwindcss-stimulus-components.js" # @6.1.3
17
17
 
18
- # Font Awesome icons (from CDN)
19
- pin "@fortawesome/fontawesome-free", to: "https://ga.jspm.io/npm:@fortawesome/fontawesome-free@7.1.0/js/all.js"
18
+ # Font Awesome icons (vendored)
19
+ pin "@fortawesome/fontawesome-free", to: "/panda/core/vendor/@fortawesome--fontawesome-free@7.2.0.js" # @7.2.0
20
20
 
21
- # Tailwind Plus Elements - Vanilla JS interactive components (from CDN)
21
+ # Tailwind Plus Elements - Vanilla JS interactive components (vendored)
22
22
  # Provides: Autocomplete, Command palette, Dialog, Disclosure, Dropdown menu, Popover, Select, Tabs
23
- # Note: Using esm.sh instead of jsdelivr for better ES module support
24
- pin "@tailwindplus/elements", to: "https://esm.sh/@tailwindplus/elements@1", preload: false
23
+ pin "@tailwindplus/elements", to: "/panda/core/vendor/@tailwindplus--elements@1.0.22.js", preload: false # @1.0.22
25
24
 
26
- # Cropper.js - Image cropping library (via esm.sh for reliable ES module support)
27
- pin "cropperjs", to: "https://esm.sh/cropperjs@1.6.2"
28
- # Note: Cropper.css is loaded separately via stylesheet_link_tag in views that use cropper
25
+ # Cropper.js - Image cropping library (vendored)
26
+ # Note: v2.x uses Web Components — no separate CSS file needed
27
+ pin "cropperjs", to: "/panda/core/vendor/cropperjs@2.1.0.js" # @2.1.0
28
+
29
+ # Vanilla Calendar Pro - Date picker library (vendored)
30
+ pin "vanilla-calendar-pro", to: "/panda/core/vendor/vanilla-calendar-pro.js" # @3.1.0
data/config/routes.rb CHANGED
@@ -16,17 +16,64 @@ Panda::Core::Engine.routes.draw do
16
16
  # Dashboard and admin routes - authentication handled by AdminController
17
17
  get "/", to: "admin/dashboard#show", as: :root
18
18
 
19
+ # Global admin search
20
+ get "/search", to: "admin/search#index", as: :search
21
+
22
+ # Editor search endpoint for link-autocomplete
23
+ namespace :editor, module: "admin/editor" do
24
+ get :search, to: "search#index", as: :search
25
+ end
26
+
19
27
  # Profile management
20
- resource :my_profile, only: %i[show edit update], controller: "admin/my_profile", path: "my_profile"
28
+ resource :my_profile, only: %i[show edit update], controller: "admin/my_profile", path: "my_profile" do
29
+ resource :logins, only: [:show], controller: "admin/my_profile/logins"
30
+ end
21
31
 
22
32
  # Settings
23
33
  resource :settings, only: [:show], controller: "admin/settings"
24
34
 
35
+ # User management
36
+ resources :users, only: %i[index show edit update], controller: "admin/users" do
37
+ member do
38
+ patch :enable
39
+ patch :disable
40
+ get :activity
41
+ get :sessions
42
+ delete "sessions/:session_id", action: :revoke_session, as: :revoke_session
43
+ end
44
+ collection do
45
+ post :invite
46
+ post :bulk_action
47
+ end
48
+ end
49
+
50
+ # File management
51
+ resources :files, controller: "admin/files"
52
+
53
+ # File category management
54
+ resources :file_categories, only: %i[index new create edit update destroy], controller: "admin/file_categories"
55
+
56
+ # Feature flag management
57
+ resources :feature_flags, only: %i[index update], controller: "admin/feature_flags"
58
+
59
+ # Tags
60
+ resources :tags, only: %i[index new create edit update destroy], controller: "admin/tags"
61
+
62
+ # CSV Import
63
+ resources :import_sessions, only: %i[index new create show], controller: "admin/import_sessions" do
64
+ member do
65
+ get :column_map
66
+ patch :update_mapping
67
+ get :preview
68
+ post :import
69
+ end
70
+ end
71
+
25
72
  # Test-only authentication endpoint (available in development and test environments)
26
73
  # This bypasses OAuth for faster, more reliable test execution
27
74
  # Development: Used by Capybara system tests which run Rails server in development mode
28
75
  # Test: Used by controller/request tests
29
- unless Rails.env.production?
76
+ if Rails.env.development? || Rails.env.test?
30
77
  get "/test_login/:user_id", to: "admin/test_sessions#create", as: :test_login
31
78
  post "/test_sessions", to: "admin/test_sessions#create", as: :test_sessions
32
79
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreatePandaCoreFileCategories < ActiveRecord::Migration[7.1]
4
+ def change
5
+ create_table :panda_core_file_categories, id: :uuid do |t|
6
+ t.string :name, null: false
7
+ t.string :slug, null: false
8
+ t.references :parent, type: :uuid, foreign_key: {to_table: :panda_core_file_categories}
9
+ t.boolean :system, null: false, default: false
10
+ t.string :icon
11
+ t.integer :position, null: false, default: 0
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :panda_core_file_categories, :slug, unique: true
16
+ add_index :panda_core_file_categories, :position
17
+
18
+ create_table :panda_core_file_categorizations, id: :uuid do |t|
19
+ t.references :file_category, null: false, type: :uuid,
20
+ foreign_key: {to_table: :panda_core_file_categories}
21
+ t.bigint :blob_id, null: false
22
+ t.timestamps
23
+ end
24
+
25
+ add_index :panda_core_file_categorizations, %i[file_category_id blob_id],
26
+ unique: true, name: :idx_file_categorizations_on_category_and_blob
27
+ add_index :panda_core_file_categorizations, :blob_id
28
+ add_foreign_key :panda_core_file_categorizations, :active_storage_blobs, column: :blob_id
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddUserManagementFieldsToPandaCoreUsers < ActiveRecord::Migration[8.1]
4
+ def change
5
+ change_table :panda_core_users, bulk: true do |t|
6
+ t.boolean :enabled, default: true, null: false
7
+ t.datetime :last_login_at
8
+ t.string :last_login_ip
9
+ t.integer :login_count, default: 0, null: false
10
+ t.uuid :invited_by_id
11
+ t.string :invitation_token
12
+ t.datetime :invitation_sent_at
13
+ t.datetime :invitation_accepted_at
14
+ end
15
+
16
+ add_index :panda_core_users, :invitation_token, unique: true
17
+ add_index :panda_core_users, :invited_by_id
18
+ add_index :panda_core_users, :enabled
19
+ add_foreign_key :panda_core_users, :panda_core_users, column: :invited_by_id
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreatePandaCoreUserActivities < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :panda_core_user_activities, id: :uuid do |t|
6
+ t.uuid :user_id, null: false
7
+ t.string :action, null: false
8
+ t.string :resource_type
9
+ t.uuid :resource_id
10
+ t.jsonb :metadata, default: {}
11
+ t.string :ip_address
12
+ t.string :user_agent
13
+
14
+ t.timestamps
15
+
16
+ t.index :user_id
17
+ t.index [:resource_type, :resource_id]
18
+ t.index :action
19
+ t.index :created_at
20
+ end
21
+
22
+ add_foreign_key :panda_core_user_activities, :panda_core_users, column: :user_id
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreatePandaCoreUserSessions < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :panda_core_user_sessions, id: :uuid do |t|
6
+ t.uuid :user_id, null: false
7
+ t.string :session_id, null: false
8
+ t.string :ip_address
9
+ t.string :user_agent
10
+ t.datetime :last_active_at
11
+ t.boolean :active, default: true, null: false
12
+ t.datetime :revoked_at
13
+ t.uuid :revoked_by_id
14
+
15
+ t.timestamps
16
+
17
+ t.index :session_id, unique: true
18
+ t.index :user_id
19
+ t.index :active
20
+ end
21
+
22
+ add_foreign_key :panda_core_user_sessions, :panda_core_users, column: :user_id
23
+ add_foreign_key :panda_core_user_sessions, :panda_core_users, column: :revoked_by_id, on_delete: :nullify
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreatePandaCorePresences < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :panda_core_presences, id: :uuid do |t|
6
+ t.string :presenceable_type, null: false
7
+ t.uuid :presenceable_id, null: false
8
+ t.uuid :user_id, null: false
9
+ t.datetime :last_seen_at, null: false
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :panda_core_presences, [:presenceable_type, :presenceable_id, :user_id],
14
+ unique: true, name: "index_unique_presence"
15
+ add_index :panda_core_presences, [:presenceable_type, :presenceable_id],
16
+ name: "index_presences_on_presenceable"
17
+ add_index :panda_core_presences, :user_id
18
+ add_index :panda_core_presences, :last_seen_at
19
+ add_foreign_key :panda_core_presences, :panda_core_users, column: :user_id
20
+ end
21
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FixActiveStorageAttachmentsRecordIdType < ActiveRecord::Migration[8.1]
4
+ def up
5
+ return unless table_exists?(:active_storage_attachments)
6
+
7
+ column = columns(:active_storage_attachments).find { |c| c.name == "record_id" }
8
+ return if column.nil? || column.sql_type == "character varying"
9
+
10
+ # Remove orphaned attachments where UUID was silently cast to 0
11
+ execute <<~SQL
12
+ DELETE FROM active_storage_attachments WHERE record_id = 0
13
+ SQL
14
+
15
+ # Drop the unique index before changing the column type
16
+ remove_index :active_storage_attachments,
17
+ name: "index_active_storage_attachments_uniqueness",
18
+ if_exists: true
19
+
20
+ change_column :active_storage_attachments, :record_id, :string, null: false
21
+
22
+ add_index :active_storage_attachments,
23
+ [:record_type, :record_id, :name, :blob_id],
24
+ unique: true,
25
+ name: "index_active_storage_attachments_uniqueness"
26
+ end
27
+
28
+ def down
29
+ return unless table_exists?(:active_storage_attachments)
30
+
31
+ remove_index :active_storage_attachments,
32
+ name: "index_active_storage_attachments_uniqueness",
33
+ if_exists: true
34
+
35
+ change_column :active_storage_attachments, :record_id, :bigint,
36
+ null: false,
37
+ using: "record_id::bigint"
38
+
39
+ add_index :active_storage_attachments,
40
+ [:record_type, :record_id, :name, :blob_id],
41
+ unique: true,
42
+ name: "index_active_storage_attachments_uniqueness"
43
+ end
44
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreatePandaCoreFeatureFlags < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :panda_core_feature_flags, id: :uuid do |t|
6
+ t.string :key, null: false
7
+ t.boolean :enabled, null: false, default: false
8
+ t.string :description
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :panda_core_feature_flags, :key, unique: true
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddMetadataToPandaCoreUsers < ActiveRecord::Migration[8.1]
4
+ def change
5
+ add_column :panda_core_users, :metadata, :jsonb, default: {}, null: false
6
+
7
+ if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
8
+ add_index :panda_core_users, :metadata, using: :gin, name: "index_panda_core_users_on_metadata"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreatePandaCoreTags < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :panda_core_tags, id: :uuid do |t|
6
+ t.string :tenant_type
7
+ t.string :tenant_id
8
+ t.string :name, null: false
9
+ t.string :colour
10
+ t.integer :taggings_count, default: 0, null: false
11
+ t.timestamps
12
+
13
+ t.index [:tenant_type, :tenant_id]
14
+ t.index [:tenant_type, :tenant_id, :name], unique: true, name: :idx_panda_core_tags_on_tenant_and_name
15
+ end
16
+
17
+ create_table :panda_core_taggings, id: :uuid do |t|
18
+ t.references :tag, null: false, type: :uuid, foreign_key: {to_table: :panda_core_tags}
19
+ t.string :taggable_type, null: false
20
+ t.string :taggable_id, null: false
21
+ t.timestamps
22
+
23
+ t.index [:taggable_type, :taggable_id], name: :idx_panda_core_taggings_on_taggable
24
+ t.index [:tag_id, :taggable_type, :taggable_id], unique: true, name: :idx_panda_core_taggings_unique
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreatePandaCoreImportSessions < ActiveRecord::Migration[8.1]
4
+ def change
5
+ create_table :panda_core_import_sessions, id: :uuid do |t|
6
+ t.string :importable_type, null: false
7
+ t.string :tenant_type
8
+ t.bigint :tenant_id
9
+ t.uuid :user_id, null: false
10
+ t.string :status, null: false, default: "pending"
11
+ t.jsonb :column_mapping, default: {}, null: false
12
+ t.integer :total_rows, default: 0, null: false
13
+ t.integer :processed_rows, default: 0, null: false
14
+ t.integer :imported_count, default: 0, null: false
15
+ t.integer :skipped_count, default: 0, null: false
16
+ t.integer :error_count, default: 0, null: false
17
+ t.jsonb :errors_log, default: [], null: false
18
+ t.datetime :started_at
19
+ t.datetime :completed_at
20
+ t.timestamps
21
+
22
+ t.index [:tenant_type, :tenant_id]
23
+ t.index :status
24
+ t.index :user_id
25
+ end
26
+
27
+ add_foreign_key :panda_core_import_sessions, :panda_core_users, column: :user_id
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ desc "Install Panda Core: create the initializer and copy migrations"
10
+
11
+ def copy_initializer
12
+ template "panda.rb", "config/initializers/panda.rb"
13
+ end
14
+
15
+ def copy_migrations
16
+ rake "panda_core:install:migrations"
17
+ end
18
+
19
+ def show_readme
20
+ say ""
21
+ say "Panda Core installed!", :green
22
+ say ""
23
+ say "Next steps:"
24
+ say " 1. Edit config/initializers/panda.rb to configure authentication"
25
+ say " 2. Run: rails db:migrate"
26
+ say " 3. Add gem 'panda-cms' to your Gemfile for CMS functionality"
27
+ say ""
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ Panda::Core.configure do |config|
4
+ # Path prefix for admin routes (e.g. "/admin", "/manage")
5
+ config.admin_path = "/admin"
6
+
7
+ # Title shown on the login page
8
+ config.login_page_title = "Admin"
9
+
10
+ # Title shown in the admin navigation bar
11
+ config.admin_title = "<%= Rails.application.class.module_parent_name %> Admin"
12
+
13
+ # Authentication providers
14
+ # Add the corresponding OmniAuth gem for each provider you enable
15
+ # (e.g. omniauth-google-oauth2, omniauth-github, omniauth-microsoft_graph).
16
+ #
17
+ # In development, GitHub OAuth works out of the box with shared dev credentials
18
+ # (restricted to localhost:3000). For production, add your own credentials via
19
+ # `rails credentials:edit`.
20
+ #
21
+ # The "developer" provider is built into OmniAuth and only active in
22
+ # development — it shows a simple form to enter a name and email.
23
+ #
24
+ # config.authentication_providers = {
25
+ # github: {
26
+ # enabled: true,
27
+ # name: "GitHub",
28
+ # client_id: if Rails.env.development?
29
+ # "Ov23liFMGyVvRrpuvyTT" # Shared Panda dev app (localhost:3000 only)
30
+ # else
31
+ # Rails.application.credentials.dig(:github, :client_id)
32
+ # end,
33
+ # client_secret: if Rails.env.development?
34
+ # "394a7024d7dd9c0ee0c8540768331d59d9e22477"
35
+ # else
36
+ # Rails.application.credentials.dig(:github, :client_secret)
37
+ # end
38
+ # },
39
+ # developer: {
40
+ # enabled: true,
41
+ # name: "Developer Login"
42
+ # }
43
+ # }
44
+
45
+ # Session cookie name
46
+ config.session_token_cookie = :panda_session
47
+
48
+ # User model classes (usually left as defaults)
49
+ config.user_class = "Panda::Core::User"
50
+ config.user_identity_class = "Panda::Core::UserIdentity"
51
+ end
52
+
53
+ # Uncomment after adding gem "panda-cms" to your Gemfile:
54
+ # Panda::CMS.configure do |config|
55
+ # # Require login to view the public site
56
+ # config.require_login_to_view = false
57
+ # end
58
+
59
+ # Uncomment after adding gem "panda-editor" to your Gemfile:
60
+ # Panda::Editor.configure do |config|
61
+ # # Additional EditorJS tools to load
62
+ # # config.editor_js_tools = []
63
+ # end
@@ -161,8 +161,11 @@ module Panda
161
161
  css_files = Dir[assets_dir.join("panda-core-*.css")].reject { |f| File.symlink?(f) }
162
162
 
163
163
  if css_files.any?
164
- # Return the most recently created file
165
- latest = css_files.max_by { |f| File.basename(f)[/\d+/].to_i }
164
+ # Return the file with the highest semantic version
165
+ latest = css_files.max_by { |f|
166
+ version_str = File.basename(f).match(/panda-core-(.+)\.css/)&.captures&.first || "0"
167
+ Gem::Version.new(version_str)
168
+ }
166
169
  return "/panda-core-assets/#{File.basename(latest)}"
167
170
  end
168
171
  else
@@ -11,7 +11,7 @@ module Panda
11
11
 
12
12
  def current_user
13
13
  return @current_user if defined?(@current_user)
14
- @current_user = User.find_by(id: session[:user_id]) if session[:user_id]
14
+ @current_user = User.find_by(id: session[Panda::Core::ADMIN_SESSION_KEY]) if session[Panda::Core::ADMIN_SESSION_KEY]
15
15
  end
16
16
 
17
17
  def require_authentication
@@ -23,12 +23,12 @@ module Panda
23
23
  end
24
24
 
25
25
  def sign_in(user)
26
- session[:user_id] = user.id
26
+ session[Panda::Core::ADMIN_SESSION_KEY] = user.id
27
27
  @current_user = user
28
28
  end
29
29
 
30
30
  def sign_out
31
- session.delete(:user_id)
31
+ session.delete(Panda::Core::ADMIN_SESSION_KEY)
32
32
  @current_user = nil
33
33
  end
34
34
  end