keepup-http 0.5.0__py3-none-any.whl
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.
- keepup_http/__init__.py +38 -0
- keepup_http/api_docs.py +76 -0
- keepup_http/body_limit.py +156 -0
- keepup_http/events_api.py +237 -0
- keepup_http/factory.py +562 -0
- keepup_http/lock_routes.py +155 -0
- keepup_http/plugin.py +53 -0
- keepup_http/routes.py +549 -0
- keepup_http/static/css/aos.css +1 -0
- keepup_http/static/css/main_nebula.css +232 -0
- keepup_http/static/css/main_new.css +869 -0
- keepup_http/static/css/tailwind.css +1 -0
- keepup_http/static/index_nebula.html +282 -0
- keepup_http/static/index_new.html +275 -0
- keepup_http/static/js/aos.js +1 -0
- keepup_http/static/js/feather-icons.js +13 -0
- keepup_http/static/js/layout_bootstrap.js +29 -0
- keepup_http/static/js/main_new.js +2076 -0
- keepup_http/static/js/tailwind.js +83 -0
- keepup_http/static/modules/css/background_tasks.css +233 -0
- keepup_http/static/modules/css/cluster.css +16 -0
- keepup_http/static/modules/css/event_manager.css +386 -0
- keepup_http/static/modules/css/integration_logs.css +33 -0
- keepup_http/static/modules/css/metrics.css +115 -0
- keepup_http/static/modules/css/modules.css +189 -0
- keepup_http/static/modules/css/themes.css +563 -0
- keepup_http/static/modules/css/users.css +278 -0
- keepup_http/static/modules/js/background_tasks.js +695 -0
- keepup_http/static/modules/js/chart.js +14 -0
- keepup_http/static/modules/js/chartjs-adapter-date-fns.bundle.min.js +7 -0
- keepup_http/static/modules/js/cluster.js +374 -0
- keepup_http/static/modules/js/event_manager.js +1007 -0
- keepup_http/static/modules/js/integration_logs.js +795 -0
- keepup_http/static/modules/js/metrics.js +929 -0
- keepup_http/static/modules/js/modules.js +1128 -0
- keepup_http/static/modules/js/themes.js +684 -0
- keepup_http/static/modules/js/users.js +898 -0
- keepup_http/web.py +214 -0
- keepup_http-0.5.0.dist-info/METADATA +29 -0
- keepup_http-0.5.0.dist-info/RECORD +43 -0
- keepup_http-0.5.0.dist-info/WHEEL +5 -0
- keepup_http-0.5.0.dist-info/entry_points.txt +2 -0
- keepup_http-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Design tokens of the active theme.
|
|
3
|
+
|
|
4
|
+
The theme owns the admin chrome: the sidebar, the navigation buttons, the
|
|
5
|
+
collapsed menu and the content area. Module stylesheets are scoped to their
|
|
6
|
+
own section and must not declare these selectors -- module CSS is injected
|
|
7
|
+
after the page stylesheets, so a module rule would win on cascade order and
|
|
8
|
+
the chrome would depend on which sections a role happens to be granted.
|
|
9
|
+
|
|
10
|
+
Every var() below carries a fallback: a theme that does not declare a token
|
|
11
|
+
must fall back to the default look, not to no styling at all.
|
|
12
|
+
========================================================================== */
|
|
13
|
+
:root {
|
|
14
|
+
/* Sidebar */
|
|
15
|
+
--sidebar-bg: #1d4ed8;
|
|
16
|
+
--sidebar-fg: #ffffff;
|
|
17
|
+
--sidebar-header-bg: #1e40af;
|
|
18
|
+
--sidebar-width: 16rem;
|
|
19
|
+
--sidebar-width-collapsed: 64px;
|
|
20
|
+
--modules-nav-max-height: calc(100vh - 200px);
|
|
21
|
+
--modules-nav-padding: 8px 0 16px 0;
|
|
22
|
+
--sidebar-scrollbar-track: #1e3a8a;
|
|
23
|
+
--sidebar-scrollbar-thumb: #60a5fa;
|
|
24
|
+
--sidebar-scrollbar-thumb-hover: #93c5fd;
|
|
25
|
+
|
|
26
|
+
/* Navigation button */
|
|
27
|
+
--nav-item-padding: 0.75rem 1.5rem;
|
|
28
|
+
--nav-item-padding-collapsed: 0.75rem;
|
|
29
|
+
--nav-item-radius: 0;
|
|
30
|
+
--nav-item-fg: #dbeafe;
|
|
31
|
+
--nav-item-bg: transparent;
|
|
32
|
+
--nav-item-fg-hover: #dbeafe;
|
|
33
|
+
--nav-item-bg-hover: #2563eb;
|
|
34
|
+
--nav-item-fg-active: #ffffff;
|
|
35
|
+
--nav-item-bg-active: #1e40af;
|
|
36
|
+
--nav-item-icon-gap: 0.75rem;
|
|
37
|
+
|
|
38
|
+
/* Adaptive layout. --layout-breakpoint is the single source of the
|
|
39
|
+
narrow/wide boundary: main_new.js reads it off :root, drives a
|
|
40
|
+
matchMedia off it and keeps the .is-narrow class on <html>, and the
|
|
41
|
+
theme's head script feeds the same value to tailwind.config so the
|
|
42
|
+
md: utilities in the sections switch at the same width as the chrome.
|
|
43
|
+
A media query cannot read a custom property, and the project has no
|
|
44
|
+
CSS build step to expand one, so the class is what the rules match on.
|
|
45
|
+
The default is 768px because that is where the md: utilities the
|
|
46
|
+
sections already use switch today. */
|
|
47
|
+
--layout-breakpoint: 768px;
|
|
48
|
+
--drawer-width: 16rem;
|
|
49
|
+
/* The drawer floats over the content, so a sidebar background that is
|
|
50
|
+
translucent against the page (nebula's is) would let the content read
|
|
51
|
+
through it. A theme with such a background overrides this one. */
|
|
52
|
+
--drawer-bg: var(--sidebar-bg, #1d4ed8);
|
|
53
|
+
--backdrop-color: rgba(15, 23, 42, 0.55);
|
|
54
|
+
--content-padding-narrow: 1rem;
|
|
55
|
+
--tap-target-min: 44px;
|
|
56
|
+
/* Above this many columns a table stops folding into cards and scrolls
|
|
57
|
+
sideways instead: six narrow columns still read as a card, six wide
|
|
58
|
+
ones do not, so the threshold is a value to tune, not a constant. */
|
|
59
|
+
--table-card-columns: 6;
|
|
60
|
+
--table-card-bg: #ffffff;
|
|
61
|
+
--table-card-border: #e5e7eb;
|
|
62
|
+
--table-card-label-fg: #6b7280;
|
|
63
|
+
/* A pinned column needs an opaque background of its own, or the columns
|
|
64
|
+
scrolling past show through it. */
|
|
65
|
+
--table-sticky-bg: #ffffff;
|
|
66
|
+
--table-sticky-header-bg: #f9fafb;
|
|
67
|
+
|
|
68
|
+
/* Shared */
|
|
69
|
+
--chrome-transition: all 0.3s ease;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Tabs */
|
|
73
|
+
.tab-content {
|
|
74
|
+
display: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.tab-content.active {
|
|
78
|
+
display: block;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.tab-button {
|
|
82
|
+
border: none;
|
|
83
|
+
background: none;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
padding: 0.5rem 1rem;
|
|
86
|
+
border-bottom: 2px solid transparent;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.tab-button.active {
|
|
90
|
+
border-bottom-color: #3b82f6;
|
|
91
|
+
color: #3b82f6;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Content sections */
|
|
95
|
+
.content-section {
|
|
96
|
+
display: none;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.content-section.active {
|
|
100
|
+
display: block;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Shared modals */
|
|
104
|
+
.modal {
|
|
105
|
+
display: none;
|
|
106
|
+
position: fixed;
|
|
107
|
+
z-index: 1000;
|
|
108
|
+
left: 0;
|
|
109
|
+
top: 0;
|
|
110
|
+
width: 100%;
|
|
111
|
+
height: 100%;
|
|
112
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.modal-content {
|
|
116
|
+
background-color: white;
|
|
117
|
+
margin: 10% auto;
|
|
118
|
+
padding: 2rem;
|
|
119
|
+
border-radius: 0.5rem;
|
|
120
|
+
width: 90%;
|
|
121
|
+
max-width: 400px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.modal.hidden {
|
|
125
|
+
opacity: 0;
|
|
126
|
+
pointer-events: none;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Notifications */
|
|
130
|
+
.notification {
|
|
131
|
+
transition: all 0.3s ease;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ==========================================================================
|
|
135
|
+
Admin chrome. Owned by the theme; module stylesheets must not declare these
|
|
136
|
+
selectors. See the tokens above and tests/frontend_style_boundary_tests.py.
|
|
137
|
+
========================================================================== */
|
|
138
|
+
|
|
139
|
+
/* Sidebar. Position and stacking used to be Tailwind utilities in the theme
|
|
140
|
+
markup (fixed inset-y-0 left-0 w-64 z-50 -translate-x-full md:translate-x-0);
|
|
141
|
+
they live here so the theme owns the panel on every width, not just its
|
|
142
|
+
colours. */
|
|
143
|
+
#sidebar {
|
|
144
|
+
position: fixed;
|
|
145
|
+
top: 0;
|
|
146
|
+
bottom: 0;
|
|
147
|
+
left: 0;
|
|
148
|
+
z-index: 50;
|
|
149
|
+
width: var(--sidebar-width, 16rem);
|
|
150
|
+
background-color: var(--sidebar-bg, #1d4ed8);
|
|
151
|
+
color: var(--sidebar-fg, #ffffff);
|
|
152
|
+
transition: var(--chrome-transition, all 0.3s ease);
|
|
153
|
+
/* A long menu has to scroll. This used to come from documentation.css, so
|
|
154
|
+
the sidebar only scrolled for roles that were granted that section. */
|
|
155
|
+
overflow-x: hidden;
|
|
156
|
+
overflow-y: auto;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Content sits beside the panel; this used to be md:ml-64 in the markup. */
|
|
160
|
+
.main-content {
|
|
161
|
+
margin-left: var(--sidebar-width, 16rem);
|
|
162
|
+
transition: var(--chrome-transition, all 0.3s ease);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* The button that opens the drawer belongs to the narrow layout only. */
|
|
166
|
+
.nav-toggle-btn {
|
|
167
|
+
display: none;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#modules-nav-container {
|
|
171
|
+
width: 100%;
|
|
172
|
+
max-height: var(--modules-nav-max-height, calc(100vh - 200px));
|
|
173
|
+
padding: var(--modules-nav-padding, 8px 0 16px 0);
|
|
174
|
+
overflow-x: hidden;
|
|
175
|
+
overflow-y: auto;
|
|
176
|
+
scrollbar-width: thin;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#modules-nav-container::-webkit-scrollbar {
|
|
180
|
+
width: 4px;
|
|
181
|
+
height: 6px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#modules-nav-container::-webkit-scrollbar-track,
|
|
185
|
+
#modules-nav-container::-webkit-scrollbar-corner {
|
|
186
|
+
background: var(--sidebar-scrollbar-track, #1e3a8a);
|
|
187
|
+
border-radius: 3px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#modules-nav-container::-webkit-scrollbar-thumb {
|
|
191
|
+
background: var(--sidebar-scrollbar-thumb, #60a5fa);
|
|
192
|
+
border-radius: 3px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#modules-nav-container::-webkit-scrollbar-thumb:hover {
|
|
196
|
+
background: var(--sidebar-scrollbar-thumb-hover, #93c5fd);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Navigation button. Its whole appearance lives here: main_new.js gives the
|
|
200
|
+
element only the semantic classes, so the theme is the single owner. */
|
|
201
|
+
.nav-item {
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
padding: var(--nav-item-padding, 0.75rem 1.5rem);
|
|
205
|
+
border-radius: var(--nav-item-radius, 0);
|
|
206
|
+
color: var(--nav-item-fg, #dbeafe);
|
|
207
|
+
background-color: var(--nav-item-bg, transparent);
|
|
208
|
+
transition: var(--chrome-transition, all 0.3s ease);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.nav-item:hover {
|
|
212
|
+
color: var(--nav-item-fg-hover, #dbeafe);
|
|
213
|
+
background-color: var(--nav-item-bg-hover, #2563eb);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.nav-item.active {
|
|
217
|
+
color: var(--nav-item-fg-active, #ffffff);
|
|
218
|
+
background-color: var(--nav-item-bg-active, #1e40af);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.nav-item i {
|
|
222
|
+
flex-shrink: 0;
|
|
223
|
+
margin-right: var(--nav-item-icon-gap, 0.75rem);
|
|
224
|
+
transition: margin 0.3s ease;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Collapsed sidebar. Declared once, for the whole panel, and only for the wide
|
|
228
|
+
layout: below the breakpoint the panel is a drawer, so a collapsed flag saved
|
|
229
|
+
on a desktop would narrow the drawer and shift the content 64px to the right
|
|
230
|
+
of a column that is not on screen. The saved value is left alone -- it is the
|
|
231
|
+
wide layout's state, and the panel comes back collapsed when the window
|
|
232
|
+
widens again. */
|
|
233
|
+
:root:not(.is-narrow) .sidebar-collapsed {
|
|
234
|
+
width: var(--sidebar-width-collapsed, 64px) !important;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
:root:not(.is-narrow) .sidebar-collapsed .nav-text {
|
|
238
|
+
display: none;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
:root:not(.is-narrow) .sidebar-collapsed .logo-text {
|
|
242
|
+
display: none;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
:root:not(.is-narrow) .sidebar-collapsed .nav-item {
|
|
246
|
+
justify-content: center;
|
|
247
|
+
padding-left: var(--nav-item-padding-collapsed, 0.75rem);
|
|
248
|
+
padding-right: var(--nav-item-padding-collapsed, 0.75rem);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
:root:not(.is-narrow) .sidebar-collapsed .nav-item i {
|
|
252
|
+
margin-right: 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
:root:not(.is-narrow) .sidebar-collapsed ~ .main-content {
|
|
256
|
+
margin-left: var(--sidebar-width-collapsed, 64px) !important;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.toggle-sidebar-btn {
|
|
260
|
+
transition: var(--chrome-transition, all 0.3s ease);
|
|
261
|
+
min-width: var(--tap-target-min, 44px);
|
|
262
|
+
min-height: var(--tap-target-min, 44px);
|
|
263
|
+
display: inline-flex;
|
|
264
|
+
align-items: center;
|
|
265
|
+
justify-content: center;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
:root:not(.is-narrow) .sidebar-collapsed .toggle-sidebar-btn {
|
|
269
|
+
transform: rotate(180deg);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* ==========================================================================
|
|
273
|
+
Narrow layout. Matched on the .is-narrow class that main_new.js keeps on
|
|
274
|
+
<html> from the --layout-breakpoint token -- a media query cannot read a
|
|
275
|
+
custom property, and there is no CSS build step here to expand one.
|
|
276
|
+
|
|
277
|
+
Declared once, for every theme. A theme redefines the token values above;
|
|
278
|
+
it must not restate these rules, or the drawer would behave differently
|
|
279
|
+
depending on which theme is active.
|
|
280
|
+
========================================================================== */
|
|
281
|
+
.is-narrow #sidebar {
|
|
282
|
+
width: var(--drawer-width, 16rem);
|
|
283
|
+
/* Never the whole screen: the content behind has to stay visible enough to
|
|
284
|
+
read as content, so tapping the backdrop is an obvious way out. */
|
|
285
|
+
max-width: 85vw;
|
|
286
|
+
background-color: var(--drawer-bg, var(--sidebar-bg, #1d4ed8));
|
|
287
|
+
transform: translateX(-100%);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.is-narrow.nav-open #sidebar {
|
|
291
|
+
transform: translateX(0);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.is-narrow .main-content {
|
|
295
|
+
margin-left: 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* The content well is padded p-6 in the theme markup: 48px of a 360px screen
|
|
299
|
+
spent on margins before the section gets to draw anything. */
|
|
300
|
+
.is-narrow .main-content > main {
|
|
301
|
+
padding: var(--content-padding-narrow, 1rem);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.is-narrow .nav-toggle-btn {
|
|
305
|
+
display: inline-flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
justify-content: center;
|
|
308
|
+
min-width: var(--tap-target-min, 44px);
|
|
309
|
+
min-height: var(--tap-target-min, 44px);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.is-narrow .nav-item {
|
|
313
|
+
min-height: var(--tap-target-min, 44px);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.nav-backdrop {
|
|
317
|
+
position: fixed;
|
|
318
|
+
inset: 0;
|
|
319
|
+
z-index: 40;
|
|
320
|
+
background-color: var(--backdrop-color, rgba(15, 23, 42, 0.55));
|
|
321
|
+
opacity: 0;
|
|
322
|
+
visibility: hidden;
|
|
323
|
+
transition: opacity 0.2s ease, visibility 0.2s ease;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.is-narrow.nav-open .nav-backdrop {
|
|
327
|
+
opacity: 1;
|
|
328
|
+
visibility: visible;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* Scrolling with the drawer open belongs to the list of sections, not to the
|
|
332
|
+
page behind it. A class rather than an inline style, so there is no previous
|
|
333
|
+
value to remember and restore. */
|
|
334
|
+
body.nav-locked {
|
|
335
|
+
overflow: hidden;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Top row. At 360px the section title, the user's name, role and status badges
|
|
339
|
+
and three buttons do not fit on one line, so the row wraps and the parts that
|
|
340
|
+
are not needed to navigate give way first. */
|
|
341
|
+
.is-narrow .app-header-row {
|
|
342
|
+
flex-wrap: wrap;
|
|
343
|
+
gap: 0.5rem;
|
|
344
|
+
padding-left: 0.75rem;
|
|
345
|
+
padding-right: 0.75rem;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.is-narrow .app-header-title {
|
|
349
|
+
/* Menu button and title take the first line and keep it whole. */
|
|
350
|
+
flex: 1 1 100%;
|
|
351
|
+
min-width: 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.is-narrow #current-section-title {
|
|
355
|
+
font-size: 1rem;
|
|
356
|
+
line-height: 1.25;
|
|
357
|
+
min-width: 0;
|
|
358
|
+
overflow: hidden;
|
|
359
|
+
text-overflow: ellipsis;
|
|
360
|
+
white-space: nowrap;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.is-narrow .app-header-actions {
|
|
364
|
+
flex: 1 1 100%;
|
|
365
|
+
flex-wrap: wrap;
|
|
366
|
+
justify-content: flex-start;
|
|
367
|
+
min-width: 0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.is-narrow #userInfo {
|
|
371
|
+
min-width: 0;
|
|
372
|
+
font-size: 0.75rem;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.is-narrow #usernameDisplay {
|
|
376
|
+
display: inline-block;
|
|
377
|
+
max-width: 9rem;
|
|
378
|
+
overflow: hidden;
|
|
379
|
+
text-overflow: ellipsis;
|
|
380
|
+
white-space: nowrap;
|
|
381
|
+
vertical-align: bottom;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* The bell has no handler behind it; on a phone the space is worth more. */
|
|
385
|
+
.is-narrow .app-header-bell {
|
|
386
|
+
display: none;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.is-narrow .app-header-actions button {
|
|
390
|
+
min-width: var(--tap-target-min, 44px);
|
|
391
|
+
min-height: var(--tap-target-min, 44px);
|
|
392
|
+
display: inline-flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
justify-content: center;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Build version strip: four labelled fields laid out in a row stretch the
|
|
398
|
+
document instead of wrapping, because space-x-* sets margins, not gaps. */
|
|
399
|
+
.is-narrow .version-panel-fields {
|
|
400
|
+
flex-wrap: wrap;
|
|
401
|
+
gap: 0.25rem 0.75rem;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.is-narrow .version-panel-fields > span {
|
|
405
|
+
margin-left: 0 !important;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* --------------------------------------------------------------------------
|
|
409
|
+
Responsive tables. The mode is chosen by the helper in main_new.js and
|
|
410
|
+
published as data-responsive-mode; everything below is the drawing of it.
|
|
411
|
+
Above the breakpoint none of this applies and the table stays a table.
|
|
412
|
+
-------------------------------------------------------------------------- */
|
|
413
|
+
|
|
414
|
+
/* Wide tables: the scroll belongs to this container, never to the document. */
|
|
415
|
+
.table-scroll {
|
|
416
|
+
overflow-x: auto;
|
|
417
|
+
-webkit-overflow-scrolling: touch;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.is-narrow .responsive-table[data-responsive-mode="scroll"] th:first-child,
|
|
421
|
+
.is-narrow .responsive-table[data-responsive-mode="scroll"] td:first-child {
|
|
422
|
+
position: sticky;
|
|
423
|
+
left: 0;
|
|
424
|
+
z-index: 1;
|
|
425
|
+
background-color: var(--table-sticky-bg, #ffffff);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.is-narrow .responsive-table[data-responsive-mode="scroll"] thead th:first-child {
|
|
429
|
+
background-color: var(--table-sticky-header-bg, #f9fafb);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* Narrow tables: one card per row, each cell labelled with its heading. */
|
|
433
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] thead {
|
|
434
|
+
display: none;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"],
|
|
438
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] tbody,
|
|
439
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] tr,
|
|
440
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] td {
|
|
441
|
+
display: block;
|
|
442
|
+
width: auto;
|
|
443
|
+
min-width: 0;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] tr {
|
|
447
|
+
margin-bottom: 0.75rem;
|
|
448
|
+
padding: 0.25rem 0.75rem;
|
|
449
|
+
border: 1px solid var(--table-card-border, #e5e7eb);
|
|
450
|
+
border-radius: 0.5rem;
|
|
451
|
+
background-color: var(--table-card-bg, #ffffff);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] td {
|
|
455
|
+
display: flex;
|
|
456
|
+
align-items: baseline;
|
|
457
|
+
justify-content: space-between;
|
|
458
|
+
gap: 0.75rem;
|
|
459
|
+
padding: 0.4rem 0;
|
|
460
|
+
border: 0;
|
|
461
|
+
text-align: right;
|
|
462
|
+
white-space: normal;
|
|
463
|
+
overflow-wrap: anywhere;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] td::before {
|
|
467
|
+
content: attr(data-label);
|
|
468
|
+
flex: 0 0 40%;
|
|
469
|
+
text-align: left;
|
|
470
|
+
font-weight: 600;
|
|
471
|
+
color: var(--table-card-label-fg, #6b7280);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* Rows the helper deliberately left unlabelled -- empty states, spanning
|
|
475
|
+
summary rows -- read as plain text rather than growing an empty label. */
|
|
476
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] td:not([data-label]) {
|
|
477
|
+
display: block;
|
|
478
|
+
text-align: left;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.is-narrow .responsive-table[data-responsive-mode="cards"] td:not([data-label])::before {
|
|
482
|
+
content: none;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/* --------------------------------------------------------------------------
|
|
486
|
+
Modals. Both shapes in this file -- the plain .modal and the .modal-overlay
|
|
487
|
+
one -- carry a fixed max-width and generous margins that leave a phone with
|
|
488
|
+
a window it cannot read. Here they fill the screen minus a margin and scroll
|
|
489
|
+
inside themselves, with the header and its close button staying put.
|
|
490
|
+
-------------------------------------------------------------------------- */
|
|
491
|
+
/* One scroll container, and it is the overlay.
|
|
492
|
+
|
|
493
|
+
The sections build their dialogs two ways -- the .modal-overlay pair in this
|
|
494
|
+
file, and, far more often, a Tailwind overlay
|
|
495
|
+
(`fixed inset-0 ... flex items-center justify-center`) holding a panel. Both
|
|
496
|
+
broke the same way on a phone, for two reasons:
|
|
497
|
+
|
|
498
|
+
* a flex item centred in a container it is taller than overflows equally
|
|
499
|
+
at both ends, and neither end can be scrolled to, so the top of the
|
|
500
|
+
dialog and the buttons at its bottom were simply unreachable;
|
|
501
|
+
* the `flex flex-col` panels put their scrolling body in a `flex-1`
|
|
502
|
+
child, whose default `min-height: auto` refuses to shrink below its
|
|
503
|
+
content. The panel grew past its own max-height instead of the body
|
|
504
|
+
scrolling inside it, and the footer went off-screen.
|
|
505
|
+
|
|
506
|
+
Rather than constrain the panel and rely on an inner scroller, the narrow
|
|
507
|
+
layout lets the panel be its natural height and gives the scroll to the
|
|
508
|
+
overlay. That removes both failures at once, needs nothing from the panel's
|
|
509
|
+
markup, and sidesteps `vh` on mobile browsers counting an address bar that
|
|
510
|
+
has already slid away. A sticky header inside a panel still sticks -- the
|
|
511
|
+
overlay is its scrollport. */
|
|
512
|
+
.is-narrow .modal-overlay,
|
|
513
|
+
.is-narrow .fixed.inset-0.items-center {
|
|
514
|
+
align-items: flex-start;
|
|
515
|
+
overflow-y: auto;
|
|
516
|
+
padding: 0.75rem;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.is-narrow .modal-overlay > *,
|
|
520
|
+
.is-narrow .fixed.inset-0.items-center > * {
|
|
521
|
+
/* w-96 is 384px, wider than a 360px screen on its own. */
|
|
522
|
+
max-width: 100%;
|
|
523
|
+
max-height: none;
|
|
524
|
+
margin-top: 0;
|
|
525
|
+
margin-bottom: 0;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.is-narrow .modal-content {
|
|
529
|
+
width: 100%;
|
|
530
|
+
padding: 1rem;
|
|
531
|
+
border-radius: 1rem;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.is-narrow .modal-header {
|
|
535
|
+
position: sticky;
|
|
536
|
+
top: 0;
|
|
537
|
+
z-index: 10;
|
|
538
|
+
padding: 1rem;
|
|
539
|
+
border-radius: 1rem 1rem 0 0;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.is-narrow .modal-body {
|
|
543
|
+
padding: 1rem;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.is-narrow .modal-title {
|
|
547
|
+
font-size: 1.125rem;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* The .modal-overlay shape pads its own content, so the sticky header has to
|
|
551
|
+
sit flush; the .modal shape pads the box instead. Both end up with the close
|
|
552
|
+
button reachable without horizontal scrolling. */
|
|
553
|
+
.is-narrow .modal-overlay .modal-content {
|
|
554
|
+
padding: 0;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/* --------------------------------------------------------------------------
|
|
558
|
+
Section content: the two shapes that overflow a phone in almost every
|
|
559
|
+
section, fixed once here rather than in twenty stylesheets.
|
|
560
|
+
-------------------------------------------------------------------------- */
|
|
561
|
+
|
|
562
|
+
/* A title on the left and a row of buttons on the right is the standard header
|
|
563
|
+
of a section and of its filter panel. At 360px the pair does not fit on one
|
|
564
|
+
line, and without wrapping the buttons are pushed past the edge. */
|
|
565
|
+
.is-narrow .content-section .flex.justify-between {
|
|
566
|
+
flex-wrap: wrap;
|
|
567
|
+
gap: 0.5rem;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/* Inputs sized in ch or with a fixed width are common in filter panels and
|
|
571
|
+
push the panel wider than the screen. */
|
|
572
|
+
.is-narrow .content-section input:not([type="checkbox"]):not([type="radio"]),
|
|
573
|
+
.is-narrow .content-section select,
|
|
574
|
+
.is-narrow .content-section textarea {
|
|
575
|
+
max-width: 100%;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* A safety net, not the fix: a section that overflows is meant to scroll inside
|
|
579
|
+
its own container, and the checks in tests/frontend_mobile_layout_tests.py
|
|
580
|
+
are what keep it that way. This only stops one missed section from making the
|
|
581
|
+
whole document slide sideways. */
|
|
582
|
+
.is-narrow body {
|
|
583
|
+
overflow-x: hidden;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/* A user's status and role */
|
|
587
|
+
.status-badge {
|
|
588
|
+
padding: 0.25rem 0.5rem;
|
|
589
|
+
border-radius: 0.25rem;
|
|
590
|
+
font-size: 0.75rem;
|
|
591
|
+
font-weight: 500;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.status-active {
|
|
595
|
+
background-color: #dcfce7;
|
|
596
|
+
color: #166534;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.status-blocked {
|
|
600
|
+
background-color: #fee2e2;
|
|
601
|
+
color: #991b1b;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.status-inactive {
|
|
605
|
+
background-color: #fee2e2;
|
|
606
|
+
color: #991b1b;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.role-badge {
|
|
610
|
+
padding: 2px 8px;
|
|
611
|
+
border-radius: 12px;
|
|
612
|
+
font-size: 0.75rem;
|
|
613
|
+
font-weight: 600;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* The application's badge in the role's place (keepup-37): the tones are the
|
|
617
|
+
theme's to colour, the application only names one. */
|
|
618
|
+
.header-badge-neutral {
|
|
619
|
+
background-color: var(--surface-muted, #eef2f7);
|
|
620
|
+
color: var(--text-strong, #1f2937);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.header-badge-positive {
|
|
624
|
+
background-color: #dcfce7;
|
|
625
|
+
color: #166534;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.header-badge-warning {
|
|
629
|
+
background-color: #fef3c7;
|
|
630
|
+
color: #92400e;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.role-admin {
|
|
634
|
+
background-color: #e9d5ff;
|
|
635
|
+
color: #7c3aed;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.role-client {
|
|
639
|
+
background-color: #dbeafe;
|
|
640
|
+
color: #1d4ed8;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/* File upload */
|
|
644
|
+
.upload-area {
|
|
645
|
+
border: 2px dashed #d1d5db;
|
|
646
|
+
border-radius: 0.5rem;
|
|
647
|
+
padding: 2rem;
|
|
648
|
+
text-align: center;
|
|
649
|
+
transition: border-color 0.3s;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.upload-area:hover {
|
|
653
|
+
border-color: #3b82f6;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/* XML sources */
|
|
657
|
+
.source-item {
|
|
658
|
+
transition: box-shadow 0.3s;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.source-item:hover {
|
|
662
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/* The version panel */
|
|
666
|
+
#versionPanel {
|
|
667
|
+
z-index: 1000;
|
|
668
|
+
font-family: monospace;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/* Further shared classes */
|
|
672
|
+
.flex-center {
|
|
673
|
+
display: flex;
|
|
674
|
+
align-items: center;
|
|
675
|
+
justify-content: center;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.text-truncate {
|
|
679
|
+
overflow: hidden;
|
|
680
|
+
text-overflow: ellipsis;
|
|
681
|
+
white-space: nowrap;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.cursor-help {
|
|
685
|
+
cursor: help;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.hidden {
|
|
689
|
+
display: none !important;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.user-info-field {
|
|
693
|
+
transition: all 0.3s ease;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.terms-link {
|
|
697
|
+
color: #2563eb;
|
|
698
|
+
text-decoration: underline;
|
|
699
|
+
transition: color 0.2s;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.terms-link:hover {
|
|
703
|
+
color: #1d4ed8;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
.phone-input {
|
|
707
|
+
font-family: 'Roboto Mono', monospace;
|
|
708
|
+
}
|
|
709
|
+
.auth-block {
|
|
710
|
+
position: fixed;
|
|
711
|
+
top: 0;
|
|
712
|
+
left: 0;
|
|
713
|
+
width: 100%;
|
|
714
|
+
height: 100%;
|
|
715
|
+
background-color: #f3f4f6;
|
|
716
|
+
z-index: 9999;
|
|
717
|
+
display: flex;
|
|
718
|
+
justify-content: center;
|
|
719
|
+
align-items: center;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/* The modal */
|
|
723
|
+
.modal-overlay {
|
|
724
|
+
position: fixed;
|
|
725
|
+
inset: 0;
|
|
726
|
+
background: rgba(0, 0, 0, 0.5);
|
|
727
|
+
backdrop-filter: blur(4px);
|
|
728
|
+
z-index: 99999;
|
|
729
|
+
display: flex;
|
|
730
|
+
align-items: center;
|
|
731
|
+
justify-content: center;
|
|
732
|
+
padding: 1rem;
|
|
733
|
+
opacity: 0;
|
|
734
|
+
transition: opacity 0.3s ease;
|
|
735
|
+
pointer-events: none;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.modal-overlay.active {
|
|
739
|
+
opacity: 1;
|
|
740
|
+
pointer-events: all;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.modal-content {
|
|
744
|
+
background: white;
|
|
745
|
+
border-radius: 1.5rem;
|
|
746
|
+
width: 100%;
|
|
747
|
+
max-width: 600px;
|
|
748
|
+
max-height: 90vh;
|
|
749
|
+
overflow-y: auto;
|
|
750
|
+
transform: scale(0.95);
|
|
751
|
+
transition: transform 0.3s ease;
|
|
752
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
753
|
+
margin: auto;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.modal-overlay.active .modal-content {
|
|
757
|
+
transform: scale(1);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.modal-header {
|
|
761
|
+
padding: 1.5rem;
|
|
762
|
+
border-bottom: 1px solid #e2e8f0;
|
|
763
|
+
display: flex;
|
|
764
|
+
align-items: center;
|
|
765
|
+
justify-content: space-between;
|
|
766
|
+
background: linear-gradient(135deg, #0ea5e9 0%, #3b82f6 100%);
|
|
767
|
+
border-radius: 1.5rem 1.5rem 0 0;
|
|
768
|
+
color: white;
|
|
769
|
+
position: sticky;
|
|
770
|
+
top: 0;
|
|
771
|
+
z-index: 10;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.modal-body {
|
|
775
|
+
padding: 1.5rem;
|
|
776
|
+
font-size: 0.95rem;
|
|
777
|
+
line-height: 1.6;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.modal-section {
|
|
781
|
+
margin-bottom: 2rem;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.modal-section:last-child {
|
|
785
|
+
margin-bottom: 0;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.modal-title {
|
|
789
|
+
font-size: 1.5rem;
|
|
790
|
+
font-weight: 600;
|
|
791
|
+
margin: 0;
|
|
792
|
+
color: white;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.section-title {
|
|
796
|
+
font-size: 1.25rem;
|
|
797
|
+
font-weight: 600;
|
|
798
|
+
color: #1e293b;
|
|
799
|
+
margin-bottom: 1rem;
|
|
800
|
+
padding-bottom: 0.5rem;
|
|
801
|
+
border-bottom: 2px solid #e2e8f0;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.terms-content {
|
|
805
|
+
background: #f8fafc;
|
|
806
|
+
padding: 1rem;
|
|
807
|
+
border-radius: 0.75rem;
|
|
808
|
+
max-height: 300px;
|
|
809
|
+
overflow-y: auto;
|
|
810
|
+
font-size: 0.9rem;
|
|
811
|
+
line-height: 1.5;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
.terms-content h3 {
|
|
815
|
+
font-size: 1.1rem;
|
|
816
|
+
font-weight: 600;
|
|
817
|
+
color: #1e293b;
|
|
818
|
+
margin: 1rem 0 0.5rem 0;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.terms-content h3:first-child {
|
|
822
|
+
margin-top: 0;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.terms-content p {
|
|
826
|
+
margin-bottom: 0.75rem;
|
|
827
|
+
color: #475569;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.close-btn {
|
|
831
|
+
background: rgba(255, 255, 255, 0.2);
|
|
832
|
+
border: none;
|
|
833
|
+
border-radius: 0.75rem;
|
|
834
|
+
padding: 0.5rem;
|
|
835
|
+
cursor: pointer;
|
|
836
|
+
transition: all 0.2s ease;
|
|
837
|
+
color: white;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.close-btn:hover {
|
|
841
|
+
background: rgba(255, 255, 255, 0.3);
|
|
842
|
+
transform: rotate(90deg);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/* Smoother scrolling */
|
|
846
|
+
.terms-content::-webkit-scrollbar {
|
|
847
|
+
width: 6px;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.terms-content::-webkit-scrollbar-track {
|
|
851
|
+
background: #f1f5f9;
|
|
852
|
+
border-radius: 3px;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.terms-content::-webkit-scrollbar-thumb {
|
|
856
|
+
background: #cbd5e1;
|
|
857
|
+
border-radius: 3px;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.terms-content::-webkit-scrollbar-thumb:hover {
|
|
861
|
+
background: #94a3b8;
|
|
862
|
+
}
|
|
863
|
+
/* The terms arrive as plain text from the server and are inserted as text, not
|
|
864
|
+
as markup: whatever the file says, it cannot bring tags into the page. */
|
|
865
|
+
.terms-plain {
|
|
866
|
+
white-space: pre-wrap;
|
|
867
|
+
font-size: 0.9rem;
|
|
868
|
+
line-height: 1.6;
|
|
869
|
+
}
|