rails_admin_next 1.0.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 +7 -0
- data/Gemfile +46 -0
- data/LICENSE.md +21 -0
- data/README.md +317 -0
- data/Rakefile +25 -0
- data/app/assets/javascripts/rails_admin/custom/ui.js +1 -0
- data/app/assets/stylesheets/rails_admin/framework.css +2375 -0
- data/app/assets/stylesheets/rails_admin/skin.css +472 -0
- data/app/assets/stylesheets/rails_admin/tokens.css +281 -0
- data/app/assets/stylesheets/rails_admin.css +47 -0
- data/app/controllers/rails_admin_next/application_controller.rb +98 -0
- data/app/controllers/rails_admin_next/main_controller.rb +185 -0
- data/app/helpers/rails_admin_next/application_helper.rb +254 -0
- data/app/helpers/rails_admin_next/form_builder.rb +173 -0
- data/app/helpers/rails_admin_next/main_helper.rb +63 -0
- data/app/views/layouts/rails_admin_next/_head.html.erb +29 -0
- data/app/views/layouts/rails_admin_next/_navigation.html.erb +18 -0
- data/app/views/layouts/rails_admin_next/_secondary_navigation.html.erb +27 -0
- data/app/views/layouts/rails_admin_next/_sidebar_navigation.html.erb +6 -0
- data/app/views/layouts/rails_admin_next/application.html.erb +42 -0
- data/app/views/layouts/rails_admin_next/content.html.erb +22 -0
- data/app/views/layouts/rails_admin_next/modal.js.erb +7 -0
- data/app/views/rails_admin_next/main/_dashboard_history.html.erb +46 -0
- data/app/views/rails_admin_next/main/_delete_notice.html.erb +36 -0
- data/app/views/rails_admin_next/main/_form_action_text.html.erb +2 -0
- data/app/views/rails_admin_next/main/_form_boolean.html.erb +15 -0
- data/app/views/rails_admin_next/main/_form_colorpicker.html.erb +6 -0
- data/app/views/rails_admin_next/main/_form_datetime.html.erb +14 -0
- data/app/views/rails_admin_next/main/_form_enumeration.html.erb +26 -0
- data/app/views/rails_admin_next/main/_form_field.html.erb +2 -0
- data/app/views/rails_admin_next/main/_form_file_upload.html.erb +18 -0
- data/app/views/rails_admin_next/main/_form_filtering_multiselect.html.erb +20 -0
- data/app/views/rails_admin_next/main/_form_filtering_select.html.erb +22 -0
- data/app/views/rails_admin_next/main/_form_multiple_file_upload.html.erb +21 -0
- data/app/views/rails_admin_next/main/_form_nested_many.html.erb +20 -0
- data/app/views/rails_admin_next/main/_form_nested_one.html.erb +19 -0
- data/app/views/rails_admin_next/main/_form_polymorphic_association.html.erb +24 -0
- data/app/views/rails_admin_next/main/_form_text.html.erb +2 -0
- data/app/views/rails_admin_next/main/_pagination.html.erb +54 -0
- data/app/views/rails_admin_next/main/_submit_buttons.html.erb +26 -0
- data/app/views/rails_admin_next/main/bulk_delete.html.erb +19 -0
- data/app/views/rails_admin_next/main/dashboard.html.erb +64 -0
- data/app/views/rails_admin_next/main/delete.html.erb +21 -0
- data/app/views/rails_admin_next/main/edit.html.erb +3 -0
- data/app/views/rails_admin_next/main/export.html.erb +149 -0
- data/app/views/rails_admin_next/main/history.html.erb +80 -0
- data/app/views/rails_admin_next/main/index.html.erb +186 -0
- data/app/views/rails_admin_next/main/new.html.erb +3 -0
- data/app/views/rails_admin_next/main/show.html.erb +30 -0
- data/config/brakeman.ignore +145 -0
- data/config/importmap.rails_admin.rb +29 -0
- data/config/initializers/active_record_extensions.rb +21 -0
- data/config/locales/rails_admin_next.en.yml +158 -0
- data/config/routes.rb +14 -0
- data/lib/generators/rails_admin_next/install_generator.rb +39 -0
- data/lib/generators/rails_admin_next/templates/initializer.erb +40 -0
- data/lib/generators/rails_admin_next/utils.rb +21 -0
- data/lib/rails_admin_next/abstract_model.rb +266 -0
- data/lib/rails_admin_next/adapters/active_record/association.rb +130 -0
- data/lib/rails_admin_next/adapters/active_record/object_extension.rb +13 -0
- data/lib/rails_admin_next/adapters/active_record/property.rb +58 -0
- data/lib/rails_admin_next/adapters/active_record.rb +371 -0
- data/lib/rails_admin_next/config/actions/base.rb +160 -0
- data/lib/rails_admin_next/config/actions/bulk_delete.rb +67 -0
- data/lib/rails_admin_next/config/actions/dashboard.rb +62 -0
- data/lib/rails_admin_next/config/actions/delete.rb +58 -0
- data/lib/rails_admin_next/config/actions/edit.rb +56 -0
- data/lib/rails_admin_next/config/actions/export.rb +47 -0
- data/lib/rails_admin_next/config/actions/history_index.rb +40 -0
- data/lib/rails_admin_next/config/actions/history_show.rb +40 -0
- data/lib/rails_admin_next/config/actions/index.rb +108 -0
- data/lib/rails_admin_next/config/actions/new.rb +69 -0
- data/lib/rails_admin_next/config/actions/show.rb +36 -0
- data/lib/rails_admin_next/config/actions/show_in_app.rb +37 -0
- data/lib/rails_admin_next/config/actions.rb +122 -0
- data/lib/rails_admin_next/config/configurable.rb +107 -0
- data/lib/rails_admin_next/config/const_load_suppressor.rb +78 -0
- data/lib/rails_admin_next/config/fields/association.rb +169 -0
- data/lib/rails_admin_next/config/fields/base.rb +391 -0
- data/lib/rails_admin_next/config/fields/collection_association.rb +90 -0
- data/lib/rails_admin_next/config/fields/factories/action_text.rb +14 -0
- data/lib/rails_admin_next/config/fields/factories/active_storage.rb +34 -0
- data/lib/rails_admin_next/config/fields/factories/association.rb +32 -0
- data/lib/rails_admin_next/config/fields/factories/encrypted_password.rb +28 -0
- data/lib/rails_admin_next/config/fields/factories/enum.rb +23 -0
- data/lib/rails_admin_next/config/fields/factories/password.rb +19 -0
- data/lib/rails_admin_next/config/fields/group.rb +77 -0
- data/lib/rails_admin_next/config/fields/singular_association.rb +62 -0
- data/lib/rails_admin_next/config/fields/types/action_text.rb +20 -0
- data/lib/rails_admin_next/config/fields/types/active_record_enum.rb +62 -0
- data/lib/rails_admin_next/config/fields/types/active_storage.rb +70 -0
- data/lib/rails_admin_next/config/fields/types/all.rb +32 -0
- data/lib/rails_admin_next/config/fields/types/belongs_to_association.rb +48 -0
- data/lib/rails_admin_next/config/fields/types/boolean.rb +70 -0
- data/lib/rails_admin_next/config/fields/types/citext.rb +15 -0
- data/lib/rails_admin_next/config/fields/types/color.rb +43 -0
- data/lib/rails_admin_next/config/fields/types/date.rb +38 -0
- data/lib/rails_admin_next/config/fields/types/datetime.rb +79 -0
- data/lib/rails_admin_next/config/fields/types/decimal.rb +23 -0
- data/lib/rails_admin_next/config/fields/types/enum.rb +52 -0
- data/lib/rails_admin_next/config/fields/types/file_upload.rb +87 -0
- data/lib/rails_admin_next/config/fields/types/float.rb +23 -0
- data/lib/rails_admin_next/config/fields/types/has_and_belongs_to_many_association.rb +16 -0
- data/lib/rails_admin_next/config/fields/types/has_many_association.rb +16 -0
- data/lib/rails_admin_next/config/fields/types/has_one_association.rb +35 -0
- data/lib/rails_admin_next/config/fields/types/hidden.rb +31 -0
- data/lib/rails_admin_next/config/fields/types/inet.rb +15 -0
- data/lib/rails_admin_next/config/fields/types/integer.rb +20 -0
- data/lib/rails_admin_next/config/fields/types/json.rb +37 -0
- data/lib/rails_admin_next/config/fields/types/multiple_active_storage.rb +82 -0
- data/lib/rails_admin_next/config/fields/types/multiple_file_upload.rb +134 -0
- data/lib/rails_admin_next/config/fields/types/numeric.rb +24 -0
- data/lib/rails_admin_next/config/fields/types/password.rb +45 -0
- data/lib/rails_admin_next/config/fields/types/polymorphic_association.rb +118 -0
- data/lib/rails_admin_next/config/fields/types/serialized.rb +28 -0
- data/lib/rails_admin_next/config/fields/types/string.rb +50 -0
- data/lib/rails_admin_next/config/fields/types/string_like.rb +25 -0
- data/lib/rails_admin_next/config/fields/types/text.rb +28 -0
- data/lib/rails_admin_next/config/fields/types/time.rb +39 -0
- data/lib/rails_admin_next/config/fields/types/timestamp.rb +16 -0
- data/lib/rails_admin_next/config/fields/types/uuid.rb +15 -0
- data/lib/rails_admin_next/config/fields/types.rb +29 -0
- data/lib/rails_admin_next/config/fields.rb +88 -0
- data/lib/rails_admin_next/config/groupable.rb +20 -0
- data/lib/rails_admin_next/config/has_description.rb +14 -0
- data/lib/rails_admin_next/config/has_fields.rb +144 -0
- data/lib/rails_admin_next/config/has_groups.rb +27 -0
- data/lib/rails_admin_next/config/hideable.rb +30 -0
- data/lib/rails_admin_next/config/inspectable.rb +39 -0
- data/lib/rails_admin_next/config/lazy_model.rb +74 -0
- data/lib/rails_admin_next/config/model.rb +126 -0
- data/lib/rails_admin_next/config/proxyable/proxy.rb +42 -0
- data/lib/rails_admin_next/config/proxyable.rb +26 -0
- data/lib/rails_admin_next/config/sections/base.rb +36 -0
- data/lib/rails_admin_next/config/sections/create.rb +13 -0
- data/lib/rails_admin_next/config/sections/edit.rb +13 -0
- data/lib/rails_admin_next/config/sections/export.rb +13 -0
- data/lib/rails_admin_next/config/sections/list.rb +63 -0
- data/lib/rails_admin_next/config/sections/modal.rb +12 -0
- data/lib/rails_admin_next/config/sections/nested.rb +12 -0
- data/lib/rails_admin_next/config/sections/show.rb +12 -0
- data/lib/rails_admin_next/config/sections/update.rb +12 -0
- data/lib/rails_admin_next/config/sections.rb +39 -0
- data/lib/rails_admin_next/config.rb +436 -0
- data/lib/rails_admin_next/engine.rb +91 -0
- data/lib/rails_admin_next/extension.rb +35 -0
- data/lib/rails_admin_next/extensions/cancancan/authorization_adapter.rb +90 -0
- data/lib/rails_admin_next/extensions/cancancan.rb +5 -0
- data/lib/rails_admin_next/extensions/controller_extension.rb +8 -0
- data/lib/rails_admin_next/extensions/paper_trail/auditing_adapter.rb +192 -0
- data/lib/rails_admin_next/extensions/paper_trail.rb +5 -0
- data/lib/rails_admin_next/extensions/pundit/authorization_adapter.rb +72 -0
- data/lib/rails_admin_next/extensions/pundit.rb +5 -0
- data/lib/rails_admin_next/extensions/url_for_extension.rb +15 -0
- data/lib/rails_admin_next/icons.rb +143 -0
- data/lib/rails_admin_next/support/composite_keys_serializer.rb +15 -0
- data/lib/rails_admin_next/support/csv_converter.rb +100 -0
- data/lib/rails_admin_next/support/hash_helper.rb +31 -0
- data/lib/rails_admin_next/support/paginated_collection.rb +79 -0
- data/lib/rails_admin_next/version.rb +17 -0
- data/lib/rails_admin_next.rb +72 -0
- data/lib/tasks/rails_admin.rake +40 -0
- data/src/rails_admin/application.js +15 -0
- data/src/rails_admin/base.js +12 -0
- data/src/rails_admin/controllers/alert_controller.js +28 -0
- data/src/rails_admin/controllers/collapse_controller.js +16 -0
- data/src/rails_admin/controllers/dropdown_controller.js +110 -0
- data/src/rails_admin/controllers/feedback_controller.js +73 -0
- data/src/rails_admin/controllers/filter_box_controller.js +346 -0
- data/src/rails_admin/controllers/filtering_multiselect_controller.js +386 -0
- data/src/rails_admin/controllers/filtering_select_controller.js +509 -0
- data/src/rails_admin/controllers/index.js +31 -0
- data/src/rails_admin/controllers/modal_controller.js +142 -0
- data/src/rails_admin/controllers/nested_form_controller.js +117 -0
- data/src/rails_admin/controllers/polymorphic_controller.js +26 -0
- data/src/rails_admin/controllers/remote_form_controller.js +173 -0
- data/src/rails_admin/controllers/sidescroll_controller.js +23 -0
- data/src/rails_admin/dom-ready.js +20 -0
- data/src/rails_admin/dom.js +19 -0
- data/src/rails_admin/i18n.js +20 -0
- data/src/rails_admin/icons.js +64 -0
- data/src/rails_admin/interactions.js +221 -0
- data/src/rails_admin/select_query.js +65 -0
- metadata +346 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* rails_admin_next — design tokens (CSS custom properties).
|
|
3
|
+
* Loaded by rails_admin.css into the ra.tokens cascade layer; see that file's header for the
|
|
4
|
+
* browser floor and the styled-vocabulary contract.
|
|
5
|
+
*
|
|
6
|
+
* Every color token is a light-dark() pair, resolved against the page scheme that the layout's
|
|
7
|
+
* <meta name="color-scheme"> advertises (RailsAdminNext.config.color_scheme: :auto/:light/:dark).
|
|
8
|
+
* The light arms are exact oklch() conversions of the original Bootstrap 5.1.3 hexes (noted
|
|
9
|
+
* alongside), precise enough to re-convert to the same sRGB value within 1/255 per channel.
|
|
10
|
+
* The dark arms keep each light arm's hue and invert the lightness ramp: the page turns to a
|
|
11
|
+
* near-black neutral, the gray ladder mirrors around gray-500, and the status colors lift their
|
|
12
|
+
* oklch lightness so they read as text against the dark background (WCAG AA). Solid fills pair
|
|
13
|
+
* with --ra-tint-target text (white in light, black in dark), so the lightened fills keep AA too.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Supported override tokens (public API)
|
|
18
|
+
*
|
|
19
|
+
* Hosts may override these tokens (unlayered, e.g. `:root { --ra-primary: #663399; }`)
|
|
20
|
+
* and every derived color — button hover/active states, focus rings, alert tints,
|
|
21
|
+
* table tints, links — follows automatically:
|
|
22
|
+
*
|
|
23
|
+
* --ra-primary, --ra-secondary, --ra-success, --ra-info, --ra-warning, --ra-danger,
|
|
24
|
+
* --ra-light, --ra-dark brand and status colors
|
|
25
|
+
* --ra-body-bg, --ra-body-color body surface and text
|
|
26
|
+
* --ra-link-color link color (defaults to a tint of --ra-primary)
|
|
27
|
+
*
|
|
28
|
+
* A single-color override applies to both schemes; hosts that want a tuned dark
|
|
29
|
+
* mode should override with their own pair, e.g.
|
|
30
|
+
* `:root { --ra-primary: light-dark(#663399, #a97fd6); }`.
|
|
31
|
+
*
|
|
32
|
+
* Every other --ra-* name is internal and may change without notice.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/* === CSS custom properties ============================================== */
|
|
36
|
+
:root {
|
|
37
|
+
/* Scheme-aware mix targets. All state derivation (hover/active shades, tints,
|
|
38
|
+
translucent overlays) mixes toward these — never toward literal black/white —
|
|
39
|
+
so the derivations flip direction under the dark scheme. */
|
|
40
|
+
--ra-shade-target: light-dark(black, white);
|
|
41
|
+
--ra-tint-target: light-dark(white, black);
|
|
42
|
+
|
|
43
|
+
/* Scheme probe for the one artifact a <color> can't express: a url() background
|
|
44
|
+
image (the .form-select caret). light-dark() exists only for colors, so this
|
|
45
|
+
registered color (@property in rails_admin.css) flips with the scheme and
|
|
46
|
+
framework.css swaps the caret data-URI under
|
|
47
|
+
@container style(--ra-scheme-probe: black). */
|
|
48
|
+
--ra-scheme-probe: light-dark(white, black);
|
|
49
|
+
|
|
50
|
+
/* Brand and status palette (public). Dark arms: same hue, lightness lifted so
|
|
51
|
+
each color passes 4.5:1 as text on the dark body bg (WCAG AA). --ra-info and
|
|
52
|
+
--ra-warning are already light enough — and always pair with dark text — so
|
|
53
|
+
their arms match. */
|
|
54
|
+
--ra-primary: light-dark(
|
|
55
|
+
oklch(57.8201% 0.228209 260.0251deg),
|
|
56
|
+
oklch(65.31% 0.17 260.0251deg)
|
|
57
|
+
); /* #0d6efd / ~#4e8df6 */
|
|
58
|
+
--ra-secondary: light-dark(
|
|
59
|
+
oklch(55.7535% 0.016495 244.8934deg),
|
|
60
|
+
oklch(66.72% 0.016495 244.8934deg)
|
|
61
|
+
); /* #6c757d / ~#8c969e */
|
|
62
|
+
--ra-success: light-dark(
|
|
63
|
+
oklch(55.2026% 0.123397 157.0121deg),
|
|
64
|
+
oklch(67.39% 0.123397 157.0121deg)
|
|
65
|
+
); /* #198754 / ~#4aad77 */
|
|
66
|
+
--ra-info: light-dark(
|
|
67
|
+
oklch(77.4929% 0.137971 218.0547deg),
|
|
68
|
+
oklch(77.4929% 0.137971 218.0547deg)
|
|
69
|
+
); /* #0dcaf0, both schemes */
|
|
70
|
+
--ra-warning: light-dark(
|
|
71
|
+
oklch(84.4171% 0.172159 84.9338deg),
|
|
72
|
+
oklch(84.4171% 0.172159 84.9338deg)
|
|
73
|
+
); /* #ffc107, both schemes */
|
|
74
|
+
--ra-danger: light-dark(
|
|
75
|
+
oklch(59.155% 0.202026 21.2388deg),
|
|
76
|
+
oklch(66.59% 0.16 21.2388deg)
|
|
77
|
+
); /* #dc3545 / ~#e56568 */
|
|
78
|
+
/* --ra-light/--ra-dark are absolutes by name: .btn-light stays a light button
|
|
79
|
+
and .table-dark a dark table in both schemes, so their arms match. The
|
|
80
|
+
scheme-flipping muted surface is --ra-surface-muted below. */
|
|
81
|
+
--ra-light: light-dark(
|
|
82
|
+
oklch(98.1639% 0.001707 247.8395deg),
|
|
83
|
+
oklch(98.1639% 0.001707 247.8395deg)
|
|
84
|
+
); /* #f8f9fa */
|
|
85
|
+
--ra-dark: light-dark(
|
|
86
|
+
oklch(26.2093% 0.009493 248.1898deg),
|
|
87
|
+
oklch(26.2093% 0.009493 248.1898deg)
|
|
88
|
+
); /* #212529 */
|
|
89
|
+
|
|
90
|
+
/* Neutrals (internal). White and black are scheme-locked absolutes on purpose:
|
|
91
|
+
the .navbar-light/.navbar-dark ladders, the solid-button text pairs, the
|
|
92
|
+
bg-white/bg-black utilities and the modal backdrop mean "really white/black",
|
|
93
|
+
not "the scheme's paper color" — giving these arms would flip the navbar
|
|
94
|
+
skins with the OS scheme. */
|
|
95
|
+
--ra-white: oklch(100% 0 0deg); /* #fff */
|
|
96
|
+
--ra-black: oklch(0% 0 0deg); /* #000 */
|
|
97
|
+
/* Gray ladder (internal). Dark arms mirror the ladder: 200 lands on Bootstrap's
|
|
98
|
+
gray-800, 300↔700, 400↔600 and 900↔200, so "subtle background" stays
|
|
99
|
+
subtle and "border" stays border-weight against the dark surface. The one
|
|
100
|
+
asymmetry: gray-600 (muted text) takes gray-500's value, not gray-400's,
|
|
101
|
+
so muted text stays visibly muted next to the dark body color. */
|
|
102
|
+
--ra-gray-200: light-dark(
|
|
103
|
+
oklch(94.1654% 0.005176 247.8791deg),
|
|
104
|
+
oklch(34.5143% 0.013291 248.2124deg)
|
|
105
|
+
); /* #e9ecef / #343a40 */
|
|
106
|
+
--ra-gray-300: light-dark(
|
|
107
|
+
oklch(91.0874% 0.006959 247.9015deg),
|
|
108
|
+
oklch(42.7562% 0.014702 248.1716deg)
|
|
109
|
+
); /* #dee2e6 / #495057 */
|
|
110
|
+
--ra-gray-400: light-dark(
|
|
111
|
+
oklch(86.7074% 0.010568 247.9486deg),
|
|
112
|
+
oklch(55.7535% 0.016495 244.8934deg)
|
|
113
|
+
); /* #ced4da / #6c757d */
|
|
114
|
+
--ra-gray-500: light-dark(
|
|
115
|
+
oklch(76.9244% 0.014518 248.0166deg),
|
|
116
|
+
oklch(76.9244% 0.014518 248.0166deg)
|
|
117
|
+
); /* #adb5bd, both schemes — the ladder's pivot */
|
|
118
|
+
--ra-gray-600: light-dark(
|
|
119
|
+
oklch(55.7535% 0.016495 244.8934deg),
|
|
120
|
+
oklch(76.9244% 0.014518 248.0166deg)
|
|
121
|
+
); /* #6c757d / #adb5bd */
|
|
122
|
+
--ra-gray-700: light-dark(
|
|
123
|
+
oklch(42.7562% 0.014702 248.1716deg),
|
|
124
|
+
oklch(91.0874% 0.006959 247.9015deg)
|
|
125
|
+
); /* #495057 / #dee2e6 */
|
|
126
|
+
--ra-gray-900: light-dark(
|
|
127
|
+
oklch(26.2093% 0.009493 248.1898deg),
|
|
128
|
+
oklch(94.1654% 0.005176 247.8791deg)
|
|
129
|
+
); /* #212529 / #e9ecef */
|
|
130
|
+
|
|
131
|
+
/* Body surface and text (public). The dark page is a near-black neutral
|
|
132
|
+
(distinct from the locked --ra-dark) under light-gray text. */
|
|
133
|
+
--ra-body-color: light-dark(
|
|
134
|
+
oklch(26.2093% 0.009493 248.1898deg),
|
|
135
|
+
oklch(91.0874% 0.006959 247.9015deg)
|
|
136
|
+
); /* #212529 / #dee2e6 */
|
|
137
|
+
--ra-body-bg: light-dark(
|
|
138
|
+
oklch(100% 0 0deg),
|
|
139
|
+
oklch(22% 0.0095 248.19deg)
|
|
140
|
+
); /* #fff / ~#171b1f */
|
|
141
|
+
|
|
142
|
+
/* The muted surface behind .bg-light (sidebar, filter cards). Light arm equals
|
|
143
|
+
--ra-light (#f8f9fa) so light rendering is unchanged; unlike --ra-light it
|
|
144
|
+
turns into a raised dark surface under the dark scheme. */
|
|
145
|
+
--ra-surface-muted: light-dark(
|
|
146
|
+
oklch(98.1639% 0.001707 247.8395deg),
|
|
147
|
+
oklch(30.6326% 0.011412 248.2deg)
|
|
148
|
+
); /* #f8f9fa / #2b3035 */
|
|
149
|
+
|
|
150
|
+
/* Semantic derivations (internal; --ra-link-color itself is public) */
|
|
151
|
+
--ra-link-color: color-mix(
|
|
152
|
+
in srgb,
|
|
153
|
+
light-dark(var(--ra-primary), var(--ra-white)) 40%,
|
|
154
|
+
var(--ra-primary)
|
|
155
|
+
); /* light: mixing primary into itself is a no-op, so this stays exactly
|
|
156
|
+
--ra-primary; dark: primary tinted 40% toward white — no fill color can
|
|
157
|
+
be both AA text on the dark bg and an AA backdrop for its own label, so
|
|
158
|
+
link text derives lighter while fills keep --ra-primary. Follows host
|
|
159
|
+
--ra-primary overrides in both schemes. */
|
|
160
|
+
--ra-link-hover-color: color-mix(
|
|
161
|
+
in srgb,
|
|
162
|
+
var(--ra-shade-target) 20%,
|
|
163
|
+
var(--ra-link-color)
|
|
164
|
+
); /* matches Bootstrap 5.1.3's shade-color($link-color, 20%): #0a58ca */
|
|
165
|
+
--ra-border-color: var(--ra-gray-300);
|
|
166
|
+
--ra-focus-ring-color: color-mix(
|
|
167
|
+
in srgb,
|
|
168
|
+
var(--ra-primary) 25%,
|
|
169
|
+
transparent
|
|
170
|
+
); /* matches Bootstrap 5.1.3's rgba(13, 110, 253, 0.25) */
|
|
171
|
+
--ra-input-focus-border-color: color-mix(
|
|
172
|
+
in srgb,
|
|
173
|
+
var(--ra-white) 50%,
|
|
174
|
+
var(--ra-primary)
|
|
175
|
+
); /* Matches Bootstrap 5.1.3's tint-color($primary, 50%) (#86b7fe). Mixes
|
|
176
|
+
toward locked white, not the flipping tint target: a focus border must
|
|
177
|
+
lift off the input surface in both schemes, and mixing toward black in
|
|
178
|
+
dark would sink it into the bg. */
|
|
179
|
+
|
|
180
|
+
/* One-off component colors (internal) */
|
|
181
|
+
--ra-code-color: light-dark(
|
|
182
|
+
oklch(59.5598% 0.20859 355.4835deg),
|
|
183
|
+
oklch(73.01% 0.15 355.4835deg)
|
|
184
|
+
); /* #d63384 / ~#ef7cac */
|
|
185
|
+
--ra-icon-close: light-dark(
|
|
186
|
+
oklch(0% 0 0deg),
|
|
187
|
+
oklch(100% 0 0deg)
|
|
188
|
+
); /* the .btn-close X glyph: matches Bootstrap 5.1.3's data-URI black exactly,
|
|
189
|
+
white in dark (the element's own opacity supplies the 50/75% strength) */
|
|
190
|
+
--ra-header-sort-shadow: light-dark(
|
|
191
|
+
oklch(98.1639% 0.001707 247.8395deg),
|
|
192
|
+
oklch(22% 0.0095 248.19deg)
|
|
193
|
+
); /* #f8f9fa halo behind sorted-header text; the dark arm matches the dark
|
|
194
|
+
body bg so it stays a surface-colored halo, not a glow */
|
|
195
|
+
--ra-mark-bg: light-dark(
|
|
196
|
+
oklch(97.6656% 0.027837 98.0579deg),
|
|
197
|
+
oklch(35% 0.055 98.0579deg)
|
|
198
|
+
); /* #fcf8e3 / ~#423b16 — muted dark amber keeps AA under the light body text */
|
|
199
|
+
--ra-nested-form-accent-1: light-dark(
|
|
200
|
+
oklch(84.7875% 0.109283 212.4507deg),
|
|
201
|
+
oklch(52% 0.075 212.4507deg)
|
|
202
|
+
); /* #6ce0f7 / ~#2a7482 */
|
|
203
|
+
--ra-nested-form-accent-2: light-dark(
|
|
204
|
+
oklch(93.1505% 0.092939 91.5476deg),
|
|
205
|
+
oklch(52% 0.07 91.5476deg)
|
|
206
|
+
); /* #ffe7a0 / ~#786837 */
|
|
207
|
+
--ra-nested-form-accent-3: light-dark(
|
|
208
|
+
oklch(85.7197% 0.115462 162.7335deg),
|
|
209
|
+
oklch(52% 0.085 162.7335deg)
|
|
210
|
+
); /* #84e8ba / ~#33785a */
|
|
211
|
+
--ra-sticky-sort-bg: light-dark(
|
|
212
|
+
oklch(94.4459% 0.016779 230.9098deg),
|
|
213
|
+
oklch(30% 0.03 230.9098deg)
|
|
214
|
+
); /* #e2eff6 / ~#1e313a */
|
|
215
|
+
|
|
216
|
+
/* Typography and misc */
|
|
217
|
+
--ra-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto,
|
|
218
|
+
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
|
|
219
|
+
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
220
|
+
--ra-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas,
|
|
221
|
+
"Liberation Mono", "Courier New", monospace;
|
|
222
|
+
--ra-gradient: linear-gradient(
|
|
223
|
+
180deg,
|
|
224
|
+
color-mix(in srgb, var(--ra-white) 15%, transparent),
|
|
225
|
+
color-mix(in srgb, var(--ra-white) 0%, transparent)
|
|
226
|
+
);
|
|
227
|
+
--ra-body-font-family: var(--ra-font-sans-serif);
|
|
228
|
+
--ra-body-font-size: 0.85rem;
|
|
229
|
+
--ra-body-font-weight: 400;
|
|
230
|
+
--ra-body-line-height: 1.5;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/*
|
|
234
|
+
* UPGRADING mapping (--bs-* -> --ra-*)
|
|
235
|
+
*
|
|
236
|
+
* Renamed (same meaning; read/override the new name):
|
|
237
|
+
* --bs-body-bg -> --ra-body-bg
|
|
238
|
+
* --bs-body-color -> --ra-body-color
|
|
239
|
+
* --bs-body-font-family -> --ra-body-font-family
|
|
240
|
+
* --bs-body-font-size -> --ra-body-font-size
|
|
241
|
+
* --bs-body-font-weight -> --ra-body-font-weight
|
|
242
|
+
* --bs-body-line-height -> --ra-body-line-height
|
|
243
|
+
* --bs-body-text-align -> --ra-body-text-align (still undeclared by default;
|
|
244
|
+
* set it to opt in, exactly like Bootstrap's knob)
|
|
245
|
+
* --bs-breadcrumb-divider -> --ra-breadcrumb-divider
|
|
246
|
+
* --bs-font-monospace -> --ra-font-monospace
|
|
247
|
+
* --bs-font-sans-serif -> --ra-font-sans-serif
|
|
248
|
+
* --bs-gradient -> --ra-gradient
|
|
249
|
+
* --bs-gutter-x -> --ra-gutter-x (grid-scoped)
|
|
250
|
+
* --bs-gutter-y -> --ra-gutter-y (grid-scoped)
|
|
251
|
+
* --bs-table-bg -> --ra-table-bg (table-scoped; likewise
|
|
252
|
+
* -accent-bg, -striped-bg, -striped-color,
|
|
253
|
+
* -active-bg, -active-color, -hover-bg, -hover-color)
|
|
254
|
+
*
|
|
255
|
+
* Renamed with a value-space change (number -> percentage):
|
|
256
|
+
* --bs-bg-opacity: 0.5 -> --ra-bg-opacity: 50%
|
|
257
|
+
* --bs-text-opacity: 0.5 -> --ra-text-opacity: 50%
|
|
258
|
+
*
|
|
259
|
+
* Previously declared but consumed by nothing — now live semantic tokens
|
|
260
|
+
* (overriding the --bs-* name never did anything; the --ra-* name works):
|
|
261
|
+
* --bs-primary -> --ra-primary --bs-warning -> --ra-warning
|
|
262
|
+
* --bs-secondary -> --ra-secondary --bs-danger -> --ra-danger
|
|
263
|
+
* --bs-success -> --ra-success --bs-light -> --ra-light
|
|
264
|
+
* --bs-info -> --ra-info --bs-dark -> --ra-dark
|
|
265
|
+
*
|
|
266
|
+
* Removed — replaced by derivation (never override these; override the base token):
|
|
267
|
+
* --bs-primary-rgb, --bs-secondary-rgb, --bs-success-rgb, --bs-info-rgb,
|
|
268
|
+
* --bs-warning-rgb, --bs-danger-rgb, --bs-light-rgb, --bs-dark-rgb,
|
|
269
|
+
* --bs-white-rgb, --bs-black-rgb, --bs-body-color-rgb, --bs-body-bg-rgb
|
|
270
|
+
* -> gone; utilities now derive translucency via
|
|
271
|
+
* color-mix(in srgb, var(--ra-*) <pct>, transparent)
|
|
272
|
+
*
|
|
273
|
+
* Removed without replacement (declared but never consumed):
|
|
274
|
+
* --bs-blue, --bs-indigo, --bs-purple, --bs-pink, --bs-red, --bs-orange,
|
|
275
|
+
* --bs-yellow, --bs-green, --bs-teal, --bs-cyan (raw color ladder; the code-pink
|
|
276
|
+
* role of --bs-pink lives on as the internal --ra-code-color),
|
|
277
|
+
* --bs-white, --bs-gray, --bs-gray-dark, --bs-gray-100 ... --bs-gray-900
|
|
278
|
+
* (the grays the CSS actually uses exist as internal --ra-gray-* tokens),
|
|
279
|
+
* --bs-position (set by .dropdown-menu-end for Bootstrap's dropdown JS,
|
|
280
|
+
* which this engine does not ship)
|
|
281
|
+
*/
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* rails_admin_next stylesheet
|
|
3
|
+
*
|
|
4
|
+
* Owned, hand-maintained CSS: a hand-authored flatten of Bootstrap 5.1.3 plus the engine's own
|
|
5
|
+
* legacy SCSS. Not vendored and does not track upstream Bootstrap. Edit it directly — there is no
|
|
6
|
+
* build step and no upstream to stay mergeable with.
|
|
7
|
+
*
|
|
8
|
+
* Browser floor: latest stable Chrome and Safari only. The engine's JS and CSS may assume :has(),
|
|
9
|
+
* <dialog>, popover, CSS nesting, oklch(), color-mix(), light-dark(), @starting-style, and CSS
|
|
10
|
+
* anchor positioning.
|
|
11
|
+
*
|
|
12
|
+
* Styled vocabulary (the public styling contract for hosts):
|
|
13
|
+
* - the Bootstrap-derived class names actually emitted by engine views/helpers/JS, plus
|
|
14
|
+
* - the host-facing config value spaces, kept even where no engine view emits them:
|
|
15
|
+
* .navbar-light / .navbar-dark (navbar_css_classes), alert-* (flash_alert_class),
|
|
16
|
+
* bg-* utilities (badges, indicators, host navbar), .badge colors, and table-* contextual
|
|
17
|
+
* rows (row_css_class conventions).
|
|
18
|
+
* Class names this file does not style are out of contract and may stay unstyled.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Cascade-layer topology (public API — the layer names are a documented contract):
|
|
23
|
+
* ra.tokens — design tokens (custom properties)
|
|
24
|
+
* ra.framework — the trimmed ex-Bootstrap component CSS
|
|
25
|
+
* ra.skin — the engine's own widgets, icons, and admin skin
|
|
26
|
+
* Later layers win; anything a host ships UNLAYERED beats all engine CSS by design,
|
|
27
|
+
* so a plain host rule overrides the admin without specificity games.
|
|
28
|
+
*/
|
|
29
|
+
@layer ra.tokens, ra.framework, ra.skin;
|
|
30
|
+
|
|
31
|
+
@import url("rails_admin/tokens.css") layer(ra.tokens);
|
|
32
|
+
@import url("rails_admin/framework.css") layer(ra.framework);
|
|
33
|
+
@import url("rails_admin/skin.css") layer(ra.skin);
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* Registration for the scheme probe declared in tokens.css. Registered as a <color> so its
|
|
37
|
+
* computed value is the RESOLVED light-dark() arm, letting framework.css swap the one artifact
|
|
38
|
+
* a <color> value can't reach — the .form-select caret's url() background image — with an
|
|
39
|
+
* @container style(--ra-scheme-probe: black) query. Lives here, outside the cascade layers,
|
|
40
|
+
* because @property registrations are document-global. The light initial value means a failed
|
|
41
|
+
* registration degrades to the light caret.
|
|
42
|
+
*/
|
|
43
|
+
@property --ra-scheme-probe {
|
|
44
|
+
syntax: "<color>";
|
|
45
|
+
inherits: true;
|
|
46
|
+
initial-value: white;
|
|
47
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_admin_next/abstract_model"
|
|
4
|
+
|
|
5
|
+
module RailsAdminNext
|
|
6
|
+
class ModelNotFound < ::StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class ObjectNotFound < ::StandardError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ActionNotAllowed < ::StandardError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class ApplicationController < Config.parent_controller.constantize
|
|
16
|
+
include RailsAdminNext::Extensions::ControllerExtension
|
|
17
|
+
|
|
18
|
+
protect_from_forgery(with: :exception)
|
|
19
|
+
|
|
20
|
+
before_action :_set_security_headers
|
|
21
|
+
before_action :_authenticate!
|
|
22
|
+
before_action :_authorize!
|
|
23
|
+
before_action :_audit!
|
|
24
|
+
|
|
25
|
+
helper_method :_current_user
|
|
26
|
+
|
|
27
|
+
attr_reader :object, :model_config, :abstract_model, :authorization_adapter
|
|
28
|
+
|
|
29
|
+
def get_model
|
|
30
|
+
@model_name = to_model_name(params[:model_name])
|
|
31
|
+
raise RailsAdminNext::ModelNotFound unless (@abstract_model = RailsAdminNext::AbstractModel.new(@model_name))
|
|
32
|
+
raise RailsAdminNext::ModelNotFound if (@model_config = @abstract_model.config).excluded?
|
|
33
|
+
|
|
34
|
+
@properties = @abstract_model.properties
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def get_object
|
|
38
|
+
raise RailsAdminNext::ObjectNotFound unless (@object = @abstract_model.get(params[:id], @model_config.scope))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_model_name(param)
|
|
42
|
+
param.split("~").collect(&:camelize).join("::")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def _current_user
|
|
46
|
+
instance_eval(&RailsAdminNext::Config.current_user_method)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
# The admin is a privileged surface: always advertise SAMEORIGIN framing, and apply the
|
|
52
|
+
# host-opted-in Content Security Policy (with a per-request nonce for the engine's inline
|
|
53
|
+
# tags) per-request so it covers only admin responses, never the host app.
|
|
54
|
+
def _set_security_headers
|
|
55
|
+
response.headers["X-Frame-Options"] = "SAMEORIGIN"
|
|
56
|
+
|
|
57
|
+
policy_block = RailsAdminNext::Config.content_security_policy
|
|
58
|
+
return unless policy_block
|
|
59
|
+
|
|
60
|
+
policy = ActionDispatch::ContentSecurityPolicy.new
|
|
61
|
+
policy_block.call(policy)
|
|
62
|
+
request.content_security_policy = policy
|
|
63
|
+
request.content_security_policy_report_only = RailsAdminNext::Config.content_security_policy_report_only
|
|
64
|
+
request.content_security_policy_nonce_generator ||= ->(_request) { SecureRandom.base64(16) }
|
|
65
|
+
request.content_security_policy_nonce_directives ||= %w[script-src style-src]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def _authenticate!
|
|
69
|
+
instance_eval(&RailsAdminNext::Config.authenticate_with)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def _authorize!
|
|
73
|
+
instance_eval(&RailsAdminNext::Config.authorize_with)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def _audit!
|
|
77
|
+
instance_eval(&RailsAdminNext::Config.audit_with)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def rails_admin_controller?
|
|
81
|
+
true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
rescue_from RailsAdminNext::ObjectNotFound do
|
|
85
|
+
flash[:error] = I18n.t("admin.flash.object_not_found", model: @model_name, id: params[:id])
|
|
86
|
+
params[:action] = "index"
|
|
87
|
+
@status_code = :not_found
|
|
88
|
+
index
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
rescue_from RailsAdminNext::ModelNotFound do
|
|
92
|
+
flash[:error] = I18n.t("admin.flash.model_not_found", model: @model_name)
|
|
93
|
+
params[:action] = "dashboard"
|
|
94
|
+
@status_code = :not_found
|
|
95
|
+
dashboard
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAdminNext
|
|
4
|
+
class MainController < RailsAdminNext::ApplicationController
|
|
5
|
+
include ActionView::Helpers::TextHelper
|
|
6
|
+
include RailsAdminNext::MainHelper
|
|
7
|
+
include RailsAdminNext::ApplicationHelper
|
|
8
|
+
|
|
9
|
+
before_action :check_for_cancel
|
|
10
|
+
|
|
11
|
+
def bulk_action
|
|
12
|
+
get_model
|
|
13
|
+
process(params[:bulk_action]) if params[:bulk_action].in?(RailsAdminNext::Config::Actions.all(:bulkable, controller: self, abstract_model: @abstract_model).collect(&:route_fragment))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def list_entries(model_config = @model_config, auth_scope_key = :index, additional_scope = get_association_scope_from_params, pagination = !(params[:associated_collection] || params[:all] || params[:bulk_ids]))
|
|
17
|
+
scope = model_config.scope
|
|
18
|
+
auth_scope = @authorization_adapter&.query(auth_scope_key, model_config.abstract_model)
|
|
19
|
+
scope = scope.merge(auth_scope) if auth_scope
|
|
20
|
+
scope = scope.instance_eval(&additional_scope) if additional_scope
|
|
21
|
+
get_collection(model_config, scope, pagination)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def action_missing(name, *_args)
|
|
27
|
+
action = RailsAdminNext::Config::Actions.find(name.to_sym)
|
|
28
|
+
raise AbstractController::ActionNotFound.new("The action '#{name}' could not be found for #{self.class.name}") unless action
|
|
29
|
+
|
|
30
|
+
get_model unless action.root?
|
|
31
|
+
get_object if action.member?
|
|
32
|
+
@authorization_adapter.try(:authorize, action.authorization_key, @abstract_model, @object)
|
|
33
|
+
@action = action.with({controller: self, abstract_model: @abstract_model, object: @object})
|
|
34
|
+
raise(ActionNotAllowed) unless @action.enabled?
|
|
35
|
+
|
|
36
|
+
@page_name = wording_for(:title)
|
|
37
|
+
|
|
38
|
+
instance_eval(&@action.controller)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def method_missing(name, *, &)
|
|
42
|
+
action = RailsAdminNext::Config::Actions.find(name.to_sym)
|
|
43
|
+
if action
|
|
44
|
+
action_missing(name, *, &)
|
|
45
|
+
else
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def respond_to_missing?(sym, include_private)
|
|
51
|
+
if RailsAdminNext::Config::Actions.find(sym)
|
|
52
|
+
true
|
|
53
|
+
else
|
|
54
|
+
super
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def back_or_index
|
|
59
|
+
allowed_return_to?(params[:return_to].to_s) ? params[:return_to] : index_path
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# A leading '/' alone isn't enough to prove same-origin: browsers normalize a leading
|
|
63
|
+
# backslash to '/', so '/\evil.com' would render as the protocol-relative '//evil.com'.
|
|
64
|
+
# Parsing with URI and comparing scheme+host+port (rather than a raw string prefix) also
|
|
65
|
+
# rejects userinfo tricks ('http://legit@evil.com') and no-boundary substring host
|
|
66
|
+
# confusion ('http://legit.com.evil.com').
|
|
67
|
+
def allowed_return_to?(url)
|
|
68
|
+
return false if url.blank? || url == request.fullpath
|
|
69
|
+
|
|
70
|
+
uri = URI.parse(url)
|
|
71
|
+
if uri.host
|
|
72
|
+
uri.scheme == request.scheme && uri.host == request.host && uri.port == request.port
|
|
73
|
+
else
|
|
74
|
+
url.start_with?("/") && !url.start_with?("//")
|
|
75
|
+
end
|
|
76
|
+
rescue URI::InvalidURIError
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def get_sort_hash(model_config)
|
|
81
|
+
field = model_config.list.fields.detect { |f| f.name.to_s == params[:sort] }
|
|
82
|
+
# If no sort param, default to the `sort_by` specified in the list config
|
|
83
|
+
field ||= model_config.list.possible_fields.detect { |f| f.name == model_config.list.sort_by.try(:to_sym) }
|
|
84
|
+
|
|
85
|
+
column =
|
|
86
|
+
if field.nil? || field.sortable == false # use default sort, asked field does not exist or is not sortable
|
|
87
|
+
model_config.list.sort_by
|
|
88
|
+
else
|
|
89
|
+
field.sort_column
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
params[:sort_reverse] ||= "false"
|
|
93
|
+
{sort: column, sort_reverse: (params[:sort_reverse] == (field&.sort_reverse&.to_s || "true"))}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def redirect_to_on_success
|
|
97
|
+
notice = I18n.t("admin.flash.successful", name: @model_config.label, action: I18n.t("admin.actions.#{@action.key}.done"))
|
|
98
|
+
if params[:_add_another]
|
|
99
|
+
redirect_to new_path(return_to: params[:return_to]), flash: {success: notice}
|
|
100
|
+
elsif params[:_add_edit]
|
|
101
|
+
redirect_to edit_path(id: @object.id, return_to: params[:return_to]), flash: {success: notice}
|
|
102
|
+
else
|
|
103
|
+
redirect_to back_or_index, flash: {success: notice}
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def visible_fields(action, model_config = @model_config)
|
|
108
|
+
model_config.send(action).with(controller: self, view: view_context, object: @object).visible_fields
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def sanitize_params_for!(action, model_config = @model_config, target_params = params[@abstract_model.param_key])
|
|
112
|
+
return unless target_params.present?
|
|
113
|
+
|
|
114
|
+
fields = visible_fields(action, model_config)
|
|
115
|
+
allowed_methods = fields.collect(&:allowed_methods).flatten.uniq.collect(&:to_s) << "id" << "_destroy"
|
|
116
|
+
fields.each { |field| field.parse_input(target_params) }
|
|
117
|
+
target_params.slice!(*allowed_methods)
|
|
118
|
+
target_params.permit! if target_params.respond_to?(:permit!)
|
|
119
|
+
fields.select(&:nested_form).each do |association|
|
|
120
|
+
nested = target_params[association.method_name]
|
|
121
|
+
# accepts_nested_attributes_for takes a hash-of-records ({idx => {…}}) or an
|
|
122
|
+
# array-of-records ([{…}]) for collections, and a single record ({…}) for singular
|
|
123
|
+
# associations. Recurse into every record regardless of the submitted cardinality so a
|
|
124
|
+
# cardinality-flipped param (e.g. an array sent to a has_many) can't smuggle keys past
|
|
125
|
+
# the slice that permit! has already blanket-permitted.
|
|
126
|
+
children_params =
|
|
127
|
+
if nested.is_a?(Array)
|
|
128
|
+
nested
|
|
129
|
+
elsif nested.respond_to?(:values)
|
|
130
|
+
association.multiple? ? nested.values : [nested]
|
|
131
|
+
else
|
|
132
|
+
[]
|
|
133
|
+
end
|
|
134
|
+
children_params.each do |children_param|
|
|
135
|
+
sanitize_params_for!(:nested, association.associated_model_config, children_param) if children_param.respond_to?(:values)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def handle_save_error(whereto = :new)
|
|
141
|
+
flash.now[:error] = I18n.t("admin.flash.error", name: @model_config.label, action: I18n.t("admin.actions.#{@action.key}.done").html_safe).html_safe
|
|
142
|
+
# full_messages can carry user-supplied values (e.g. interpolated into a custom validation
|
|
143
|
+
# message), so escape each before marking the assembled list html_safe — otherwise a
|
|
144
|
+
# `<script>` in a validation message would render live in the flash.
|
|
145
|
+
escaped_messages = @object.errors.full_messages.map { |message| ERB::Util.html_escape(message) }
|
|
146
|
+
flash.now[:error] += %(<br>- #{escaped_messages.join("<br>- ")}).html_safe
|
|
147
|
+
|
|
148
|
+
respond_to do |format|
|
|
149
|
+
format.html { render whereto, status: :not_acceptable }
|
|
150
|
+
format.js { render whereto, layout: "rails_admin_next/modal", status: :not_acceptable, content_type: Mime[:html].to_s }
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def check_for_cancel
|
|
155
|
+
cancelling = params[:_continue] || (params[:bulk_action] && !params[:bulk_ids])
|
|
156
|
+
return unless cancelling
|
|
157
|
+
|
|
158
|
+
redirect_to(back_or_index, notice: I18n.t("admin.flash.noaction"))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def get_collection(model_config, scope, pagination)
|
|
162
|
+
section = (@action.key == :export) ? model_config.export : model_config.list
|
|
163
|
+
eager_loads = section.fields.flat_map(&:eager_load_values)
|
|
164
|
+
options = {}
|
|
165
|
+
options = options.merge(page: (params[:page] || 1).to_i, per: params[:per] || model_config.list.items_per_page) if pagination
|
|
166
|
+
options = options.merge(include: eager_loads) unless eager_loads.blank?
|
|
167
|
+
options = options.merge(get_sort_hash(model_config))
|
|
168
|
+
options = options.merge(query: params[:query]) if params[:query].present?
|
|
169
|
+
options = options.merge(filters: params[:f]) if params[:f].present?
|
|
170
|
+
options = options.merge(bulk_ids: params[:bulk_ids]) if params[:bulk_ids]
|
|
171
|
+
model_config.abstract_model.all(options, scope)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def get_association_scope_from_params
|
|
175
|
+
return nil unless params[:associated_collection].present?
|
|
176
|
+
|
|
177
|
+
source_abstract_model = RailsAdminNext::AbstractModel.new(to_model_name(params[:source_abstract_model]))
|
|
178
|
+
source_model_config = source_abstract_model.config
|
|
179
|
+
source_object = source_abstract_model.get(params[:source_object_id])
|
|
180
|
+
action = params[:current_action].in?(%w[create update]) ? params[:current_action] : "edit"
|
|
181
|
+
@association = source_model_config.send(action).fields.detect { |f| f.name == params[:associated_collection].to_sym }.with(controller: self, object: source_object)
|
|
182
|
+
@association.associated_collection_scope
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|