administratum 0.2.4

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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/DESIGN.md +68 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +431 -0
  5. data/Rakefile +6 -0
  6. data/app/admin/administratum/user_admin.rb +39 -0
  7. data/app/assets/builds/administratum.css +2 -0
  8. data/app/assets/javascripts/administratum.js +250 -0
  9. data/app/assets/tailwind/administratum/components.css +199 -0
  10. data/app/assets/tailwind/administratum/index.css +8 -0
  11. data/app/assets/tailwind/administratum/theme.css +62 -0
  12. data/app/controllers/administratum/application_controller.rb +31 -0
  13. data/app/controllers/administratum/dashboard_controller.rb +7 -0
  14. data/app/controllers/administratum/page.rb +51 -0
  15. data/app/controllers/administratum/password_resets_controller.rb +42 -0
  16. data/app/controllers/administratum/resource.rb +224 -0
  17. data/app/controllers/administratum/sessions_controller.rb +33 -0
  18. data/app/helpers/administratum/application_helper.rb +11 -0
  19. data/app/helpers/administratum/form_helper.rb +115 -0
  20. data/app/helpers/administratum/island_helper.rb +16 -0
  21. data/app/helpers/administratum/layout_helper.rb +81 -0
  22. data/app/helpers/administratum/resource_helper.rb +199 -0
  23. data/app/mailers/administratum/application_mailer.rb +8 -0
  24. data/app/mailers/administratum/user_mailer.rb +11 -0
  25. data/app/models/administratum/application_record.rb +7 -0
  26. data/app/models/administratum/user.rb +13 -0
  27. data/app/views/administratum/dashboard/index.html.erb +16 -0
  28. data/app/views/administratum/fields/_belongs_to.html.erb +1 -0
  29. data/app/views/administratum/fields/_boolean.html.erb +1 -0
  30. data/app/views/administratum/fields/_date.html.erb +1 -0
  31. data/app/views/administratum/fields/_datetime.html.erb +1 -0
  32. data/app/views/administratum/fields/_email.html.erb +1 -0
  33. data/app/views/administratum/fields/_has_many_ids.html.erb +1 -0
  34. data/app/views/administratum/fields/_hidden.html.erb +1 -0
  35. data/app/views/administratum/fields/_island.html.erb +4 -0
  36. data/app/views/administratum/fields/_json.html.erb +1 -0
  37. data/app/views/administratum/fields/_multi_select.html.erb +1 -0
  38. data/app/views/administratum/fields/_number.html.erb +1 -0
  39. data/app/views/administratum/fields/_password.html.erb +1 -0
  40. data/app/views/administratum/fields/_select.html.erb +1 -0
  41. data/app/views/administratum/fields/_string.html.erb +1 -0
  42. data/app/views/administratum/fields/_textarea.html.erb +1 -0
  43. data/app/views/administratum/fields/_wrapper.html.erb +11 -0
  44. data/app/views/administratum/pages/index.html.erb +7 -0
  45. data/app/views/administratum/password_resets/edit.html.erb +17 -0
  46. data/app/views/administratum/password_resets/new.html.erb +11 -0
  47. data/app/views/administratum/resources/_attributes.html.erb +11 -0
  48. data/app/views/administratum/resources/_form.html.erb +14 -0
  49. data/app/views/administratum/resources/_table.html.erb +42 -0
  50. data/app/views/administratum/resources/edit.html.erb +7 -0
  51. data/app/views/administratum/resources/index.html.erb +17 -0
  52. data/app/views/administratum/resources/new.html.erb +7 -0
  53. data/app/views/administratum/resources/show.html.erb +7 -0
  54. data/app/views/administratum/sessions/new.html.erb +18 -0
  55. data/app/views/administratum/shared/_card.html.erb +9 -0
  56. data/app/views/administratum/shared/_filters.html.erb +43 -0
  57. data/app/views/administratum/shared/_flash.html.erb +9 -0
  58. data/app/views/administratum/shared/_form_errors.html.erb +10 -0
  59. data/app/views/administratum/shared/_nav.html.erb +10 -0
  60. data/app/views/administratum/shared/_page_header.html.erb +17 -0
  61. data/app/views/administratum/shared/_panel.html.erb +24 -0
  62. data/app/views/administratum/shared/_topbar.html.erb +29 -0
  63. data/app/views/administratum/user_mailer/reset_password.html.erb +4 -0
  64. data/app/views/administratum/user_mailer/reset_password.text.erb +7 -0
  65. data/app/views/layouts/administratum/application.html.erb +25 -0
  66. data/app/views/layouts/administratum/authentication.html.erb +32 -0
  67. data/config/locales/en.yml +65 -0
  68. data/config/routes.rb +16 -0
  69. data/db/migrate/20261016000000_create_administratum_users.rb +17 -0
  70. data/lib/administratum/configuration.rb +66 -0
  71. data/lib/administratum/definition.rb +283 -0
  72. data/lib/administratum/dsl.rb +195 -0
  73. data/lib/administratum/engine.rb +31 -0
  74. data/lib/administratum/field_types.rb +77 -0
  75. data/lib/administratum/page_dsl.rb +48 -0
  76. data/lib/administratum/paths.rb +85 -0
  77. data/lib/administratum/ransackable.rb +41 -0
  78. data/lib/administratum/registry.rb +95 -0
  79. data/lib/administratum/routing.rb +57 -0
  80. data/lib/administratum/tailwind.rb +34 -0
  81. data/lib/administratum/version.rb +5 -0
  82. data/lib/administratum.rb +43 -0
  83. data/lib/generators/administratum/install/install_generator.rb +42 -0
  84. data/lib/generators/administratum/install/templates/administratum.rb +41 -0
  85. data/lib/generators/administratum/resource/resource_generator.rb +30 -0
  86. data/lib/generators/administratum/resource/templates/resource.rb.tt +20 -0
  87. data/lib/tasks/administratum.rake +32 -0
  88. metadata +209 -0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Administratum
4
+ class ApplicationMailer < ActionMailer::Base
5
+ default from: -> { Administratum.config.mailer_sender }
6
+ layout false
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Administratum
4
+ class UserMailer < ApplicationMailer
5
+ def reset_password
6
+ @user = params[:user]
7
+ @url = administratum.edit_password_reset_url(token: params[:token])
8
+ mail to: @user.email, subject: t("administratum.password_resets.subject", app: Administratum.config.app_name)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Administratum
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Administratum
4
+ class User < ApplicationRecord
5
+ self.table_name = "administratum_users"
6
+
7
+ devise :database_authenticatable, :recoverable, :rememberable, :validatable
8
+
9
+ def to_s
10
+ email
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ <%= page_header title: t("administratum.dashboard"), breadcrumbs: [] %>
2
+
3
+ <div class="adm-container adm-page-body">
4
+ <% if administratum_config.dashboard %>
5
+ <%= render administratum_config.dashboard %>
6
+ <% else %>
7
+ <div class="adm-grid">
8
+ <% Administratum.registry.menu_items.each do |resource| %>
9
+ <%= link_to Administratum::Paths.new(resource, self).index, class: "adm-card adm-tile" do %>
10
+ <span class="adm-tile-label"><%= resource.menu_label %></span>
11
+ <span class="adm-tile-value"><%= number_with_delimiter(resource.definition.model.count) %></span>
12
+ <% end %>
13
+ <% end %>
14
+ </div>
15
+ <% end %>
16
+ </div>
@@ -0,0 +1 @@
1
+ <%= form.select field.name, field_collection(field, resource), { include_blank: field.options[:placeholder] || field.options.fetch(:include_blank, true) }, **input_options(field, form:, class: "adm-input") %>
@@ -0,0 +1 @@
1
+ <%= form.checkbox field.name, **input_options(field, form:, class: "adm-checkbox") %>
@@ -0,0 +1 @@
1
+ <%= adm_date_field form, field.name, value:, **input_options(field, form:) %>
@@ -0,0 +1 @@
1
+ <%= adm_datetime_field form, field.name, value:, **input_options(field, form:) %>
@@ -0,0 +1 @@
1
+ <%= form.email_field field.name, **input_options(field, form:, class: "adm-input", autocomplete: "off") %>
@@ -0,0 +1 @@
1
+ <%= form.select field.name, field_collection(field, resource), {}, **input_options(field, form:, class: "adm-input", multiple: true, size: field.options.fetch(:size, 6)) %>
@@ -0,0 +1 @@
1
+ <%= form.hidden_field field.name, **input_options(field) %>
@@ -0,0 +1,4 @@
1
+ <%# A field rendered by a JS island. The island receives the input name and current value and must render its own input(s). %>
2
+ <% extra = field.options[:props] %>
3
+ <% extra = extra.arity.zero? ? instance_exec(&extra) : instance_exec(resource, &extra) if extra.respond_to?(:call) %>
4
+ <%= island field.options.fetch(:island), props: { name: form.field_name(field.name), id: form.field_id(field.name), value: value }.merge(extra || {}) %>
@@ -0,0 +1 @@
1
+ <%= form.text_area field.name, **input_options(field, form:, class: "adm-input adm-code", rows: field.options.fetch(:rows, 8), value: value.is_a?(String) || value.nil? ? value : JSON.pretty_generate(value)) %>
@@ -0,0 +1 @@
1
+ <%= form.select field.name, field_collection(field, resource), {}, **input_options(field, form:, class: "adm-input", multiple: true, size: field.options.fetch(:size, 6)) %>
@@ -0,0 +1 @@
1
+ <%= form.number_field field.name, **input_options(field, form:, class: "adm-input", step: field.options.fetch(:step, "any"), min: field.options[:min], max: field.options[:max]) %>
@@ -0,0 +1 @@
1
+ <%= form.password_field field.name, **input_options(field, form:, class: "adm-input", autocomplete: "new-password") %>
@@ -0,0 +1 @@
1
+ <%= form.select field.name, field_collection(field, resource), { include_blank: field.options[:placeholder] || field.options.fetch(:include_blank, false) }, **input_options(field, form:, class: "adm-input") %>
@@ -0,0 +1 @@
1
+ <%= form.text_field field.name, **input_options(field, form:, class: "adm-input") %>
@@ -0,0 +1 @@
1
+ <%= form.text_area field.name, **input_options(field, form:, class: "adm-input", rows: field.options.fetch(:rows, 4)) %>
@@ -0,0 +1,11 @@
1
+ <% errors = field_errors(form, field) %>
2
+ <div class="<%= class_names("adm-field adm-field-horizontal", "adm-field-invalid" => errors.any?) %>">
3
+ <div class="adm-field-label">
4
+ <%= form.label field.name, class: "adm-label" do %><%= field_label(form, field) %><% if field_required?(form, field) %><span class="adm-required" aria-hidden="true">*</span><% end %><% end %>
5
+ </div>
6
+ <div class="adm-field-control">
7
+ <%= input %>
8
+ <% if field.hint %><p class="adm-hint" id="<%= form.field_id(field.name, :hint) %>"><%= field.hint %></p><% end %>
9
+ <% errors.each do |message| %><p class="adm-error"><%= message %></p><% end %>
10
+ </div>
11
+ </div>
@@ -0,0 +1,7 @@
1
+ <%= page_header title: page_class.page_title do %>
2
+ <%= buttons_for(:page) %>
3
+ <% end %>
4
+
5
+ <div class="adm-container adm-page-body">
6
+ <%= render "content" %>
7
+ </div>
@@ -0,0 +1,17 @@
1
+ <% content_for :title, t("administratum.password_resets.new_password") %>
2
+ <h1 class="adm-auth-title"><%= t("administratum.password_resets.new_password") %></h1>
3
+ <% if @errors.present? %>
4
+ <div class="adm-alert" role="alert"><ul><% @errors.each do |message| %><li><%= message %></li><% end %></ul></div>
5
+ <% end %>
6
+ <%= form_with url: administratum.password_reset_path, method: :patch, class: "adm-form" do |f| %>
7
+ <%= f.hidden_field :token, value: @token %>
8
+ <div class="adm-field">
9
+ <%= f.label :password, t("administratum.sessions.password"), class: "adm-label" %>
10
+ <%= f.password_field :password, class: "adm-input", required: true, autofocus: true, autocomplete: "new-password" %>
11
+ </div>
12
+ <div class="adm-field">
13
+ <%= f.label :password_confirmation, t("administratum.password_resets.confirmation"), class: "adm-label" %>
14
+ <%= f.password_field :password_confirmation, class: "adm-input", required: true, autocomplete: "new-password" %>
15
+ </div>
16
+ <%= f.submit t("administratum.password_resets.save"), class: "adm-btn adm-btn-primary adm-btn-block" %>
17
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <% content_for :title, t("administratum.password_resets.title") %>
2
+ <h1 class="adm-auth-title"><%= t("administratum.password_resets.title") %></h1>
3
+ <p class="adm-muted adm-auth-lead"><%= t("administratum.password_resets.lead") %></p>
4
+ <%= form_with url: administratum.password_reset_path, class: "adm-form" do |f| %>
5
+ <div class="adm-field">
6
+ <%= f.label :email, t("administratum.sessions.email"), class: "adm-label" %>
7
+ <%= f.email_field :email, class: "adm-input", required: true, autofocus: true, autocomplete: "username" %>
8
+ </div>
9
+ <%= f.submit t("administratum.password_resets.submit"), class: "adm-btn adm-btn-primary adm-btn-block" %>
10
+ <% end %>
11
+ <p class="adm-auth-footer"><%= link_to t("administratum.password_resets.back"), administratum.sign_in_path, class: "adm-link" %></p>
@@ -0,0 +1,11 @@
1
+ <section class="adm-card">
2
+ <dl class="adm-attributes">
3
+ <% definition.resolved_show_attributes.each do |attribute| %>
4
+ <% next unless condition_met?(attribute.condition, @resource) %>
5
+ <div class="adm-attribute">
6
+ <dt><%= column_label(attribute) %></dt>
7
+ <dd><%= column_value(@resource, attribute) %></dd>
8
+ </div>
9
+ <% end %>
10
+ </dl>
11
+ </section>
@@ -0,0 +1,14 @@
1
+ <section class="adm-card">
2
+ <%= administratum_form(class: "adm-form-flush") do |form| %>
3
+ <% if form.object.errors.any? %>
4
+ <div class="adm-form-errors"><%= render "administratum/shared/form_errors", record: form.object %></div>
5
+ <% end %>
6
+ <div class="adm-form-body">
7
+ <%= render_fields(form) %>
8
+ </div>
9
+ <footer class="adm-form-actions">
10
+ <%= form.submit class: "adm-btn adm-btn-primary" %>
11
+ <%= link_to t("administratum.cancel"), (@resource.persisted? && definition.action_enabled?(:show) ? resource_path : (definition.action_enabled?(:index) ? collection_path : (parent_path || administratum.root_path))), class: "adm-btn adm-btn-secondary" %>
12
+ </footer>
13
+ <% end %>
14
+ </section>
@@ -0,0 +1,42 @@
1
+ <section class="adm-card">
2
+ <div class="adm-table-wrap">
3
+ <table class="adm-table">
4
+ <thead>
5
+ <tr>
6
+ <% definition.index_columns.each do |column| %>
7
+ <th scope="col"><%= column.sortable ? sort_link(@search, column.name, column_label(column)) : column_label(column) %></th>
8
+ <% end %>
9
+ <th scope="col" class="adm-table-actions"><span class="adm-sr-only"><%= t("administratum.actions.title") %></span></th>
10
+ </tr>
11
+ </thead>
12
+ <tbody>
13
+ <% @resources.each do |record| %>
14
+ <tr>
15
+ <% definition.index_columns.each_with_index do |column, index| %>
16
+ <td>
17
+ <% if index.zero? && !column.block && definition.action_enabled?(:show) %>
18
+ <%= link_to column_value(record, column), resource_path(record), class: "adm-link" %>
19
+ <% else %>
20
+ <%= column_value(record, column) %>
21
+ <% end %>
22
+ </td>
23
+ <% end %>
24
+ <td class="adm-table-actions"><div class="adm-btn-group"><%= buttons_for(:row, record) %></div></td>
25
+ </tr>
26
+ <% end %>
27
+ <% if @resources.empty? %>
28
+ <tr><td colspan="<%= definition.index_columns.size + 1 %>" class="adm-empty"><%= t("administratum.empty", resources: resource_class.plural_label.downcase) %></td></tr>
29
+ <% end %>
30
+ </tbody>
31
+ </table>
32
+ </div>
33
+ <footer class="adm-card-footer adm-table-footer">
34
+ <div class="adm-muted">
35
+ <%== pagy_info(@pagy) %>
36
+ <% if definition.csv %>
37
+ · <%= link_to t("administratum.download_csv"), url_for(format: :csv, q: request.query_parameters[:q], scope: current_index_scope&.name), class: "adm-link" %>
38
+ <% end %>
39
+ </div>
40
+ <div class="adm-pagy"><%== pagy_nav(@pagy) if @pagy.pages > 1 %></div>
41
+ </footer>
42
+ </section>
@@ -0,0 +1,7 @@
1
+ <%= page_header title: t("administratum.actions.edit_resource", title: record_title(@resource)) do %>
2
+ <%= buttons_for(:edit) %>
3
+ <% end %>
4
+
5
+ <div class="adm-container adm-page-body">
6
+ <%= render definition.form_template || "form" %>
7
+ </div>
@@ -0,0 +1,17 @@
1
+ <%= page_header title: resource_class.plural_label do %>
2
+ <% if filters? %>
3
+ <button type="button" class="adm-btn adm-btn-secondary" data-adm-offcanvas-open="adm-filters"><%= t("administratum.filters.title") %></button>
4
+ <% end %>
5
+ <%= buttons_for(:index, nil) %>
6
+ <% end %>
7
+
8
+ <div class="adm-container adm-page-body">
9
+ <%= index_scope_tabs %>
10
+ <%= render "table" %>
11
+ </div>
12
+
13
+ <% if filters? %>
14
+ <% content_for :panels do %>
15
+ <%= render "administratum/shared/filters" %>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,7 @@
1
+ <%= page_header title: t("administratum.actions.new", resource: resource_class.singular_label) do %>
2
+ <%= buttons_for(:new) %>
3
+ <% end %>
4
+
5
+ <div class="adm-container adm-page-body">
6
+ <%= render definition.form_template || "form" %>
7
+ </div>
@@ -0,0 +1,7 @@
1
+ <%= page_header title: record_title(@resource) do %>
2
+ <%= buttons_for(:show) %>
3
+ <% end %>
4
+
5
+ <div class="adm-container adm-page-body">
6
+ <%= render "attributes" %>
7
+ </div>
@@ -0,0 +1,18 @@
1
+ <% content_for :title, t("administratum.sessions.title") %>
2
+ <h1 class="adm-auth-title"><%= t("administratum.sessions.title") %></h1>
3
+ <%= form_with url: administratum.sign_in_path, class: "adm-form" do |f| %>
4
+ <div class="adm-field">
5
+ <%= f.label :email, t("administratum.sessions.email"), class: "adm-label" %>
6
+ <%= f.email_field :email, value: params[:email], class: "adm-input", required: true, autofocus: true, autocomplete: "username" %>
7
+ </div>
8
+ <div class="adm-field">
9
+ <%= f.label :password, t("administratum.sessions.password"), class: "adm-label" %>
10
+ <%= f.password_field :password, class: "adm-input", required: true, autocomplete: "current-password" %>
11
+ </div>
12
+ <label class="adm-check">
13
+ <%= f.checkbox :remember_me, class: "adm-checkbox" %>
14
+ <span><%= t("administratum.sessions.remember_me") %></span>
15
+ </label>
16
+ <%= f.submit t("administratum.sessions.submit"), class: "adm-btn adm-btn-primary adm-btn-block" %>
17
+ <% end %>
18
+ <p class="adm-auth-footer"><%= link_to t("administratum.password_resets.link"), administratum.new_password_reset_path, class: "adm-link" %></p>
@@ -0,0 +1,9 @@
1
+ <section class="adm-card">
2
+ <% if title %>
3
+ <header class="adm-card-header"><h2 class="adm-card-title"><%= title %></h2></header>
4
+ <% end %>
5
+ <div class="<%= flush ? "adm-card-flush" : "adm-card-body" %>"><%= body %></div>
6
+ <% if footer %>
7
+ <footer class="adm-card-footer"><%= footer %></footer>
8
+ <% end %>
9
+ </section>
@@ -0,0 +1,43 @@
1
+ <div class="adm-offcanvas" data-adm-offcanvas="adm-filters" hidden>
2
+ <div class="adm-offcanvas-backdrop" data-adm-offcanvas-close></div>
3
+ <aside class="adm-offcanvas-panel" role="dialog" aria-modal="true" aria-labelledby="adm-filters-title">
4
+ <header class="adm-offcanvas-header">
5
+ <h2 id="adm-filters-title" class="adm-card-title"><%= t("administratum.filters.title") %></h2>
6
+ <button type="button" class="adm-icon-btn" data-adm-offcanvas-close aria-label="<%= t("administratum.close") %>">&times;</button>
7
+ </header>
8
+ <div class="adm-offcanvas-body">
9
+ <%= search_form_for @search, url: collection_path, method: :get, html: { class: "adm-form" } do |f| %>
10
+ <%= hidden_field_tag :scope, current_index_scope.name, id: nil if current_index_scope %>
11
+ <% definition.filters.each do |filter| %>
12
+ <div class="adm-field">
13
+ <% label = filter.label || definition.model.human_attribute_name(filter.name) %>
14
+ <% case filter.type %>
15
+ <% when :select %>
16
+ <%= f.label filter.keys.first, label, class: "adm-label" %>
17
+ <%= f.select filter.keys.first, filter_collection(filter), { include_blank: t("administratum.filters.any") }, class: "adm-input" %>
18
+ <% when :boolean %>
19
+ <%= f.label filter.keys.first, label, class: "adm-label" %>
20
+ <%= f.select filter.keys.first, [[t("administratum.yes"), true], [t("administratum.no"), false]], { include_blank: t("administratum.filters.any") }, class: "adm-input" %>
21
+ <% when :date_range %>
22
+ <span class="adm-label"><%= label %></span>
23
+ <div class="adm-field-row">
24
+ <%= adm_date_field f, filter.keys.first, "aria-label": t("administratum.filters.from", field: label) %>
25
+ <%= adm_date_field f, filter.keys.last, "aria-label": t("administratum.filters.to", field: label) %>
26
+ </div>
27
+ <% when :number %>
28
+ <%= f.label filter.keys.first, label, class: "adm-label" %>
29
+ <%= f.number_field filter.keys.first, class: "adm-input" %>
30
+ <% else %>
31
+ <%= f.label filter.keys.first, label, class: "adm-label" %>
32
+ <%= f.search_field filter.keys.first, class: "adm-input" %>
33
+ <% end %>
34
+ </div>
35
+ <% end %>
36
+ <div class="adm-form-footer">
37
+ <%= f.submit t("administratum.filters.apply"), class: "adm-btn adm-btn-primary", name: nil %>
38
+ <%= link_to t("administratum.filters.clear"), collection_path(scope: current_index_scope&.name), class: "adm-btn adm-btn-secondary" %>
39
+ </div>
40
+ <% end %>
41
+ </div>
42
+ </aside>
43
+ </div>
@@ -0,0 +1,9 @@
1
+ <div class="adm-flash-stack" aria-live="polite">
2
+ <% flash.each do |type, message| %>
3
+ <% next unless message.is_a?(String) %>
4
+ <div class="adm-flash <%= type.to_s == "alert" ? "adm-flash-alert" : "adm-flash-notice" %>" data-adm-flash role="status">
5
+ <span><%= message %></span>
6
+ <button type="button" class="adm-flash-close" data-adm-dismiss aria-label="<%= t("administratum.close") %>">&times;</button>
7
+ </div>
8
+ <% end %>
9
+ </div>
@@ -0,0 +1,10 @@
1
+ <% if record.errors.any? %>
2
+ <div class="adm-alert" role="alert">
3
+ <p class="adm-alert-title"><%= t("administratum.errors.title", count: record.errors.count) %></p>
4
+ <ul>
5
+ <% record.errors.full_messages.each do |message| %>
6
+ <li><%= message %></li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
10
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <nav id="adm-nav" class="adm-nav" aria-label="<%= t("administratum.menu") %>">
2
+ <div class="adm-container">
3
+ <ul class="adm-nav-list">
4
+ <li><%= link_to t("administratum.dashboard"), administratum.root_path, class: class_names("adm-nav-link", "adm-nav-link-active" => controller_name == "dashboard") %></li>
5
+ <% administratum_nav_items.each do |item| %>
6
+ <li><%= link_to item[:label], item[:path], class: class_names("adm-nav-link", "adm-nav-link-active" => item[:active]), "aria-current": (item[:active] ? "page" : nil) %></li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
10
+ </nav>
@@ -0,0 +1,17 @@
1
+ <div class="adm-page-header">
2
+ <div class="adm-container adm-page-header-inner">
3
+ <div class="adm-page-heading">
4
+ <% if breadcrumbs.any? %>
5
+ <ol class="adm-breadcrumb">
6
+ <% breadcrumbs.each do |label, path| %>
7
+ <li class="adm-breadcrumb-item"><%= path ? link_to(label, path) : label %></li>
8
+ <% end %>
9
+ </ol>
10
+ <% end %>
11
+ <h1 class="adm-page-title"><%= title %></h1>
12
+ </div>
13
+ <% if actions.present? %>
14
+ <div class="adm-page-actions"><%= actions %></div>
15
+ <% end %>
16
+ </div>
17
+ </div>
@@ -0,0 +1,24 @@
1
+ <div class="adm-offcanvas" data-adm-offcanvas="<%= id %>" hidden>
2
+ <div class="adm-offcanvas-backdrop" data-adm-offcanvas-close></div>
3
+ <aside class="adm-offcanvas-panel" role="dialog" aria-modal="true" aria-labelledby="<%= id %>-title">
4
+ <header class="adm-offcanvas-header">
5
+ <h2 id="<%= id %>-title" class="adm-card-title"><%= title %></h2>
6
+ <button type="button" class="adm-icon-btn" data-adm-offcanvas-close aria-label="<%= t("administratum.close") %>">&times;</button>
7
+ </header>
8
+ <div class="adm-offcanvas-body">
9
+ <% if button.panel %>
10
+ <%= render button.panel, resource: record, button: button %>
11
+ <% else %>
12
+ <%= form_with url: target, method: :post, multipart: true, class: "adm-form" do |f| %>
13
+ <div class="adm-field">
14
+ <%= f.label :file, t("administratum.upload.file"), class: "adm-label" %>
15
+ <%= f.file_field :file, class: "adm-input", required: true, accept: (button.upload.is_a?(String) ? button.upload : nil) %>
16
+ </div>
17
+ <div class="adm-form-footer">
18
+ <%= f.submit t("administratum.upload.submit"), class: "adm-btn adm-btn-primary" %>
19
+ </div>
20
+ <% end %>
21
+ <% end %>
22
+ </div>
23
+ </aside>
24
+ </div>
@@ -0,0 +1,29 @@
1
+ <header class="adm-topbar">
2
+ <div class="adm-container adm-topbar-inner">
3
+ <button type="button" class="adm-icon-btn md:hidden" data-adm-toggle="adm-nav" aria-controls="adm-nav" aria-expanded="false" aria-label="<%= t("administratum.menu") %>">
4
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="adm-icon" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
5
+ </button>
6
+ <%= link_to administratum.root_path, class: "adm-brand" do %>
7
+ <% if administratum_config.logo %>
8
+ <%= image_tag administratum_config.logo, alt: administratum_config.app_name, class: "adm-logo" %>
9
+ <% else %>
10
+ <span class="adm-brand-name"><%= administratum_config.app_name %></span>
11
+ <% end %>
12
+ <% end %>
13
+ <div class="adm-topbar-actions">
14
+ <button type="button" class="adm-icon-btn" data-adm-theme-toggle aria-label="<%= t("administratum.toggle_theme") %>">
15
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="adm-icon" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
16
+ </button>
17
+ <% if current_admin %>
18
+ <div class="adm-dropdown" data-adm-dropdown>
19
+ <button type="button" class="adm-btn adm-btn-ghost" data-adm-dropdown-toggle aria-haspopup="true" aria-expanded="false">
20
+ <%= current_admin.email %>
21
+ </button>
22
+ <div class="adm-dropdown-menu" data-adm-dropdown-menu hidden>
23
+ <%= button_to t("administratum.sessions.sign_out"), administratum.sign_out_path, method: :delete, class: "adm-dropdown-item" %>
24
+ </div>
25
+ </div>
26
+ <% end %>
27
+ </div>
28
+ </div>
29
+ </header>
@@ -0,0 +1,4 @@
1
+ <p><%= t("administratum.password_resets.email.greeting", email: @user.email) %></p>
2
+ <p><%= t("administratum.password_resets.email.body", app: Administratum.config.app_name) %></p>
3
+ <p><%= link_to t("administratum.password_resets.email.link"), @url %></p>
4
+ <p><%= t("administratum.password_resets.email.ignore") %></p>
@@ -0,0 +1,7 @@
1
+ <%= t("administratum.password_resets.email.greeting", email: @user.email) %>
2
+
3
+ <%= t("administratum.password_resets.email.body", app: Administratum.config.app_name) %>
4
+
5
+ <%= @url %>
6
+
7
+ <%= t("administratum.password_resets.email.ignore") %>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= I18n.locale %>">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title><%= administratum_title %></title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ <%= administratum_color_scheme_script %>
10
+ <%= administratum_stylesheet_tag %>
11
+ <%= administratum_theme_style %>
12
+ <%= javascript_include_tag "administratum", type: "module" %>
13
+ <%= administratum_head %>
14
+ <%= yield :head %>
15
+ </head>
16
+ <body class="adm-body">
17
+ <%= render "administratum/shared/topbar" %>
18
+ <%= render "administratum/shared/nav" %>
19
+ <main class="adm-main">
20
+ <%= yield %>
21
+ </main>
22
+ <%= yield :panels %>
23
+ <%= render "administratum/shared/flash" %>
24
+ </body>
25
+ </html>
@@ -0,0 +1,32 @@
1
+ <!DOCTYPE html>
2
+ <html lang="<%= I18n.locale %>">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title><%= administratum_title %></title>
7
+ <%= csrf_meta_tags %>
8
+ <%= csp_meta_tag %>
9
+ <%= administratum_color_scheme_script %>
10
+ <%= administratum_stylesheet_tag %>
11
+ <%= administratum_theme_style %>
12
+ <%= javascript_include_tag "administratum", type: "module" %>
13
+ <%= administratum_head %>
14
+ </head>
15
+ <body class="adm-body adm-auth">
16
+ <div class="adm-auth-panel">
17
+ <div class="adm-auth-brand">
18
+ <% if administratum_config.logo %>
19
+ <%= image_tag administratum_config.logo, alt: administratum_config.app_name, class: "adm-logo" %>
20
+ <% else %>
21
+ <span class="adm-brand-name"><%= administratum_config.app_name %></span>
22
+ <% end %>
23
+ </div>
24
+ <div class="adm-card">
25
+ <div class="adm-card-body">
26
+ <%= yield %>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <%= render "administratum/shared/flash" %>
31
+ </body>
32
+ </html>
@@ -0,0 +1,65 @@
1
+ en:
2
+ administratum:
3
+ "yes": "Yes"
4
+ "no": "No"
5
+ dashboard: Dashboard
6
+ menu: Menu
7
+ close: Close
8
+ cancel: Cancel
9
+ confirm: Are you sure?
10
+ toggle_theme: Toggle dark mode
11
+ empty: No %{resources} yet.
12
+ download_csv: Download CSV
13
+ actions:
14
+ title: Actions
15
+ new: New %{resource}
16
+ edit: Edit
17
+ edit_resource: Edit %{title}
18
+ view: View
19
+ destroy: Delete
20
+ flash:
21
+ created: "%{resource} was created."
22
+ updated: "%{resource} was updated."
23
+ destroyed: "%{resource} was deleted."
24
+ errors:
25
+ title:
26
+ one: 1 error prevented saving
27
+ other: "%{count} errors prevented saving"
28
+ scope_tabs: Views
29
+ filters:
30
+ title: Filters
31
+ apply: Apply
32
+ clear: Clear
33
+ any: Any
34
+ from: "%{field} from"
35
+ to: "%{field} to"
36
+ upload:
37
+ file: File
38
+ submit: Upload
39
+ sessions:
40
+ title: Sign in
41
+ email: Email
42
+ password: Password
43
+ remember_me: Remember me
44
+ submit: Sign in
45
+ sign_out: Sign out
46
+ invalid: Invalid email or password.
47
+ unauthenticated: Please sign in to continue.
48
+ signed_out: You have been signed out.
49
+ password_resets:
50
+ link: Forgot your password?
51
+ title: Reset password
52
+ lead: Enter your email and we'll send you a link to choose a new password.
53
+ submit: Send reset link
54
+ back: Back to sign in
55
+ sent: If that email belongs to an admin, a reset link is on its way.
56
+ new_password: Choose a new password
57
+ confirmation: Confirm password
58
+ save: Save password
59
+ updated: Your password was changed. Please sign in.
60
+ subject: Reset your %{app} admin password
61
+ email:
62
+ greeting: Hello %{email},
63
+ body: Someone asked to reset your %{app} admin password. Use the link below to choose a new one.
64
+ link: Choose a new password
65
+ ignore: If you didn't ask for this, you can ignore this email.