pg_reports 0.8.2 → 0.9.1
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/CHANGELOG.md +48 -0
- data/app/controllers/pg_reports/dashboard_controller.rb +104 -6
- data/app/views/layouts/pg_reports/application.html.erb +712 -214
- data/app/views/pg_reports/dashboard/_database_selector.html.erb +2 -1
- data/app/views/pg_reports/dashboard/_settings_modal.html.erb +55 -0
- data/app/views/pg_reports/dashboard/_show_modals.html.erb +11 -49
- data/app/views/pg_reports/dashboard/_show_scripts.html.erb +101 -90
- data/app/views/pg_reports/dashboard/_show_styles.html.erb +392 -476
- data/app/views/pg_reports/dashboard/index.html.erb +217 -493
- data/app/views/pg_reports/dashboard/show.html.erb +13 -11
- data/config/brakeman.ignore +24 -1
- data/config/locales/en.yml +58 -50
- data/config/locales/ru.yml +58 -50
- data/config/locales/uk.yml +58 -50
- data/config/routes.rb +1 -0
- data/lib/pg_reports/connection/error_translator.rb +43 -0
- data/lib/pg_reports/dashboard/reports_registry.rb +10 -0
- data/lib/pg_reports/explain_analyzer.rb +0 -3
- data/lib/pg_reports/query_monitor.rb +15 -3
- data/lib/pg_reports/version.rb +1 -1
- metadata +2 -1
|
@@ -5,30 +5,69 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<meta name="pg-reports-root" content="<%= request.script_name.presence || PgReports::Engine.routes.url_helpers.root_path %>">
|
|
7
7
|
<title><%= t("pg_reports.ui.branding.page_title") %></title>
|
|
8
|
-
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%235561d4'/%3E%3Crect x='5' y='18' width='5' height='9' rx='1' fill='%23fff'/%3E%3Crect x='13.5' y='12' width='5' height='15' rx='1' fill='%23fff'/%3E%3Crect x='22' y='6' width='5' height='21' rx='1' fill='%23fff'/%3E%3C/svg%3E">
|
|
9
9
|
<% if PgReports.config.load_external_fonts %>
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
12
|
-
<link href="https://fonts.googleapis.com/css2?family=
|
|
12
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
13
13
|
<% end %>
|
|
14
14
|
<style>
|
|
15
15
|
:root {
|
|
16
|
-
--bg-primary: #
|
|
17
|
-
--bg-secondary: #
|
|
18
|
-
--bg-tertiary: #
|
|
19
|
-
--bg-card: #
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
|
|
16
|
+
--bg-primary: #141618;
|
|
17
|
+
--bg-secondary: #1a1d20;
|
|
18
|
+
--bg-tertiary: #212427;
|
|
19
|
+
--bg-card: #1a1d20;
|
|
20
|
+
--bg-hover: #272b2f;
|
|
21
|
+
--border-color: #2c3034;
|
|
22
|
+
--border-strong: #3b4045;
|
|
23
|
+
|
|
24
|
+
--text-primary: #d3d6d9;
|
|
25
|
+
--text-secondary: #8d9299;
|
|
26
|
+
--text-muted: #666b71;
|
|
27
|
+
--text-tertiary: #666b71;
|
|
28
|
+
|
|
24
29
|
--accent-purple: #7c8af6;
|
|
25
30
|
--accent-blue: #5c9eed;
|
|
26
31
|
--accent-green: #45a87a;
|
|
27
32
|
--accent-amber: #c89030;
|
|
28
33
|
--accent-rose: #d45d6e;
|
|
29
|
-
--accent-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
--accent-red: #d45d6e;
|
|
35
|
+
|
|
36
|
+
/* Tints derived from the accents above. Every status surface uses these
|
|
37
|
+
two levels — fill and hairline — so tinted areas stay in one family. */
|
|
38
|
+
--purple-soft: rgba(124, 138, 246, 0.10);
|
|
39
|
+
--purple-line: rgba(124, 138, 246, 0.32);
|
|
40
|
+
--blue-soft: rgba(92, 158, 237, 0.10);
|
|
41
|
+
--blue-line: rgba(92, 158, 237, 0.32);
|
|
42
|
+
--green-soft: rgba(69, 168, 122, 0.10);
|
|
43
|
+
--green-line: rgba(69, 168, 122, 0.32);
|
|
44
|
+
--amber-soft: rgba(200, 144, 48, 0.10);
|
|
45
|
+
--amber-line: rgba(200, 144, 48, 0.34);
|
|
46
|
+
--rose-soft: rgba(212, 93, 110, 0.10);
|
|
47
|
+
--rose-line: rgba(212, 93, 110, 0.34);
|
|
48
|
+
|
|
49
|
+
--btn-primary-bg: #5561d4;
|
|
50
|
+
--btn-primary-bg-hover: #616de0;
|
|
51
|
+
--btn-primary-bg-active: #4a55c2;
|
|
52
|
+
--btn-danger-bg: #b64d5c;
|
|
53
|
+
--btn-danger-bg-hover: #c45a6a;
|
|
54
|
+
--btn-danger-bg-active: #a34351;
|
|
55
|
+
|
|
56
|
+
--flow-gap: 0.875rem;
|
|
57
|
+
--flow-gap-lg: 1.25rem;
|
|
58
|
+
|
|
59
|
+
--radius-sm: 4px;
|
|
60
|
+
--radius: 6px;
|
|
61
|
+
--radius-lg: 8px;
|
|
62
|
+
--control-h: 2.125rem;
|
|
63
|
+
--control-h-sm: 1.75rem;
|
|
64
|
+
|
|
65
|
+
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.3);
|
|
66
|
+
--shadow-2: 0 6px 20px rgba(0, 0, 0, 0.38);
|
|
67
|
+
--shadow-3: 0 12px 40px rgba(0, 0, 0, 0.45);
|
|
68
|
+
|
|
69
|
+
--font-sans: <%= PgReports.config.load_external_fonts ? "'Inter', " : "" %>-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
|
70
|
+
--font-mono: <%= PgReports.config.load_external_fonts ? "'JetBrains Mono', " : "" %>ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
32
71
|
}
|
|
33
72
|
|
|
34
73
|
* {
|
|
@@ -38,11 +77,28 @@
|
|
|
38
77
|
}
|
|
39
78
|
|
|
40
79
|
body {
|
|
41
|
-
font-family:
|
|
80
|
+
font-family: var(--font-sans);
|
|
42
81
|
background: var(--bg-primary);
|
|
43
82
|
color: var(--text-primary);
|
|
44
83
|
min-height: 100vh;
|
|
45
|
-
line-height: 1.
|
|
84
|
+
line-height: 1.5;
|
|
85
|
+
font-size: 0.875rem;
|
|
86
|
+
-webkit-font-smoothing: antialiased;
|
|
87
|
+
-moz-osx-font-smoothing: grayscale;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Numbers that refresh in place must not reflow as digits change. */
|
|
91
|
+
.results-table,
|
|
92
|
+
.query-results-table,
|
|
93
|
+
.metric-value,
|
|
94
|
+
.explain-stat-value {
|
|
95
|
+
font-variant-numeric: tabular-nums;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
:focus-visible {
|
|
99
|
+
outline: 2px solid var(--accent-blue);
|
|
100
|
+
outline-offset: 2px;
|
|
101
|
+
border-radius: var(--radius-sm);
|
|
46
102
|
}
|
|
47
103
|
|
|
48
104
|
.container {
|
|
@@ -62,89 +118,72 @@
|
|
|
62
118
|
}
|
|
63
119
|
|
|
64
120
|
.logo {
|
|
65
|
-
display: flex;
|
|
66
|
-
align-items: center;
|
|
67
|
-
gap: 0.75rem;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.logo-icon {
|
|
71
|
-
width: 32px;
|
|
72
|
-
height: 32px;
|
|
73
|
-
background: var(--accent-purple);
|
|
74
|
-
border-radius: 6px;
|
|
75
|
-
display: flex;
|
|
76
|
-
align-items: center;
|
|
77
|
-
justify-content: center;
|
|
78
|
-
font-size: 1rem;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.logo-text {
|
|
82
121
|
display: flex;
|
|
83
122
|
flex-direction: column;
|
|
84
|
-
gap:
|
|
85
|
-
|
|
123
|
+
gap: 0.125rem;
|
|
124
|
+
min-width: 0;
|
|
86
125
|
}
|
|
87
126
|
|
|
88
|
-
.logo
|
|
89
|
-
font-size: 1.1rem;
|
|
90
|
-
font-weight: 600;
|
|
91
|
-
color: var(--text-primary);
|
|
92
|
-
line-height: 1.15;
|
|
93
|
-
margin-top: -3px;
|
|
127
|
+
.logo h1 {
|
|
94
128
|
display: flex;
|
|
95
129
|
align-items: baseline;
|
|
96
130
|
gap: 0.5rem;
|
|
131
|
+
font-size: 1.0625rem;
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
letter-spacing: -0.01em;
|
|
134
|
+
color: var(--text-primary);
|
|
135
|
+
line-height: 1.2;
|
|
97
136
|
}
|
|
98
137
|
|
|
99
138
|
.logo-version {
|
|
100
|
-
font-
|
|
101
|
-
font-
|
|
139
|
+
font-family: var(--font-mono);
|
|
140
|
+
font-size: 0.6875rem;
|
|
141
|
+
font-weight: 400;
|
|
102
142
|
color: var(--text-muted);
|
|
103
|
-
letter-spacing: 0.02em;
|
|
104
143
|
}
|
|
105
144
|
|
|
106
145
|
.logo-subtitle {
|
|
107
|
-
font-size: 0.
|
|
146
|
+
font-size: 0.75rem;
|
|
108
147
|
color: var(--text-muted);
|
|
109
|
-
line-height: 1.
|
|
148
|
+
line-height: 1.2;
|
|
110
149
|
}
|
|
111
150
|
|
|
112
151
|
.header-badge {
|
|
113
|
-
display: flex;
|
|
152
|
+
display: inline-flex;
|
|
114
153
|
align-items: center;
|
|
115
154
|
gap: 0.4rem;
|
|
116
|
-
height:
|
|
155
|
+
height: var(--control-h);
|
|
117
156
|
padding: 0 0.75rem;
|
|
118
157
|
background: var(--bg-tertiary);
|
|
119
158
|
border: 1px solid var(--border-color);
|
|
120
|
-
border-radius:
|
|
159
|
+
border-radius: var(--radius-sm);
|
|
121
160
|
font-size: 0.75rem;
|
|
161
|
+
color: var(--text-secondary);
|
|
122
162
|
}
|
|
123
163
|
|
|
124
164
|
.header-badge-clickable {
|
|
125
165
|
cursor: pointer;
|
|
126
166
|
font-family: inherit;
|
|
127
|
-
|
|
128
|
-
transition: border-color 0.15s ease, background 0.15s ease;
|
|
167
|
+
transition: border-color 0.12s ease, background-color 0.12s ease;
|
|
129
168
|
}
|
|
130
169
|
|
|
131
170
|
.header-badge-clickable:hover {
|
|
132
|
-
background: var(--bg-
|
|
133
|
-
border-color: var(--
|
|
171
|
+
background: var(--bg-hover);
|
|
172
|
+
border-color: var(--border-strong);
|
|
173
|
+
color: var(--text-primary);
|
|
134
174
|
}
|
|
135
175
|
|
|
136
176
|
.badge-info-icon {
|
|
137
177
|
color: var(--text-muted);
|
|
138
|
-
|
|
139
|
-
line-height: 1;
|
|
178
|
+
line-height: 0;
|
|
140
179
|
}
|
|
141
180
|
|
|
142
181
|
.badge-dot {
|
|
143
|
-
width:
|
|
144
|
-
height:
|
|
182
|
+
width: 7px;
|
|
183
|
+
height: 7px;
|
|
184
|
+
flex: none;
|
|
145
185
|
border-radius: 50%;
|
|
146
186
|
background: var(--accent-green);
|
|
147
|
-
animation: pulse 2s infinite;
|
|
148
187
|
}
|
|
149
188
|
|
|
150
189
|
.badge-dot.warning {
|
|
@@ -153,13 +192,14 @@
|
|
|
153
192
|
|
|
154
193
|
@keyframes pulse {
|
|
155
194
|
0%, 100% { opacity: 1; }
|
|
156
|
-
50% { opacity: 0.
|
|
195
|
+
50% { opacity: 0.45; }
|
|
157
196
|
}
|
|
158
197
|
|
|
159
198
|
/* Categories Grid */
|
|
160
199
|
.categories-grid {
|
|
161
200
|
display: grid;
|
|
162
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
201
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
202
|
+
align-items: start;
|
|
163
203
|
gap: 1rem;
|
|
164
204
|
margin-bottom: 1.5rem;
|
|
165
205
|
}
|
|
@@ -167,12 +207,13 @@
|
|
|
167
207
|
.category-card {
|
|
168
208
|
background: var(--bg-card);
|
|
169
209
|
border: 1px solid var(--border-color);
|
|
170
|
-
border-radius:
|
|
210
|
+
border-radius: var(--radius);
|
|
171
211
|
padding: 1rem;
|
|
212
|
+
transition: border-color 0.12s ease;
|
|
172
213
|
}
|
|
173
214
|
|
|
174
215
|
.category-card:hover {
|
|
175
|
-
border-color:
|
|
216
|
+
border-color: var(--border-strong);
|
|
176
217
|
}
|
|
177
218
|
|
|
178
219
|
.category-header {
|
|
@@ -183,28 +224,25 @@
|
|
|
183
224
|
}
|
|
184
225
|
|
|
185
226
|
.category-icon {
|
|
186
|
-
width:
|
|
187
|
-
height:
|
|
188
|
-
|
|
189
|
-
display: flex;
|
|
190
|
-
align-items: center;
|
|
191
|
-
justify-content: center;
|
|
192
|
-
font-size: 0.95rem;
|
|
193
|
-
background: transparent !important;
|
|
194
|
-
color: var(--text-secondary) !important;
|
|
227
|
+
width: 1rem;
|
|
228
|
+
height: 1rem;
|
|
229
|
+
color: var(--text-secondary);
|
|
195
230
|
}
|
|
196
231
|
|
|
197
232
|
.category-title {
|
|
198
|
-
font-size:
|
|
233
|
+
font-size: 0.9375rem;
|
|
199
234
|
font-weight: 600;
|
|
235
|
+
letter-spacing: -0.01em;
|
|
200
236
|
}
|
|
201
237
|
|
|
202
238
|
.category-count {
|
|
203
239
|
margin-left: auto;
|
|
204
|
-
padding: 0.
|
|
240
|
+
padding: 0.15rem 0.45rem;
|
|
205
241
|
background: var(--bg-tertiary);
|
|
206
|
-
border
|
|
207
|
-
|
|
242
|
+
border: 1px solid var(--border-color);
|
|
243
|
+
border-radius: var(--radius-sm);
|
|
244
|
+
font-size: 0.6875rem;
|
|
245
|
+
font-variant-numeric: tabular-nums;
|
|
208
246
|
color: var(--text-muted);
|
|
209
247
|
}
|
|
210
248
|
|
|
@@ -217,15 +255,16 @@
|
|
|
217
255
|
.report-link {
|
|
218
256
|
display: flex;
|
|
219
257
|
align-items: center;
|
|
258
|
+
gap: 0.75rem;
|
|
220
259
|
justify-content: space-between;
|
|
221
|
-
padding: 0.
|
|
260
|
+
padding: 0.4rem 0.6rem;
|
|
222
261
|
background: transparent;
|
|
223
262
|
border: 1px solid transparent;
|
|
224
|
-
border-radius:
|
|
263
|
+
border-radius: var(--radius-sm);
|
|
225
264
|
color: var(--text-secondary);
|
|
226
265
|
text-decoration: none;
|
|
227
|
-
font-size: 0.
|
|
228
|
-
transition: background 0.1s, color 0.1s;
|
|
266
|
+
font-size: 0.8125rem;
|
|
267
|
+
transition: background-color 0.1s ease, color 0.1s ease;
|
|
229
268
|
}
|
|
230
269
|
|
|
231
270
|
.report-link:hover {
|
|
@@ -234,26 +273,29 @@
|
|
|
234
273
|
}
|
|
235
274
|
|
|
236
275
|
.report-link .arrow {
|
|
276
|
+
width: 0.8125rem;
|
|
277
|
+
height: 0.8125rem;
|
|
278
|
+
color: var(--text-muted);
|
|
237
279
|
opacity: 0;
|
|
238
|
-
|
|
239
|
-
transition: all 0.15s ease;
|
|
280
|
+
transition: opacity 0.12s ease;
|
|
240
281
|
}
|
|
241
282
|
|
|
242
|
-
.report-link:hover .arrow
|
|
283
|
+
.report-link:hover .arrow,
|
|
284
|
+
.report-link:focus-visible .arrow {
|
|
243
285
|
opacity: 1;
|
|
244
|
-
transform: translateX(0);
|
|
245
286
|
}
|
|
246
287
|
|
|
247
288
|
.report-badge-new {
|
|
248
289
|
display: inline-block;
|
|
249
290
|
margin-left: 0.4rem;
|
|
250
|
-
padding: 0.
|
|
251
|
-
font-size: 0.
|
|
291
|
+
padding: 0.05rem 0.3rem;
|
|
292
|
+
font-size: 0.5625rem;
|
|
252
293
|
font-weight: 600;
|
|
253
|
-
letter-spacing: 0.
|
|
254
|
-
line-height: 1.
|
|
294
|
+
letter-spacing: 0.06em;
|
|
295
|
+
line-height: 1.4;
|
|
255
296
|
color: var(--accent-green);
|
|
256
|
-
background:
|
|
297
|
+
background: var(--green-soft);
|
|
298
|
+
border: 1px solid var(--green-line);
|
|
257
299
|
border-radius: 3px;
|
|
258
300
|
vertical-align: middle;
|
|
259
301
|
}
|
|
@@ -269,6 +311,7 @@
|
|
|
269
311
|
display: flex;
|
|
270
312
|
align-items: center;
|
|
271
313
|
gap: 0.4rem;
|
|
314
|
+
min-height: var(--control-h);
|
|
272
315
|
font-size: 0.75rem;
|
|
273
316
|
color: var(--text-muted);
|
|
274
317
|
}
|
|
@@ -276,13 +319,41 @@
|
|
|
276
319
|
.breadcrumb a {
|
|
277
320
|
color: var(--text-secondary);
|
|
278
321
|
text-decoration: none;
|
|
279
|
-
transition: color 0.
|
|
322
|
+
transition: color 0.12s ease, background-color 0.12s ease;
|
|
280
323
|
}
|
|
281
324
|
|
|
282
325
|
.breadcrumb a:hover {
|
|
283
326
|
color: var(--text-primary);
|
|
284
327
|
}
|
|
285
328
|
|
|
329
|
+
/* First crumb doubles as the back action — the arrow makes the affordance
|
|
330
|
+
explicit without a second control duplicating it in the header. */
|
|
331
|
+
.breadcrumb-back {
|
|
332
|
+
display: inline-flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
gap: 0.35rem;
|
|
335
|
+
margin-left: -0.4rem;
|
|
336
|
+
padding: 0.2rem 0.45rem;
|
|
337
|
+
border-radius: var(--radius-sm);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.breadcrumb-back:hover {
|
|
341
|
+
background: var(--bg-tertiary);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.breadcrumb-back .icon {
|
|
345
|
+
width: 0.8125rem;
|
|
346
|
+
height: 0.8125rem;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.breadcrumb-sep {
|
|
350
|
+
color: var(--border-strong);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.breadcrumb-current {
|
|
354
|
+
color: var(--text-secondary);
|
|
355
|
+
}
|
|
356
|
+
|
|
286
357
|
.report-header {
|
|
287
358
|
display: flex;
|
|
288
359
|
align-items: flex-start;
|
|
@@ -306,62 +377,163 @@
|
|
|
306
377
|
gap: 0.5rem;
|
|
307
378
|
}
|
|
308
379
|
|
|
380
|
+
/* Buttons — one solid, classic control. Height, radius and weight come from
|
|
381
|
+
the tokens so every variant and every page lines up on the same grid. */
|
|
309
382
|
.btn {
|
|
310
383
|
display: inline-flex;
|
|
311
384
|
align-items: center;
|
|
312
385
|
justify-content: center;
|
|
313
386
|
gap: 0.4rem;
|
|
314
|
-
height:
|
|
315
|
-
padding: 0
|
|
387
|
+
height: var(--control-h);
|
|
388
|
+
padding: 0 0.875rem;
|
|
316
389
|
border: 1px solid transparent;
|
|
317
|
-
border-radius:
|
|
390
|
+
border-radius: var(--radius-sm);
|
|
318
391
|
font-family: inherit;
|
|
319
|
-
font-size: 0.
|
|
392
|
+
font-size: 0.8125rem;
|
|
320
393
|
font-weight: 500;
|
|
394
|
+
line-height: 1;
|
|
321
395
|
cursor: pointer;
|
|
322
|
-
|
|
396
|
+
user-select: none;
|
|
323
397
|
white-space: nowrap;
|
|
324
398
|
text-decoration: none;
|
|
399
|
+
transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
325
400
|
}
|
|
326
401
|
|
|
327
402
|
.btn-primary {
|
|
328
|
-
background:
|
|
329
|
-
color: var(--
|
|
330
|
-
|
|
403
|
+
background: var(--btn-primary-bg);
|
|
404
|
+
border-color: var(--btn-primary-bg);
|
|
405
|
+
color: #fff;
|
|
331
406
|
}
|
|
332
407
|
|
|
333
408
|
.btn-primary:hover {
|
|
334
|
-
background:
|
|
335
|
-
border-color:
|
|
409
|
+
background: var(--btn-primary-bg-hover);
|
|
410
|
+
border-color: var(--btn-primary-bg-hover);
|
|
336
411
|
}
|
|
337
412
|
|
|
338
|
-
.btn-
|
|
413
|
+
.btn-primary:active {
|
|
414
|
+
background: var(--btn-primary-bg-active);
|
|
415
|
+
border-color: var(--btn-primary-bg-active);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.btn-secondary,
|
|
419
|
+
.btn-telegram {
|
|
339
420
|
background: var(--bg-tertiary);
|
|
421
|
+
color: var(--text-primary);
|
|
422
|
+
border-color: var(--border-color);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.btn-secondary:hover,
|
|
426
|
+
.btn-telegram:hover {
|
|
427
|
+
background: var(--bg-hover);
|
|
428
|
+
border-color: var(--border-strong);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.btn-secondary:active,
|
|
432
|
+
.btn-telegram:active {
|
|
433
|
+
background: var(--bg-tertiary);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.btn-ghost {
|
|
437
|
+
background: transparent;
|
|
340
438
|
color: var(--text-secondary);
|
|
341
|
-
border:
|
|
439
|
+
border-color: transparent;
|
|
342
440
|
}
|
|
343
441
|
|
|
344
|
-
.btn-
|
|
345
|
-
background:
|
|
442
|
+
.btn-ghost:hover {
|
|
443
|
+
background: var(--bg-tertiary);
|
|
346
444
|
color: var(--text-primary);
|
|
347
445
|
}
|
|
348
446
|
|
|
349
|
-
.btn-
|
|
447
|
+
.btn-danger {
|
|
448
|
+
background: var(--btn-danger-bg);
|
|
449
|
+
border-color: var(--btn-danger-bg);
|
|
450
|
+
color: #fff;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.btn-danger:hover {
|
|
454
|
+
background: var(--btn-danger-bg-hover);
|
|
455
|
+
border-color: var(--btn-danger-bg-hover);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.btn-danger:active {
|
|
459
|
+
background: var(--btn-danger-bg-active);
|
|
460
|
+
border-color: var(--btn-danger-bg-active);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* Destructive, but not the page's main action: neutral until hovered. */
|
|
464
|
+
.btn-muted {
|
|
350
465
|
background: var(--bg-tertiary);
|
|
466
|
+
border-color: var(--border-color);
|
|
351
467
|
color: var(--text-secondary);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.btn-muted:hover {
|
|
471
|
+
background: var(--rose-soft);
|
|
472
|
+
border-color: var(--rose-line);
|
|
473
|
+
color: var(--accent-rose);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.btn-small {
|
|
477
|
+
height: var(--control-h-sm);
|
|
478
|
+
padding: 0 0.625rem;
|
|
479
|
+
font-size: 0.75rem;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Square control sized to match .btn — for a lone icon with no label. */
|
|
483
|
+
.btn-icon {
|
|
484
|
+
width: var(--control-h);
|
|
485
|
+
height: var(--control-h);
|
|
486
|
+
padding: 0;
|
|
487
|
+
flex: none;
|
|
488
|
+
display: inline-flex;
|
|
489
|
+
align-items: center;
|
|
490
|
+
justify-content: center;
|
|
491
|
+
background: var(--bg-tertiary);
|
|
352
492
|
border: 1px solid var(--border-color);
|
|
493
|
+
border-radius: var(--radius-sm);
|
|
494
|
+
color: var(--text-secondary);
|
|
495
|
+
cursor: pointer;
|
|
496
|
+
transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
353
497
|
}
|
|
354
498
|
|
|
355
|
-
.btn-
|
|
356
|
-
background:
|
|
499
|
+
.btn-icon:hover {
|
|
500
|
+
background: var(--bg-hover);
|
|
501
|
+
border-color: var(--border-strong);
|
|
357
502
|
color: var(--text-primary);
|
|
358
503
|
}
|
|
359
504
|
|
|
360
|
-
.btn
|
|
361
|
-
|
|
505
|
+
.btn-small.btn-icon {
|
|
506
|
+
width: var(--control-h-sm);
|
|
507
|
+
height: var(--control-h-sm);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.btn:disabled,
|
|
511
|
+
.btn-icon:disabled {
|
|
512
|
+
opacity: 0.45;
|
|
362
513
|
cursor: not-allowed;
|
|
363
514
|
}
|
|
364
515
|
|
|
516
|
+
.btn:disabled:hover,
|
|
517
|
+
.btn-icon:disabled:hover {
|
|
518
|
+
background: inherit;
|
|
519
|
+
border-color: inherit;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* Icons — stroke-drawn, inherit the parent's colour and optical size. */
|
|
523
|
+
.icon {
|
|
524
|
+
width: 1em;
|
|
525
|
+
height: 1em;
|
|
526
|
+
flex: none;
|
|
527
|
+
fill: none;
|
|
528
|
+
stroke: currentColor;
|
|
529
|
+
stroke-width: 1.75;
|
|
530
|
+
stroke-linecap: round;
|
|
531
|
+
stroke-linejoin: round;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.icon-lg { width: 1.125rem; height: 1.125rem; }
|
|
535
|
+
.icon-sm { width: 0.875rem; height: 0.875rem; }
|
|
536
|
+
|
|
365
537
|
/* Results Table */
|
|
366
538
|
.results-container {
|
|
367
539
|
background: var(--bg-card);
|
|
@@ -468,63 +640,177 @@
|
|
|
468
640
|
}
|
|
469
641
|
|
|
470
642
|
/* Error state */
|
|
643
|
+
.error-title {
|
|
644
|
+
font-weight: 600;
|
|
645
|
+
color: var(--accent-rose);
|
|
646
|
+
}
|
|
647
|
+
|
|
471
648
|
.error-message {
|
|
472
|
-
padding: 0.875rem;
|
|
473
|
-
background:
|
|
474
|
-
border: 1px solid
|
|
475
|
-
border-radius:
|
|
649
|
+
padding: 0.875rem 1rem;
|
|
650
|
+
background: var(--rose-soft);
|
|
651
|
+
border: 1px solid var(--rose-line);
|
|
652
|
+
border-radius: var(--radius);
|
|
476
653
|
color: var(--accent-rose);
|
|
477
|
-
font-size: 0.
|
|
654
|
+
font-size: 0.8125rem;
|
|
655
|
+
line-height: 1.6;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.error-message .code-block {
|
|
659
|
+
color: var(--text-primary);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/* Vertical rhythm. The global `* { margin: 0 }` reset leaves prose
|
|
663
|
+
containers with no spacing of their own, and per-element margins drifted
|
|
664
|
+
into asymmetric gaps (1rem above a code block, 0 below). One rule per
|
|
665
|
+
container spaces any two siblings identically, whatever their tags. */
|
|
666
|
+
.modal-body > * + *,
|
|
667
|
+
.modal-body li > * + *,
|
|
668
|
+
.error-message > * + *,
|
|
669
|
+
.problem-modal-body > * + *,
|
|
670
|
+
.doc-block > * + *,
|
|
671
|
+
.explain-query-section > * + *,
|
|
672
|
+
.explain-params-section > * + *,
|
|
673
|
+
#explain-content > * + *,
|
|
674
|
+
#run-query-content > * + *,
|
|
675
|
+
.callout-body > * + * {
|
|
676
|
+
margin-top: var(--flow-gap);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.modal-body > ol > li + li {
|
|
680
|
+
margin-top: var(--flow-gap-lg);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.modal-body > .modal-actions,
|
|
684
|
+
.modal-body > .migration-actions {
|
|
685
|
+
margin-top: var(--flow-gap-lg);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/* Code block — one definition for every fenced snippet in the UI. */
|
|
689
|
+
.code-block,
|
|
690
|
+
.modal-body pre {
|
|
691
|
+
display: block;
|
|
692
|
+
padding: 0.6875rem 0.875rem;
|
|
693
|
+
background: var(--bg-primary);
|
|
694
|
+
border: 1px solid var(--border-color);
|
|
695
|
+
border-radius: var(--radius-sm);
|
|
696
|
+
font-family: var(--font-mono);
|
|
697
|
+
font-size: 0.78rem;
|
|
698
|
+
line-height: 1.6;
|
|
699
|
+
color: var(--text-primary);
|
|
700
|
+
white-space: pre;
|
|
701
|
+
overflow-x: auto;
|
|
702
|
+
tab-size: 2;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.code-block-wrap {
|
|
706
|
+
white-space: pre-wrap;
|
|
707
|
+
word-break: break-word;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/* Callout — an inline notice inside a panel or modal. */
|
|
711
|
+
.callout {
|
|
712
|
+
display: flex;
|
|
713
|
+
align-items: flex-start;
|
|
714
|
+
gap: 0.5rem;
|
|
715
|
+
padding: 0.75rem 0.875rem;
|
|
716
|
+
border: 1px solid var(--border-color);
|
|
717
|
+
border-radius: var(--radius-sm);
|
|
718
|
+
margin-bottom: 1rem;
|
|
719
|
+
font-size: 0.8125rem;
|
|
720
|
+
line-height: 1.6;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
.callout .icon {
|
|
724
|
+
margin-top: 0.22rem;
|
|
725
|
+
width: 0.875rem;
|
|
726
|
+
height: 0.875rem;
|
|
478
727
|
}
|
|
479
728
|
|
|
480
|
-
|
|
729
|
+
.callout p {
|
|
730
|
+
margin: 0.25rem 0 0;
|
|
731
|
+
color: var(--text-secondary);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.callout-warning {
|
|
735
|
+
background: var(--amber-soft);
|
|
736
|
+
border-color: var(--amber-line);
|
|
737
|
+
color: var(--accent-amber);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.callout-danger {
|
|
741
|
+
background: var(--rose-soft);
|
|
742
|
+
border-color: var(--rose-line);
|
|
743
|
+
color: var(--accent-rose);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/* Page-level flash sits above the header, like .db-error-banner. */
|
|
747
|
+
.page-notice {
|
|
748
|
+
margin-bottom: 1.25rem;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/* Toast notification — one definition for every page. */
|
|
481
752
|
.toast {
|
|
482
753
|
position: fixed;
|
|
483
754
|
bottom: 1.5rem;
|
|
484
755
|
right: 1.5rem;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
border
|
|
756
|
+
max-width: 26rem;
|
|
757
|
+
padding: 0.625rem 0.875rem;
|
|
758
|
+
background: var(--bg-tertiary);
|
|
759
|
+
border: 1px solid var(--border-strong);
|
|
760
|
+
border-radius: var(--radius);
|
|
489
761
|
color: var(--text-primary);
|
|
490
|
-
font-size: 0.
|
|
491
|
-
box-shadow:
|
|
492
|
-
transform: translateY(
|
|
762
|
+
font-size: 0.8125rem;
|
|
763
|
+
box-shadow: var(--shadow-2);
|
|
764
|
+
transform: translateY(0.5rem);
|
|
493
765
|
opacity: 0;
|
|
494
|
-
|
|
495
|
-
|
|
766
|
+
visibility: hidden;
|
|
767
|
+
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
|
|
768
|
+
z-index: 2000;
|
|
496
769
|
}
|
|
497
770
|
|
|
498
771
|
.toast.show {
|
|
499
772
|
transform: translateY(0);
|
|
500
773
|
opacity: 1;
|
|
774
|
+
visibility: visible;
|
|
501
775
|
}
|
|
502
776
|
|
|
503
|
-
.toast.success
|
|
504
|
-
|
|
777
|
+
.toast.success,
|
|
778
|
+
.toast.toast-success {
|
|
779
|
+
border-color: var(--green-line);
|
|
780
|
+
background: var(--green-soft);
|
|
505
781
|
}
|
|
506
782
|
|
|
507
|
-
.toast.error
|
|
508
|
-
|
|
783
|
+
.toast.error,
|
|
784
|
+
.toast.toast-error {
|
|
785
|
+
border-color: var(--rose-line);
|
|
786
|
+
background: var(--rose-soft);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.toast.warning,
|
|
790
|
+
.toast.toast-warning {
|
|
791
|
+
border-color: var(--amber-line);
|
|
792
|
+
background: var(--amber-soft);
|
|
509
793
|
}
|
|
510
794
|
|
|
511
795
|
/* Modal */
|
|
512
796
|
.modal {
|
|
513
797
|
position: fixed;
|
|
514
798
|
inset: 0;
|
|
515
|
-
background: rgba(
|
|
799
|
+
background: rgba(10, 11, 12, 0.72);
|
|
516
800
|
display: flex;
|
|
517
801
|
align-items: center;
|
|
518
802
|
justify-content: center;
|
|
803
|
+
padding: 1.5rem;
|
|
519
804
|
z-index: 1000;
|
|
520
805
|
}
|
|
521
806
|
|
|
522
807
|
.modal-content {
|
|
523
808
|
background: var(--bg-card);
|
|
524
|
-
border: 1px solid var(--border-
|
|
525
|
-
border-radius:
|
|
809
|
+
border: 1px solid var(--border-strong);
|
|
810
|
+
border-radius: var(--radius);
|
|
811
|
+
box-shadow: var(--shadow-3);
|
|
526
812
|
max-width: 900px;
|
|
527
|
-
width:
|
|
813
|
+
width: 100%;
|
|
528
814
|
max-height: 90vh;
|
|
529
815
|
overflow: auto;
|
|
530
816
|
scrollbar-width: thin;
|
|
@@ -578,21 +864,25 @@
|
|
|
578
864
|
}
|
|
579
865
|
|
|
580
866
|
.modal-close {
|
|
581
|
-
width:
|
|
582
|
-
height:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
867
|
+
width: 1.75rem;
|
|
868
|
+
height: 1.75rem;
|
|
869
|
+
display: inline-flex;
|
|
870
|
+
align-items: center;
|
|
871
|
+
justify-content: center;
|
|
872
|
+
background: transparent;
|
|
873
|
+
border: 1px solid transparent;
|
|
874
|
+
border-radius: var(--radius-sm);
|
|
586
875
|
color: var(--text-muted);
|
|
587
876
|
font-size: 1.125rem;
|
|
877
|
+
line-height: 1;
|
|
588
878
|
cursor: pointer;
|
|
589
|
-
transition:
|
|
879
|
+
transition: background-color 0.12s ease, color 0.12s ease;
|
|
590
880
|
}
|
|
591
881
|
|
|
592
882
|
.modal-close:hover {
|
|
593
|
-
background: var(--
|
|
594
|
-
border-color: var(--
|
|
595
|
-
color:
|
|
883
|
+
background: var(--bg-tertiary);
|
|
884
|
+
border-color: var(--border-color);
|
|
885
|
+
color: var(--text-primary);
|
|
596
886
|
}
|
|
597
887
|
|
|
598
888
|
.modal-body {
|
|
@@ -611,18 +901,66 @@
|
|
|
611
901
|
align-items: flex-start;
|
|
612
902
|
}
|
|
613
903
|
|
|
904
|
+
.header-actions {
|
|
905
|
+
width: 100%;
|
|
906
|
+
flex-wrap: wrap;
|
|
907
|
+
}
|
|
908
|
+
|
|
614
909
|
.report-header {
|
|
615
910
|
flex-direction: column;
|
|
911
|
+
gap: 0.75rem;
|
|
616
912
|
}
|
|
617
913
|
|
|
618
914
|
.report-actions {
|
|
619
915
|
width: 100%;
|
|
916
|
+
flex-wrap: wrap;
|
|
620
917
|
}
|
|
621
918
|
|
|
622
|
-
|
|
919
|
+
/* Only action bars stretch their buttons — elsewhere a stretched button
|
|
920
|
+
overruns the label sitting next to it. */
|
|
921
|
+
.header-actions .btn,
|
|
922
|
+
.report-actions .btn,
|
|
923
|
+
.modal-actions .btn {
|
|
623
924
|
flex: 1;
|
|
624
925
|
justify-content: center;
|
|
625
926
|
}
|
|
927
|
+
|
|
928
|
+
/* The selectors take a line of their own instead of squeezing the trail. */
|
|
929
|
+
.breadcrumb {
|
|
930
|
+
flex-wrap: wrap;
|
|
931
|
+
row-gap: 0.5rem;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
.breadcrumb-spacer {
|
|
935
|
+
flex-basis: 100%;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.results-header {
|
|
939
|
+
flex-direction: column;
|
|
940
|
+
align-items: flex-start;
|
|
941
|
+
gap: 0.5rem;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
.results-meta {
|
|
945
|
+
flex-wrap: wrap;
|
|
946
|
+
gap: 0.25rem 0.75rem;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
.modal {
|
|
950
|
+
padding: 0.75rem;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
.modal-large {
|
|
954
|
+
width: 100%;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/* Placed by `right`, a toast wider than the screen hangs off the left. */
|
|
958
|
+
.toast {
|
|
959
|
+
left: 0.875rem;
|
|
960
|
+
right: 0.875rem;
|
|
961
|
+
bottom: 0.875rem;
|
|
962
|
+
max-width: none;
|
|
963
|
+
}
|
|
626
964
|
}
|
|
627
965
|
|
|
628
966
|
/* ============================================
|
|
@@ -677,7 +1015,7 @@
|
|
|
677
1015
|
color: var(--text-secondary);
|
|
678
1016
|
border: 1px solid var(--border-color);
|
|
679
1017
|
border-radius: 6px;
|
|
680
|
-
box-shadow:
|
|
1018
|
+
box-shadow: var(--shadow-2);
|
|
681
1019
|
font-size: 0.75rem;
|
|
682
1020
|
font-weight: 400;
|
|
683
1021
|
line-height: 1.5;
|
|
@@ -797,7 +1135,7 @@
|
|
|
797
1135
|
}
|
|
798
1136
|
|
|
799
1137
|
.query-item:hover {
|
|
800
|
-
border-color: var(--
|
|
1138
|
+
border-color: var(--border-strong);
|
|
801
1139
|
}
|
|
802
1140
|
|
|
803
1141
|
.query-item:last-child {
|
|
@@ -917,7 +1255,7 @@
|
|
|
917
1255
|
background: var(--bg-card);
|
|
918
1256
|
border: 1px solid var(--border-color);
|
|
919
1257
|
border-radius: 6px;
|
|
920
|
-
box-shadow:
|
|
1258
|
+
box-shadow: var(--shadow-2);
|
|
921
1259
|
z-index: 1000;
|
|
922
1260
|
min-width: 160px;
|
|
923
1261
|
overflow: hidden;
|
|
@@ -961,7 +1299,7 @@
|
|
|
961
1299
|
background: var(--bg-card);
|
|
962
1300
|
border: 1px solid var(--border-color);
|
|
963
1301
|
border-radius: 6px;
|
|
964
|
-
box-shadow:
|
|
1302
|
+
box-shadow: var(--shadow-2);
|
|
965
1303
|
z-index: 100;
|
|
966
1304
|
overflow: hidden;
|
|
967
1305
|
}
|
|
@@ -985,24 +1323,6 @@
|
|
|
985
1323
|
color: var(--text-primary);
|
|
986
1324
|
}
|
|
987
1325
|
|
|
988
|
-
.btn-small {
|
|
989
|
-
height: 30px;
|
|
990
|
-
padding: 0 0.875rem;
|
|
991
|
-
font-size: 0.75rem;
|
|
992
|
-
line-height: 1;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
.btn-danger {
|
|
996
|
-
background: var(--accent-rose);
|
|
997
|
-
color: white;
|
|
998
|
-
border-color: var(--accent-rose);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
.btn-danger:hover {
|
|
1002
|
-
opacity: 0.9;
|
|
1003
|
-
transform: translateY(-1px);
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
1326
|
/* IDE Settings Modal */
|
|
1007
1327
|
.settings-label {
|
|
1008
1328
|
color: var(--text-secondary);
|
|
@@ -1010,44 +1330,57 @@
|
|
|
1010
1330
|
font-size: 0.9rem;
|
|
1011
1331
|
}
|
|
1012
1332
|
|
|
1013
|
-
.
|
|
1333
|
+
/* Settings modal sections. The option rows are shared by every section —
|
|
1334
|
+
language, IDE, and whatever is added next. */
|
|
1335
|
+
.settings-section-title {
|
|
1336
|
+
font-size: 0.6875rem;
|
|
1337
|
+
font-weight: 600;
|
|
1338
|
+
line-height: 1.4;
|
|
1339
|
+
color: var(--text-muted);
|
|
1340
|
+
text-transform: uppercase;
|
|
1341
|
+
letter-spacing: 0.06em;
|
|
1342
|
+
margin-bottom: 0.5rem;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
.settings-options {
|
|
1014
1346
|
display: flex;
|
|
1015
1347
|
flex-direction: column;
|
|
1016
|
-
gap: 0.
|
|
1348
|
+
gap: 0.375rem;
|
|
1017
1349
|
}
|
|
1018
1350
|
|
|
1019
|
-
.
|
|
1351
|
+
.settings-option {
|
|
1020
1352
|
display: flex;
|
|
1021
1353
|
align-items: center;
|
|
1022
|
-
gap: 0.
|
|
1023
|
-
padding: 0.75rem
|
|
1354
|
+
gap: 0.625rem;
|
|
1355
|
+
padding: 0.5rem 0.75rem;
|
|
1024
1356
|
background: var(--bg-tertiary);
|
|
1025
1357
|
border: 1px solid var(--border-color);
|
|
1026
|
-
border-radius:
|
|
1358
|
+
border-radius: var(--radius-sm);
|
|
1027
1359
|
cursor: pointer;
|
|
1028
|
-
transition:
|
|
1360
|
+
transition: background-color 0.12s ease, border-color 0.12s ease;
|
|
1029
1361
|
}
|
|
1030
1362
|
|
|
1031
|
-
.
|
|
1032
|
-
background: var(--bg-
|
|
1033
|
-
border-color: var(--
|
|
1363
|
+
.settings-option:hover {
|
|
1364
|
+
background: var(--bg-hover);
|
|
1365
|
+
border-color: var(--border-strong);
|
|
1034
1366
|
}
|
|
1035
1367
|
|
|
1036
|
-
.
|
|
1037
|
-
background:
|
|
1038
|
-
border-color: var(--
|
|
1368
|
+
.settings-option:has(input:checked) {
|
|
1369
|
+
background: var(--purple-soft);
|
|
1370
|
+
border-color: var(--purple-line);
|
|
1039
1371
|
}
|
|
1040
1372
|
|
|
1041
|
-
.
|
|
1373
|
+
.settings-option input[type="radio"] {
|
|
1042
1374
|
accent-color: var(--accent-purple);
|
|
1375
|
+
flex: none;
|
|
1043
1376
|
}
|
|
1044
1377
|
|
|
1045
|
-
.
|
|
1378
|
+
.settings-option span {
|
|
1046
1379
|
color: var(--text-secondary);
|
|
1047
|
-
font-size: 0.
|
|
1380
|
+
font-size: 0.8125rem;
|
|
1048
1381
|
}
|
|
1049
1382
|
|
|
1050
|
-
.
|
|
1383
|
+
.settings-option:has(input:checked) span {
|
|
1051
1384
|
color: var(--text-primary);
|
|
1052
1385
|
}
|
|
1053
1386
|
|
|
@@ -1081,12 +1414,14 @@
|
|
|
1081
1414
|
}
|
|
1082
1415
|
|
|
1083
1416
|
.db-selector {
|
|
1084
|
-
background: var(--bg-
|
|
1417
|
+
background: var(--bg-tertiary);
|
|
1085
1418
|
color: var(--text-primary);
|
|
1086
|
-
border: 1px solid var(--border-color
|
|
1087
|
-
border-radius:
|
|
1088
|
-
|
|
1089
|
-
|
|
1419
|
+
border: 1px solid var(--border-color);
|
|
1420
|
+
border-radius: var(--radius-sm);
|
|
1421
|
+
height: var(--control-h-sm);
|
|
1422
|
+
padding: 0 0.5rem;
|
|
1423
|
+
font-family: inherit;
|
|
1424
|
+
font-size: 0.8125rem;
|
|
1090
1425
|
cursor: pointer;
|
|
1091
1426
|
width: auto;
|
|
1092
1427
|
max-width: 100%;
|
|
@@ -1098,13 +1433,12 @@
|
|
|
1098
1433
|
}
|
|
1099
1434
|
|
|
1100
1435
|
.db-selector:hover {
|
|
1101
|
-
border-color: var(--
|
|
1436
|
+
border-color: var(--border-strong);
|
|
1102
1437
|
}
|
|
1103
1438
|
|
|
1104
|
-
.db-selector:focus {
|
|
1105
|
-
outline:
|
|
1106
|
-
|
|
1107
|
-
box-shadow: 0 0 0 2px rgba(107, 159, 232, 0.2);
|
|
1439
|
+
.db-selector:focus-visible {
|
|
1440
|
+
outline: 2px solid var(--accent-blue);
|
|
1441
|
+
outline-offset: 1px;
|
|
1108
1442
|
}
|
|
1109
1443
|
|
|
1110
1444
|
.db-selector-static {
|
|
@@ -1124,11 +1458,11 @@
|
|
|
1124
1458
|
align-items: center;
|
|
1125
1459
|
gap: 0.375rem;
|
|
1126
1460
|
padding: 0.375rem 0.625rem;
|
|
1127
|
-
background:
|
|
1128
|
-
border: 1px solid
|
|
1129
|
-
border-radius:
|
|
1461
|
+
background: var(--rose-soft);
|
|
1462
|
+
border: 1px solid var(--rose-line);
|
|
1463
|
+
border-radius: var(--radius-sm);
|
|
1130
1464
|
font-size: 0.8125rem;
|
|
1131
|
-
color: var(--accent-red
|
|
1465
|
+
color: var(--accent-red);
|
|
1132
1466
|
cursor: help;
|
|
1133
1467
|
}
|
|
1134
1468
|
|
|
@@ -1136,47 +1470,123 @@
|
|
|
1136
1470
|
flex: 1;
|
|
1137
1471
|
}
|
|
1138
1472
|
|
|
1139
|
-
.breadcrumb {
|
|
1140
|
-
display: flex;
|
|
1141
|
-
align-items: center;
|
|
1142
|
-
gap: 0.5rem;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
1473
|
.db-error-banner {
|
|
1146
|
-
background:
|
|
1147
|
-
border: 1px solid
|
|
1148
|
-
border-radius:
|
|
1149
|
-
padding: 0.875rem
|
|
1474
|
+
background: var(--rose-soft);
|
|
1475
|
+
border: 1px solid var(--rose-line);
|
|
1476
|
+
border-radius: var(--radius);
|
|
1477
|
+
padding: 0.875rem 1rem;
|
|
1150
1478
|
margin-bottom: 1rem;
|
|
1151
1479
|
display: flex;
|
|
1152
1480
|
flex-direction: column;
|
|
1153
|
-
|
|
1481
|
+
align-items: flex-start;
|
|
1482
|
+
gap: var(--flow-gap);
|
|
1154
1483
|
}
|
|
1155
1484
|
|
|
1156
1485
|
.db-error-banner strong {
|
|
1157
|
-
color: var(--accent-red
|
|
1158
|
-
font-
|
|
1486
|
+
color: var(--accent-red);
|
|
1487
|
+
font-weight: 600;
|
|
1159
1488
|
}
|
|
1160
1489
|
|
|
1161
1490
|
.db-error-banner span {
|
|
1162
|
-
font-size: 0.875rem;
|
|
1163
1491
|
color: var(--text-secondary);
|
|
1164
1492
|
}
|
|
1165
1493
|
|
|
1166
1494
|
.db-error-banner code {
|
|
1167
|
-
font-family:
|
|
1168
|
-
font-size: 0.
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
border
|
|
1495
|
+
font-family: var(--font-mono);
|
|
1496
|
+
font-size: 0.78rem;
|
|
1497
|
+
line-height: 1.6;
|
|
1498
|
+
background: var(--bg-primary);
|
|
1499
|
+
border: 1px solid var(--border-color);
|
|
1500
|
+
padding: 0.5rem 0.75rem;
|
|
1501
|
+
border-radius: var(--radius-sm);
|
|
1172
1502
|
color: var(--text-primary);
|
|
1173
|
-
|
|
1174
|
-
|
|
1503
|
+
max-width: 100%;
|
|
1504
|
+
overflow-x: auto;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/* Responsive, for the panels defined above this point. */
|
|
1508
|
+
@media (max-width: 768px) {
|
|
1509
|
+
.query-monitoring-header {
|
|
1510
|
+
flex-direction: column;
|
|
1511
|
+
align-items: flex-start;
|
|
1512
|
+
gap: 0.75rem;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
.query-monitoring-title {
|
|
1516
|
+
flex-wrap: wrap;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
.query-monitoring-controls {
|
|
1520
|
+
width: 100%;
|
|
1521
|
+
flex-wrap: wrap;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/* Anchored to its chip, this tooltip runs off the screen and stretches
|
|
1525
|
+
the page with it; on a phone it reads as a bottom sheet instead. */
|
|
1526
|
+
.query-monitoring-scope::after {
|
|
1527
|
+
position: fixed;
|
|
1528
|
+
inset: auto 0.875rem 0.875rem;
|
|
1529
|
+
width: auto;
|
|
1530
|
+
max-width: none;
|
|
1531
|
+
}
|
|
1175
1532
|
}
|
|
1176
1533
|
</style>
|
|
1177
1534
|
</head>
|
|
1178
1535
|
<body>
|
|
1536
|
+
<%# Icon sprite. One definition per glyph, referenced with
|
|
1537
|
+
<svg class="icon"><use href="#i-name"></use></svg>. Stroke-drawn and
|
|
1538
|
+
colourless so every icon inherits its context's colour and size. %>
|
|
1539
|
+
<svg width="0" height="0" aria-hidden="true" focusable="false" style="position:absolute">
|
|
1540
|
+
<defs>
|
|
1541
|
+
<symbol id="i-queries" viewBox="0 0 24 24"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8Z"/></symbol>
|
|
1542
|
+
<symbol id="i-indexes" viewBox="0 0 24 24"><path d="M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01"/></symbol>
|
|
1543
|
+
<symbol id="i-tables" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9.5h18M9 9.5V20"/></symbol>
|
|
1544
|
+
<symbol id="i-connections" viewBox="0 0 24 24"><path d="M9 2v6M15 2v6M6 8h12v4a6 6 0 0 1-12 0V8ZM12 18v4"/></symbol>
|
|
1545
|
+
<symbol id="i-system" viewBox="0 0 24 24"><rect x="2.5" y="4" width="19" height="12" rx="2"/><path d="M8 20h8M12 16v4"/></symbol>
|
|
1546
|
+
<symbol id="i-schema" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m20 20-4.2-4.2"/></symbol>
|
|
1547
|
+
<symbol id="i-link" viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7L11.8 5.2"/><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7"/></symbol>
|
|
1548
|
+
<symbol id="i-activity" viewBox="0 0 24 24"><path d="M3 12h4l3 8 4-16 3 8h4"/></symbol>
|
|
1549
|
+
<symbol id="i-database" viewBox="0 0 24 24"><ellipse cx="12" cy="5.5" rx="8" ry="3.5"/><path d="M4 5.5v13c0 1.9 3.6 3.5 8 3.5s8-1.6 8-3.5v-13M4 12c0 1.9 3.6 3.5 8 3.5s8-1.6 8-3.5"/></symbol>
|
|
1550
|
+
<symbol id="i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5.2l3.2 2"/></symbol>
|
|
1551
|
+
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="4" y="10.5" width="16" height="10.5" rx="2"/><path d="M8 10.5V7a4 4 0 0 1 8 0v3.5"/></symbol>
|
|
1552
|
+
<symbol id="i-settings" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 14.6a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.03 1.56V21a2 2 0 1 1-4 0v-.1A1.7 1.7 0 0 0 8.9 19.3a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.7 1.7 0 0 0 .34-1.87 1.7 1.7 0 0 0-1.56-1.03H3a2 2 0 1 1 0-4h.1A1.7 1.7 0 0 0 4.7 8.9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.7 1.7 0 0 0 1.87.34H9.1A1.7 1.7 0 0 0 10.13 3V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1.03 1.56 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.7 1.7 0 0 0-.34 1.87v.08A1.7 1.7 0 0 0 21 10.13H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1.03Z"/></symbol>
|
|
1553
|
+
<symbol id="i-check" viewBox="0 0 24 24"><path d="m4.5 12.5 5 5 10-11"/></symbol>
|
|
1554
|
+
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M12 3.5 2.8 19.5h18.4L12 3.5Z"/><path d="M12 9.5v4.2M12 17h.01"/></symbol>
|
|
1555
|
+
<symbol id="i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v5.5M12 7.8h.01"/></symbol>
|
|
1556
|
+
<symbol id="i-play" viewBox="0 0 24 24"><path d="M7 4.5v15l13-7.5-13-7.5Z"/></symbol>
|
|
1557
|
+
<symbol id="i-pause" viewBox="0 0 24 24"><path d="M9 4.5v15M15 4.5v15"/></symbol>
|
|
1558
|
+
<symbol id="i-chevron-right" viewBox="0 0 24 24"><path d="m9.5 5.5 6.5 6.5-6.5 6.5"/></symbol>
|
|
1559
|
+
<symbol id="i-arrow-left" viewBox="0 0 24 24"><path d="M20 12H4.5M10.5 5.5 4 12l6.5 6.5"/></symbol>
|
|
1560
|
+
<symbol id="i-download" viewBox="0 0 24 24"><path d="M12 3.5v12M7 11l5 5 5-5M4 20h16"/></symbol>
|
|
1561
|
+
<symbol id="i-copy" viewBox="0 0 24 24"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M15 6V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h1"/></symbol>
|
|
1562
|
+
<symbol id="i-send" viewBox="0 0 24 24"><path d="M21 3 3 10.5l7 3 3 7L21 3Z"/></symbol>
|
|
1563
|
+
<symbol id="i-trash" viewBox="0 0 24 24"><path d="M4 6.5h16M9.5 6.5V4h5v2.5M6.5 6.5 7.4 20a1 1 0 0 0 1 1h7.2a1 1 0 0 0 1-1l.9-13.5"/></symbol>
|
|
1564
|
+
<symbol id="i-pin" viewBox="0 0 24 24"><path d="M9 3.5h6l-.8 5.2 3.3 3.3H6.5l3.3-3.3L9 3.5ZM12 12v8.5"/></symbol>
|
|
1565
|
+
<symbol id="i-file" viewBox="0 0 24 24"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z"/><path d="M14 3v5h5"/></symbol>
|
|
1566
|
+
<symbol id="i-book" viewBox="0 0 24 24"><path d="M4 4.5A1.5 1.5 0 0 1 5.5 3H19v15H5.5A1.5 1.5 0 0 0 4 19.5v-15ZM4 19.5A1.5 1.5 0 0 0 5.5 21H19v-3"/></symbol>
|
|
1567
|
+
<symbol id="i-external" viewBox="0 0 24 24"><path d="M14 4h6v6M20 4l-8.5 8.5"/><path d="M18 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h5"/></symbol>
|
|
1568
|
+
<symbol id="i-bulb" viewBox="0 0 24 24"><path d="M9.5 18h5M10 21h4"/><path d="M12 3a6 6 0 0 0-3.5 10.9c.6.5 1 1.3 1 2.1h5c0-.8.4-1.6 1-2.1A6 6 0 0 0 12 3Z"/></symbol>
|
|
1569
|
+
<symbol id="i-question" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M9.5 9.5a2.5 2.5 0 1 1 3.4 2.3c-.6.2-.9.8-.9 1.4v.6M12 16.8h.01"/></symbol>
|
|
1570
|
+
<symbol id="i-sliders" viewBox="0 0 24 24"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></symbol>
|
|
1571
|
+
</defs>
|
|
1572
|
+
</svg>
|
|
1573
|
+
|
|
1179
1574
|
<div class="container">
|
|
1575
|
+
<%# Redirects out of an unavailable report set flash[:alert]; without this
|
|
1576
|
+
the user would just land back on the dashboard with no explanation. %>
|
|
1577
|
+
<% if flash[:alert].present? %>
|
|
1578
|
+
<div class="callout callout-warning page-notice" role="alert">
|
|
1579
|
+
<svg class="icon" aria-hidden="true"><use href="#i-alert"></use></svg>
|
|
1580
|
+
<span><%= flash[:alert] %></span>
|
|
1581
|
+
</div>
|
|
1582
|
+
<% end %>
|
|
1583
|
+
<% if flash[:notice].present? %>
|
|
1584
|
+
<div class="callout page-notice" role="status">
|
|
1585
|
+
<svg class="icon" aria-hidden="true"><use href="#i-info"></use></svg>
|
|
1586
|
+
<span><%= flash[:notice] %></span>
|
|
1587
|
+
</div>
|
|
1588
|
+
<% end %>
|
|
1589
|
+
|
|
1180
1590
|
<%= yield %>
|
|
1181
1591
|
</div>
|
|
1182
1592
|
|
|
@@ -1219,6 +1629,94 @@
|
|
|
1219
1629
|
}
|
|
1220
1630
|
});
|
|
1221
1631
|
|
|
1632
|
+
// The "feature is disabled, here is how to enable it" panel. Four callers
|
|
1633
|
+
// had drifted into hand-spaced copies of this markup (<br><br>,
|
|
1634
|
+
// indentation, inline styles); they all render through here now.
|
|
1635
|
+
window.pgReportsDisabledNotice = function(title) {
|
|
1636
|
+
const i18n = PG_REPORTS_I18N.modals;
|
|
1637
|
+
const esc = (t) => String(t == null ? '' : t)
|
|
1638
|
+
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1639
|
+
return [
|
|
1640
|
+
'<div class="error-message">',
|
|
1641
|
+
'<p class="error-title">' + esc(title) + '</p>',
|
|
1642
|
+
'<p>' + esc(i18n.query_execution_disabled_intro) + '</p>',
|
|
1643
|
+
'<pre class="code-block">PgReports.configure do |config|\n config.allow_raw_query_execution = true\nend</pre>',
|
|
1644
|
+
'<p>' + esc(i18n.query_execution_disabled_env_note) + '</p>',
|
|
1645
|
+
'<pre class="code-block">PG_REPORTS_ALLOW_RAW_QUERY_EXECUTION=true</pre>',
|
|
1646
|
+
'</div>'
|
|
1647
|
+
].join('');
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
// Settings modal. Shared by the dashboard and the report pages; the language
|
|
1651
|
+
// section posts a form (server-side session), the IDE section only writes
|
|
1652
|
+
// localStorage, so there is nothing to submit for it.
|
|
1653
|
+
// Picking a language posts a form and the page reloads (everything on it has
|
|
1654
|
+
// to re-render in the new language), which would leave the user staring at a
|
|
1655
|
+
// dashboard with the settings they were editing gone. Remember that the
|
|
1656
|
+
// modal was open and bring it back on the way in.
|
|
1657
|
+
const SETTINGS_REOPEN_KEY = 'pgReportsSettingsReopen';
|
|
1658
|
+
|
|
1659
|
+
function rememberSettingsOpen() {
|
|
1660
|
+
try {
|
|
1661
|
+
sessionStorage.setItem(SETTINGS_REOPEN_KEY, '1');
|
|
1662
|
+
} catch (e) {
|
|
1663
|
+
// Storage can be unavailable (private mode, blocked cookies); losing the
|
|
1664
|
+
// reopen is a cosmetic downgrade, not a reason to block the switch.
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function consumeSettingsReopen() {
|
|
1669
|
+
try {
|
|
1670
|
+
if (!sessionStorage.getItem(SETTINGS_REOPEN_KEY)) return false;
|
|
1671
|
+
sessionStorage.removeItem(SETTINGS_REOPEN_KEY);
|
|
1672
|
+
return true;
|
|
1673
|
+
} catch (e) {
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
function showSettingsModal() {
|
|
1679
|
+
const modal = document.getElementById('settings-modal');
|
|
1680
|
+
if (!modal) return;
|
|
1681
|
+
|
|
1682
|
+
modal.style.display = 'flex';
|
|
1683
|
+
loadSettingsState();
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function closeSettingsModal() {
|
|
1687
|
+
const modal = document.getElementById('settings-modal');
|
|
1688
|
+
if (modal) modal.style.display = 'none';
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
function setDefaultIde(ideKey) {
|
|
1692
|
+
if (ideKey) {
|
|
1693
|
+
localStorage.setItem('pgReportsDefaultIde', ideKey);
|
|
1694
|
+
} else {
|
|
1695
|
+
localStorage.removeItem('pgReportsDefaultIde');
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function getDefaultIde() {
|
|
1700
|
+
return localStorage.getItem('pgReportsDefaultIde') || '';
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// The language radios are rendered already checked by the server; only the
|
|
1704
|
+
// IDE choice lives in localStorage and has to be restored on open.
|
|
1705
|
+
function loadSettingsState() {
|
|
1706
|
+
const currentIde = getDefaultIde();
|
|
1707
|
+
document.querySelectorAll('input[name="default-ide"]').forEach(radio => {
|
|
1708
|
+
radio.checked = (radio.value === currentIde);
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
1713
|
+
document.getElementById('settings-modal')?.addEventListener('click', function(e) {
|
|
1714
|
+
if (e.target === this) closeSettingsModal();
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
if (consumeSettingsReopen()) showSettingsModal();
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1222
1720
|
function showToast(message, type = 'success') {
|
|
1223
1721
|
const toast = document.getElementById('toast');
|
|
1224
1722
|
toast.textContent = message;
|