katalyst-koi 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (206) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +23 -0
  4. data/Upgrade.md +6 -0
  5. data/app/assets/builds/koi/admin.css +1 -0
  6. data/app/assets/builds/koi/nav_items.css +1 -0
  7. data/app/assets/config/koi.js +10 -0
  8. data/app/assets/images/koi/application/chevron-right.svg +10 -0
  9. data/app/assets/images/koi/application/glyphicons-halflings-white.png +0 -0
  10. data/app/assets/images/koi/application/glyphicons-halflings.png +0 -0
  11. data/app/assets/images/koi/application/icon-collapse-down.png +0 -0
  12. data/app/assets/images/koi/application/icon-collapse-up.png +0 -0
  13. data/app/assets/images/koi/application/icon-file-doc.png +0 -0
  14. data/app/assets/images/koi/application/icon-file-img.png +0 -0
  15. data/app/assets/images/koi/application/icon-file-pdf.png +0 -0
  16. data/app/assets/images/koi/application/icon-file-ppt.png +0 -0
  17. data/app/assets/images/koi/application/icon-file-unknown.png +0 -0
  18. data/app/assets/images/koi/application/icon-file-xls.png +0 -0
  19. data/app/assets/images/koi/application/icon-file-zip.png +0 -0
  20. data/app/assets/images/koi/application/icon-form-date-picker.png +0 -0
  21. data/app/assets/images/koi/application/icon-form-error.png +0 -0
  22. data/app/assets/images/koi/application/icon-index-sort-ascending.png +0 -0
  23. data/app/assets/images/koi/application/icon-index-sort-descending.png +0 -0
  24. data/app/assets/images/koi/application/icon-index-sort.png +0 -0
  25. data/app/assets/images/koi/application/icon-index-sortable.png +0 -0
  26. data/app/assets/images/koi/application/icon-menu-cursor.png +0 -0
  27. data/app/assets/images/koi/application/icon-overlay-add.png +0 -0
  28. data/app/assets/images/koi/application/icon-overlay-close.png +0 -0
  29. data/app/assets/images/koi/application/icon-sortable.png +0 -0
  30. data/app/assets/images/koi/application/jcrop.gif +0 -0
  31. data/app/assets/images/koi/application/loading.gif +0 -0
  32. data/app/assets/images/koi/application/select-arrow.svg +3 -0
  33. data/app/assets/images/koi/application/select_arrow.png +0 -0
  34. data/app/assets/images/koi/application/sort-ascending.png +0 -0
  35. data/app/assets/images/koi/application/sort-descending.png +0 -0
  36. data/app/assets/javascripts/koi/admin.js +4 -0
  37. data/app/assets/javascripts/koi/controllers/application.js +11 -0
  38. data/app/assets/javascripts/koi/controllers/document_field_controller.js +26 -0
  39. data/app/assets/javascripts/koi/controllers/file_field_controller.js +143 -0
  40. data/app/assets/javascripts/koi/controllers/flash_controller.js +12 -0
  41. data/app/assets/javascripts/koi/controllers/form_request_submit_controller.js +11 -0
  42. data/app/assets/javascripts/koi/controllers/image_field_controller.js +24 -0
  43. data/app/assets/javascripts/koi/controllers/index.js +6 -0
  44. data/app/assets/javascripts/koi/controllers/index_actions_controller.js +61 -0
  45. data/app/assets/javascripts/koi/controllers/keyboard_controller.js +149 -0
  46. data/app/assets/javascripts/koi/controllers/navigation_controller.js +84 -0
  47. data/app/assets/javascripts/koi/controllers/navigation_toggle_controller.js +7 -0
  48. data/app/assets/javascripts/koi/controllers/show_hide_controller.js +25 -0
  49. data/app/assets/javascripts/koi/controllers/sluggable_controller.js +30 -0
  50. data/app/assets/javascripts/koi/controllers/webauthn_authentication_controller.js +23 -0
  51. data/app/assets/javascripts/koi/controllers/webauthn_registration_controller.js +30 -0
  52. data/app/assets/javascripts/koi/utils/transition.js +220 -0
  53. data/app/assets/stylesheets/koi/admin.scss +27 -0
  54. data/app/assets/stylesheets/koi/base/_button.scss +122 -0
  55. data/app/assets/stylesheets/koi/base/_icon.scss +29 -0
  56. data/app/assets/stylesheets/koi/base/_index.scss +18 -0
  57. data/app/assets/stylesheets/koi/base/_input.scss +13 -0
  58. data/app/assets/stylesheets/koi/base/_link.scss +26 -0
  59. data/app/assets/stylesheets/koi/base/_list.scss +11 -0
  60. data/app/assets/stylesheets/koi/base/_typography.scss +160 -0
  61. data/app/assets/stylesheets/koi/components/_actions-group.scss +7 -0
  62. data/app/assets/stylesheets/koi/components/_image-field.scss +33 -0
  63. data/app/assets/stylesheets/koi/components/_index-actions.scss +69 -0
  64. data/app/assets/stylesheets/koi/components/_index-table.scss +91 -0
  65. data/app/assets/stylesheets/koi/components/_index.scss +6 -0
  66. data/app/assets/stylesheets/koi/components/_item-table.scss +33 -0
  67. data/app/assets/stylesheets/koi/components/_pagy.scss +41 -0
  68. data/app/assets/stylesheets/koi/layouts/_banner.scss +7 -0
  69. data/app/assets/stylesheets/koi/layouts/_content.scss +40 -0
  70. data/app/assets/stylesheets/koi/layouts/_flash.scss +41 -0
  71. data/app/assets/stylesheets/koi/layouts/_header.scss +62 -0
  72. data/app/assets/stylesheets/koi/layouts/_index.scss +48 -0
  73. data/app/assets/stylesheets/koi/layouts/_main.scss +23 -0
  74. data/app/assets/stylesheets/koi/layouts/_navigation.scss +156 -0
  75. data/app/assets/stylesheets/koi/layouts/_stack.scss +13 -0
  76. data/app/assets/stylesheets/koi/pages/_index.scss +1 -0
  77. data/app/assets/stylesheets/koi/pages/_login.scss +40 -0
  78. data/app/assets/stylesheets/koi/themes/_content.scss +5 -0
  79. data/app/assets/stylesheets/koi/themes/_govuk.scss +52 -0
  80. data/app/assets/stylesheets/koi/themes/_index.scss +5 -0
  81. data/app/assets/stylesheets/koi/themes/_kpop.scss +5 -0
  82. data/app/assets/stylesheets/koi/themes/_navigation.scss +5 -0
  83. data/app/assets/stylesheets/koi/themes/_trix.scss +32 -0
  84. data/app/assets/stylesheets/koi/utils/_breakpoints.scss +13 -0
  85. data/app/assets/stylesheets/koi/utils/_hide.scss +11 -0
  86. data/app/assets/stylesheets/koi/utils/_index.scss +2 -0
  87. data/app/assets/stylesheets/koi/utils/_typography.scss +24 -0
  88. data/app/components/koi/header/edit_component.rb +58 -0
  89. data/app/components/koi/header/index_component.rb +23 -0
  90. data/app/components/koi/header/new_component.rb +40 -0
  91. data/app/components/koi/header/show_component.rb +51 -0
  92. data/app/components/koi/header_component.html.erb +16 -0
  93. data/app/components/koi/header_component.rb +28 -0
  94. data/app/components/koi/index_table_component.rb +21 -0
  95. data/app/controllers/admin/admin_users_controller.rb +88 -0
  96. data/app/controllers/admin/application_controller.rb +9 -0
  97. data/app/controllers/admin/caches_controller.rb +11 -0
  98. data/app/controllers/admin/credentials_controller.rb +64 -0
  99. data/app/controllers/admin/dashboards_controller.rb +7 -0
  100. data/app/controllers/admin/sessions_controller.rb +78 -0
  101. data/app/controllers/admin/url_rewrites_controller.rb +87 -0
  102. data/app/controllers/concerns/koi/controller/has_admin_users.rb +49 -0
  103. data/app/controllers/concerns/koi/controller/has_webauthn.rb +45 -0
  104. data/app/controllers/concerns/koi/controller/is_admin_controller.rb +52 -0
  105. data/app/helpers/katalyst/content/editor/errors.rb +21 -0
  106. data/app/helpers/katalyst/navigation/editor/errors.rb +21 -0
  107. data/app/helpers/koi/application_helper.rb +7 -0
  108. data/app/helpers/koi/date_helper.rb +36 -0
  109. data/app/helpers/koi/definition_list_helper.rb +92 -0
  110. data/app/helpers/koi/index_actions_helper.rb +99 -0
  111. data/app/jobs/koi/application_job.rb +6 -0
  112. data/app/mailers/koi/application_mailer.rb +8 -0
  113. data/app/models/admin/credential.rb +14 -0
  114. data/app/models/admin/user.rb +51 -0
  115. data/app/models/application_record.rb +5 -0
  116. data/app/models/concerns/koi/model/archivable.rb +55 -0
  117. data/app/models/url_rewrite.rb +25 -0
  118. data/app/views/admin/admin_users/_admin.html+row.erb +4 -0
  119. data/app/views/admin/admin_users/_authentication.html.erb +15 -0
  120. data/app/views/admin/admin_users/_fields.html.erb +4 -0
  121. data/app/views/admin/admin_users/edit.html.erb +11 -0
  122. data/app/views/admin/admin_users/index.html.erb +9 -0
  123. data/app/views/admin/admin_users/new.html.erb +11 -0
  124. data/app/views/admin/admin_users/show.html.erb +22 -0
  125. data/app/views/admin/credentials/new.html.erb +14 -0
  126. data/app/views/admin/dashboards/show.html.erb +1 -0
  127. data/app/views/admin/sessions/new.html.erb +19 -0
  128. data/app/views/admin/shared/icons/_close.html.erb +8 -0
  129. data/app/views/admin/shared/icons/_cross.html.erb +3 -0
  130. data/app/views/admin/shared/icons/_menu.html.erb +3 -0
  131. data/app/views/admin/shared/icons/_refresh.html.erb +8 -0
  132. data/app/views/admin/url_rewrites/_form_fields.html.erb +3 -0
  133. data/app/views/admin/url_rewrites/_url_rewrite.html+row.erb +7 -0
  134. data/app/views/admin/url_rewrites/edit.html.erb +12 -0
  135. data/app/views/admin/url_rewrites/index.html.erb +10 -0
  136. data/app/views/admin/url_rewrites/new.html.erb +11 -0
  137. data/app/views/admin/url_rewrites/show.html.erb +16 -0
  138. data/app/views/katalyst/content/asides/_aside.html+form.erb +18 -0
  139. data/app/views/katalyst/content/columns/_column.html+form.erb +18 -0
  140. data/app/views/katalyst/content/contents/_content.html+form.erb +20 -0
  141. data/app/views/katalyst/content/figures/_figure.html+form.erb +17 -0
  142. data/app/views/katalyst/content/groups/_group.html+form.erb +18 -0
  143. data/app/views/katalyst/content/items/_item.html+form.erb +18 -0
  144. data/app/views/katalyst/content/sections/_section.html+form.erb +18 -0
  145. data/app/views/katalyst/navigation/items/_button.html.erb +15 -0
  146. data/app/views/katalyst/navigation/items/_heading.html.erb +11 -0
  147. data/app/views/katalyst/navigation/items/_link.html.erb +13 -0
  148. data/app/views/katalyst/navigation/menus/edit.html.erb +12 -0
  149. data/app/views/katalyst/navigation/menus/new.html.erb +9 -0
  150. data/app/views/katalyst/navigation/menus/show.html.erb +18 -0
  151. data/app/views/layouts/koi/_environment.html.erb +4 -0
  152. data/app/views/layouts/koi/_flash.html.erb +8 -0
  153. data/app/views/layouts/koi/_header.html.erb +11 -0
  154. data/app/views/layouts/koi/_navigation.html.erb +13 -0
  155. data/app/views/layouts/koi/_navigation_collapse.html.erb +3 -0
  156. data/app/views/layouts/koi/_navigation_header.html.erb +6 -0
  157. data/app/views/layouts/koi/_navigation_item.html.erb +12 -0
  158. data/app/views/layouts/koi/application.html.erb +59 -0
  159. data/app/views/layouts/koi/login.html.erb +29 -0
  160. data/config/importmap.rb +9 -0
  161. data/config/initializers/flipper.rb +13 -0
  162. data/config/initializers/pagy.rb +1 -0
  163. data/config/initializers/time_formats.rb +5 -0
  164. data/config/locales/koi.en.yml +18 -0
  165. data/config/locales/pagy.en.yml +6 -0
  166. data/config/routes.rb +25 -0
  167. data/db/migrate/20120220130849_devise_create_admins.rb +56 -0
  168. data/db/migrate/20130509235316_add_url_rewriter.rb +13 -0
  169. data/db/migrate/20230213053854_convert_devise_admins_to_rails.rb +7 -0
  170. data/db/migrate/20230412023411_create_admin_user_credentials.rb +20 -0
  171. data/db/migrate/20230531063707_update_admin_users.rb +37 -0
  172. data/db/migrate/20230602033610_add_archived_to_admin_users.rb +7 -0
  173. data/db/seeds.rb +9 -0
  174. data/lib/generators/koi/active_record/active_record_generator.rb +43 -0
  175. data/lib/generators/koi/admin/USAGE +8 -0
  176. data/lib/generators/koi/admin/admin_generator.rb +20 -0
  177. data/lib/generators/koi/admin_controller/USAGE +17 -0
  178. data/lib/generators/koi/admin_controller/admin_controller_generator.rb +51 -0
  179. data/lib/generators/koi/admin_controller/templates/controller.rb.tt +81 -0
  180. data/lib/generators/koi/admin_controller/templates/controller_spec.rb.tt +135 -0
  181. data/lib/generators/koi/admin_route/admin_route_generator.rb +62 -0
  182. data/lib/generators/koi/admin_views/USAGE +12 -0
  183. data/lib/generators/koi/admin_views/admin_views_generator.rb +54 -0
  184. data/lib/generators/koi/admin_views/templates/_fields.html.erb.tt +3 -0
  185. data/lib/generators/koi/admin_views/templates/_record.html+row.erb.tt +10 -0
  186. data/lib/generators/koi/admin_views/templates/edit.html.erb.tt +12 -0
  187. data/lib/generators/koi/admin_views/templates/index.html.erb.tt +7 -0
  188. data/lib/generators/koi/admin_views/templates/new.html.erb.tt +11 -0
  189. data/lib/generators/koi/admin_views/templates/show.html.erb.tt +18 -0
  190. data/lib/govuk_design_system_formbuilder/concerns/file_element.rb +115 -0
  191. data/lib/govuk_design_system_formbuilder/elements/document.rb +59 -0
  192. data/lib/govuk_design_system_formbuilder/elements/image.rb +86 -0
  193. data/lib/katalyst/koi.rb +3 -0
  194. data/lib/koi/caching.rb +15 -0
  195. data/lib/koi/config.rb +11 -0
  196. data/lib/koi/engine.rb +40 -0
  197. data/lib/koi/form_builder.rb +76 -0
  198. data/lib/koi/menu/builder.rb +68 -0
  199. data/lib/koi/menu.rb +46 -0
  200. data/lib/koi/middleware/url_redirect.rb +44 -0
  201. data/lib/koi/release.rb +52 -0
  202. data/lib/koi/version.rb +5 -0
  203. data/lib/koi.rb +37 -0
  204. data/spec/factories/admins.rb +9 -0
  205. data/spec/factories/url_rewrites.rb +9 -0
  206. metadata +430 -0
@@ -0,0 +1,15 @@
1
+ <h2>Authentication</h2>
2
+
3
+ <%= table_with(collection: current_admin.credentials, class: "index-table") do |t, c| %>
4
+ <%= t.cell :nickname %>
5
+ <%= t.cell :sign_count %>
6
+ <%= t.cell :actions, label: "" do %>
7
+ <%= button_to "Remove device", admin_admin_user_credential_path(admin, c), method: :delete, class: "button button--text" %>
8
+ <% end if admin == current_admin %>
9
+ <% end %>
10
+
11
+ <% if admin == current_admin %>
12
+ <div class="actions-group">
13
+ <%= kpop_link_to "Add this device", new_admin_admin_user_credential_path(admin), class: "button button--primary" %>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <%= form.govuk_text_field :email, label: { size: "s" } %>
2
+ <%= form.govuk_text_field :name, label: { size: "s" } %>
3
+ <%= form.govuk_password_field :password, label: { size: "s", text: "Password#{' (optional)' if form.object.persisted?}" } %>
4
+ <%= form.govuk_check_box_field :archived, small: true, label: { size: "s" } if form.object.persisted? %>
@@ -0,0 +1,11 @@
1
+ <% content_for :header do %>
2
+ <%= render Koi::Header::EditComponent.new(resource: admin) %>
3
+ <% end %>
4
+
5
+ <%= form_with(model: admin, url: admin_admin_user_path(admin)) do |form| %>
6
+ <%= render "fields", form: %>
7
+
8
+ <div class="actions">
9
+ <%= form.admin_save %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% content_for :header do %>
2
+ <%= render Koi::Header::IndexComponent.new(model: Admin::User) %>
3
+ <% end %>
4
+
5
+ <%= koi_index_actions create: true, search: true do %>
6
+ <%= select_tag(:scope, options_for_select([["Active", :active], ["All", :all], ["Archived", :archived]], params[:scope])) %>
7
+ <% end %>
8
+
9
+ <%= render table %>
@@ -0,0 +1,11 @@
1
+ <% content_for :header do %>
2
+ <%= render Koi::Header::NewComponent.new(model: Admin::User) %>
3
+ <% end %>
4
+
5
+ <%= form_with(model: admin, url: admin_admin_users_path) do |form| %>
6
+ <%= render "fields", form: %>
7
+
8
+ <div class="actions">
9
+ <%= form.admin_save %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <% content_for :header do %>
2
+ <%= render Koi::Header::ShowComponent.new(resource: admin) %>
3
+ <% end %>
4
+
5
+ <%= definition_list(class: "item-table") do |builder| %>
6
+ <%= builder.item admin, :name %>
7
+ <%= builder.item admin, :email %>
8
+ <%= builder.item admin, :created_at %>
9
+ <%= builder.item admin, :last_sign_in_at, label: { text: "Last sign in" } %>
10
+ <%= builder.item admin, :archived? %>
11
+ <% end %>
12
+
13
+ <div class="actions">
14
+ <% if admin.archived? %>
15
+ <%= button_to "Delete", admin_admin_user_path(admin),
16
+ class: "button button--secondary",
17
+ method: :delete,
18
+ form: { data: { turbo_confirm: "Are you sure?" } } %>
19
+ <% end %>
20
+ </div>
21
+
22
+ <%= render("authentication", admin:) %>
@@ -0,0 +1,14 @@
1
+ <%= render_kpop title: "Register device" do %>
2
+ <%= form_with model: current_admin.credentials.new,
3
+ url: admin_admin_user_credentials_path(current_admin),
4
+ data: {
5
+ controller: "webauthn-registration",
6
+ action: "submit->webauthn-registration#submit",
7
+ webauthn_registration_options_value: { publicKey: options },
8
+ } do |form| %>
9
+ <%= form.govuk_text_field :nickname %>
10
+ <%= form.hidden_field :response, data: { webauthn_registration_target: "response" } %>
11
+
12
+ <%= form.admin_save %>
13
+ <% end %>
14
+ <% end %>
@@ -0,0 +1 @@
1
+ <% content_for :title, "Dashboard" %>
@@ -0,0 +1,19 @@
1
+ <%= render "layouts/koi/navigation_header" %>
2
+ <%= form_with(
3
+ model: admin_user,
4
+ url: admin_session_path,
5
+ data: {
6
+ controller: "webauthn-authentication",
7
+ webauthn_authentication_options_value: { publicKey: webauthn_auth_options },
8
+ },
9
+ ) do |f| %>
10
+ <%= f.govuk_fieldset legend: nil do %>
11
+ <%= f.govuk_email_field :email, autofocus: true, autocomplete: "email" %>
12
+ <%= f.govuk_password_field :password, autocomplete: "current-password" %>
13
+ <%= f.hidden_field :response, data: { webauthn_authentication_target: "response" } %>
14
+ <% end %>
15
+ <div class="actions-group">
16
+ <%= f.admin_save "Log in" %>
17
+ <%= f.button "🔑", type: :button, class: "button button--secondary", data: { action: "webauthn-authentication#authenticate" } %>
18
+ </div>
19
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <svg viewBox='0 0 18 18' width='<%= options[:width] %>' height='<%= options[:height] %>' class="icon-close">
2
+ <g id="Search-V2" sketch:type="MSPage">
3
+ <g id="Search-Results" transform="translate(-1074.000000, -111.000000)" sketch:type="MSArtboardGroup">
4
+ <path id="Imported-Layers" sketch:type="MSShapeGroup" fill="none" stroke="<%= options[:stroke] %>" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke" d="
5
+ M1075.326,112L1091,127.675 M1090.674,112.073L1075,127.747" />
6
+ </g>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox='0 0 16 16' width='<%= options[:width] %>' height='<%= options[:height] %>' class="icon-cross">
2
+ <path fill="<%= options[:fill] %>" d="M15.854 12.854c-0-0-0-0-0-0l-4.854-4.854 4.854-4.854c0-0 0-0 0-0 0.052-0.052 0.090-0.113 0.114-0.178 0.066-0.178 0.028-0.386-0.114-0.529l-2.293-2.293c-0.143-0.143-0.351-0.181-0.529-0.114-0.065 0.024-0.126 0.062-0.178 0.114 0 0-0 0-0 0l-4.854 4.854-4.854-4.854c-0-0-0-0-0-0-0.052-0.052-0.113-0.090-0.178-0.114-0.178-0.066-0.386-0.029-0.529 0.114l-2.293 2.293c-0.143 0.143-0.181 0.351-0.114 0.529 0.024 0.065 0.062 0.126 0.114 0.178 0 0 0 0 0 0l4.854 4.854-4.854 4.854c-0 0-0 0-0 0-0.052 0.052-0.090 0.113-0.114 0.178-0.066 0.178-0.029 0.386 0.114 0.529l2.293 2.293c0.143 0.143 0.351 0.181 0.529 0.114 0.065-0.024 0.126-0.062 0.178-0.114 0-0 0-0 0-0l4.854-4.854 4.854 4.854c0 0 0 0 0 0 0.052 0.052 0.113 0.090 0.178 0.114 0.178 0.066 0.386 0.029 0.529-0.114l2.293-2.293c0.143-0.143 0.181-0.351 0.114-0.529-0.024-0.065-0.062-0.126-0.114-0.178z"></path>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg viewBox='0 0 16 16' width='<%= options[:width] %>' height='<%= options[:height] %>' class="icon-menu">
2
+ <path fill="<%= options[:fill] %>" d="M1 3h14v3h-14zM1 7h14v3h-14zM1 11h14v3h-14z"></path>
3
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg viewBox="0 0 25 20" width='<%= options[:width] %>' height='<%= options[:height] %>' class="icon-refresh">
2
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
3
+ <g id="Fill-2211-+-Fill-2212" transform="translate(1.000000, 0.000000)" fill="<%= options[:fill] %>" class="fill">
4
+ <path d="M23.3965,12.0071 L20.9765,7.8211 C20.9365,7.7521 20.8905,7.6871 20.8365,7.6301 C20.6685,7.4461 20.4385,7.3271 20.1855,7.3051 C19.8595,7.2801 19.5425,7.4091 19.3325,7.6581 L16.1185,11.4901 C15.7635,11.9131 15.8195,12.5441 16.2425,12.8991 C16.6655,13.2541 17.2965,13.1981 17.6505,12.7751 L19.1065,11.0401 C18.5535,13.8721 16.2895,16.3811 13.3045,17.0801 C10.7765,17.6741 8.1615,16.9351 6.3115,15.1041 C5.9185,14.7161 5.2855,14.7191 4.8965,15.1121 C4.5085,15.5051 4.5115,16.1371 4.9045,16.5261 C6.7145,18.3171 9.1025,19.2821 11.5715,19.2821 C12.2985,19.2821 13.0335,19.1981 13.7615,19.0281 C17.3675,18.1831 20.1395,15.2271 20.9755,11.8161 L21.6655,13.0081 C21.8505,13.3291 22.1865,13.5071 22.5315,13.5071 C22.7015,13.5071 22.8735,13.4651 23.0315,13.3731 C23.5095,13.0971 23.6725,12.4851 23.3965,12.0071" id="Fill-2211" vector-effect="non-scaling-stroke"></path>
5
+ <path d="M6.9453,7.6707 C6.5223,7.3167 5.8913,7.3717 5.5363,7.7947 L3.8143,9.8497 C3.8483,8.5677 4.2113,7.3007 4.8993,6.1517 C6.0103,4.2977 7.8273,2.9577 9.8823,2.4767 C12.5783,1.8437 15.3913,2.7437 17.2243,4.8217 C17.5893,5.2357 18.2213,5.2747 18.6353,4.9097 C19.0493,4.5447 19.0893,3.9127 18.7243,3.4987 C16.4023,0.8667 12.8393,-0.2733 9.4263,0.5287 C6.8473,1.1337 4.5713,2.8087 3.1843,5.1247 C2.5603,6.1657 2.1543,7.2857 1.9543,8.4347 L1.3883,7.4927 C1.1033,7.0197 0.4893,6.8667 0.0163,7.1497 C-0.4577,7.4347 -0.6107,8.0487 -0.3267,8.5227 L2.2333,12.7847 C2.4003,13.0617 2.6903,13.2407 3.0123,13.2667 C3.0393,13.2687 3.0653,13.2697 3.0913,13.2697 C3.3853,13.2697 3.6663,13.1397 3.8573,12.9117 L7.0693,9.0797 C7.4243,8.6567 7.3683,8.0257 6.9453,7.6707" id="Fill-2212" vector-effect="non-scaling-stroke"></path>
6
+ </g>
7
+ </g>
8
+ </svg>
@@ -0,0 +1,3 @@
1
+ <%= form.govuk_text_field :from, label: { size: "s" }, hint: { text: "Should begin with /" } %>
2
+ <%= form.govuk_text_field :to, label: { size: "s" } %>
3
+ <%= form.govuk_check_box_field :active, small: true %>
@@ -0,0 +1,7 @@
1
+ <%= row.cell :from do |cell| %>
2
+ <%= link_to cell.value, admin_url_rewrite_path(url_rewrite) %>
3
+ <% end %>
4
+ <%= row.cell :to %>
5
+ <%= row.cell :active do |cell| %>
6
+ <%= cell.value ? "Yes" : "No" %>
7
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <% content_for :header do %>
2
+ <%= render Koi::Header::EditComponent.new(resource: url_rewrite) %>
3
+ <% end %>
4
+
5
+ <%= form_with(model: url_rewrite, url: admin_url_rewrite_path(url_rewrite)) do |form| %>
6
+ <%= render "form_fields", form: %>
7
+
8
+ <div class="actions">
9
+ <%= form.admin_save %>
10
+ <%= form.admin_delete %>
11
+ </div>
12
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <%# locals: { table:, collection } %>
2
+ <% content_for :header do %>
3
+ <%= render Koi::Header::IndexComponent.new(model: UrlRewrite) %>
4
+ <% end %>
5
+
6
+ <%= koi_index_actions create: true, search: true do |form| %>
7
+ <%= select_tag(:scope, options_for_select([["Active", :active], ["All", :all], ["Inactive", :inactive]], params[:scope])) %>
8
+ <% end %>
9
+
10
+ <%= render(table) %>
@@ -0,0 +1,11 @@
1
+ <% content_for :header do %>
2
+ <%= render Koi::Header::NewComponent.new(model: UrlRewrite) %>
3
+ <% end %>
4
+
5
+ <%= form_with(model: url_rewrite, url: admin_url_rewrites_path) do |form| %>
6
+ <%= render "form_fields", form: %>
7
+
8
+ <div class="actions">
9
+ <%= form.admin_save %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <% content_for :header do %>
2
+ <%= render Koi::Header::ShowComponent.new(resource: url_rewrite) do |component| %>
3
+ <%= component.with_action "Preview", url_rewrite.to, target: "_blank" %>
4
+ <% end %>
5
+ <% end %>
6
+
7
+ <%= definition_list(class: "item-table") do |builder| %>
8
+ <%= builder.items_with(model: url_rewrite, attributes: UrlRewrite.attribute_names.sort, skip_blank: true) %>
9
+ <% end %>
10
+
11
+ <div class="actions-group">
12
+ <%= button_to "Delete", admin_url_rewrite_path(url_rewrite),
13
+ class: "button button--secondary",
14
+ method: :delete,
15
+ form: { data: { turbo_confirm: "Are you sure?" } } %>
16
+ </div>
@@ -0,0 +1,18 @@
1
+ <%= form_with model: aside, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= render "hidden_fields", form: %>
3
+
4
+ <%= form.govuk_fieldset legend: nil do %>
5
+ <%= form.govuk_text_field :heading %>
6
+ <%= form.govuk_collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself,
7
+ legend: { text: "Heading style" } %>
8
+ <% end %>
9
+
10
+ <%= form.govuk_select :background, Katalyst::Content.config.backgrounds %>
11
+
12
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
13
+
14
+ <div class="actions">
15
+ <%= form.admin_save "Done" %>
16
+ <%= form.admin_discard %>
17
+ </div>
18
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <%= form_with model: column, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= render "hidden_fields", form: %>
3
+
4
+ <%= form.govuk_fieldset legend: nil do %>
5
+ <%= form.govuk_text_field :heading %>
6
+ <%= form.govuk_collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself,
7
+ legend: { text: "Heading style" } %>
8
+ <% end %>
9
+
10
+ <%= form.govuk_select :background, Katalyst::Content.config.backgrounds %>
11
+
12
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
13
+
14
+ <div class="actions">
15
+ <%= form.admin_save "Done" %>
16
+ <%= form.admin_discard %>
17
+ </div>
18
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <%= form_with model: content, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= render "hidden_fields", form: %>
3
+
4
+ <%= form.govuk_fieldset legend: nil do %>
5
+ <%= form.govuk_text_field :heading %>
6
+ <%= form.govuk_collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself,
7
+ legend: { text: "Heading style" } %>
8
+ <% end %>
9
+
10
+ <%= form.govuk_select :background, Katalyst::Content.config.backgrounds %>
11
+
12
+ <%= form.govuk_rich_text_area :content, **content_editor_rich_text_options(class: "content") %>
13
+
14
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
15
+
16
+ <div class="actions">
17
+ <%= form.admin_save "Done" %>
18
+ <%= form.admin_discard %>
19
+ </div>
20
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <%= form_with model: figure, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= render "hidden_fields", form: %>
3
+
4
+ <%= form.govuk_image_field :image do %>
5
+ <%= form.govuk_text_field :heading, label: { text: "Alternate text" } %>
6
+ <%= form.govuk_text_field :caption %>
7
+ <% end %>
8
+
9
+ <%= form.govuk_select :background, Katalyst::Content.config.backgrounds %>
10
+
11
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
12
+
13
+ <div class="actions">
14
+ <%= form.admin_save "Done" %>
15
+ <%= form.admin_discard %>
16
+ </div>
17
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <%= form_with model: group, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= render "hidden_fields", form: %>
3
+
4
+ <%= form.govuk_fieldset legend: nil do %>
5
+ <%= form.govuk_text_field :heading %>
6
+ <%= form.govuk_collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself,
7
+ legend: { text: "Heading style" } %>
8
+ <% end %>
9
+
10
+ <%= form.govuk_select :background, Katalyst::Content.config.backgrounds %>
11
+
12
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
13
+
14
+ <div class="actions">
15
+ <%= form.admin_save "Done" %>
16
+ <%= form.admin_discard %>
17
+ </div>
18
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <%= form_with model: item, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= render "hidden_fields", form: %>
3
+
4
+ <%= form.govuk_fieldset legend: nil do %>
5
+ <%= form.govuk_text_field :heading %>
6
+ <%= form.govuk_collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself,
7
+ legend: { text: "Heading style" } %>
8
+ <% end %>
9
+
10
+ <%= form.govuk_select :background, Katalyst::Content.config.backgrounds %>
11
+
12
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
13
+
14
+ <div class="actions">
15
+ <%= form.admin_save "Done" %>
16
+ <%= form.admin_discard %>
17
+ </div>
18
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <%= form_with model: section, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= render "hidden_fields", form: %>
3
+
4
+ <%= form.govuk_fieldset legend: nil do %>
5
+ <%= form.govuk_text_field :heading %>
6
+ <%= form.govuk_collection_radio_buttons :heading_style, Katalyst::Content.config.heading_styles, :itself, :itself,
7
+ legend: { text: "Heading style" } %>
8
+ <% end %>
9
+
10
+ <%= form.govuk_select :background, Katalyst::Content.config.backgrounds %>
11
+
12
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
13
+
14
+ <div class="actions">
15
+ <%= form.admin_save "Done" %>
16
+ <%= form.admin_discard %>
17
+ </div>
18
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <%= form_with model: item, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= form.hidden_field :type %>
3
+ <%= form.govuk_text_field :title %>
4
+ <%= form.govuk_text_field :url %>
5
+
6
+ <%= form.govuk_select :http_method, Katalyst::Navigation::Button::HTTP_METHODS %>
7
+
8
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
9
+ <%= form.govuk_select :target, Katalyst::Navigation::Item::TARGETS %>
10
+
11
+ <div class="actions">
12
+ <%= form.admin_save "Done" %>
13
+ <%= form.admin_discard %>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <%= form_with model: item, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= form.hidden_field :type %>
3
+ <%= form.govuk_text_field :title %>
4
+
5
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
6
+
7
+ <div class="actions">
8
+ <%= form.admin_save "Done" %>
9
+ <%= form.admin_discard %>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <%= form_with model: item, scope: :item, url: path, builder: Koi::FormBuilder do |form| %>
2
+ <%= form.hidden_field :type %>
3
+ <%= form.govuk_text_field :title %>
4
+ <%= form.govuk_text_field :url %>
5
+
6
+ <%= form.govuk_check_box_field :visible, small: true, label: { text: "Visible?" } %>
7
+ <%= form.govuk_select :target, Katalyst::Navigation::Item::TARGETS %>
8
+
9
+ <div class="actions">
10
+ <%= form.admin_save "Done" %>
11
+ <%= form.admin_discard %>
12
+ </div>
13
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <% content_for :title, "Edit navigation" %>
2
+ <% content_for(:breadcrumbs) { link_to "Navigations", katalyst_navigation.menus_path } %>
3
+
4
+ <%= form_with model: menu, url: katalyst_navigation.menu_path(menu), builder: Koi::FormBuilder do |form| %>
5
+ <%= form.govuk_text_field :title %>
6
+ <%= form.govuk_text_field :slug %>
7
+ <%= form.govuk_text_field :depth %>
8
+ <div class="actions">
9
+ <%= form.admin_save %>
10
+ <%= form.admin_delete url: katalyst_navigation.menu_path(menu) %>
11
+ </div>
12
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% content_for :title, "New navigation" %>
2
+ <% content_for(:breadcrumbs) { link_to "Navigations", katalyst_navigation.menus_path } %>
3
+
4
+ <%= form_with model: menu, url: katalyst_navigation.menus_path, builder: Koi::FormBuilder do |form| %>
5
+ <%= form.govuk_text_field :title %>
6
+ <%= form.govuk_text_field :slug %>
7
+ <%= form.govuk_text_field :depth %>
8
+ <%= form.admin_save %>
9
+ <% end %>
@@ -0,0 +1,18 @@
1
+ <% content_for :title, menu.title %>
2
+ <% content_for(:breadcrumbs) { link_to "Navigations", katalyst_navigation.menus_path } %>
3
+
4
+ <%= navigation_editor_status_bar menu: %>
5
+
6
+ <%= navigation_editor_menu menu: do |form| %>
7
+ <div role="rowheader">
8
+ <h4>Title</h4>
9
+ <h4>Url</h4>
10
+ <h4>Actions</h4>
11
+ </div>
12
+
13
+ <%= navigation_editor_list menu: %>
14
+ <% end %>
15
+
16
+ <%= content_for :sidebar do %>
17
+ <%= render "katalyst/navigation/menus/new_items", menu: %>
18
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <header role="banner" aria-label="Environment" data-environment="<%= Rails.env %>">
2
+ <span class="environment"><%= Rails.env.capitalize %></span>
3
+ <span class="release">(<%= Koi::Release.version %>)</span>
4
+ </header>
@@ -0,0 +1,8 @@
1
+ <ul id="flash" aria-label="Notifications" data-controller="flash" class="stack">
2
+ <% flash.each do |type, message| %>
3
+ <%= tag.li role: "alertdialog", class: type do %>
4
+ <%= message %>
5
+ <%= tag.button "Close", aria: { label: "Dismiss" }, data: { action: "flash#close" } %>
6
+ <% end %>
7
+ <% end %>
8
+ </ul>
@@ -0,0 +1,11 @@
1
+ <header>
2
+ <h1 class="heading"><%= yield(:title) %></h1>
3
+
4
+ <div class="breadcrumbs" role="navigation" aria-label="Breadcrumbs">
5
+ <%= yield(:breadcrumbs) %>
6
+ </div>
7
+
8
+ <div class="actions" role="navigation" aria-label="Related pages">
9
+ <%= yield(:actions) %>
10
+ </div>
11
+ </header>
@@ -0,0 +1,13 @@
1
+ <nav role="navigation" aria-label="Main menu" aria-expanded="true" data-controller="navigation"
2
+ data-action="shortcut:go@document->navigation#focus
3
+ navigation:toggle@document->navigation#toggle
4
+ shortcut:nav-toggle@document->navigation#toggle">
5
+ <%= render "layouts/koi/navigation_header" %>
6
+ <div class="filter">
7
+ <input type="search" placeholder="Filter menu" autocomplete="off"
8
+ data-navigation-target="filter"
9
+ data-action="input->navigation#filter change->navigation#filter keydown.enter->navigation#go">
10
+ </div>
11
+ <%= navigation_menu_with(menu: Koi::Menu.admin_menu(self)) %>
12
+ </nav>
13
+ <%= render "layouts/koi/navigation_collapse" %>
@@ -0,0 +1,3 @@
1
+ <div class="navigation-collapse" data-controller="navigation-toggle" data-action="click->navigation-toggle#trigger">
2
+ <%= image_tag "koi/application/chevron-right.svg" %>
3
+ </div>
@@ -0,0 +1,6 @@
1
+ <header>
2
+ <h2 class="site-name"><%= URI.parse(root_url).host %></h2>
3
+ <h3 class="admin-name">Koi Admin</h3>
4
+ <%# default, prefer using an icon in a Koi override %>
5
+ <span class="site-icon"><%= URI.parse(root_url).host[0] %></span>
6
+ </header>
@@ -0,0 +1,12 @@
1
+ <li class="<%= "selected" if request.path.eql?(item_url) %>">
2
+ <% if item_url.is_a?(Hash) %>
3
+ <a href="#"><%= item_name %></a>
4
+ <ul>
5
+ <% item_url.each do |child_name,child_url| %>
6
+ <%= render "layouts/koi/navigation_item", item_name: child_name, item_url: child_url %>
7
+ <% end %>
8
+ </ul>
9
+ <% else %>
10
+ <a href="<%= item_url %>"><%= item_name %></a>
11
+ <% end %>
12
+ </li>
@@ -0,0 +1,59 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <!-- meta -->
5
+ <meta charset="UTF-8">
6
+
7
+ <!-- title -->
8
+ <title>Koi <%= "- #{yield :title}" if content_for? :title %></title>
9
+
10
+ <!-- META -->
11
+ <%= csrf_meta_tags %>
12
+ <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=0.6667">
13
+ <meta name="robots" content="noindex,nofollow">
14
+ <%= Koi::Release.meta_tags(self) %>
15
+
16
+ <!-- STYLES -->
17
+ <link rel="stylesheet" href="https://unpkg.com/modern-css-reset/dist/reset.min.css">
18
+ <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
19
+ <%= stylesheet_link_tag "koi/admin" %>
20
+
21
+ <!-- SCRIPTS -->
22
+ <%= javascript_importmap_tags "koi/admin" %>
23
+ </head>
24
+ <body data-controller="keyboard"
25
+ data-action="keyup->keyboard#event"
26
+ data-keyboard-mapping-value="
27
+ Slash->shortcut:search
28
+ KeyN->shortcut:create
29
+ KeyG->shortcut:go
30
+ BracketLeft->shortcut:nav-toggle
31
+ Escape->shortcut:cancel
32
+ ArrowLeft->shortcut:page-prev
33
+ ArrowRight->shortcut:page-next
34
+ ">
35
+ <%= render "layouts/koi/navigation" %>
36
+ <main>
37
+ <% if content_for? :header %>
38
+ <%= yield :header %>
39
+ <% else %>
40
+ <%= render "layouts/koi/header" %>
41
+ <% end %>
42
+
43
+ <div class="page <%= "has-sidebar" if content_for?(:sidebar) %>">
44
+ <div class="page--content stack" role="main">
45
+ <%= render "layouts/koi/flash" unless flash.empty? %>
46
+ <%= yield %>
47
+ </div>
48
+ <%= tag.div class: "page--sidebar" do %>
49
+ <%= yield :sidebar %>
50
+ <% end if content_for?(:sidebar) %>
51
+ </div>
52
+ </main>
53
+ <%= render "layouts/koi/environment" %>
54
+ <%= scrim_tag %>
55
+ <%= kpop_frame_tag do %>
56
+ <%= content_for :kpop %>
57
+ <% end %>
58
+ </body>
59
+ </html>
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <!-- meta -->
5
+ <meta charset="UTF-8">
6
+
7
+ <!-- title -->
8
+ <title>Koi</title>
9
+
10
+ <!-- META -->
11
+ <%= csrf_meta_tags %>
12
+ <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=0.6667">
13
+ <meta name="robots" content="noindex,nofollow">
14
+ <%= yield :meta %>
15
+
16
+ <!-- STYLES -->
17
+ <link rel="stylesheet" href="https://unpkg.com/modern-css-reset/dist/reset.min.css">
18
+ <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
19
+ <%= stylesheet_link_tag "koi/admin" %>
20
+
21
+ <!-- SCRIPTS -->
22
+ <%= javascript_importmap_tags "koi/admin" %>
23
+ </head>
24
+ <body class="admin-login">
25
+ <main class="stack">
26
+ <%= yield %>
27
+ </main>
28
+ </body>
29
+ </html>
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
4
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
5
+ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
6
+ pin "@github/webauthn-json/browser-ponyfill", to: "https://ga.jspm.io/npm:@github/webauthn-json@2.1.1/dist/esm/webauthn-json.browser-ponyfill.js"
7
+
8
+ pin_all_from Koi::Engine.root.join("app/assets/javascripts/koi"),
9
+ under: "koi", preload: Rails.env.test?
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Register koi admin roles with flipper for easy toggling
4
+
5
+ return unless Object.const_defined?("Flipper")
6
+
7
+ Flipper.register(:admins) do |actor, _context|
8
+ actor.respond_to?("role") && ((actor.role.eql? "Admin") || (actor.role.eql? "Super"))
9
+ end
10
+
11
+ Flipper.register(:super_admins) do |actor, _context|
12
+ actor.respond_to?("role") && (actor.role.eql? "Super")
13
+ end