mat_views 0.2.0 → 0.3.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 +4 -4
- data/README.md +4 -4
- data/app/assets/images/mat_views/android-chrome-192x192.png +0 -0
- data/app/assets/images/mat_views/android-chrome-512x512.png +0 -0
- data/app/assets/images/mat_views/apple-touch-icon.png +0 -0
- data/app/assets/images/mat_views/favicon-16x16.png +0 -0
- data/app/assets/images/mat_views/favicon-32x32.png +0 -0
- data/app/assets/images/mat_views/favicon-48x48.png +0 -0
- data/app/assets/images/mat_views/favicon.ico +0 -0
- data/app/assets/images/mat_views/favicon.svg +18 -0
- data/app/assets/images/mat_views/logo.svg +18 -0
- data/app/assets/images/mat_views/mask-icon.svg +5 -0
- data/app/assets/stylesheets/mat_views/application.css +323 -12
- data/app/controllers/mat_views/admin/application_controller.rb +135 -0
- data/app/controllers/mat_views/admin/dashboard_controller.rb +32 -0
- data/app/controllers/mat_views/admin/mat_view_definitions_controller.rb +248 -0
- data/app/controllers/mat_views/admin/preferences_controller.rb +91 -0
- data/app/controllers/mat_views/admin/runs_controller.rb +74 -0
- data/app/helpers/mat_views/admin/ui_helper.rb +385 -0
- data/app/javascript/mat_views/application.js +8 -0
- data/app/javascript/mat_views/controllers/application.js +10 -0
- data/app/javascript/mat_views/controllers/details_controller.js +122 -0
- data/app/javascript/mat_views/controllers/drawer_controller.js +252 -0
- data/app/javascript/mat_views/controllers/filter_controller.js +90 -0
- data/app/javascript/mat_views/controllers/flash_controller.js +13 -0
- data/app/javascript/mat_views/controllers/index.js +10 -0
- data/app/javascript/mat_views/controllers/mv_confirm_controller.js +281 -0
- data/app/javascript/mat_views/controllers/submitter_controller.js +15 -0
- data/app/javascript/mat_views/controllers/tabs_controller.js +67 -0
- data/app/javascript/mat_views/controllers/timezone_controller.js +16 -0
- data/app/javascript/mat_views/controllers/tooltip_controller.js +328 -0
- data/app/javascript/mat_views/controllers/turbo_frame_lifecycle_controller.js +49 -0
- data/app/jobs/mat_views/application_job.rb +2 -2
- data/app/jobs/mat_views/create_view_job.rb +9 -8
- data/app/jobs/mat_views/delete_view_job.rb +8 -8
- data/app/jobs/mat_views/refresh_view_job.rb +8 -9
- data/app/models/concerns/mat_views_i18n.rb +139 -0
- data/app/models/mat_views/application_record.rb +1 -0
- data/app/models/mat_views/mat_view_definition.rb +12 -7
- data/app/models/mat_views/mat_view_run.rb +11 -13
- data/app/views/layouts/mat_views/_footer.html.erb +41 -0
- data/app/views/layouts/mat_views/_header.html.erb +25 -0
- data/app/views/layouts/mat_views/admin.html.erb +47 -0
- data/app/views/layouts/mat_views/turbo_frame.html.erb +3 -0
- data/app/views/mat_views/admin/dashboard/index.html.erb +33 -0
- data/app/views/mat_views/admin/mat_view_definitions/_definition_actions.html.erb +94 -0
- data/app/views/mat_views/admin/mat_view_definitions/_table.html.erb +48 -0
- data/app/views/mat_views/admin/mat_view_definitions/empty.html.erb +1 -0
- data/app/views/mat_views/admin/mat_view_definitions/form.html.erb +79 -0
- data/app/views/mat_views/admin/mat_view_definitions/index.html.erb +10 -0
- data/app/views/mat_views/admin/mat_view_definitions/show.html.erb +40 -0
- data/app/views/mat_views/admin/preferences/show.html.erb +50 -0
- data/app/views/mat_views/admin/runs/_table.html.erb +61 -0
- data/app/views/mat_views/admin/runs/index.html.erb +38 -0
- data/app/views/mat_views/admin/runs/show.html.erb +64 -0
- data/app/views/mat_views/admin/ui/_card.html.erb +15 -0
- data/app/views/mat_views/admin/ui/_details.html.erb +10 -0
- data/app/views/mat_views/admin/ui/_flash.html.erb +6 -0
- data/app/views/mat_views/admin/ui/_table.html.erb +8 -0
- data/config/importmap.rb +9 -0
- data/config/locales/en-AU-ocker.yml +187 -0
- data/config/locales/en-AU.yml +187 -0
- data/config/locales/en-BB.yml +187 -0
- data/config/locales/en-BD.yml +187 -0
- data/config/locales/en-BE.yml +187 -0
- data/config/locales/en-BORK.yml +187 -0
- data/config/locales/en-BS.yml +187 -0
- data/config/locales/en-BZ.yml +187 -0
- data/config/locales/en-CA.yml +187 -0
- data/config/locales/en-CM.yml +187 -0
- data/config/locales/en-CY.yml +187 -0
- data/config/locales/en-EG.yml +187 -0
- data/config/locales/en-FJ.yml +187 -0
- data/config/locales/en-GB.yml +187 -0
- data/config/locales/en-GH.yml +187 -0
- data/config/locales/en-GI.yml +187 -0
- data/config/locales/en-GM.yml +187 -0
- data/config/locales/en-GY.yml +187 -0
- data/config/locales/en-HK.yml +187 -0
- data/config/locales/en-IE.yml +187 -0
- data/config/locales/en-IN.yml +187 -0
- data/config/locales/en-JM.yml +187 -0
- data/config/locales/en-KE.yml +187 -0
- data/config/locales/en-LK.yml +187 -0
- data/config/locales/en-LOL.yml +187 -0
- data/config/locales/en-LR.yml +187 -0
- data/config/locales/en-MS.yml +187 -0
- data/config/locales/en-MT.yml +187 -0
- data/config/locales/en-MW.yml +187 -0
- data/config/locales/en-MY.yml +187 -0
- data/config/locales/en-NG.yml +187 -0
- data/config/locales/en-NP.yml +187 -0
- data/config/locales/en-NZ.yml +187 -0
- data/config/locales/en-PG.yml +187 -0
- data/config/locales/en-PH.yml +187 -0
- data/config/locales/en-PK.yml +187 -0
- data/config/locales/en-RW.yml +187 -0
- data/config/locales/en-SCOT.yml +187 -0
- data/config/locales/en-SG.yml +187 -0
- data/config/locales/en-SHAKESPEARE.yml +187 -0
- data/config/locales/en-SL.yml +187 -0
- data/config/locales/en-SS.yml +187 -0
- data/config/locales/en-TH.yml +187 -0
- data/config/locales/en-TT.yml +187 -0
- data/config/locales/en-TZ.yml +187 -0
- data/config/locales/en-UG.yml +187 -0
- data/config/locales/en-US-pirate.yml +187 -0
- data/config/locales/en-US.yml +187 -0
- data/config/locales/en-YODA.yml +187 -0
- data/config/locales/en-ZA.yml +187 -0
- data/config/locales/en-ZW.yml +187 -0
- data/config/locales/en.yml +187 -0
- data/config/routes.rb +27 -3
- data/lib/generators/mat_views/install/templates/create_mat_view_definitions.rb +7 -7
- data/lib/generators/mat_views/install/templates/create_mat_view_runs.rb +5 -5
- data/lib/mat_views/admin/auth_bridge.rb +93 -0
- data/lib/mat_views/admin/default_auth.rb +61 -0
- data/lib/mat_views/configuration.rb +9 -0
- data/lib/mat_views/engine.rb +50 -2
- data/lib/mat_views/helpers/ui_test_ids.rb +43 -0
- data/lib/mat_views/services/base_service.rb +46 -38
- data/lib/mat_views/services/check_matview_exists.rb +76 -0
- data/lib/mat_views/services/concurrent_refresh.rb +9 -6
- data/lib/mat_views/services/create_view.rb +15 -15
- data/lib/mat_views/services/delete_view.rb +8 -11
- data/lib/mat_views/services/regular_refresh.rb +6 -5
- data/lib/mat_views/services/swap_refresh.rb +11 -9
- data/lib/mat_views/version.rb +1 -1
- data/lib/mat_views.rb +10 -4
- data/lib/tasks/helpers.rb +13 -13
- data/lib/tasks/mat_views_tasks.rake +15 -15
- metadata +130 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7adba77306f5d4a4f8738625ad9b584f13c84c082a307bd4473e82fcba93936e
|
4
|
+
data.tar.gz: f8c61cb31ff80d591dac050abe18237aed700532d0b536614fc29b79bade91f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b4408b95e63107d1650ca3392e20ff2484837f817abbdce2716909e1647478c7562bc379c38283a99825440c258bff7d7b52dcabbc5d2c54ae68aeac946a731
|
7
|
+
data.tar.gz: 5a684e7384de30dcc5f4af1d10bb4999a8f104dbc7b3c112998b34ba3018cafb650ab24ef4737c2c11ed8259ec4ed711784e38b9ea31af70011b77b26b5dab44
|
data/README.md
CHANGED
@@ -65,19 +65,19 @@ defn = MatViews::MatViewDefinition.create!(
|
|
65
65
|
|
66
66
|
```ruby
|
67
67
|
# Create
|
68
|
-
MatViews::Services::CreateView.new(defn, force: true).
|
68
|
+
MatViews::Services::CreateView.new(defn, force: true).call
|
69
69
|
MatViews::CreateViewJob.perform_later(defn.id, force: true)
|
70
70
|
|
71
71
|
# Refresh
|
72
|
-
MatViews::Services::RegularRefresh.new(defn, row_count_strategy: :estimated).
|
72
|
+
MatViews::Services::RegularRefresh.new(defn, row_count_strategy: :estimated).call
|
73
73
|
MatViews::RefreshViewJob.perform_later(defn.id, row_count_strategy: :exact)
|
74
74
|
|
75
75
|
# Delete
|
76
|
-
MatViews::Services::DeleteView.new(defn, cascade: false, if_exists: true).
|
76
|
+
MatViews::Services::DeleteView.new(defn, cascade: false, if_exists: true).call
|
77
77
|
MatViews::DeleteViewJob.perform_later(defn.id, cascade: true)
|
78
78
|
```
|
79
79
|
|
80
|
-
**Uniform response**: `status`, `
|
80
|
+
**Uniform response**: `status`, `meta`, `success?` / `error?`.
|
81
81
|
|
82
82
|
---
|
83
83
|
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<svg width="128" height="128" viewBox="0 0 256 256" fill="none"
|
3
|
+
xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
4
|
+
<title id="title">MatViews Monogram</title>
|
5
|
+
<desc id="desc">Stylized M and V mark for Materialized Views. Transparent background.</desc>
|
6
|
+
|
7
|
+
<!-- subtle outer ring -->
|
8
|
+
<circle cx="128" cy="128" r="110" stroke="#0F172A" stroke-opacity="0.18" stroke-width="10" fill="none"/>
|
9
|
+
|
10
|
+
<!-- V (accent) -->
|
11
|
+
<path d="M64 64 L128 192 L192 64" stroke="#10B981" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
|
12
|
+
|
13
|
+
<!-- M (primary) -->
|
14
|
+
<path d="M48 176 L92 80 L128 144 L164 80 L208 176" stroke="#0F172A" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
|
15
|
+
|
16
|
+
<!-- optional tiny dot to balance negative space -->
|
17
|
+
<circle cx="128" cy="208" r="4" fill="#10B981" fill-opacity="0.9"/>
|
18
|
+
</svg>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<svg width="128" height="128" viewBox="0 0 256 256" fill="none"
|
3
|
+
xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
|
4
|
+
<title id="title">MatViews Monogram</title>
|
5
|
+
<desc id="desc">Stylized M and V mark for Materialized Views. Transparent background.</desc>
|
6
|
+
|
7
|
+
<!-- subtle outer ring -->
|
8
|
+
<circle cx="128" cy="128" r="110" stroke="#0F172A" stroke-opacity="0.18" stroke-width="10" fill="none"/>
|
9
|
+
|
10
|
+
<!-- V (accent) -->
|
11
|
+
<path d="M64 64 L128 192 L192 64" stroke="#10B981" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
|
12
|
+
|
13
|
+
<!-- M (primary) -->
|
14
|
+
<path d="M48 176 L92 80 L128 144 L164 80 L208 176" stroke="#0F172A" stroke-width="20" stroke-linecap="round" stroke-linejoin="round"/>
|
15
|
+
|
16
|
+
<!-- optional tiny dot to balance negative space -->
|
17
|
+
<circle cx="128" cy="208" r="4" fill="#10B981" fill-opacity="0.9"/>
|
18
|
+
</svg>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" fill="none">
|
3
|
+
<path d="M64 64 L128 192 L192 64" stroke="currentColor" stroke-width="28" stroke-linecap="round" stroke-linejoin="round"/>
|
4
|
+
<path d="M48 176 L92 80 L128 144 L164 80 L208 176" stroke="currentColor" stroke-width="28" stroke-linecap="round" stroke-linejoin="round"/>
|
5
|
+
</svg>
|
@@ -1,15 +1,326 @@
|
|
1
1
|
/*
|
2
|
-
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
-
* listed below.
|
4
|
-
*
|
5
|
-
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
-
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
-
*
|
8
|
-
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
-
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
-
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
-
* It is generally better to create a new file per style scope.
|
12
|
-
*
|
13
2
|
*= require_tree .
|
14
3
|
*= require_self
|
15
|
-
|
4
|
+
*/
|
5
|
+
|
6
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
7
|
+
THEME TOKENS
|
8
|
+
- Define semantic variables once.
|
9
|
+
- Light theme is default on :root.
|
10
|
+
- Dark theme overrides via [data-theme="dark"] or prefers-color-scheme.
|
11
|
+
─────────────────────────────────────────────────────────────────────────── */
|
12
|
+
|
13
|
+
:root{
|
14
|
+
/* typography */
|
15
|
+
--font-base:14px/1.4 Inter,system-ui;
|
16
|
+
--font-head:'Space Grotesk';
|
17
|
+
--font-mono:'JetBrains Mono',monospace;
|
18
|
+
|
19
|
+
/* semantic colors (light defaults) */
|
20
|
+
--color-text:#0f172a;
|
21
|
+
--color-sub:#475569;
|
22
|
+
--color-muted:#334155;
|
23
|
+
|
24
|
+
--color-bg:#f8fafc;
|
25
|
+
--color-surface:#ffffff;
|
26
|
+
--color-elev-shadow:rgba(15,23,42,.04);
|
27
|
+
|
28
|
+
--color-white:#ffffff;
|
29
|
+
--color-border:#e2e8f0;
|
30
|
+
--color-border-light:#f1f5f9;
|
31
|
+
--color-border-mid:#cbd5e1;
|
32
|
+
--color-dark:#111827; /* hover for primary in light */
|
33
|
+
--color-primary:#0f172a;
|
34
|
+
|
35
|
+
--color-danger:#dc2626;
|
36
|
+
--color-danger-dark:#b91c1c;
|
37
|
+
|
38
|
+
--color-success-bg:#ecfdf5;
|
39
|
+
--color-success:#047857;
|
40
|
+
--color-success-border:#a7f3d0;
|
41
|
+
|
42
|
+
--color-running-bg:#fffbeb;
|
43
|
+
--color-running:#92400e;
|
44
|
+
--color-running-border:#fde68a;
|
45
|
+
|
46
|
+
--color-failed-bg:#fef2f2;
|
47
|
+
--color-failed:#b91c1c;
|
48
|
+
--color-failed-border:#fecaca;
|
49
|
+
|
50
|
+
--color-link:#3b82f6;
|
51
|
+
|
52
|
+
/* component-specific surfaces (light) */
|
53
|
+
--card-surface:var(--color-surface);
|
54
|
+
--table-head-bg:#eaeaea;
|
55
|
+
--btn-ghost-hover:#eef2f7;
|
56
|
+
--btn-secondary-hover:#f1f5f9;
|
57
|
+
|
58
|
+
/* overlay / popovers */
|
59
|
+
--overlay-scrim:rgba(15,23,42,.35);
|
60
|
+
--tooltip-bg:#0f172a;
|
61
|
+
--tooltip-shadow:rgba(2,6,23,.20);
|
62
|
+
|
63
|
+
/* transitions */
|
64
|
+
--elev-shadow-lg:rgba(15,23,42,.10);
|
65
|
+
|
66
|
+
color-scheme: light;
|
67
|
+
}
|
68
|
+
|
69
|
+
/* Dark theme overrides (manual: <html data-theme="dark">) */
|
70
|
+
:root[data-theme="dark"]{
|
71
|
+
--color-text:#e5e7eb;
|
72
|
+
--color-sub:#94a3b8;
|
73
|
+
--color-muted:#cbd5e1;
|
74
|
+
--color-bg:#0b1020;
|
75
|
+
--color-surface:#0f172a;
|
76
|
+
--color-elev-shadow:rgba(0,0,0,.25);
|
77
|
+
--color-white:#0f172a;
|
78
|
+
--color-border:#1f2937;
|
79
|
+
--color-border-light:#111827;
|
80
|
+
--color-border-mid:#334155;
|
81
|
+
--color-dark:#f8fafc;
|
82
|
+
--color-primary:#e5e7eb;
|
83
|
+
--color-danger:#ef4444;
|
84
|
+
--color-danger-dark:#dc2626;
|
85
|
+
--color-success-bg:#052e25;
|
86
|
+
--color-success:#34d399;
|
87
|
+
--color-success-border:#065f46;
|
88
|
+
--color-running-bg:#3a2a03;
|
89
|
+
--color-running:#fbbf24;
|
90
|
+
--color-running-border:#854d0e;
|
91
|
+
--color-failed-bg:#3a0d0d;
|
92
|
+
--color-failed:#fca5a5;
|
93
|
+
--color-failed-border:#7f1d1d;
|
94
|
+
--color-link:#60a5fa;
|
95
|
+
--card-surface:var(--color-surface);
|
96
|
+
--table-head-bg:#0f172a;
|
97
|
+
--btn-ghost-hover:#0f172a;
|
98
|
+
--btn-secondary-hover:#111827;
|
99
|
+
--overlay-scrim:rgba(3,7,18,.55);
|
100
|
+
--tooltip-bg:#0b1222;
|
101
|
+
--tooltip-shadow:rgba(0,0,0,.35);
|
102
|
+
|
103
|
+
color-scheme: dark;
|
104
|
+
}
|
105
|
+
|
106
|
+
/* Auto-dark by system setting unless explicitly overridden by data-theme */
|
107
|
+
@media (prefers-color-scheme: dark){
|
108
|
+
:root:not([data-theme]){
|
109
|
+
/* repeat the same overrides as the block above, or factor them with a custom @mixin in SCSS */
|
110
|
+
color-scheme: dark;
|
111
|
+
}
|
112
|
+
}
|
113
|
+
:root{ color-scheme: light; }
|
114
|
+
|
115
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
116
|
+
BASE
|
117
|
+
─────────────────────────────────────────────────────────────────────────── */
|
118
|
+
|
119
|
+
html,body{height:100%}
|
120
|
+
body{
|
121
|
+
display:flex;flex-direction:column;min-height:100vh;
|
122
|
+
font:var(--font-base);color:var(--color-text);background:var(--color-bg)
|
123
|
+
}
|
124
|
+
h1,h2,h3,h4,h5,h6{font-weight:600;color:var(--color-text);font-family:var(--font-head)}
|
125
|
+
code,pre{font-family:var(--font-mono);font-size:.9rem;color:var(--color-muted)}
|
126
|
+
a{color:var(--color-link);text-decoration:none}
|
127
|
+
a:hover{text-decoration:underline}
|
128
|
+
pre{background:var(--card-surface);border:1px solid var(--color-border);border-radius:8px;box-shadow:0 1px 2px var(--color-elev-shadow);padding:.75rem;overflow:auto;margin: 0;white-space:pre-wrap;}
|
129
|
+
|
130
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
131
|
+
LAYOUT
|
132
|
+
─────────────────────────────────────────────────────────────────────────── */
|
133
|
+
|
134
|
+
.mv-main{flex:1 1 auto}
|
135
|
+
.mv-container{background:transparent;margin:0 auto;max-width:72rem;padding:1rem 1.25rem}
|
136
|
+
.mv-header{background:var(--card-surface);border-bottom:1px solid var(--color-border);color:var(--color-sub);font-size:.9rem;padding:1rem 0}
|
137
|
+
.mv-header-row{display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between}
|
138
|
+
.mv-header-row.even{justify-content:space-evenly}
|
139
|
+
.mv-brand{align-items:center;color:var(--color-text);display:flex;font-weight:600;gap:.6rem;text-decoration:none}
|
140
|
+
.mv-logo{height:28px;width:28px}
|
141
|
+
.mv-footer{background:var(--card-surface);border-top:1px solid var(--color-border);color:var(--color-sub);font-size:.9rem;margin-top:auto;padding-top:1rem}
|
142
|
+
.mv-footer-row{display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between}
|
143
|
+
.row-item {display: flex; align-items: center; gap: 6px;}
|
144
|
+
|
145
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
146
|
+
CARDS
|
147
|
+
─────────────────────────────────────────────────────────────────────────── */
|
148
|
+
|
149
|
+
.mv-card{background:var(--card-surface);border:1px solid var(--color-border);border-radius:12px;box-shadow:0 1px 2px var(--color-elev-shadow)}
|
150
|
+
.mv-card-b{padding:1rem}
|
151
|
+
.mv-card-h{border-bottom:1px solid var(--color-border);font-family:var(--font-head);font-weight:600;padding:.75rem 1rem}
|
152
|
+
.mv-card-f{border-top:1px solid var(--color-border);padding:.5rem 1rem;text-align:right;font-size:.85rem;}
|
153
|
+
|
154
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
155
|
+
TOOLBAR
|
156
|
+
─────────────────────────────────────────────────────────────────────────── */
|
157
|
+
.mv-toolbar{display:flex;justify-content:flex-end;margin:.5rem 0;}
|
158
|
+
.mv-toolbar.start{justify-content: flex-start;}
|
159
|
+
.mv-toolbar.center{justify-content: center;flex: 1;}
|
160
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
161
|
+
BUTTONS
|
162
|
+
─────────────────────────────────────────────────────────────────────────── */
|
163
|
+
|
164
|
+
.mv-btn{align-items:center;border:1px solid transparent;border-radius:8px;box-sizing:border-box;cursor:pointer;display:inline-flex;font-weight:500;gap:.5rem;padding:.4rem .7rem;text-decoration:none}
|
165
|
+
.mv-btn--ghost{background:transparent;color:var(--color-muted)}
|
166
|
+
.mv-btn--ghost:hover{background:var(--btn-ghost-hover)}
|
167
|
+
.mv-btn--lg{font-size:.9rem;height:44px;max-height:44px;padding:0 .8rem}
|
168
|
+
.mv-btn--md{font-size:.8rem;height:36px;max-height:36px;padding:0 .55rem}
|
169
|
+
.mv-btn--negative{background:var(--color-danger);color:#fff}
|
170
|
+
.mv-btn--negative:hover{background:var(--color-danger-dark)}
|
171
|
+
.mv-btn--primary{background:var(--color-primary);color:#fff}
|
172
|
+
[data-theme="dark"] .mv-btn--primary{color:#0b1020} /* ensure contrast on dark */
|
173
|
+
.mv-btn--primary:hover{background:var(--color-dark)}
|
174
|
+
.mv-btn--secondary{background:var(--card-surface);border-color:var(--color-border-mid);color:var(--color-primary)}
|
175
|
+
.mv-btn--secondary:hover{background:var(--btn-secondary-hover)}
|
176
|
+
.mv-btn--sm{font-size:.6rem;height:28px;max-height:28px;padding:0 .4rem}
|
177
|
+
.mv-btn.underline{text-decoration:underline;}
|
178
|
+
|
179
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
180
|
+
TABLES
|
181
|
+
─────────────────────────────────────────────────────────────────────────── */
|
182
|
+
|
183
|
+
.mv-table{background:var(--card-surface);border:1px solid var(--color-border);border-collapse:collapse;border-radius:10px;overflow:hidden;width:100%}
|
184
|
+
.mv-th{background:var(--table-head-bg);border-bottom:1px solid var(--color-border);color:var(--color-sub);font-family:var(--font-head);font-weight:600;padding:.5rem .75rem;text-align:left}
|
185
|
+
.mv-td{border-bottom:1px solid var(--color-border-light);padding:.5rem .75rem}
|
186
|
+
.mv-tr:last-child .mv-td{border-bottom:none}
|
187
|
+
|
188
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
189
|
+
BADGES
|
190
|
+
─────────────────────────────────────────────────────────────────────────── */
|
191
|
+
|
192
|
+
.mv-badge{border:1px solid transparent;border-radius:6px;display:inline-block;font-size:.75rem;font-weight:600;padding:.15rem .4rem}
|
193
|
+
.mv-badge--failed{background:var(--color-failed-bg);border-color:var(--color-failed-border);color:var(--color-failed)}
|
194
|
+
.mv-badge--running{background:var(--color-running-bg);border-color:var(--color-running-border);color:var(--color-running)}
|
195
|
+
.mv-badge--success{background:var(--color-success-bg);border-color:var(--color-success-border);color:var(--color-success)}
|
196
|
+
|
197
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
198
|
+
Details/Summary
|
199
|
+
─────────────────────────────────────────────────────────────────────────── */
|
200
|
+
details summary{cursor:pointer;}
|
201
|
+
details summary:focus{outline:none}
|
202
|
+
.mv-details summary::-webkit-details-marker { display: none; }
|
203
|
+
.mv-details summary {display: inline-flex;align-items: center;gap: .5rem;cursor: pointer;}
|
204
|
+
.mv-chevron {flex: 0 0 auto;transition: transform 200ms ease;transform-origin: 50% 50%;}
|
205
|
+
.mv-details[open] .mv-chevron { transform: rotate(90deg); }
|
206
|
+
.mv-details__content {overflow: hidden;will-change: height;margin:0;}
|
207
|
+
[dir="rtl"] .mv-details[open] .mv-chevron { transform: rotate(-90deg); }
|
208
|
+
|
209
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
210
|
+
TABS
|
211
|
+
─────────────────────────────────────────────────────────────────────────── */
|
212
|
+
|
213
|
+
.mv-tabs{border-bottom:1px solid var(--color-border);display:flex;gap:1rem;margin:1rem 0}
|
214
|
+
.mv-tab{border-bottom:2px solid transparent;color:var(--color-muted);font-family:var(--font-head);padding:.5rem .5rem;text-decoration:none}
|
215
|
+
.mv-tab--on{border-color:var(--color-primary)}
|
216
|
+
.mv-tab:hover{border-color:var(--color-border-mid)}
|
217
|
+
|
218
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
219
|
+
FORMS
|
220
|
+
─────────────────────────────────────────────────────────────────────────── */
|
221
|
+
|
222
|
+
.mv-form{display:block;padding:0 1rem;max-width:100%}
|
223
|
+
.mv-form--no-pad{padding:0}
|
224
|
+
.mv-field{display:flex;flex-direction:column;gap:.25rem;margin:.75rem 0}
|
225
|
+
.mv-field-inline-row{display:inline-flex;flex-direction:row;align-items:center;gap:.75rem; margin: 1rem 0;}
|
226
|
+
.mv-field-inline{display:inline-flex;flex-direction:row;align-items:center;gap:.5rem}
|
227
|
+
.mv-input,.mv-textarea,.mv-select{background:var(--card-surface);border:1px solid var(--color-border-mid);border-radius:8px;padding:.45rem .6rem;color:var(--color-text)}
|
228
|
+
.mv-label{color:var(--color-muted);font-size:.9rem;font-weight:600;}
|
229
|
+
.mv-label--sublabel{font-weight:300;margin:0;font-size:.85rem;}
|
230
|
+
.mv-textarea{min-height:10rem;resize:vertical}
|
231
|
+
.mv-hint{color:var(--color-sub);font-size:.75rem;}
|
232
|
+
|
233
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
234
|
+
FLASH
|
235
|
+
─────────────────────────────────────────────────────────────────────────── */
|
236
|
+
|
237
|
+
.mv-flash{border-radius:10px;font-size:.9rem;margin:.5rem 0;padding:.5rem .75rem}
|
238
|
+
.mv-flash--err{background:var(--color-failed-bg);border:1px solid var(--color-failed-border);color:var(--color-failed)}
|
239
|
+
.mv-flash--ok{background:var(--color-success-bg);border:1px solid var(--color-success-border);color:#065f46}
|
240
|
+
|
241
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
242
|
+
UTILS
|
243
|
+
─────────────────────────────────────────────────────────────────────────── */
|
244
|
+
|
245
|
+
.mv-text-right{text-align:end} /* logical for LTR/RTL */
|
246
|
+
|
247
|
+
.mv-td-actions{text-align:end}
|
248
|
+
.mv-actions{display:inline-flex;flex-wrap:wrap;gap:12px;justify-content:flex-end}
|
249
|
+
.mv-action-group{background:var(--card-surface);border:1px solid var(--color-border);border-radius:12px;box-shadow:0 1px 2px var(--color-elev-shadow);display:inline-flex;flex-direction:column;padding:10px 12px}
|
250
|
+
.mv-action-title{align-items:center;color:var(--color-sub);display:flex;font-size:.72rem;font-weight:600;gap:6px;letter-spacing:.02em;margin-bottom:.5rem;text-transform:uppercase}
|
251
|
+
.mv-buttons{align-items:center;display:inline-flex;flex-direction:column;gap:6px;align-items: stretch;}
|
252
|
+
.mv-buttons .mv-btn{width:100%;justify-content:center;}
|
253
|
+
.mv-icon{margin-inline-end:.05rem;margin-inline-start: .05rem;}
|
254
|
+
.mv-mv-missing{align-items:center;color:#92400e;display:inline-flex;font-size:.88rem;gap:6px;}
|
255
|
+
.mv-status-icon{align-items:center;display:inline-flex;}
|
256
|
+
.mv-status-missing{color:#dc2626}
|
257
|
+
.mv-status-ok{color:#16a34a}
|
258
|
+
|
259
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
260
|
+
DRAWER
|
261
|
+
─────────────────────────────────────────────────────────────────────────── */
|
262
|
+
|
263
|
+
.mv-drawer-root{inset:0;pointer-events:none;position:fixed;z-index:60}
|
264
|
+
.mv-drawer-root.is-open{pointer-events:auto}
|
265
|
+
.mv-drawer-overlay{background:var(--overlay-scrim);inset:0;opacity:0;position:absolute;transition:opacity .18s ease}
|
266
|
+
.mv-drawer-root.is-open .mv-drawer-overlay{opacity:1}
|
267
|
+
.mv-drawer{
|
268
|
+
background:var(--card-surface);border-left:1px solid var(--color-border);
|
269
|
+
box-shadow:-8px 0 24px var(--elev-shadow-lg);display:flex;flex-direction:column;
|
270
|
+
height:100%;max-width:90vw;position:absolute;right:0;top:0;
|
271
|
+
transform:translateX(100%);transition:transform .22s ease;width:32rem
|
272
|
+
}
|
273
|
+
.mv-drawer-root.is-open .mv-drawer{transform:translateX(0)}
|
274
|
+
.mv-drawer-body{height:100%;overflow:auto;padding:0}
|
275
|
+
.mv-drawer-body .mv-card{border-left:0;border-radius:0;border-right:0}
|
276
|
+
.mv-drawer-action{background:transparent;border:0;color:var(--color-muted);cursor:pointer;font-size:1.25rem}
|
277
|
+
.mv-drawer-head{align-items:center;border-bottom:1px solid var(--color-border);display:flex;justify-content:space-between;padding:.75rem 1rem;font-family:var(--font-head);}
|
278
|
+
.mv-drawer-head h2{margin:0}
|
279
|
+
|
280
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
281
|
+
TOOLTIP + CONFIRM
|
282
|
+
─────────────────────────────────────────────────────────────────────────── */
|
283
|
+
|
284
|
+
.mv-tooltip{background:var(--tooltip-bg);border-radius:6px;box-shadow:0 4px 16px var(--tooltip-shadow);color:#fff;font-size:12px;line-height:1.2;opacity:0;padding:6px 8px;transition:opacity .15s ease;z-index:9999}
|
285
|
+
.mv-tooltip[data-show="true"]{opacity:1}
|
286
|
+
.mv-tooltip__arrow{background:var(--tooltip-bg);box-shadow:-1px -1px 1px rgba(2,6,23,.08);height:8px;position:absolute;transform:rotate(45deg);width:8px}
|
287
|
+
.mv-tooltip__content{display:block;white-space:nowrap}
|
288
|
+
.mv-tooltip--bottom .mv-tooltip__arrow{left:50%;margin-left:-4px;top:-4px}
|
289
|
+
.mv-tooltip--left .mv-tooltip__arrow{right:-4px;top:50%;margin-top:-4px}
|
290
|
+
.mv-tooltip--right .mv-tooltip__arrow{left:-4px;top:50%;margin-top:-4px}
|
291
|
+
.mv-tooltip--top .mv-tooltip__arrow{bottom:-4px;left:50%;margin-left:-4px}
|
292
|
+
|
293
|
+
.mv-confirm{background:var(--card-surface);border:1px solid var(--color-border);border-radius:8px;box-shadow:0 4px 16px var(--tooltip-shadow);font-size:.85rem;max-width:260px;opacity:0;padding:.75rem;transition:opacity .15s ease;z-index:9999}
|
294
|
+
.mv-confirm[data-show="true"]{opacity:1}
|
295
|
+
.mv-confirm__content{margin-bottom:.5rem;color:var(--color-text)}
|
296
|
+
.mv-confirm__buttons{display:flex;gap:.5rem;justify-content:flex-end}
|
297
|
+
.mv-confirm__arrow{background:var(--card-surface);border-left:1px solid var(--color-border);border-top:1px solid var(--color-border);box-shadow:-1px -1px 1px rgba(2,6,23,.08);width:10px;height:10px;position:absolute;transform:rotate(45deg)}
|
298
|
+
.mv-confirm--bottom .mv-confirm__arrow{ top:-5px; left:50%; margin-left:-5px }
|
299
|
+
.mv-confirm--top .mv-confirm__arrow{ bottom:-5px; left:50%; margin-left:-5px; transform:rotate(225deg) }
|
300
|
+
.mv-confirm--left .mv-confirm__arrow{ right:-5px; top:50%; margin-top:-5px; transform:rotate(135deg) }
|
301
|
+
.mv-confirm--right .mv-confirm__arrow{ left:-5px; top:50%; margin-top:-5px; transform:rotate(-45deg) }
|
302
|
+
|
303
|
+
/* ─────────────────────────────────────────────────────────────────────────────
|
304
|
+
RTL SUPPORT (no class changes; flip where needed)
|
305
|
+
─────────────────────────────────────────────────────────────────────────── */
|
306
|
+
|
307
|
+
[dir="rtl"] .mv-brand{flex-direction:row-reverse}
|
308
|
+
[dir="rtl"] .mv-status-icon{margin-inline-start:0;margin-inline-end:.5rem}
|
309
|
+
[dir="rtl"] .mv-mv-missing{margin-inline-end:0;margin-inline-start:.5rem}
|
310
|
+
[dir="rtl"] .mv-th{text-align:right}
|
311
|
+
[dir="rtl"] .mv-td-actions{text-align:start}
|
312
|
+
|
313
|
+
/* Drawer opens from the *left* in RTL */
|
314
|
+
[dir="rtl"] .mv-drawer{
|
315
|
+
left:0; right:auto;
|
316
|
+
border-left:0; border-right:1px solid var(--color-border);
|
317
|
+
box-shadow:8px 0 24px var(--elev-shadow-lg);
|
318
|
+
transform:translateX(-100%);
|
319
|
+
}
|
320
|
+
[dir="rtl"] .mv-drawer-root.is-open .mv-drawer{transform:translateX(0)}
|
321
|
+
|
322
|
+
/* Tooltip/Confirm arrow horizontal flip in RTL */
|
323
|
+
[dir="rtl"] .mv-tooltip--left .mv-tooltip__arrow{left:-4px; right:auto}
|
324
|
+
[dir="rtl"] .mv-tooltip--right .mv-tooltip__arrow{right:-4px; left:auto}
|
325
|
+
[dir="rtl"] .mv-confirm--left .mv-confirm__arrow{left:-5px; right:auto; transform:rotate(-45deg)}
|
326
|
+
[dir="rtl"] .mv-confirm--right .mv-confirm__arrow{right:-5px; left:auto; transform:rotate(135deg)}
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright Codevedas Inc. 2025-present
|
4
|
+
#
|
5
|
+
# This source code is licensed under the MIT license found in the
|
6
|
+
# LICENSE file in the root directory of this source tree.
|
7
|
+
|
8
|
+
module MatViews
|
9
|
+
module Admin
|
10
|
+
# MatViews::Admin::ApplicationController
|
11
|
+
# --------------------------------------
|
12
|
+
# Base controller for the MatViews admin interface.
|
13
|
+
#
|
14
|
+
# Responsibilities:
|
15
|
+
# - Provides authentication and authorization via MatViews::Admin::AuthBridge.
|
16
|
+
# - Applies the `mat_views/admin` layout and includes UI helpers.
|
17
|
+
# - Manages locale (`I18n.locale`) and enforces language parameter consistency.
|
18
|
+
# - Sets the browser time zone around each request when provided via cookies.
|
19
|
+
# - Exposes `mat_views_data_theme` (light/dark) for theming via cookies.
|
20
|
+
# - Provides frame helpers (`render_frame`, `ensure_frame`) to support Turbo-driven
|
21
|
+
# admin UI navigation.
|
22
|
+
#
|
23
|
+
# Filters:
|
24
|
+
# - `before_action`: sets locale and redirects to enforce `lang` consistency.
|
25
|
+
# - `around_action`: wraps requests in the browser’s time zone if valid.
|
26
|
+
#
|
27
|
+
# Methods:
|
28
|
+
# - {#default_url_options} ensures `lang` param is included in generated URLs.
|
29
|
+
# - {#set_time_zone} runs the request in the cookie-provided time zone if valid.
|
30
|
+
# - {#render_frame} renders a UI frame partial given `frame_id`.
|
31
|
+
# - {#ensure_frame} requires a `frame_id` param for frame-only actions.
|
32
|
+
# - {#redirect_to_lang} redirects when the URL `lang` param differs from `I18n.locale`.
|
33
|
+
# - {#set_mat_views_locale} sets the session-defined or default locale.
|
34
|
+
# - {#mat_views_data_theme} returns `light`, `dark`, or `nil` for theming.
|
35
|
+
#
|
36
|
+
class ApplicationController < ActionController::Base
|
37
|
+
include MatViews::Admin::AuthBridge
|
38
|
+
|
39
|
+
helper MatViews::Admin::UiHelper
|
40
|
+
helper MatViews::Helpers::UiTestIds
|
41
|
+
layout 'mat_views/admin'
|
42
|
+
|
43
|
+
before_action :set_mat_views_locale, :redirect_to_lang
|
44
|
+
helper_method :mat_views_data_theme
|
45
|
+
around_action :set_time_zone
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Default URL options, ensuring `lang` is always included.
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
#
|
53
|
+
# @return [Hash{Symbol => String}]
|
54
|
+
def default_url_options
|
55
|
+
{ lang: params[:lang].presence || I18n.locale }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Wraps the request in the browser’s time zone if one is set in cookies.
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
#
|
62
|
+
# @yield the block representing the request lifecycle
|
63
|
+
# @return [void]
|
64
|
+
def set_time_zone(&)
|
65
|
+
browser_tz = cookies[:browser_tz]
|
66
|
+
if browser_tz.present? && ActiveSupport::TimeZone[browser_tz]
|
67
|
+
Time.use_zone(browser_tz, &)
|
68
|
+
else
|
69
|
+
yield
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Ensures a `frame_id` param is present.
|
74
|
+
# If missing, redirects to the admin root with an alert.
|
75
|
+
#
|
76
|
+
# @api private
|
77
|
+
#
|
78
|
+
# @return [void]
|
79
|
+
def ensure_frame
|
80
|
+
@frame_id = params[:frame_id]
|
81
|
+
if @frame_id.present?
|
82
|
+
@frame_action = params[:frame_action]
|
83
|
+
return
|
84
|
+
end
|
85
|
+
redirect_to admin_root_path, alert: I18n.t('mat_views.errors.frame_only')
|
86
|
+
end
|
87
|
+
|
88
|
+
# Redirects to enforce that the `lang` param matches `I18n.locale`.
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
#
|
92
|
+
# @return [void]
|
93
|
+
def redirect_to_lang
|
94
|
+
locale_str = locale.to_s
|
95
|
+
return if params[:lang] == locale_str
|
96
|
+
|
97
|
+
lang = locale_str
|
98
|
+
redirect_to url_for(params.permit!.to_h.merge(lang: lang)), status: :see_other
|
99
|
+
end
|
100
|
+
|
101
|
+
# Sets the locale for MatViews admin requests.
|
102
|
+
# Falls back to default locale if session value is invalid.
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
#
|
106
|
+
# @return [void]
|
107
|
+
def set_mat_views_locale
|
108
|
+
I18n.locale = if (loc = session[:mat_views_locale]).present? && MatViews::Engine.available_locales.map(&:to_s).include?(loc)
|
109
|
+
loc
|
110
|
+
else
|
111
|
+
MatViews::Engine.default_locale
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns the current theme for the admin UI.
|
116
|
+
#
|
117
|
+
# @api private
|
118
|
+
#
|
119
|
+
# @return ["light", "dark", nil] the theme stored in cookies, or `nil` if invalid
|
120
|
+
def mat_views_data_theme
|
121
|
+
theme = cookies[:theme].to_s
|
122
|
+
%w[light dark].include?(theme) ? theme : nil
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the current locale.
|
126
|
+
#
|
127
|
+
# @api private
|
128
|
+
#
|
129
|
+
# @return [Symbol] the current I18n locale
|
130
|
+
def locale
|
131
|
+
I18n.locale
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright Codevedas Inc. 2025-present
|
4
|
+
#
|
5
|
+
# This source code is licensed under the MIT license found in the
|
6
|
+
# LICENSE file in the root directory of this source tree.
|
7
|
+
|
8
|
+
module MatViews
|
9
|
+
module Admin
|
10
|
+
# MatViews::Admin::DashboardController
|
11
|
+
# ------------------------------------
|
12
|
+
# Controller for the MatViews admin dashboard.
|
13
|
+
#
|
14
|
+
# Responsibilities:
|
15
|
+
# - Provides the landing page (`index`) for the admin interface.
|
16
|
+
# - Authorizes access via {ApplicationController#authorize_mat_views!}.
|
17
|
+
# - Prepares placeholder metrics content (future: aggregated refresh metrics).
|
18
|
+
#
|
19
|
+
class DashboardController < ApplicationController
|
20
|
+
# GET /:lang/admin
|
21
|
+
#
|
22
|
+
# Renders the admin dashboard. Currently sets a placeholder message
|
23
|
+
# until metric aggregation is implemented.
|
24
|
+
#
|
25
|
+
# @return [void]
|
26
|
+
def index
|
27
|
+
authorize_mat_views!(:read, :mat_views_dashboard)
|
28
|
+
@metrics_note = 'Metrics coming soon (see: Aggregate refresh metrics for reporting).'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|