thecore_ui_rails_admin 3.4.1 → 3.5.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: deee8b9dbd8c0c0d6f3edcd15638de8077f03e7e5d9955722c2fb3737e2826e1
4
- data.tar.gz: 4ab2d4a33bf63565b40753bf3ac89bdaab0e6f14f3c76f6a637ac95df0f23b51
3
+ metadata.gz: cdcb1fa31ff5193b98c15b1243db136f723b4bf7e5379c1245d0a0c8117e54f2
4
+ data.tar.gz: dad5d6adc509ab31d1b0bf55e64209e4f162dd0d4d2db7cea787ea59cbfa64b7
5
5
  SHA512:
6
- metadata.gz: d1ad98f4a0c002c89caf0f76c8f18c2e64921e6870c3d14a4896118833d04df49d424c08edc0edc52a8123c4bee5219cd7dcd4e07ef2b4abda054f0effcc9449
7
- data.tar.gz: 3ae440087a6a07edc9901029916f5fa6079a567f7d92b87724d4032cf94d2f6dbcc1150142714a18ccc8847639eaa60661c5057d9068d226045187f9fb96e71f
6
+ metadata.gz: ef1d926329dd1f13591c0d4eb03a7d6f0d65c6187073877148be3959c50469a7babf291156ff23578cda6a4376c862f3641fad82159957fae0df225cb377f757
7
+ data.tar.gz: 27279ea7e643428354c4b6f412dd6ea46d38691a8f0c2cb4cb410b6b9fb6f777b8ecc1f887628657b1e0fd86414a692868b7e10938d882681cc7c82085e06328
@@ -1,7 +1,6 @@
1
1
  //= require 'thecore_ui_commons'
2
2
  //= require 'rails_admin/custom/thecore/ui'
3
3
  //= require 'channels/index'
4
-
5
4
  const adjustIframe = function (obj) {
6
5
  console.log("Resizing");
7
6
  obj.style.height = obj.contentWindow.document.body.scrollHeight + 'px';
@@ -19,12 +18,22 @@ $(document).on('turbo:load', function (event) {
19
18
  console.log(" - Hostname:", currentURL.hostname);
20
19
  console.log(" - Port:", currentURL.port);
21
20
  console.log(" - Pathname:", currentURL.pathname);
21
+ // Get the current user ID from a meta tag
22
+ document.currentUserId = document.querySelector('meta[name="current-user-id"]').getAttribute('content');
23
+ // Get the current url pathname, is the first string between app/ and the first / encountered, i.e. /app/location/bulk/action, in this case is location
24
+ document.currentModelName = currentURL.pathname.split('/')[2];
25
+ console.log(" - Controller:", document.currentModelName);
22
26
  console.log(" - Search:", currentURL.search);
23
27
  currentURL.searchParams.forEach((v, k) => {
24
28
  console.log(` - ${k}: ${v}`);
25
29
  })
26
30
  console.log(" - Hash:", currentURL.hash);
27
31
 
32
+
33
+ // Remove any existing logo to avoid duplicates
34
+ $(".navbar-brand-logo").remove();
35
+ // Prepend the custom logo to the navbar
36
+
28
37
  // Add the app_logo before the Title
29
38
  const customer_logo_path = "<%= asset_path('app_logo.svg') %>";
30
39
  $(".navbar-brand").prepend(`<img class="navbar-brand-logo" alt="Customer Logo" src="${customer_logo_path}"/>`);
@@ -38,6 +47,109 @@ $(document).on('turbo:load', function (event) {
38
47
  link.href = link.href.split('?')[0] + currentQuery;
39
48
  }
40
49
  });
50
+
51
+ // If URL ends with /bulk_action, uncheck all checkboxes
52
+ if (currentURL.pathname.endsWith('/bulk_action')) {
53
+ const checkboxes = document.querySelectorAll('input[type="checkbox"][name^="schema"]');
54
+ checkboxes.forEach(checkbox => {
55
+ checkbox.checked = false;
56
+ });
57
+ // Also uncheck the main checkbox if it exists
58
+ const mainCheckbox = document.querySelector('input[type="checkbox"][name="all"]');
59
+ if (mainCheckbox) {
60
+ mainCheckbox.checked = false;
61
+ }
62
+
63
+ // Use the fetch to get all the already saved checkboxes:
64
+ fetch('/app/general_computation?' + new URLSearchParams({
65
+ verb: "load",
66
+ model: "UserPreference",
67
+ "finders[user_id]": document.currentUserId,
68
+ "finders[name]": `export_${document.currentModelName}`
69
+ }), {
70
+ headers: {
71
+ 'Content-Type': 'application/json',
72
+ 'Accept': 'application/json',
73
+ 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
74
+ }
75
+ })
76
+ .then(response => {
77
+ if (response.ok) {
78
+ return response.json()
79
+ // The value field in the response bears the ids of the input checkbox type to set at checked true
80
+ } else {
81
+ return {
82
+ message: {
83
+ value: []
84
+ }
85
+ }
86
+ }
87
+ })
88
+ .then(message => {
89
+ message.message.value.forEach(id => {
90
+ const selectedCheckbox = document.querySelector(`#${id}`);
91
+ if (selectedCheckbox) {
92
+ selectedCheckbox.checked = true;
93
+ }
94
+ })
95
+ })
96
+
97
+ // Add to the fieldset with id fields_to_export a button with class btn btn-info which, if clicked, gets the checked status of all checkboxes with name starting with schema and using the current session login, saves them to the ruby on rails controller User in order to be saved to the settings json field.
98
+ const fieldset = document.getElementById('fields_to_export');
99
+ if (fieldset) {
100
+ const saveButton = document.createElement('button');
101
+ saveButton.type = 'button';
102
+ saveButton.className = 'btn btn-info';
103
+ saveButton.textContent = 'Save Selected Fields as Default';
104
+ saveButton.style.marginLeft = '10px';
105
+ saveButton.onclick = function () {
106
+ const selectedFields = [];
107
+ const checkboxes = document.querySelectorAll('input[type="checkbox"][name^="schema"]');
108
+ checkboxes.forEach(checkbox => {
109
+ if (checkbox.checked) {
110
+ selectedFields.push(checkbox.id);
111
+ }
112
+ });
113
+ // Send the selected fields to the server using fetch API, emulating a POST request to /app/user of type form json
114
+ fetch('/app/general_computation', {
115
+ method: 'POST',
116
+ headers: {
117
+ 'Content-Type': 'application/json',
118
+ 'Accept': 'application/json',
119
+ 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
120
+ },
121
+ // The body will have the current_user id, a name which is created by concattenating export and the current model name, and the selected fields as an array
122
+ body: JSON.stringify({
123
+ verb: "upsert",
124
+ model: "UserPreference",
125
+ finders: {
126
+ user_id: document.currentUserId,
127
+ name: `export_${document.currentModelName}`
128
+ },
129
+ fields: {
130
+ value: selectedFields
131
+ }
132
+ })
133
+ })
134
+ .then(response => {
135
+ if (response.ok) {
136
+ alert('Default export fields saved successfully.');
137
+ } else {
138
+ alert('Error saving default export fields.');
139
+ }
140
+ })
141
+ .catch(error => {
142
+ console.error('Error:', error);
143
+ alert('Error saving default export fields.');
144
+ });
145
+ };
146
+ // add the button to a div with row class, then att the div to the fieldset
147
+ const buttonRow = document.createElement('div');
148
+ buttonRow.className = 'row mt-3';
149
+ buttonRow.appendChild(saveButton);
150
+ fieldset.append(buttonRow);
151
+ }
152
+ }
41
153
  });
42
154
 
43
155
  const sidepanel = "body > div.container-fluid > div > div.col-sm-3.col-md-2.flex-wrap.p-0"
@@ -6,9 +6,28 @@ body {
6
6
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
7
7
  }
8
8
 
9
- form.new_user {
9
+ form.new_user,
10
+ form.button_to {
10
11
  display: flex;
11
12
  flex-direction: column;
13
+ align-items: center;
14
+ justify-content: center;
15
+ }
16
+
17
+ // The buttons with type submit inside a form with class button_to
18
+ form.button_to button[type=submit] {
19
+ // Make it like the input[type=submit]
20
+ background-color: $primary;
21
+ color: white;
22
+ border: 0;
23
+ border-radius: 2em;
24
+ padding: 0.5em 1em;
25
+ font-size: 1em;
26
+ font-weight: 600;
27
+ cursor: pointer;
28
+ box-shadow: 0 0 0.5em rgba($primary, 0.5);
29
+ transition: background-color 0.3s, box-shadow 0.3s;
30
+ width: 70%;
12
31
  }
13
32
 
14
33
  label {
@@ -30,6 +49,10 @@ label {
30
49
  background-color: white !important;
31
50
  max-width: 1136px;
32
51
 
52
+ div.logos, div.login-methods {
53
+ width: 50%;
54
+ }
55
+
33
56
  .field,
34
57
  .actions {
35
58
  width: 70%;
@@ -62,7 +85,7 @@ label {
62
85
  flex-direction: column;
63
86
  align-items: center;
64
87
  justify-content: center;
65
- flex-basis: 50%;
88
+ // flex-basis: 50%;
66
89
  flex-grow: 0;
67
90
  }
68
91
  }
@@ -72,4 +72,10 @@ body > div.container-fluid > div > div.col-sm-9.col-sm-offset-3.col-md-10.col-md
72
72
  height: 1.7em;
73
73
  }
74
74
 
75
+ // Hide and remove export for found objects, is safer to have the possibility to export only selected rows to avoid big CPU usage.
76
+ .export_collection_link,
77
+ #list form div div.row div.col-sm-6:nth-child(2) {
78
+ display: none;
79
+ }
80
+
75
81
  @import 'rails_admin/custom/thecore/theming';
@@ -0,0 +1,19 @@
1
+ module Api::UserPreference
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ # Use self.json_attrs to drive json rendering for
6
+ # API model responses (index, show and update ones).
7
+ # For reference:
8
+ # https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
9
+ # The object passed accepts only these keys:
10
+ # - only: list [] of model fields to be shown in JSON serialization
11
+ # - except: exclude these fields from the JSON serialization, is a list []
12
+ # - methods: include the result of some method defined in the model
13
+ # - include: include associated models, it's an object {} which also accepts the keys described here
14
+ cattr_accessor :json_attrs
15
+ self.json_attrs = ::ModelDrivenApi.smart_merge (json_attrs || {}), {}
16
+
17
+ # Custom action callable by the API must be defined in /app/models/concerns/endpoints/
18
+ end
19
+ end
@@ -0,0 +1,78 @@
1
+ class Endpoints::UserPreference < NonCrudEndpoints
2
+ # self.desc 'UserPreference', :test, {
3
+ # # Define the action name using openapi swagger format
4
+ # get: {
5
+ # summary: "Test API Custom Action",
6
+ # description: "This is a test API custom action",
7
+ # operationId: "test",
8
+ # tags: ["Test"],
9
+ # parameters: [
10
+ # {
11
+ # name: "explain",
12
+ # in: "query",
13
+ # description: "Explain the action by returning this openapi schema",
14
+ # required: true,
15
+ # schema: {
16
+ # type: "boolean"
17
+ # }
18
+ # }
19
+ # ],
20
+ # responses: {
21
+ # 200 => {
22
+ # description: "The openAPI json schema for this action",
23
+ # content: {
24
+ # "application/json": {
25
+ # schema: {
26
+ # type: "object",
27
+ # additionalProperties: true
28
+ # }
29
+ # }
30
+ # }
31
+ # },
32
+ # 501 => {
33
+ # error: :string,
34
+ # }
35
+ # }
36
+ # },
37
+ # post: {
38
+ # summary: "Test API Custom Action",
39
+ # description: "This is a test API custom action",
40
+ # operationId: "test",
41
+ # tags: ["Test"],
42
+ # requestBody: {
43
+ # required: true,
44
+ # content: {
45
+ # "application/json": {}
46
+ # }
47
+ # },
48
+ # responses: {
49
+ # 200 => {
50
+ # description: "The openAPI json schema for this action",
51
+ # # This will return the object with a message string and a params object
52
+ # content: {
53
+ # "application/json": {
54
+ # schema: {
55
+ # type: "object",
56
+ # properties: {
57
+ # message: {
58
+ # type: "string"
59
+ # },
60
+ # params: {
61
+ # type: "object",
62
+ # additionalProperties: true
63
+ # }
64
+ # }
65
+ # }
66
+ # }
67
+ # }
68
+ # },
69
+ # 501 => {
70
+ # error: :string,
71
+ # }
72
+ # }
73
+ # }
74
+ # }
75
+ # def test(params)
76
+ # return { message: "Hello World From Test API Custom Action called test", params: params }, 200
77
+ # end
78
+ end
@@ -0,0 +1,11 @@
1
+ module RailsAdmin::UserPreference
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ rails_admin do
6
+ navigation_label I18n.t('admin.registries.label')
7
+ navigation_icon 'fa fa-file'
8
+ visible false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ class UserPreference < ApplicationRecord
2
+ include Api::UserPreference
3
+ include RailsAdmin::UserPreference
4
+ belongs_to :user, inverse_of: :user_preferences
5
+ validates :name, presence: true, uniqueness: { scope: :user_id }
6
+ validates :value, presence: true
7
+ end
@@ -4,6 +4,14 @@
4
4
  <head>
5
5
  <%= render "layouts/rails_admin/head" %>
6
6
  <%= render "layouts/rails_admin/controller_assets" %>
7
+
8
+ <!-- meta tags to get current user id and current model name which in rails_admin is not controller_name, since it's always main, but the abstract model one for use in custom javascript -->
9
+ <meta name="current-user-id" content="<%= current_user.id %>">
10
+ <%
11
+ =begin%>
12
+ <meta name="current-model-name" content="<%= @abstract_model.param_key %>">
13
+ <%
14
+ =end%>
7
15
  </head>
8
16
  <body class="rails_admin">
9
17
  <div data-i18n-options="<%= I18n.t("admin.js").to_json %>" id="admin-js"></div>
@@ -45,6 +45,7 @@ Rails.application.configure do
45
45
  Target.send :include, ThecoreUiRailsAdminTargetConcern
46
46
  ThecoreSettings::Setting.send :include, ThecoreUiRailsAdminSettingsConcern
47
47
 
48
+ require 'root_actions/general_computation'
48
49
  require 'root_actions/active_job_monitor'
49
50
  require 'member_actions/change_password'
50
51
  require 'member_actions/test_ldap_server'
@@ -1,3 +1,5 @@
1
1
  Rails.application.config.assets.precompile += %w(
2
2
  channels/index.js
3
3
  )
4
+
5
+ Rails.application.config.assets.precompile += %w( rails_admin/actions/general_computation.js rails_admin/actions/general_computation.css )
@@ -13,5 +13,8 @@ module ExportConcern
13
13
  # In index, instead, I show it only if there are records in the current view
14
14
  bindings[:controller].action_name == "index" ? (authorized? && !bindings[:controller].instance_variable_get("@objects").blank?) : true
15
15
  end
16
+ register_instance_option :bulkable? do
17
+ true
18
+ end
16
19
  end
17
20
  end
@@ -6,6 +6,8 @@ module ThecoreUiRailsAdminUserConcern
6
6
 
7
7
  included do
8
8
  has_many :saved_filters, class_name: 'SavedFilter', foreign_key: :admin_user_id, dependent: :destroy
9
+ has_many :user_preferences, class_name: 'UserPreference', foreign_key: :user_id, dependent: :destroy, inverse_of: :user
10
+ accepts_nested_attributes_for :user_preferences, allow_destroy: true
9
11
 
10
12
  # def admin_enum
11
13
  # [["✔",true],['✘',false]]
@@ -0,0 +1,12 @@
1
+ class CreateUserPreferences < ActiveRecord::Migration[7.2]
2
+ def change
3
+ create_table :user_preferences do |t|
4
+ t.references :user, null: false, foreign_key: true
5
+ t.string :name
6
+ t.jsonb :value
7
+
8
+ t.timestamps
9
+ end
10
+ add_index :user_preferences, :name
11
+ end
12
+ end
@@ -0,0 +1,59 @@
1
+ RailsAdmin::Config::Actions.add_action "general_computation", :base, :root do
2
+ show_in_sidebar false
3
+ show_in_navigation false
4
+ breadcrumb_parent [nil]
5
+ # This ensures the action only shows up for Users
6
+ # visible? authorized?
7
+ # Not a member action
8
+ member false
9
+ # Not a colleciton action
10
+ collection false
11
+ # Have a look at https://fontawesome.com/v5/search for available icons
12
+ link_icon 'fas fa-file'
13
+ # The controller which will be used to compute the action and the REST verbs it will respond to
14
+ http_methods [:get, :post, :put, :patch, :delete]
15
+ # Adding the controller which is needed to compute calls from the ui
16
+ controller do
17
+ proc do # This is needed because we need that this code is re-evaluated each time is called
18
+ if request.format.json?
19
+ # PArams sent by the new call are:
20
+ # {
21
+ # action: "upsert",
22
+ # model: "UserPreference",
23
+ # finders: {
24
+ # user_id: currentUserId,
25
+ # name: `export_${currentModelName}`
26
+ # },
27
+ # fields: {
28
+ # value: selectedFields
29
+ # }
30
+ # }
31
+ #
32
+ case params[:verb]
33
+ when "upsert"
34
+ params.permit!
35
+ result = params[:model].camelize.constantize.where(params[:finders]).first_or_initialize.update(params[:fields])
36
+ if result
37
+ status = 200
38
+ message = "User preference saved"
39
+ else
40
+ status = 422
41
+ message = "Error saving user preference: #{result.errors.full_messages.join(", ")}"
42
+ end
43
+ when "load"
44
+ params.permit!
45
+ result = params[:model].camelize.constantize.where(params[:finders]).first
46
+ message = result
47
+ status = result.nil? ? 404 : 200
48
+ end
49
+
50
+ # This is the code that is executed when the action is called
51
+ # It is executed in the context of the controller
52
+ # So you can access all the controller methods
53
+ # and instance variables
54
+ ActionCable.server.broadcast("messages", { topic: :general_computation, status: status, message: message})
55
+ render json: {message: message.presence || "No message"}.to_json, status: status.presence || 200
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,3 +1,3 @@
1
1
  module ThecoreUiRailsAdmin
2
- VERSION = "3.4.1".freeze
2
+ VERSION = "3.5.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.4.1
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
@@ -77,7 +77,11 @@ files:
77
77
  - app/assets/stylesheets/rails_admin/custom/thecore/variables.scss
78
78
  - app/assets/stylesheets/rails_admin/custom/theming.scss
79
79
  - app/assets/stylesheets/rails_admin/custom/variables.scss
80
+ - app/models/concerns/api/user_preference.rb
81
+ - app/models/concerns/endpoints/user_preference.rb
82
+ - app/models/concerns/rails_admin/user_preference.rb
80
83
  - app/models/saved_filter.rb
84
+ - app/models/user_preference.rb
81
85
  - app/views/layouts/rails_admin/_controller_assets.html.erb
82
86
  - app/views/layouts/rails_admin/application.html.erb
83
87
  - app/views/rails_admin/main/_dashboard_block.html.erb
@@ -85,6 +89,7 @@ files:
85
89
  - app/views/rails_admin/main/active_job_monitor.html.erb
86
90
  - app/views/rails_admin/main/change_password.html.erb
87
91
  - app/views/rails_admin/main/dashboard.html.erb
92
+ - app/views/rails_admin/main/general_computation.html.erb
88
93
  - app/views/rails_admin/main/import_users_from_ldap.html.erb
89
94
  - app/views/rails_admin/main/load_filters.html.erb
90
95
  - app/views/rails_admin/main/save_filter.html.erb
@@ -111,6 +116,7 @@ files:
111
116
  - db/migrate/20250206222412_add_locale_to_user.rb
112
117
  - db/migrate/20250429155934_create_saved_filters.rb
113
118
  - db/migrate/20250430081805_rename_model_name_to_abstract_model_name_in_saved_filter.rb
119
+ - db/migrate/20250916092441_create_user_preferences.rb
114
120
  - db/seeds.rb
115
121
  - lib/collection_actions/load_filters.rb
116
122
  - lib/collection_actions/save_filters.rb
@@ -120,6 +126,7 @@ files:
120
126
  - lib/rails_admin_abstract_controller.rb
121
127
  - lib/rails_admin_filter_controller_helper.rb
122
128
  - lib/root_actions/active_job_monitor.rb
129
+ - lib/root_actions/general_computation.rb
123
130
  - lib/tasks/thecore_ui_rails_admin_tasks.rake
124
131
  - lib/thecore_ui_rails_admin.rb
125
132
  - lib/thecore_ui_rails_admin/engine.rb
@@ -145,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
152
  - !ruby/object:Gem::Version
146
153
  version: '0'
147
154
  requirements: []
148
- rubygems_version: 3.6.7
155
+ rubygems_version: 3.6.9
149
156
  specification_version: 4
150
157
  summary: Thecore Backend UI based on Rails Admin.
151
158
  test_files: []