thecore_ui_rails_admin 3.6.1 → 3.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: babf45ecdbfd260e90d7a50dedb8c40e5a3c881a0da48ea510f490f2aac0c2d7
4
- data.tar.gz: 0b06f34722a73e632fddbb2dd12bcb760c7ec60aa5537056a7d3e2cf090af3ea
3
+ metadata.gz: 68bdc2c266f2da01e7c620cad865a2a41960dd7be738643d8a97bb52e09bc164
4
+ data.tar.gz: 60447392948bb9d54f028b2a8eb19d7fd5e8664e1202959de8ad0390cafcf55f
5
5
  SHA512:
6
- metadata.gz: 5cd0d92cd912e7354d80b2a1323a9db11bb7233617f7f6d7c42d06669b9ece0f1b2226d40143baa64b93e21b19aa8f83104c1c25d0ea607d63c6577fe3b5e9cb
7
- data.tar.gz: f7246b699c1ef4ca2a0647cb13d9a05a6995baaef3ca0236463cda571bf4a662009b2f52d896bcb8ecf55e17ea5e27b808464b62b366df5367e2b0da5373d8ec
6
+ metadata.gz: 4230e1a6cb30a8af5bca7ef1424c862ff8d235be31d17f6fa86163c12e3940d089e2b11cfe169021c3bed18ea576e3b932b85f05719319868267707b4e7733d1
7
+ data.tar.gz: ade89d04b3ab490a86c8753cb92c8a560ea2fe9da16248ffbcb8c30c9ac1251f13f7955bd386db9cc627b0d9c31fdc91832fb2ddcd0968cf3a5efaa12f16d452
data/README.md CHANGED
@@ -2,6 +2,41 @@ This is part of [Thecore framework](https://github.com/gabrieletassoni/thecore/t
2
2
 
3
3
  ---
4
4
 
5
+ ## Default `navigation_label`/icon for models without a `RailsAdmin::ModelName` concern
6
+
7
+ Per [ADR 0001](https://github.com/gabrieletassoni/thecore/blob/release/3/docs/adr/0001-application-record-defaults-over-generated-concerns.md)
8
+ (host app), any `ApplicationRecord` subclass that does **not** define its own
9
+ `RailsAdmin::ModelName` concern still gets a sensible `navigation_label` and
10
+ a real default icon (`fa fa-table`) in the RailsAdmin sidebar, instead of
11
+ falling back to RailsAdmin's own unconfigured default (no group / no icon).
12
+
13
+ This is provided by `ThecoreUiRailsAdminDefaultNavigationConcern`
14
+ (`config/initializers/concern_default_navigation.rb`), registered into
15
+ [`ThecoreBackendCommons::DefaultModuleRegistry`](../thecore_backend_commons/README.md)
16
+ so it is included automatically into every model at class-definition time —
17
+ no generated concern file required.
18
+
19
+ The default `navigation_label` reuses the exact same i18n key
20
+ (`I18n.t('admin.registries.label')`) that the Thecore VS Code extension's
21
+ `addModel` command already hardcodes into every freshly generated
22
+ `RailsAdmin::ModelName` concern, so a model relying on this default reads
23
+ identically, to an end user, to one whose generated-and-never-customized
24
+ concern was kept around.
25
+
26
+ **Field-level configuration is never defaulted** — `hide`, `sticky`,
27
+ `configure :field`, custom `list`/`edit` blocks, etc. stay exclusively in
28
+ hand-written `RailsAdmin::ModelName` concerns. A model that already has its
29
+ own concern is unaffected: its own `navigation_label`/`navigation_icon` (if
30
+ set) and all field-level config still win over/apply alongside the default.
31
+
32
+ > **Temporary dependency note**: this feature requires
33
+ > `ThecoreBackendCommons::DefaultModuleRegistry`, merged into
34
+ > `thecore_backend_commons`'s `release/3` branch but not yet published to
35
+ > RubyGems. The `Gemfile` pins a git source for this
36
+ > (`github: "gabrieletassoni/thecore_backend_commons", branch: "release/3"`)
37
+ > until a real release ships — remove it once `thecore_backend_commons`
38
+ > cuts a version satisfying the gemspec's `>= 3.4` constraint.
39
+
5
40
  ## Push Notification Test (RailsAdmin root action)
6
41
 
7
42
  A built-in admin UI for sending Web Push test notifications. It lets you pick one or more active subscribers and fire a real notification immediately — useful for verifying the end-to-end VAPID setup without writing any code.
@@ -1,7 +1,7 @@
1
1
  <span class="hide-toolbar"></span>
2
2
 
3
3
  <div class="container mt-4">
4
- <h2>Push Notification Test</h2>
4
+ <h2><%= t("admin.actions.push_notification_test.title") %></h2>
5
5
 
6
6
  <% if flash[:success].present? %>
7
7
  <div class="alert alert-success"><%= flash[:success] %></div>
@@ -10,10 +10,41 @@
10
10
  <div class="alert alert-danger"><%= flash[:error] %></div>
11
11
  <% end %>
12
12
 
13
- <%= form_with url: push_notification_test_path, method: :post, local: true do |form| %>
13
+ <datalist id="subscriber-email-list">
14
+ <% @subscriber_emails.each do |email| %>
15
+ <option value="<%= email %>">
16
+ <% end %>
17
+ </datalist>
18
+
19
+ <%= form_with url: push_notification_test_path, method: :get, local: true do |f| %>
20
+ <div class="card mb-4">
21
+ <div class="card-header d-flex align-items-center justify-content-between">
22
+ <h5 class="mb-0"><%= t("admin.actions.push_notification_test.filter_title") %></h5>
23
+ <span class="badge bg-secondary">
24
+ <%= t("admin.actions.push_notification_test.total_count", count: @total_count) %>
25
+ </span>
26
+ </div>
27
+ <div class="card-body">
28
+ <div class="input-group">
29
+ <%= f.text_field :q,
30
+ value: params[:q],
31
+ class: "form-control",
32
+ placeholder: t("admin.actions.push_notification_test.filter_placeholder"),
33
+ list: "subscriber-email-list",
34
+ autocomplete: "off" %>
35
+ <%= f.submit t("admin.actions.push_notification_test.filter_button"),
36
+ class: "btn btn-secondary" %>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <% end %>
41
+
42
+ <%= form_with url: push_notification_test_path, method: :post, local: true, id: "send-push-form" do |form| %>
43
+ <%= hidden_field_tag :q, params[:q] %>
44
+
14
45
  <div class="card mb-4">
15
46
  <div class="card-header">
16
- <h5 class="mb-0">Select Subscribers</h5>
47
+ <h5 class="mb-0"><%= t("admin.actions.push_notification_test.select_subscribers") %></h5>
17
48
  </div>
18
49
  <div class="card-body">
19
50
  <% if @subscribers.any? %>
@@ -22,7 +53,7 @@
22
53
  <%= check_box_tag "subscriber_ids[]",
23
54
  subscriber.id,
24
55
  false,
25
- class: "form-check-input",
56
+ class: "form-check-input subscriber-checkbox",
26
57
  id: "subscriber_#{subscriber.id}" %>
27
58
  <label class="form-check-label" for="subscriber_<%= subscriber.id %>">
28
59
  <strong><%= subscriber.user&.email || "user ##{subscriber.user_id}" %></strong>
@@ -33,44 +64,73 @@
33
64
  </label>
34
65
  </div>
35
66
  <% end %>
67
+ <% elsif params[:q].present? %>
68
+ <p class="text-muted"><%= t("admin.actions.push_notification_test.no_results") %></p>
36
69
  <% else %>
37
- <p class="text-muted">No active subscribers found.</p>
70
+ <p class="text-muted">
71
+ <%= t("admin.actions.push_notification_test.disclaimer_no_filter", count: @total_count) %>
72
+ </p>
38
73
  <% end %>
39
74
  </div>
40
75
  </div>
41
76
 
42
- <div class="card mb-4">
43
- <div class="card-header">
44
- <h5 class="mb-0">Message</h5>
45
- </div>
46
- <div class="card-body">
47
- <div class="mb-3">
48
- <%= form.label :title, "Title", class: "form-label" %>
49
- <%= form.text_field :title, class: "form-control", required: true,
50
- placeholder: "Notification title" %>
77
+ <% if @subscribers.any? %>
78
+ <div class="card mb-4">
79
+ <div class="card-header">
80
+ <h5 class="mb-0"><%= t("admin.actions.push_notification_test.message_title") %></h5>
51
81
  </div>
82
+ <div class="card-body">
83
+ <div class="mb-3">
84
+ <%= form.label :title, t("admin.actions.push_notification_test.title_label"), class: "form-label" %>
85
+ <%= form.text_field :title, class: "form-control", required: true,
86
+ placeholder: t("admin.actions.push_notification_test.title_placeholder") %>
87
+ </div>
52
88
 
53
- <div class="mb-3">
54
- <%= form.label :body, "Body", class: "form-label" %>
55
- <%= form.text_area :body, class: "form-control", rows: 3, required: true,
56
- placeholder: "Notification body text" %>
57
- </div>
89
+ <div class="mb-3">
90
+ <%= form.label :body, t("admin.actions.push_notification_test.body_label"), class: "form-label" %>
91
+ <%= form.text_area :body, class: "form-control", rows: 3,
92
+ placeholder: t("admin.actions.push_notification_test.body_placeholder") %>
93
+ </div>
58
94
 
59
- <div class="mb-3">
60
- <%= form.label :url, "URL (optional)", class: "form-label" %>
61
- <%= form.url_field :url, class: "form-control",
62
- placeholder: "https://example.com/target-page" %>
63
- </div>
95
+ <div class="mb-3">
96
+ <%= form.label :url, t("admin.actions.push_notification_test.url_label"), class: "form-label" %>
97
+ <%= form.url_field :url, class: "form-control",
98
+ placeholder: "https://example.com/target-page" %>
99
+ </div>
64
100
 
65
- <div class="mb-3">
66
- <%= form.label :icon, "Icon URL (optional)", class: "form-label" %>
67
- <%= form.url_field :icon, class: "form-control",
68
- placeholder: "https://example.com/icon.png" %>
69
- </div>
101
+ <div class="mb-3">
102
+ <%= form.label :icon, t("admin.actions.push_notification_test.icon_label"), class: "form-label" %>
103
+ <%= form.url_field :icon, class: "form-control",
104
+ placeholder: "https://example.com/icon.png" %>
105
+ </div>
70
106
 
71
- <%= form.submit "Send Test", class: "btn btn-primary",
72
- disabled: @subscribers.empty? %>
107
+ <%= form.submit t("admin.actions.push_notification_test.send_button"),
108
+ class: "btn btn-primary",
109
+ id: "send-push-btn" %>
110
+ </div>
73
111
  </div>
74
- </div>
112
+ <% end %>
75
113
  <% end %>
76
114
  </div>
115
+
116
+ <script>
117
+ (function () {
118
+ var form = document.getElementById("send-push-form");
119
+ if (!form) return;
120
+
121
+ var threshold = <%= @large_subscriber_threshold %>;
122
+ var warningTemplate = "<%= j t('admin.actions.push_notification_test.large_selection_warning',
123
+ count: '__N__',
124
+ threshold: @large_subscriber_threshold) %>";
125
+
126
+ form.addEventListener("submit", function (e) {
127
+ var checked = form.querySelectorAll(".subscriber-checkbox:checked").length;
128
+ if (checked > threshold) {
129
+ var msg = warningTemplate.replace("__N__", checked);
130
+ if (!confirm(msg)) {
131
+ e.preventDefault();
132
+ }
133
+ }
134
+ });
135
+ })();
136
+ </script>
@@ -0,0 +1,64 @@
1
+ require 'active_support/concern'
2
+
3
+ # Default RailsAdmin `navigation_label`/`navigation_icon` for any
4
+ # `ApplicationRecord` subclass that has no explicit `RailsAdmin::ModelName`
5
+ # concern of its own -- see ADR 0001
6
+ # (vendor/external/thecore/docs/adr/0001-application-record-defaults-over-generated-concerns.md
7
+ # in the host app) and GitHub issue
8
+ # gabrieletassoni/thecore_ui_rails_admin#7.
9
+ #
10
+ # Registered into `ThecoreBackendCommons::DefaultModuleRegistry` below, so
11
+ # it is `include`d automatically into every model at class-definition time
12
+ # (via `ApplicationRecord.inherited`) instead of requiring a per-model
13
+ # `RailsAdmin::ModelName` concern file to be generated just to get these two
14
+ # mechanically-derivable values.
15
+ #
16
+ # Deliberately does NOT default any field-level configuration (`hide`,
17
+ # `sticky`, `configure :field`, custom `list`/`edit` blocks, ...) -- per
18
+ # ADR 0001's research, every existing hand-written `RailsAdmin::ModelName`
19
+ # concern in the `mytask` engine carries real field-level content, so only
20
+ # the navigation bits below are safe to default. Field-level config stays
21
+ # exclusively in hand-written concerns.
22
+ #
23
+ # `navigation_label` intentionally reuses the exact SAME fixed i18n key
24
+ # (`I18n.t('admin.registries.label')`) that the Thecore VS Code extension's
25
+ # `addModel` template
26
+ # (vendor/external/thecore_code_extension/templates/addModel/rails_admin_concern.rb
27
+ # in the host app) already hardcodes into every freshly generated
28
+ # `RailsAdmin::ModelName` concern -- so a model relying on this default
29
+ # looks, to an end user, exactly like one whose generated-and-never-customized
30
+ # concern was kept around. `DEFAULT_ICON` is a real, ready-to-use FontAwesome
31
+ # icon (no "TODO: customize" placeholder needed).
32
+ #
33
+ # == Why an explicit concern's own `navigation_label`/`navigation_icon` wins
34
+ #
35
+ # This module is `include`d into a model class from
36
+ # `ApplicationRecord.inherited`, which fires -- and so evaluates this
37
+ # `included do rails_admin do ... end end` block, registering RailsAdmin's
38
+ # *first* deferred config block for that model -- before the model class
39
+ # body itself runs its own `include RailsAdmin::ModelName` statement, whose
40
+ # block is registered *second*. RailsAdmin evaluates same-origin deferred
41
+ # blocks in registration order (see
42
+ # `RailsAdmin::Config::LazyModel#target` in the `rails_admin` gem), and
43
+ # `navigation_label`/`navigation_icon` are plain last-write-wins setters
44
+ # (see `RailsAdmin::Config::Configurable::ClassMethods#register_instance_option`),
45
+ # so whichever call runs last -- the hand-written concern's, when present --
46
+ # overrides the default set here.
47
+ module ThecoreUiRailsAdminDefaultNavigationConcern
48
+ extend ActiveSupport::Concern
49
+
50
+ DEFAULT_ICON = 'fa fa-table'.freeze
51
+
52
+ included do
53
+ rails_admin do
54
+ navigation_label I18n.t('admin.registries.label')
55
+ navigation_icon ThecoreUiRailsAdminDefaultNavigationConcern::DEFAULT_ICON
56
+ end
57
+ end
58
+ end
59
+
60
+ Rails.application.config.to_prepare do
61
+ ThecoreBackendCommons::DefaultModuleRegistry.register(
62
+ ThecoreUiRailsAdminDefaultNavigationConcern
63
+ )
64
+ end
@@ -60,6 +60,22 @@ en:
60
60
  breadcrumb: Test Push Notification
61
61
  success: "Sent test notification to %{count} subscriber(s)"
62
62
  error_blank_title: "Title can't be blank"
63
+ filter_title: "Filter Subscribers"
64
+ filter_placeholder: "Search by email..."
65
+ filter_button: "Search"
66
+ total_count: "%{count} active subscriber(s)"
67
+ disclaimer_no_filter: "There are %{count} active subscriber(s) in the system. Start typing an email address in the search field above to filter and select recipients."
68
+ no_results: "No active subscribers found for this email."
69
+ select_subscribers: "Select Subscribers"
70
+ message_title: "Message"
71
+ title_label: "Title"
72
+ title_placeholder: "Notification title"
73
+ body_label: "Body"
74
+ body_placeholder: "Notification body text"
75
+ url_label: "URL (optional)"
76
+ icon_label: "Icon URL (optional)"
77
+ send_button: "Send Test"
78
+ large_selection_warning: "You are about to send to %{count} subscriber(s). This may take a while. Do you want to continue?"
63
79
  scopes:
64
80
  _all: All
65
81
  flash:
@@ -60,6 +60,22 @@ it:
60
60
  breadcrumb: Test Notifica Push
61
61
  success: "Notifica di test inviata a %{count} sottoscrittore/i"
62
62
  error_blank_title: "Il titolo non può essere vuoto"
63
+ filter_title: "Filtra Subscriber"
64
+ filter_placeholder: "Cerca per email..."
65
+ filter_button: "Cerca"
66
+ total_count: "%{count} subscriber/i attivi"
67
+ disclaimer_no_filter: "Ci sono %{count} subscriber/i attivi nel sistema. Inizia a digitare un indirizzo email nel campo di ricerca per filtrare e selezionare i destinatari."
68
+ no_results: "Nessun subscriber attivo trovato per questa email."
69
+ select_subscribers: "Seleziona Subscriber"
70
+ message_title: "Messaggio"
71
+ title_label: "Titolo"
72
+ title_placeholder: "Titolo della notifica"
73
+ body_label: "Testo"
74
+ body_placeholder: "Testo del messaggio"
75
+ url_label: "URL (opzionale)"
76
+ icon_label: "URL icona (opzionale)"
77
+ send_button: "Invia Test"
78
+ large_selection_warning: "Stai per inviare a %{count} subscriber/i. L'operazione potrebbe richiedere del tempo. Vuoi continuare?"
63
79
  scopes:
64
80
  _all: Tutti
65
81
  flash:
@@ -1,3 +1,5 @@
1
+ LARGE_SUBSCRIBER_THRESHOLD = 10
2
+
1
3
  RailsAdmin::Config::Actions.add_action "push_notification_test", :base, :root do
2
4
  show_in_sidebar true
3
5
  show_in_navigation false
@@ -12,7 +14,16 @@ RailsAdmin::Config::Actions.add_action "push_notification_test", :base, :root do
12
14
 
13
15
  controller do
14
16
  proc do
15
- @subscribers = PushSubscriber.active.includes(:user)
17
+ @large_subscriber_threshold = LARGE_SUBSCRIBER_THRESHOLD
18
+ @total_count = PushSubscriber.active.count
19
+ @subscriber_emails = User.joins(:push_subscribers).merge(PushSubscriber.active).distinct.pluck(:email)
20
+
21
+ filter_query = params[:q].presence
22
+ @subscribers = if filter_query
23
+ PushSubscriber.active.includes(:user).joins(:user).where("users.email ILIKE ?", "%#{filter_query}%")
24
+ else
25
+ []
26
+ end
16
27
 
17
28
  if request.post?
18
29
  params.permit!
@@ -1,3 +1,3 @@
1
1
  module ThecoreUiRailsAdmin
2
- VERSION = "3.6.1".freeze
2
+ VERSION = "3.8.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thecore_ui_rails_admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.1
4
+ version: 3.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.18'
54
+ - !ruby/object:Gem::Dependency
55
+ name: thecore_backend_commons
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '3.4'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '3.4'
54
68
  description: Holds all base dependencies and configurations to have a thecore integrated
55
69
  with Rails Admin.
56
70
  email:
@@ -103,6 +117,7 @@ files:
103
117
  - config/initializers/assets.rb
104
118
  - config/initializers/concern_action.rb
105
119
  - config/initializers/concern_bulk_delete.rb
120
+ - config/initializers/concern_default_navigation.rb
106
121
  - config/initializers/concern_export.rb
107
122
  - config/initializers/concern_permission.rb
108
123
  - config/initializers/concern_permission_role.rb