messagefoundry-webconsole 0.2.15__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.
- messagefoundry_webconsole/__init__.py +128 -0
- messagefoundry_webconsole/_auth.py +841 -0
- messagefoundry_webconsole/_html.py +539 -0
- messagefoundry_webconsole/_security.py +253 -0
- messagefoundry_webconsole/_service.py +22 -0
- messagefoundry_webconsole/_static.py +78 -0
- messagefoundry_webconsole/mount.py +103 -0
- messagefoundry_webconsole/pages/__init__.py +25 -0
- messagefoundry_webconsole/pages/_common.py +21 -0
- messagefoundry_webconsole/pages/account.py +771 -0
- messagefoundry_webconsole/pages/admin.py +480 -0
- messagefoundry_webconsole/pages/audit.py +66 -0
- messagefoundry_webconsole/pages/config.py +113 -0
- messagefoundry_webconsole/pages/connections.py +519 -0
- messagefoundry_webconsole/pages/messages.py +689 -0
- messagefoundry_webconsole/pages/monitoring.py +853 -0
- messagefoundry_webconsole/pages/uploaded_logs.py +252 -0
- messagefoundry_webconsole/routes/__init__.py +6 -0
- messagefoundry_webconsole/routes/_common.py +45 -0
- messagefoundry_webconsole/routes/account.py +451 -0
- messagefoundry_webconsole/routes/admin.py +505 -0
- messagefoundry_webconsole/routes/audit.py +39 -0
- messagefoundry_webconsole/routes/config.py +67 -0
- messagefoundry_webconsole/routes/connection_writes.py +248 -0
- messagefoundry_webconsole/routes/core.py +1077 -0
- messagefoundry_webconsole/routes/monitoring.py +94 -0
- messagefoundry_webconsole/routes/monitoring_writes.py +205 -0
- messagefoundry_webconsole/routes/oidc.py +172 -0
- messagefoundry_webconsole/routes/search.py +204 -0
- messagefoundry_webconsole/routes/sso.py +88 -0
- messagefoundry_webconsole/routes/status.py +178 -0
- messagefoundry_webconsole/routes/uploaded_logs.py +181 -0
- messagefoundry_webconsole/static/app.css +345 -0
- messagefoundry_webconsole/static/app.js +1506 -0
- messagefoundry_webconsole/static/csp-probe.js +9 -0
- messagefoundry_webconsole-0.2.15.dist-info/METADATA +63 -0
- messagefoundry_webconsole-0.2.15.dist-info/RECORD +40 -0
- messagefoundry_webconsole-0.2.15.dist-info/WHEEL +4 -0
- messagefoundry_webconsole-0.2.15.dist-info/licenses/LICENSE +662 -0
- messagefoundry_webconsole-0.2.15.dist-info/licenses/NOTICE +31 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: AGPL-3.0-or-later */
|
|
2
|
+
/* First-party styles for the /ui ops dashboard (ADR 0065). Same-origin; CSP style-src 'self'. */
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
--bg: #0f1115;
|
|
6
|
+
--panel: #171a21;
|
|
7
|
+
--ink: #e6e9ef;
|
|
8
|
+
--muted: #9aa4b2;
|
|
9
|
+
--line: #262b34;
|
|
10
|
+
--accent: #5b9dff;
|
|
11
|
+
--foundry: #f59e0b; /* molten amber — the "Foundry" wordmark accent (brand guidelines) */
|
|
12
|
+
--ok: #3fb950;
|
|
13
|
+
--warn: #d29922;
|
|
14
|
+
--bad: #f85149;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
* { box-sizing: border-box; }
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
margin: 0;
|
|
21
|
+
font: 14px/1.5 -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
|
|
22
|
+
color: var(--ink);
|
|
23
|
+
background: var(--bg);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
nav {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
padding: 0.6rem 1rem;
|
|
31
|
+
background: var(--panel);
|
|
32
|
+
border-bottom: 1px solid var(--line);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
nav .brand { margin-right: 1rem; text-decoration: none; color: inherit; }
|
|
36
|
+
|
|
37
|
+
/* MessageFoundry wordmark (brand guidelines, June 2026): one camelCase word — "Message" in the base
|
|
38
|
+
ink, "Foundry" in molten amber — set in extra-bold system sans with tightened tracking. The amber
|
|
39
|
+
is confined to the wordmark and headings here, never body text. */
|
|
40
|
+
.wordmark { font-weight: 800; letter-spacing: -0.02em; white-space: nowrap; }
|
|
41
|
+
.wordmark .wm-foundry { color: var(--foundry); }
|
|
42
|
+
.wordmark .wm-tm {
|
|
43
|
+
font-size: 0.65em; font-weight: 500; color: var(--muted); margin-left: 0.06em;
|
|
44
|
+
}
|
|
45
|
+
nav .navlinks { display: flex; align-items: center; flex-wrap: wrap; gap: 0.15rem; }
|
|
46
|
+
|
|
47
|
+
/* Grouped nav as dropdowns: each area is a menu opening on :hover AND :focus-within (keyboard-reachable,
|
|
48
|
+
no JS — inside the CSP 'self' policy). The toggle is a <button>; the items are links inside the panel. */
|
|
49
|
+
.navgroup { position: relative; }
|
|
50
|
+
.navtop {
|
|
51
|
+
background: none; border: 0; font: inherit; cursor: pointer; color: var(--muted);
|
|
52
|
+
padding: 0.35rem 0.55rem; border-radius: 6px; white-space: nowrap;
|
|
53
|
+
}
|
|
54
|
+
.navtop:hover, .navgroup:focus-within .navtop, .navtop.active { color: var(--accent); }
|
|
55
|
+
.navmenu {
|
|
56
|
+
display: none; position: absolute; top: 100%; left: 0; margin-top: 0.3rem; z-index: 30;
|
|
57
|
+
min-width: 12rem; flex-direction: column; padding: 0.3rem;
|
|
58
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
|
|
59
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
|
60
|
+
}
|
|
61
|
+
/* Invisible bridge across the margin gap so a hover moving button→menu doesn't fall through and close it. */
|
|
62
|
+
.navmenu::before { content: ""; position: absolute; left: 0; right: 0; top: -0.3rem; height: 0.3rem; }
|
|
63
|
+
.navgroup:hover .navmenu, .navgroup:focus-within .navmenu { display: flex; }
|
|
64
|
+
.navlinks .navgroup:last-child .navmenu { left: auto; right: 0; } /* keep the rightmost menu on-screen */
|
|
65
|
+
.navmenu a {
|
|
66
|
+
color: var(--muted); text-decoration: none; white-space: nowrap;
|
|
67
|
+
padding: 0.35rem 0.6rem; border-radius: 5px;
|
|
68
|
+
}
|
|
69
|
+
.navmenu a:hover, .navmenu a.active { color: var(--accent); background: var(--bg); }
|
|
70
|
+
/* Right cluster: the live status glyphs + Sign out, held together so nav's space-between keeps the links
|
|
71
|
+
left and this block flush right. */
|
|
72
|
+
.navright { display: flex; align-items: center; gap: 0.9rem; }
|
|
73
|
+
|
|
74
|
+
/* Live nav status glyphs (app.js polls /ui/nav-status, every page): the alerts bell + engine-health heart.
|
|
75
|
+
Neutral gray until the first poll; then health = green / orange / blinking-red and alerts = severity-
|
|
76
|
+
colored or gray. `color` drives the monochrome SVG tint (fill=currentColor). */
|
|
77
|
+
.navstatus { display: inline-flex; align-items: center; gap: 0.55rem; }
|
|
78
|
+
/* Each glyph is a link to its detail page (bell → /ui/alerts, heart → /ui/status): no underline, tinted by
|
|
79
|
+
`color`, with a hover/focus affordance so it reads as clickable. */
|
|
80
|
+
.navstat {
|
|
81
|
+
display: inline-flex; align-items: center; color: var(--muted); line-height: 0;
|
|
82
|
+
text-decoration: none; cursor: pointer; border-radius: 4px;
|
|
83
|
+
}
|
|
84
|
+
.navstat:hover { opacity: 0.75; }
|
|
85
|
+
.navstat:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
86
|
+
.navstat .statglyph { display: block; }
|
|
87
|
+
.health-ok { color: var(--ok); }
|
|
88
|
+
.health-warn { color: var(--warn); }
|
|
89
|
+
.health-down { color: var(--bad); animation: mf-health-blink 1.1s ease-in-out infinite; }
|
|
90
|
+
.alerts-none { color: var(--muted); }
|
|
91
|
+
.alerts-info { color: var(--accent); }
|
|
92
|
+
.alerts-warning { color: var(--warn); }
|
|
93
|
+
.alerts-critical { color: var(--bad); }
|
|
94
|
+
@keyframes mf-health-blink {
|
|
95
|
+
0%, 100% { opacity: 1; }
|
|
96
|
+
50% { opacity: 0.2; }
|
|
97
|
+
}
|
|
98
|
+
/* Honor reduced-motion: a critical engine stays solid red instead of blinking. */
|
|
99
|
+
@media (prefers-reduced-motion: reduce) {
|
|
100
|
+
.health-down { animation: none; }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
nav .logout button, .filters button, .login button {
|
|
104
|
+
background: var(--accent); color: #04122b; border: 0; border-radius: 6px;
|
|
105
|
+
padding: 0.35rem 0.8rem; cursor: pointer; font-weight: 600;
|
|
106
|
+
}
|
|
107
|
+
nav .logout button { background: transparent; color: var(--muted); border: 1px solid var(--line); }
|
|
108
|
+
button[disabled] { opacity: 0.5; cursor: not-allowed; }
|
|
109
|
+
|
|
110
|
+
main { padding: 1rem; }
|
|
111
|
+
/* Content-area links (page bodies, cards — e.g. "View messages →", "← Connections"): the UA default
|
|
112
|
+
blue/purple-visited is unreadable on the dark background, so tint them with the accent. Author origin
|
|
113
|
+
beats the UA :visited rule, so both states read the same. Nav/table links are styled elsewhere. */
|
|
114
|
+
main a, main a:visited { color: var(--accent); }
|
|
115
|
+
h1 { font-size: 1.25rem; }
|
|
116
|
+
h2 { font-size: 1rem; color: var(--muted); margin-top: 1.5rem; }
|
|
117
|
+
|
|
118
|
+
table.grid { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }
|
|
119
|
+
table.grid th, table.grid td {
|
|
120
|
+
text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid var(--line);
|
|
121
|
+
white-space: nowrap; font-variant-numeric: tabular-nums;
|
|
122
|
+
}
|
|
123
|
+
table.grid th { color: var(--muted); font-weight: 600; }
|
|
124
|
+
table.grid a { color: var(--accent); text-decoration: none; }
|
|
125
|
+
/* Key/value readout tables (rows_table adjustable=False): plain + wrapping, NOT the adjustable grid.
|
|
126
|
+
Long values (paths, ids) wrap instead of tripping a horizontal scrollbar — app.js leaves these alone
|
|
127
|
+
(no data-mf-table), so they stay a simple width:100% table with no fixed layout / scroll wrapper. */
|
|
128
|
+
table.grid.info th, table.grid.info td { white-space: normal; overflow-wrap: anywhere; }
|
|
129
|
+
|
|
130
|
+
/* Adjustable grid tables (app.js, data-mf-table): keyboard-operable click-to-sort + drag-to-resize
|
|
131
|
+
columns, remembered per table. Best-practice layout (TanStack / AG Grid / W3C-APG): widths live on
|
|
132
|
+
injected <col> elements and the table gets an explicit px width = their SUM inside an overflow-x:auto
|
|
133
|
+
wrapper (NOT width:100%, which redistributes leftover space and snaps a resized column back wider than
|
|
134
|
+
set); sort is a native <button> cycling asc/desc/none with aria-sort on exactly one header. */
|
|
135
|
+
.mf-table-scroll { overflow-x: auto; }
|
|
136
|
+
table.grid.mf-adjustable { table-layout: fixed; width: auto; } /* app.js sets the explicit px width */
|
|
137
|
+
table.grid.mf-adjustable th { position: relative; }
|
|
138
|
+
table.grid.mf-adjustable td { overflow: hidden; text-overflow: ellipsis; }
|
|
139
|
+
button.mf-sort {
|
|
140
|
+
display: block; width: 100%; margin: 0; padding: 0; cursor: pointer;
|
|
141
|
+
background: none; border: 0; color: inherit; font: inherit; text-align: left;
|
|
142
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
143
|
+
}
|
|
144
|
+
button.mf-sort:hover { color: var(--ink); }
|
|
145
|
+
button.mf-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
146
|
+
.mf-sort-arrow { color: var(--accent); font-size: 0.8em; }
|
|
147
|
+
th .mf-resize {
|
|
148
|
+
position: absolute; top: 0; right: 0; width: 9px; height: 100%; cursor: col-resize; z-index: 1;
|
|
149
|
+
}
|
|
150
|
+
th .mf-resize:hover { box-shadow: inset -2px 0 0 var(--accent); }
|
|
151
|
+
/* Drag-to-reorder: drop-target indicator (a left edge marking where the dragged column will land). */
|
|
152
|
+
table.grid.mf-adjustable th.mf-drop { box-shadow: inset 3px 0 0 var(--accent); }
|
|
153
|
+
body.mf-col-resizing, body.mf-col-resizing * { cursor: col-resize !important; user-select: none !important; }
|
|
154
|
+
|
|
155
|
+
/* Connections filter-as-you-type box (app.js, data-mf-conns-filter). */
|
|
156
|
+
.filterbox {
|
|
157
|
+
margin: 0; padding: 0.4rem 0.6rem; width: 18rem; max-width: 100%; box-sizing: border-box;
|
|
158
|
+
background: var(--panel); color: var(--ink); border: 1px solid var(--line); border-radius: 6px;
|
|
159
|
+
}
|
|
160
|
+
.filterbox::placeholder { color: var(--muted); }
|
|
161
|
+
.filterbox:focus { outline: none; border-color: var(--accent); }
|
|
162
|
+
/* Connection detail link (right of the name) + the message-log From/To date fields. */
|
|
163
|
+
/* The ⓘ connection-details link sits after the name→messages link in the same cell; give it a clear gap so
|
|
164
|
+
it isn't a mis-click hazard next to the primary name link. */
|
|
165
|
+
.detail-link { font-size: 1em; color: var(--muted); margin-left: 0.9rem; padding: 0 0.15rem; text-decoration: none; vertical-align: middle; }
|
|
166
|
+
.detail-link:hover { color: var(--accent); }
|
|
167
|
+
.filters .dtfield { display: inline-flex; gap: 0.25rem; align-items: center; color: var(--muted); }
|
|
168
|
+
|
|
169
|
+
.status { padding: 0.05rem 0.45rem; border-radius: 999px; font-size: 0.8rem; }
|
|
170
|
+
.status-running { color: var(--ok); }
|
|
171
|
+
.status-stopped, .status-draining { color: var(--muted); }
|
|
172
|
+
.status-failed, .status-error { color: var(--bad); }
|
|
173
|
+
.status-filtered, .status-stopping { color: var(--warn); }
|
|
174
|
+
/* #233 (ADR 0111): present in the config but deployed=false — an intentional, benign state, so it reads
|
|
175
|
+
in the informational accent (never the muted grey of "stopped", which means it SHOULD be running). */
|
|
176
|
+
.status-not_deployed { color: var(--accent); }
|
|
177
|
+
|
|
178
|
+
pre.raw {
|
|
179
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
|
|
180
|
+
padding: 0.8rem; overflow: auto; max-height: 60vh; white-space: pre-wrap; word-break: break-word;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.filters { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 0.5rem 0; }
|
|
184
|
+
.filters input { background: var(--bg); color: var(--ink); border: 1px solid var(--line);
|
|
185
|
+
border-radius: 6px; padding: 0.3rem 0.5rem; }
|
|
186
|
+
.pager { color: var(--muted); }
|
|
187
|
+
|
|
188
|
+
.card { max-width: 360px; margin: 4rem auto; background: var(--panel);
|
|
189
|
+
border: 1px solid var(--line); border-radius: 10px; padding: 1.5rem; }
|
|
190
|
+
.card .login { display: flex; flex-direction: column; gap: 0.75rem; }
|
|
191
|
+
.card label { display: flex; flex-direction: column; gap: 0.25rem; color: var(--muted); }
|
|
192
|
+
.card input { background: var(--bg); color: var(--ink); border: 1px solid var(--line);
|
|
193
|
+
border-radius: 6px; padding: 0.4rem 0.5rem; }
|
|
194
|
+
/* The connection-detail view reuses the card frame but needs room for its tables — the 360px login-card
|
|
195
|
+
width is too cramped — so widen it and drop the tall login top-margin. */
|
|
196
|
+
.card.detail-card { max-width: 760px; margin: 1.5rem auto; }
|
|
197
|
+
.banner { color: var(--warn); }
|
|
198
|
+
|
|
199
|
+
.ctls { display: flex; gap: 0.3rem; }
|
|
200
|
+
.ctl { display: inline; margin: 0; }
|
|
201
|
+
.ctl button {
|
|
202
|
+
background: transparent; color: var(--muted); border: 1px solid var(--line);
|
|
203
|
+
border-radius: 5px; padding: 0.15rem 0.5rem; font-size: 0.8rem; cursor: pointer;
|
|
204
|
+
}
|
|
205
|
+
.ctl button:hover { color: var(--accent); border-color: var(--accent); }
|
|
206
|
+
|
|
207
|
+
/* Connections filter + bulk-action toolbar, sitting on the page-header row (ADR 0065). */
|
|
208
|
+
.page-head { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin: 0.5rem 0; }
|
|
209
|
+
.page-head h1 { margin: 0; }
|
|
210
|
+
.page-head .ctlbar { margin-left: auto; } /* push the filter + dropdown/Go to the right, header stays left */
|
|
211
|
+
.ctlbar { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 0; }
|
|
212
|
+
.ctlbar .filterbox { margin: 0; }
|
|
213
|
+
.ctlbar select {
|
|
214
|
+
background: var(--bg); color: var(--ink); border: 1px solid var(--line);
|
|
215
|
+
border-radius: 6px; padding: 0.3rem 0.5rem;
|
|
216
|
+
}
|
|
217
|
+
.ctlbar button {
|
|
218
|
+
background: var(--accent); color: #04122b; border: 0; border-radius: 6px;
|
|
219
|
+
padding: 0.35rem 0.8rem; cursor: pointer; font-weight: 600;
|
|
220
|
+
}
|
|
221
|
+
.ctlbar span { color: var(--muted); }
|
|
222
|
+
|
|
223
|
+
/* Selection checkbox column — keep it tight (one checkbox per cell). */
|
|
224
|
+
table.grid th:has(> [data-mf-conns-all]), table.grid td:has(> .rowcb) {
|
|
225
|
+
width: 1%; text-align: center;
|
|
226
|
+
}
|
|
227
|
+
.rowcb { margin: 0; cursor: pointer; }
|
|
228
|
+
|
|
229
|
+
/* Object-of-interest flag (#131): a per-row toggle button (TOML-managed) or a read-only glyph
|
|
230
|
+
(code-first). Amber when flagged (the brand accent), muted outline otherwise. */
|
|
231
|
+
.flagbtn {
|
|
232
|
+
background: transparent; border: 0; cursor: pointer; padding: 0 0.2rem; line-height: 1;
|
|
233
|
+
color: var(--muted); font-size: 1rem; border-radius: 4px;
|
|
234
|
+
}
|
|
235
|
+
.flagbtn:hover { color: var(--foundry); }
|
|
236
|
+
.flagbtn.flagged { color: var(--foundry); }
|
|
237
|
+
.flagbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
238
|
+
.flagform { margin: 0; }
|
|
239
|
+
.flag { color: var(--muted); }
|
|
240
|
+
.flag.flagged { color: var(--foundry); }
|
|
241
|
+
/* Flagged-only view toggle in the toolbar. */
|
|
242
|
+
.flagfilter { display: inline-flex; align-items: center; gap: 0.3rem; color: var(--muted);
|
|
243
|
+
white-space: nowrap; cursor: pointer; }
|
|
244
|
+
.flagfilter input { cursor: pointer; }
|
|
245
|
+
|
|
246
|
+
/* "Waiting for Reply" per-message outbound state (#136): a small accent badge beside the status pill,
|
|
247
|
+
shown while an outbound awaits an MLLP ACK past its display delay (transient — clears on the next
|
|
248
|
+
poll/ws swap). */
|
|
249
|
+
.waiting { color: var(--accent); font-size: 0.75rem; margin-left: 0.4rem; white-space: nowrap; }
|
|
250
|
+
|
|
251
|
+
.muted { color: var(--muted); }
|
|
252
|
+
.detail-head { display: flex; align-items: center; gap: 1rem; }
|
|
253
|
+
.livestats { color: var(--muted); font-variant-numeric: tabular-nums; min-height: 1.2rem;
|
|
254
|
+
margin: 0.25rem 0 0.5rem; }
|
|
255
|
+
|
|
256
|
+
ul.tree { list-style: none; margin: 0; padding-left: 1rem; border-left: 1px solid var(--line); }
|
|
257
|
+
ul.tree .tlabel { color: var(--muted); }
|
|
258
|
+
ul.tree .tval { color: var(--ink); white-space: pre-wrap; word-break: break-word; }
|
|
259
|
+
|
|
260
|
+
/* Edit-and-resubmit editor (ADR 0090 §9, BACKLOG #153). The editable COPY, the "Modified" badge that
|
|
261
|
+
app.js reveals on the first change, and the mode/outbound controls. */
|
|
262
|
+
.btn-link { color: var(--accent); text-decoration: none; font-size: 0.85rem; }
|
|
263
|
+
.btn-link:hover { text-decoration: underline; }
|
|
264
|
+
.edit-form { max-width: 900px; }
|
|
265
|
+
.edit-raw {
|
|
266
|
+
width: 100%; box-sizing: border-box; background: var(--bg); color: var(--ink);
|
|
267
|
+
border: 1px solid var(--line); border-radius: 6px; padding: 0.5rem;
|
|
268
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.85rem;
|
|
269
|
+
white-space: pre; overflow-wrap: normal; overflow-x: auto; tab-size: 2;
|
|
270
|
+
}
|
|
271
|
+
.edit-mode-row { display: flex; flex-direction: column; gap: 0.3rem; margin: 0.5rem 0; }
|
|
272
|
+
.edit-mode-row label { color: var(--ink); }
|
|
273
|
+
.edit-to-row { flex-direction: row; align-items: center; gap: 0.5rem; }
|
|
274
|
+
.edit-to-row input { background: var(--bg); color: var(--ink); border: 1px solid var(--line);
|
|
275
|
+
border-radius: 6px; padding: 0.35rem 0.5rem; }
|
|
276
|
+
.edit-ctls { align-items: center; margin-top: 0.6rem; }
|
|
277
|
+
.edit-ctls button.primary {
|
|
278
|
+
background: var(--accent); color: #04122b; border: 0; border-radius: 6px;
|
|
279
|
+
padding: 0.35rem 0.9rem; cursor: pointer; font-weight: 600;
|
|
280
|
+
}
|
|
281
|
+
.edit-ctls button[disabled] { opacity: 0.5; cursor: default; }
|
|
282
|
+
.badge-modified {
|
|
283
|
+
color: var(--warn); border: 1px solid var(--warn); border-radius: 999px;
|
|
284
|
+
padding: 0.05rem 0.55rem; font-size: 0.75rem; font-weight: 600;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* Flow & trends (BACKLOG #76): status-colored inline-SVG data-flow graph + queue-trend chart. All
|
|
288
|
+
colours come from the shared theme tokens; the node fill is driven by the live status CLASS (never an
|
|
289
|
+
operator-assigned colour). Wide graphs/charts scroll inside their own wrapper (the page body never
|
|
290
|
+
scrolls horizontally). */
|
|
291
|
+
.flowgraph-wrap, .trendchart-wrap { overflow-x: auto; max-width: 100%; margin-top: 0.5rem; }
|
|
292
|
+
.flowgraph, .trendchart { display: block; }
|
|
293
|
+
.gcolhead { fill: var(--muted); font-size: 12px; font-weight: 600; }
|
|
294
|
+
.gnodelabel { fill: var(--ink); font-size: 12px; }
|
|
295
|
+
/* Node box: a translucent status tint + a solid status-coloured stroke (so a colour-blind operator still
|
|
296
|
+
reads the box outline + the label). "logic" = router/handler nodes (no live status). */
|
|
297
|
+
.gnode { fill: var(--panel); stroke: var(--line); stroke-width: 1.5px; }
|
|
298
|
+
.gnode-logic { stroke: var(--muted); }
|
|
299
|
+
.gnode-running { stroke: var(--ok); }
|
|
300
|
+
.gnode-stopped, .gnode-draining { stroke: var(--muted); }
|
|
301
|
+
.gnode-failed, .gnode-error { stroke: var(--bad); }
|
|
302
|
+
.gnode-filtered, .gnode-stopping { stroke: var(--warn); }
|
|
303
|
+
.gnode-not_deployed { stroke: var(--accent); }
|
|
304
|
+
.gedge { stroke: var(--line); stroke-width: 1.5px; }
|
|
305
|
+
.gedge-declared, .gedge-literal { stroke: var(--muted); }
|
|
306
|
+
.gedge-heuristic { stroke: var(--muted); stroke-dasharray: 4 3; opacity: 0.7; }
|
|
307
|
+
/* Trend chart: axes + up-to-6 overlaid series (colour classes reused by the legend swatches). */
|
|
308
|
+
.trendchart .axis { stroke: var(--line); stroke-width: 1px; }
|
|
309
|
+
.trendchart .axislabel { fill: var(--muted); font-size: 11px; }
|
|
310
|
+
.trendchart .series { stroke-width: 1.5px; }
|
|
311
|
+
.series-0 { stroke: var(--accent); }
|
|
312
|
+
.series-1 { stroke: var(--ok); }
|
|
313
|
+
.series-2 { stroke: var(--warn); }
|
|
314
|
+
.series-3 { stroke: var(--bad); }
|
|
315
|
+
.series-4 { stroke: var(--foundry); }
|
|
316
|
+
.series-5 { stroke: var(--muted); }
|
|
317
|
+
.chart-legend { display: flex; flex-wrap: wrap; gap: 0.9rem; margin: 0.4rem 0; color: var(--muted);
|
|
318
|
+
font-size: 0.8rem; }
|
|
319
|
+
.legend-item { display: inline-flex; align-items: center; gap: 0.35rem; }
|
|
320
|
+
.legend-item .swatch { display: inline-block; width: 0.8rem; height: 0.2rem; border-radius: 2px; }
|
|
321
|
+
.swatch.series-0 { background: var(--accent); }
|
|
322
|
+
.swatch.series-1 { background: var(--ok); }
|
|
323
|
+
.swatch.series-2 { background: var(--warn); }
|
|
324
|
+
.swatch.series-3 { background: var(--bad); }
|
|
325
|
+
.swatch.series-4 { background: var(--foundry); }
|
|
326
|
+
.swatch.series-5 { background: var(--muted); }
|
|
327
|
+
|
|
328
|
+
/* ASVS 3.7.5 — the fail-VISIBLE hardening detects. `.mf-insecure-banner` styles both the two
|
|
329
|
+
script-raised banners (insecure context, CSP not enforced) and the server-rendered
|
|
330
|
+
scripts-blocked banner below, so all three read the same. */
|
|
331
|
+
.mf-insecure-banner {
|
|
332
|
+
margin: 0;
|
|
333
|
+
padding: 0.6rem 1rem;
|
|
334
|
+
background: var(--bad);
|
|
335
|
+
color: #fff;
|
|
336
|
+
font-weight: 600;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* The scripts-blocked banner is rendered by the SERVER and is visible by default; the nonce'd mark
|
|
340
|
+
script in the page shell stamps `mf-scripts-ok` on <html>, which hides it before first paint. A
|
|
341
|
+
browser that enforces CSP without understanding nonce sources — or one with JavaScript disabled —
|
|
342
|
+
never runs that script, so the banner STANDS and the degradation is visible instead of silent.
|
|
343
|
+
Hiding it from CSS (not from JS) is deliberate: `style-src 'self'` keeps working on exactly the
|
|
344
|
+
clients whose `script-src` blocks everything. */
|
|
345
|
+
.mf-scripts-ok #mf-scripts-blocked-banner { display: none; }
|