@fonderie/vue-admin-screens 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1742 -634
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -2
- package/dist/index.d.ts +62 -2
- package/dist/index.js +1707 -600
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/screens/AdminShell.ts
|
|
2
2
|
import { ConfigEditorScreen, ConfigListScreen } from "@fonderie/vue-config-admin-screens";
|
|
3
3
|
import { TemplateEditorScreen, TemplateListScreen } from "@fonderie/vue-courier-admin-screens";
|
|
4
|
-
import { computed as computed2, defineComponent as
|
|
4
|
+
import { computed as computed2, defineComponent as defineComponent14, h as h16, onBeforeUnmount, onMounted, ref as ref7 } from "vue";
|
|
5
5
|
|
|
6
6
|
// src/styles.ts
|
|
7
7
|
var t = {
|
|
@@ -23,7 +23,32 @@ var t = {
|
|
|
23
23
|
trackingDisplay: "var(--fonderie-tracking-display,-0.05em)",
|
|
24
24
|
shadowCard: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
|
|
25
25
|
};
|
|
26
|
-
var
|
|
26
|
+
var tint = (token, pct) => `color-mix(in srgb, ${token} ${pct}%, transparent)`;
|
|
27
|
+
var toneColor = {
|
|
28
|
+
ok: t.accentStrong,
|
|
29
|
+
bad: t.danger,
|
|
30
|
+
// Amber on white fails contrast; mixing in the text colour darkens it in
|
|
31
|
+
// light mode and lightens it in dark, from the same two tokens.
|
|
32
|
+
warn: `color-mix(in srgb, ${t.warning} 70%, ${t.text})`,
|
|
33
|
+
neutral: t.muted,
|
|
34
|
+
info: t.text
|
|
35
|
+
};
|
|
36
|
+
var control = {
|
|
37
|
+
display: "inline-flex",
|
|
38
|
+
alignItems: "center",
|
|
39
|
+
justifyContent: "center",
|
|
40
|
+
gap: 6,
|
|
41
|
+
height: 32,
|
|
42
|
+
boxSizing: "border-box",
|
|
43
|
+
borderRadius: 6,
|
|
44
|
+
padding: "0 12px",
|
|
45
|
+
fontSize: 13,
|
|
46
|
+
fontWeight: 500,
|
|
47
|
+
fontFamily: "inherit",
|
|
48
|
+
lineHeight: 1,
|
|
49
|
+
whiteSpace: "nowrap"
|
|
50
|
+
};
|
|
51
|
+
var raw = {
|
|
27
52
|
shell: {
|
|
28
53
|
display: "flex",
|
|
29
54
|
minHeight: "100vh",
|
|
@@ -31,137 +56,568 @@ var styles = {
|
|
|
31
56
|
color: t.text,
|
|
32
57
|
background: t.bg
|
|
33
58
|
},
|
|
34
|
-
//
|
|
35
|
-
//
|
|
59
|
+
// Sticky, full height, scrolls on its own: the nav never scrolls away with
|
|
60
|
+
// a long table, and the footer (session controls) stays in reach.
|
|
36
61
|
nav: {
|
|
37
|
-
width:
|
|
38
|
-
borderRight: `1px solid ${t.border}`,
|
|
39
|
-
padding: 16,
|
|
62
|
+
width: 248,
|
|
40
63
|
flexShrink: 0,
|
|
41
|
-
|
|
64
|
+
boxSizing: "border-box",
|
|
65
|
+
borderRight: `1px solid ${t.border}`,
|
|
66
|
+
background: t.surface,
|
|
67
|
+
position: "sticky",
|
|
68
|
+
top: 0,
|
|
69
|
+
height: "100vh",
|
|
70
|
+
display: "flex",
|
|
71
|
+
flexDirection: "column"
|
|
72
|
+
},
|
|
73
|
+
navBrand: {
|
|
74
|
+
display: "flex",
|
|
75
|
+
alignItems: "center",
|
|
76
|
+
gap: 10,
|
|
77
|
+
padding: "18px 16px 14px",
|
|
78
|
+
borderBottom: `1px solid ${t.borderLight}`
|
|
79
|
+
},
|
|
80
|
+
navMark: {
|
|
81
|
+
width: 28,
|
|
82
|
+
height: 28,
|
|
83
|
+
borderRadius: 7,
|
|
84
|
+
background: t.text,
|
|
85
|
+
color: t.surface,
|
|
86
|
+
display: "grid",
|
|
87
|
+
placeItems: "center",
|
|
88
|
+
flexShrink: 0
|
|
89
|
+
},
|
|
90
|
+
navBrandTitle: {
|
|
91
|
+
fontSize: 14,
|
|
92
|
+
fontWeight: 600,
|
|
93
|
+
letterSpacing: t.trackingDisplay,
|
|
94
|
+
lineHeight: 1.2,
|
|
95
|
+
overflow: "hidden",
|
|
96
|
+
textOverflow: "ellipsis",
|
|
97
|
+
whiteSpace: "nowrap"
|
|
42
98
|
},
|
|
99
|
+
navScroll: { flex: 1, overflowY: "auto", padding: "8px 10px 16px" },
|
|
43
100
|
navGroup: {
|
|
44
101
|
fontSize: 11,
|
|
45
102
|
textTransform: "uppercase",
|
|
46
103
|
letterSpacing: 0.6,
|
|
47
104
|
color: t.muted,
|
|
48
|
-
margin: "16px
|
|
105
|
+
margin: "16px 8px 4px",
|
|
49
106
|
fontWeight: 600
|
|
50
107
|
},
|
|
51
108
|
navItem: {
|
|
52
|
-
display: "
|
|
109
|
+
display: "flex",
|
|
110
|
+
alignItems: "center",
|
|
111
|
+
gap: 10,
|
|
53
112
|
width: "100%",
|
|
54
113
|
textAlign: "left",
|
|
55
114
|
background: "none",
|
|
56
115
|
border: "none",
|
|
57
|
-
borderRadius:
|
|
58
|
-
padding: "
|
|
116
|
+
borderRadius: 6,
|
|
117
|
+
padding: "7px 8px",
|
|
59
118
|
cursor: "pointer",
|
|
60
|
-
fontSize:
|
|
61
|
-
|
|
62
|
-
|
|
119
|
+
fontSize: 13.5,
|
|
120
|
+
fontWeight: 500,
|
|
121
|
+
color: t.muted,
|
|
122
|
+
fontFamily: "inherit",
|
|
123
|
+
position: "relative"
|
|
124
|
+
},
|
|
125
|
+
navItemActive: { background: tint(t.accent, 12), color: t.text, fontWeight: 600 },
|
|
126
|
+
navIconActive: { color: t.accentStrong },
|
|
127
|
+
navFooter: {
|
|
128
|
+
borderTop: `1px solid ${t.borderLight}`,
|
|
129
|
+
padding: 12,
|
|
130
|
+
display: "flex",
|
|
131
|
+
flexDirection: "column",
|
|
132
|
+
gap: 10
|
|
133
|
+
},
|
|
134
|
+
// Phone layout: a top bar with the menu button replaces the sidebar, which
|
|
135
|
+
// opens as a drawer over the content.
|
|
136
|
+
topbar: {
|
|
137
|
+
position: "sticky",
|
|
138
|
+
top: 0,
|
|
139
|
+
zIndex: 20,
|
|
140
|
+
display: "flex",
|
|
141
|
+
alignItems: "center",
|
|
142
|
+
gap: 10,
|
|
143
|
+
padding: "10px 16px",
|
|
144
|
+
background: t.surface,
|
|
145
|
+
borderBottom: `1px solid ${t.border}`
|
|
146
|
+
},
|
|
147
|
+
drawer: {
|
|
148
|
+
position: "fixed",
|
|
149
|
+
inset: 0,
|
|
150
|
+
zIndex: 30,
|
|
151
|
+
display: "flex"
|
|
152
|
+
},
|
|
153
|
+
scrim: { flex: 1, background: "rgba(0,0,0,.35)" },
|
|
154
|
+
main: { flex: 1, minWidth: 0 },
|
|
155
|
+
container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
|
|
156
|
+
// ── page header ──
|
|
157
|
+
header: {
|
|
158
|
+
display: "flex",
|
|
159
|
+
alignItems: "flex-start",
|
|
160
|
+
justifyContent: "space-between",
|
|
161
|
+
gap: 16,
|
|
162
|
+
marginBottom: 24,
|
|
163
|
+
flexWrap: "wrap"
|
|
63
164
|
},
|
|
64
|
-
navItemActive: { background: t.surfaceAlt, fontWeight: 600, color: t.accentStrong },
|
|
65
|
-
main: { flex: 1, padding: 24, maxWidth: 960 },
|
|
66
|
-
container: { padding: 24, maxWidth: 960 },
|
|
67
165
|
title: {
|
|
68
|
-
fontSize:
|
|
166
|
+
fontSize: 22,
|
|
69
167
|
fontWeight: 600,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
168
|
+
margin: 0,
|
|
169
|
+
letterSpacing: t.trackingDisplay,
|
|
170
|
+
lineHeight: 1.25
|
|
73
171
|
},
|
|
172
|
+
lead: { color: t.muted, fontSize: 13.5, margin: "4px 0 0", lineHeight: 1.5 },
|
|
173
|
+
actions: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" },
|
|
74
174
|
subtitle: {
|
|
75
175
|
fontSize: 15,
|
|
76
176
|
fontWeight: 600,
|
|
77
|
-
marginTop:
|
|
78
|
-
marginBottom:
|
|
79
|
-
letterSpacing: t.trackingDisplay
|
|
177
|
+
marginTop: 32,
|
|
178
|
+
marginBottom: 10,
|
|
179
|
+
letterSpacing: t.trackingDisplay,
|
|
180
|
+
display: "flex",
|
|
181
|
+
alignItems: "center",
|
|
182
|
+
gap: 8
|
|
183
|
+
},
|
|
184
|
+
// ── feedback ──
|
|
185
|
+
status: { padding: "24px 0", color: t.muted, fontSize: 13.5 },
|
|
186
|
+
error: {
|
|
187
|
+
color: t.danger,
|
|
188
|
+
background: tint(t.danger, 8),
|
|
189
|
+
border: `1px solid ${tint(t.danger, 25)}`,
|
|
190
|
+
borderRadius: t.radiusLg,
|
|
191
|
+
padding: "10px 14px",
|
|
192
|
+
marginBottom: 16,
|
|
193
|
+
fontSize: 13.5
|
|
194
|
+
},
|
|
195
|
+
notice: {
|
|
196
|
+
color: t.text,
|
|
197
|
+
background: tint(t.warning, 10),
|
|
198
|
+
border: `1px solid ${tint(t.warning, 35)}`,
|
|
199
|
+
borderRadius: t.radiusLg,
|
|
200
|
+
padding: "10px 14px",
|
|
201
|
+
marginBottom: 16,
|
|
202
|
+
fontSize: 13.5
|
|
80
203
|
},
|
|
81
|
-
status: { padding: 12, color: t.muted },
|
|
82
|
-
error: { color: t.danger, marginBottom: 12, fontSize: 14 },
|
|
83
204
|
ok: { color: t.accentStrong, fontWeight: 600 },
|
|
84
205
|
bad: { color: t.danger, fontWeight: 600 },
|
|
85
206
|
advice: { color: t.warning },
|
|
86
207
|
muted: { color: t.muted, fontSize: 13 },
|
|
87
|
-
mono: { fontFamily: t.mono, fontSize:
|
|
88
|
-
|
|
208
|
+
mono: { fontFamily: t.mono, fontSize: 12.5 },
|
|
209
|
+
code: {
|
|
210
|
+
fontFamily: t.mono,
|
|
211
|
+
fontSize: 12,
|
|
212
|
+
background: t.surfaceAlt,
|
|
213
|
+
border: `1px solid ${t.borderLight}`,
|
|
214
|
+
borderRadius: 4,
|
|
215
|
+
padding: "1px 5px"
|
|
216
|
+
},
|
|
217
|
+
// ── surfaces ──
|
|
218
|
+
// Tables and lists are cards themselves, so every screen gets the framed,
|
|
219
|
+
// rounded look without wrapping each one. The last row's rule is removed in
|
|
220
|
+
// the shell stylesheet (a pseudo-selector); embedded without the shell it
|
|
221
|
+
// simply doubles against the frame.
|
|
222
|
+
table: {
|
|
223
|
+
width: "100%",
|
|
224
|
+
borderCollapse: "separate",
|
|
225
|
+
borderSpacing: 0,
|
|
226
|
+
fontSize: 13.5,
|
|
227
|
+
background: t.surface,
|
|
228
|
+
border: `1px solid ${t.border}`,
|
|
229
|
+
borderRadius: t.radiusLg,
|
|
230
|
+
overflow: "hidden",
|
|
231
|
+
boxShadow: t.shadowCard
|
|
232
|
+
},
|
|
89
233
|
th: {
|
|
90
234
|
textAlign: "left",
|
|
91
235
|
borderBottom: `1px solid ${t.border}`,
|
|
92
|
-
padding: "
|
|
236
|
+
padding: "9px 14px",
|
|
93
237
|
fontWeight: 600,
|
|
94
|
-
|
|
238
|
+
fontSize: 11.5,
|
|
239
|
+
textTransform: "uppercase",
|
|
240
|
+
letterSpacing: 0.4,
|
|
241
|
+
color: t.muted,
|
|
242
|
+
background: t.surfaceAlt,
|
|
243
|
+
whiteSpace: "nowrap"
|
|
244
|
+
},
|
|
245
|
+
td: {
|
|
246
|
+
borderBottom: `1px solid ${t.borderLight}`,
|
|
247
|
+
padding: "11px 14px",
|
|
248
|
+
verticalAlign: "top"
|
|
249
|
+
},
|
|
250
|
+
list: {
|
|
251
|
+
listStyle: "none",
|
|
252
|
+
padding: 0,
|
|
253
|
+
margin: 0,
|
|
254
|
+
background: t.surface,
|
|
255
|
+
border: `1px solid ${t.border}`,
|
|
256
|
+
borderRadius: t.radiusLg,
|
|
257
|
+
overflow: "hidden",
|
|
258
|
+
boxShadow: t.shadowCard
|
|
259
|
+
},
|
|
260
|
+
row: { borderBottom: `1px solid ${t.borderLight}`, padding: "12px 16px" },
|
|
261
|
+
card: {
|
|
262
|
+
background: t.surface,
|
|
263
|
+
border: `1px solid ${t.border}`,
|
|
264
|
+
borderRadius: t.radiusLg,
|
|
265
|
+
padding: 16,
|
|
266
|
+
boxShadow: t.shadowCard
|
|
267
|
+
},
|
|
268
|
+
grid: {
|
|
269
|
+
display: "grid",
|
|
270
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))",
|
|
271
|
+
gap: 12,
|
|
272
|
+
marginBottom: 24
|
|
273
|
+
},
|
|
274
|
+
statLabel: { fontSize: 12, color: t.muted, fontWeight: 500 },
|
|
275
|
+
statValue: {
|
|
276
|
+
fontSize: 26,
|
|
277
|
+
fontWeight: 600,
|
|
278
|
+
letterSpacing: t.trackingDisplay,
|
|
279
|
+
lineHeight: 1.2,
|
|
280
|
+
marginTop: 6
|
|
281
|
+
},
|
|
282
|
+
statHint: { fontSize: 12, color: t.muted, marginTop: 2 },
|
|
283
|
+
empty: {
|
|
284
|
+
background: t.surface,
|
|
285
|
+
border: `1px dashed ${t.border}`,
|
|
286
|
+
borderRadius: t.radiusLg,
|
|
287
|
+
padding: "40px 24px",
|
|
288
|
+
textAlign: "center",
|
|
289
|
+
color: t.muted,
|
|
290
|
+
fontSize: 13.5
|
|
291
|
+
},
|
|
292
|
+
emptyTitle: { color: t.text, fontWeight: 600, fontSize: 14.5, margin: "10px 0 4px" },
|
|
293
|
+
dl: { display: "grid", gridTemplateColumns: "160px 1fr", margin: 0 },
|
|
294
|
+
dt: {
|
|
295
|
+
color: t.muted,
|
|
296
|
+
fontSize: 13,
|
|
297
|
+
padding: "10px 16px",
|
|
298
|
+
borderBottom: `1px solid ${t.borderLight}`
|
|
299
|
+
},
|
|
300
|
+
dd: {
|
|
301
|
+
margin: 0,
|
|
302
|
+
padding: "10px 16px",
|
|
303
|
+
borderBottom: `1px solid ${t.borderLight}`,
|
|
304
|
+
fontSize: 13.5,
|
|
305
|
+
minWidth: 0
|
|
95
306
|
},
|
|
96
|
-
td: { borderBottom: `1px solid ${t.borderLight}`, padding: "6px 8px", verticalAlign: "top" },
|
|
97
|
-
list: { listStyle: "none", padding: 0, margin: 0 },
|
|
98
|
-
row: { borderBottom: `1px solid ${t.borderLight}`, padding: "8px 0" },
|
|
99
307
|
badge: {
|
|
100
|
-
display: "inline-
|
|
308
|
+
display: "inline-flex",
|
|
309
|
+
alignItems: "center",
|
|
310
|
+
gap: 5,
|
|
101
311
|
borderRadius: 999,
|
|
102
|
-
padding: "
|
|
103
|
-
fontSize:
|
|
312
|
+
padding: "2px 8px",
|
|
313
|
+
fontSize: 11.5,
|
|
314
|
+
fontWeight: 500,
|
|
315
|
+
lineHeight: 1.5,
|
|
104
316
|
background: t.surfaceAlt,
|
|
105
317
|
border: `1px solid ${t.border}`,
|
|
106
|
-
color: t.muted
|
|
318
|
+
color: t.muted,
|
|
319
|
+
whiteSpace: "nowrap"
|
|
107
320
|
},
|
|
321
|
+
pill: {
|
|
322
|
+
display: "inline-flex",
|
|
323
|
+
alignItems: "center",
|
|
324
|
+
gap: 6,
|
|
325
|
+
borderRadius: 999,
|
|
326
|
+
padding: "2px 9px 2px 8px",
|
|
327
|
+
fontSize: 12,
|
|
328
|
+
fontWeight: 600,
|
|
329
|
+
lineHeight: 1.5,
|
|
330
|
+
whiteSpace: "nowrap",
|
|
331
|
+
verticalAlign: "baseline"
|
|
332
|
+
},
|
|
333
|
+
dot: { width: 6, height: 6, borderRadius: 999, flexShrink: 0, background: "currentColor" },
|
|
334
|
+
// ── controls ──
|
|
108
335
|
button: {
|
|
336
|
+
...control,
|
|
109
337
|
background: t.surface,
|
|
110
338
|
border: `1px solid ${t.border}`,
|
|
111
|
-
borderRadius: t.radius,
|
|
112
|
-
padding: "4px 10px",
|
|
113
|
-
cursor: "pointer",
|
|
114
|
-
fontSize: 13,
|
|
115
339
|
color: t.text,
|
|
116
|
-
|
|
340
|
+
cursor: "pointer",
|
|
117
341
|
boxShadow: t.shadowCard
|
|
118
342
|
},
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
343
|
+
buttonPrimary: {
|
|
344
|
+
...control,
|
|
345
|
+
background: t.text,
|
|
346
|
+
border: `1px solid ${t.text}`,
|
|
347
|
+
color: t.surface,
|
|
348
|
+
cursor: "pointer"
|
|
349
|
+
},
|
|
350
|
+
buttonDanger: {
|
|
351
|
+
...control,
|
|
123
352
|
background: t.surface,
|
|
353
|
+
border: `1px solid ${tint(t.danger, 40)}`,
|
|
354
|
+
color: t.danger,
|
|
355
|
+
cursor: "pointer"
|
|
356
|
+
},
|
|
357
|
+
buttonGhost: {
|
|
358
|
+
...control,
|
|
359
|
+
background: "none",
|
|
360
|
+
border: "1px solid transparent",
|
|
361
|
+
color: t.muted,
|
|
362
|
+
cursor: "pointer",
|
|
363
|
+
padding: "0 8px"
|
|
364
|
+
},
|
|
365
|
+
link: {
|
|
366
|
+
background: "none",
|
|
367
|
+
border: "none",
|
|
368
|
+
padding: 0,
|
|
369
|
+
color: t.text,
|
|
370
|
+
fontFamily: "inherit",
|
|
371
|
+
fontSize: "inherit",
|
|
372
|
+
fontWeight: 500,
|
|
373
|
+
cursor: "pointer",
|
|
374
|
+
textAlign: "left"
|
|
375
|
+
},
|
|
376
|
+
input: {
|
|
377
|
+
height: 32,
|
|
378
|
+
boxSizing: "border-box",
|
|
124
379
|
border: `1px solid ${t.border}`,
|
|
125
|
-
borderRadius:
|
|
126
|
-
padding:
|
|
127
|
-
|
|
380
|
+
borderRadius: 6,
|
|
381
|
+
padding: "0 10px",
|
|
382
|
+
fontSize: 13.5,
|
|
383
|
+
fontFamily: "inherit",
|
|
384
|
+
background: t.surface,
|
|
385
|
+
color: t.text,
|
|
386
|
+
minWidth: 0
|
|
128
387
|
},
|
|
388
|
+
toolbar: { display: "flex", gap: 8, alignItems: "center", marginBottom: 16, flexWrap: "wrap" },
|
|
129
389
|
accent: { color: t.accent }
|
|
130
390
|
};
|
|
391
|
+
var UNITLESS = /* @__PURE__ */ new Set([
|
|
392
|
+
"fontWeight",
|
|
393
|
+
"lineHeight",
|
|
394
|
+
"opacity",
|
|
395
|
+
"zIndex",
|
|
396
|
+
"flex",
|
|
397
|
+
"flexGrow",
|
|
398
|
+
"flexShrink",
|
|
399
|
+
"order"
|
|
400
|
+
]);
|
|
401
|
+
function withUnits(sheet) {
|
|
402
|
+
const out = {};
|
|
403
|
+
for (const [name, rule] of Object.entries(sheet)) {
|
|
404
|
+
const fixed = {};
|
|
405
|
+
for (const [prop, value] of Object.entries(rule)) {
|
|
406
|
+
fixed[prop] = typeof value === "number" && !UNITLESS.has(prop) ? `${value}px` : value;
|
|
407
|
+
}
|
|
408
|
+
out[name] = fixed;
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
}
|
|
412
|
+
var styles = withUnits(raw);
|
|
413
|
+
|
|
414
|
+
// src/ui.ts
|
|
415
|
+
import { h } from "vue";
|
|
416
|
+
|
|
417
|
+
// src/icons.ts
|
|
418
|
+
var ICONS = {
|
|
419
|
+
attention: ["M22 12h-4l-3 9L9 3l-3 9H2"],
|
|
420
|
+
modules: [
|
|
421
|
+
"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z",
|
|
422
|
+
"M3.3 7 12 12l8.7-5",
|
|
423
|
+
"M12 22V12"
|
|
424
|
+
],
|
|
425
|
+
environment: [
|
|
426
|
+
"M4 21v-7",
|
|
427
|
+
"M4 10V3",
|
|
428
|
+
"M12 21v-9",
|
|
429
|
+
"M12 8V3",
|
|
430
|
+
"M20 21v-5",
|
|
431
|
+
"M20 12V3",
|
|
432
|
+
"M1 14h6",
|
|
433
|
+
"M9 8h6",
|
|
434
|
+
"M17 16h6"
|
|
435
|
+
],
|
|
436
|
+
doctor: [
|
|
437
|
+
"M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"
|
|
438
|
+
],
|
|
439
|
+
routes: [
|
|
440
|
+
"M6 3v12",
|
|
441
|
+
"M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",
|
|
442
|
+
"M6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",
|
|
443
|
+
"M18 9a9 9 0 0 1-9 9"
|
|
444
|
+
],
|
|
445
|
+
users: [
|
|
446
|
+
"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",
|
|
447
|
+
"M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z",
|
|
448
|
+
"M22 21v-2a4 4 0 0 0-3-3.87",
|
|
449
|
+
"M16 3.13a4 4 0 0 1 0 7.75"
|
|
450
|
+
],
|
|
451
|
+
catalog: [
|
|
452
|
+
"M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",
|
|
453
|
+
"M7.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z"
|
|
454
|
+
],
|
|
455
|
+
subscriber: ["M2 7h20v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2z", "M2 11h20", "M6 15h4"],
|
|
456
|
+
settings: [
|
|
457
|
+
"M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",
|
|
458
|
+
"M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"
|
|
459
|
+
],
|
|
460
|
+
templates: [
|
|
461
|
+
"M22 7l-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7",
|
|
462
|
+
"M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"
|
|
463
|
+
],
|
|
464
|
+
audit: ["M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z", "M9 12l2 2 4-4"],
|
|
465
|
+
log: ["M12 8v4l3 3", "M3.05 11a9 9 0 1 1 .5 4m-.5 5v-5h5"],
|
|
466
|
+
tokens: [
|
|
467
|
+
"M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z",
|
|
468
|
+
"M16.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z"
|
|
469
|
+
],
|
|
470
|
+
migrations: [
|
|
471
|
+
"M12 22c4.97 0 9-1.79 9-4V6",
|
|
472
|
+
"M3 6v12c0 2.21 4.03 4 9 4",
|
|
473
|
+
"M12 10c4.97 0 9-1.79 9-4s-4.03-4-9-4-9 1.79-9 4 4.03 4 9 4z",
|
|
474
|
+
"M3 12c0 2.21 4.03 4 9 4s9-1.79 9-4"
|
|
475
|
+
],
|
|
476
|
+
menu: ["M4 6h16", "M4 12h16", "M4 18h16"],
|
|
477
|
+
close: ["M18 6 6 18", "m6 6 12 12"],
|
|
478
|
+
refresh: [
|
|
479
|
+
"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",
|
|
480
|
+
"M21 3v5h-5",
|
|
481
|
+
"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",
|
|
482
|
+
"M8 16H3v5"
|
|
483
|
+
],
|
|
484
|
+
check: ["M20 6 9 17l-5-5"],
|
|
485
|
+
alert: [
|
|
486
|
+
"M12 9v4",
|
|
487
|
+
"M12 17h.01",
|
|
488
|
+
"M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
|
489
|
+
],
|
|
490
|
+
search: ["M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", "m21 21-4.3-4.3"],
|
|
491
|
+
back: ["m12 19-7-7 7-7", "M19 12H5"],
|
|
492
|
+
logout: ["M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4", "m16 17 5-5-5-5", "M21 12H9"],
|
|
493
|
+
lock: [
|
|
494
|
+
"M5 11h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2z",
|
|
495
|
+
"M7 11V7a5 5 0 0 1 10 0v4"
|
|
496
|
+
],
|
|
497
|
+
plus: ["M12 5v14", "M5 12h14"],
|
|
498
|
+
mark: [
|
|
499
|
+
"M4 3h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z",
|
|
500
|
+
"M15 3h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z",
|
|
501
|
+
"M15 14h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1z",
|
|
502
|
+
"M4 14h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1z"
|
|
503
|
+
]
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// src/ui.ts
|
|
507
|
+
function icon(name, size = 16, style) {
|
|
508
|
+
return h(
|
|
509
|
+
"svg",
|
|
510
|
+
{
|
|
511
|
+
width: size,
|
|
512
|
+
height: size,
|
|
513
|
+
viewBox: "0 0 24 24",
|
|
514
|
+
fill: "none",
|
|
515
|
+
stroke: "currentColor",
|
|
516
|
+
"stroke-width": 2,
|
|
517
|
+
"stroke-linecap": "round",
|
|
518
|
+
"stroke-linejoin": "round",
|
|
519
|
+
"aria-hidden": "true",
|
|
520
|
+
style: { flexShrink: 0, ...style }
|
|
521
|
+
},
|
|
522
|
+
ICONS[name].map((d) => h("path", { key: d, d }))
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
function pageHeader(title, lead, actions) {
|
|
526
|
+
return h("div", { style: styles.header }, [
|
|
527
|
+
h("div", { style: { minWidth: 0 } }, [
|
|
528
|
+
h("h1", { style: styles.title }, title),
|
|
529
|
+
lead ? h("p", { style: styles.lead }, lead) : null
|
|
530
|
+
]),
|
|
531
|
+
actions?.filter(Boolean).length ? h("div", { style: styles.actions }, actions) : null
|
|
532
|
+
]);
|
|
533
|
+
}
|
|
534
|
+
function pill(tone, text, dot = true) {
|
|
535
|
+
const color = toneColor[tone];
|
|
536
|
+
return h(
|
|
537
|
+
"span",
|
|
538
|
+
{ style: { ...styles.pill, color, background: tint(color, tone === "neutral" ? 10 : 13) } },
|
|
539
|
+
[dot ? h("span", { style: styles.dot }) : null, text]
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
function stat(label, value, hint, tone) {
|
|
543
|
+
return h("div", { style: styles.card }, [
|
|
544
|
+
h("div", { style: styles.statLabel }, label),
|
|
545
|
+
h(
|
|
546
|
+
"div",
|
|
547
|
+
{ style: { ...styles.statValue, ...tone ? { color: toneColor[tone] } : {} } },
|
|
548
|
+
value
|
|
549
|
+
),
|
|
550
|
+
hint ? h("div", { style: styles.statHint }, hint) : null
|
|
551
|
+
]);
|
|
552
|
+
}
|
|
553
|
+
function empty(title, body, iconName = "check") {
|
|
554
|
+
return h("div", { style: styles.empty }, [
|
|
555
|
+
icon(iconName, 22),
|
|
556
|
+
h("div", { style: styles.emptyTitle }, title),
|
|
557
|
+
body ? h("div", body) : null
|
|
558
|
+
]);
|
|
559
|
+
}
|
|
560
|
+
function methodChip(method) {
|
|
561
|
+
const tone = method === "GET" ? "ok" : method === "DELETE" ? "bad" : method === "POST" ? "info" : "warn";
|
|
562
|
+
const color = toneColor[tone];
|
|
563
|
+
return h(
|
|
564
|
+
"span",
|
|
565
|
+
{
|
|
566
|
+
style: {
|
|
567
|
+
...styles.mono,
|
|
568
|
+
fontSize: "11px",
|
|
569
|
+
fontWeight: 700,
|
|
570
|
+
color,
|
|
571
|
+
background: tint(color, 12),
|
|
572
|
+
borderRadius: "4px",
|
|
573
|
+
padding: "2px 6px",
|
|
574
|
+
display: "inline-block",
|
|
575
|
+
minWidth: "44px",
|
|
576
|
+
textAlign: "center"
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
method
|
|
580
|
+
);
|
|
581
|
+
}
|
|
131
582
|
|
|
132
583
|
// src/screens/AdminLogScreen.ts
|
|
133
584
|
import { useAdminLog } from "@fonderie/vue-admin";
|
|
134
|
-
import { defineComponent, h as
|
|
585
|
+
import { defineComponent, h as h3 } from "vue";
|
|
135
586
|
|
|
136
587
|
// src/screens/common.ts
|
|
137
|
-
import { h } from "vue";
|
|
588
|
+
import { h as h2 } from "vue";
|
|
138
589
|
function page(title, state, content, toolbar = [], options = {}) {
|
|
139
|
-
const body = state.isLoading.value ?
|
|
590
|
+
const body = state.isLoading.value ? h2("p", { style: styles.status }, options.loadingText ?? "Loading\u2026") : state.error.value ? h2(
|
|
140
591
|
"p",
|
|
141
592
|
{ style: styles.error, role: "alert" },
|
|
142
593
|
(options.errorText ?? ((e) => e.explanation))(state.error.value)
|
|
143
594
|
) : content();
|
|
144
|
-
return
|
|
145
|
-
h("div", { style: styles.toolbar }, [
|
|
146
|
-
h("h1", { style: { ...styles.title, marginBottom: 0 } }, title),
|
|
147
|
-
...toolbar
|
|
148
|
-
]),
|
|
149
|
-
body
|
|
150
|
-
]);
|
|
595
|
+
return h2("div", { style: styles.container }, [pageHeader(title, options.lead, toolbar), body]);
|
|
151
596
|
}
|
|
152
597
|
function table(headers, rows) {
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
|
|
598
|
+
return h2("table", { style: styles.table }, [
|
|
599
|
+
h2("thead", [
|
|
600
|
+
h2(
|
|
156
601
|
"tr",
|
|
157
|
-
headers.map(
|
|
602
|
+
headers.map(
|
|
603
|
+
(t2) => typeof t2 === "string" ? h2("th", { style: styles.th }, t2) : h2("th", { style: { ...styles.th, ...t2.style } }, t2.label)
|
|
604
|
+
)
|
|
158
605
|
)
|
|
159
606
|
]),
|
|
160
|
-
|
|
607
|
+
h2("tbody", rows)
|
|
161
608
|
]);
|
|
162
609
|
}
|
|
163
|
-
var td = (children, extra = {}) =>
|
|
164
|
-
var refreshButton = (label, disabled, onClick) =>
|
|
610
|
+
var td = (children, extra = {}) => h2("td", { style: { ...styles.td, ...extra } }, children);
|
|
611
|
+
var refreshButton = (label, disabled, onClick) => h2("button", { type: "button", style: styles.button, disabled, onClick }, [
|
|
612
|
+
icon("refresh", 14),
|
|
613
|
+
disabled ? "Working\u2026" : label
|
|
614
|
+
]);
|
|
615
|
+
var loadMoreButton = (onClick) => h2(
|
|
616
|
+
"button",
|
|
617
|
+
{ type: "button", style: { ...styles.button, marginTop: "12px" }, onClick },
|
|
618
|
+
"Load more"
|
|
619
|
+
);
|
|
620
|
+
var actionButton = (label, disabled, onClick, style = styles.button) => h2("button", { type: "button", style, disabled, onClick }, label);
|
|
165
621
|
|
|
166
622
|
// src/screens/AdminLogScreen.ts
|
|
167
623
|
var AdminLogScreen = defineComponent({
|
|
@@ -174,147 +630,269 @@ var AdminLogScreen = defineComponent({
|
|
|
174
630
|
const { entries, hasMore, isLoading, error, refresh, loadMore } = useAdminLog(props.client, {
|
|
175
631
|
limit: props.pageSize
|
|
176
632
|
});
|
|
177
|
-
return () =>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
633
|
+
return () => h3("div", { style: styles.container }, [
|
|
634
|
+
pageHeader(
|
|
635
|
+
"Admin log",
|
|
636
|
+
"Every request made through this surface, newest first \u2014 refused ones included.",
|
|
637
|
+
[refreshButton("Refresh", isLoading.value, () => void refresh())]
|
|
638
|
+
),
|
|
639
|
+
error.value ? h3(
|
|
183
640
|
"p",
|
|
184
641
|
{ style: styles.error, role: "alert" },
|
|
185
642
|
error.value.status === 404 ? "The admin log is off \u2014 give AdminModule a store." : error.value.explanation
|
|
186
|
-
) : [
|
|
643
|
+
) : entries.value.length === 0 && !isLoading.value ? empty("No requests yet", void 0, "log") : [
|
|
187
644
|
table(
|
|
188
645
|
["When", "Actor", "Request", "Status", "Module"],
|
|
189
646
|
entries.value.map(
|
|
190
|
-
(e) =>
|
|
191
|
-
td(new Date(e.at).toLocaleString(),
|
|
647
|
+
(e) => h3("tr", { key: e.id }, [
|
|
648
|
+
td(new Date(e.at).toLocaleString(), {
|
|
649
|
+
...styles.muted,
|
|
650
|
+
whiteSpace: "nowrap"
|
|
651
|
+
}),
|
|
192
652
|
td(e.actor),
|
|
193
|
-
td(`${e.method} ${e.path}`, styles.mono),
|
|
194
653
|
td([
|
|
195
|
-
|
|
196
|
-
"span",
|
|
197
|
-
{ style: e.status >= 400 ? styles.bad : styles.ok },
|
|
198
|
-
String(e.status)
|
|
199
|
-
),
|
|
654
|
+
methodChip(e.method),
|
|
200
655
|
" ",
|
|
201
|
-
|
|
656
|
+
h3("span", { style: { ...styles.mono, wordBreak: "break-all" } }, e.path)
|
|
202
657
|
]),
|
|
658
|
+
td(
|
|
659
|
+
[
|
|
660
|
+
pill(
|
|
661
|
+
e.status >= 500 ? "bad" : e.status >= 400 ? "warn" : "ok",
|
|
662
|
+
String(e.status)
|
|
663
|
+
),
|
|
664
|
+
" ",
|
|
665
|
+
h3("span", { style: styles.muted }, `${e.durationMs} ms`)
|
|
666
|
+
],
|
|
667
|
+
{ whiteSpace: "nowrap" }
|
|
668
|
+
),
|
|
203
669
|
td(e.module, styles.muted)
|
|
204
670
|
])
|
|
205
671
|
)
|
|
206
672
|
),
|
|
207
|
-
isLoading.value ?
|
|
208
|
-
hasMore.value && !isLoading.value ?
|
|
209
|
-
"button",
|
|
210
|
-
{
|
|
211
|
-
type: "button",
|
|
212
|
-
style: { ...styles.button, marginTop: "12px" },
|
|
213
|
-
onClick: () => void loadMore()
|
|
214
|
-
},
|
|
215
|
-
"Load more"
|
|
216
|
-
) : null
|
|
673
|
+
isLoading.value ? h3("p", { style: styles.status }, "Loading\u2026") : null,
|
|
674
|
+
hasMore.value && !isLoading.value ? loadMoreButton(() => void loadMore()) : null
|
|
217
675
|
]
|
|
218
676
|
]);
|
|
219
677
|
}
|
|
220
678
|
});
|
|
221
679
|
|
|
222
680
|
// src/screens/AttentionScreen.ts
|
|
223
|
-
import { useAttention } from "@fonderie/vue-admin";
|
|
224
|
-
import { defineComponent as defineComponent2, h as
|
|
681
|
+
import { useAttention, useManifest } from "@fonderie/vue-admin";
|
|
682
|
+
import { defineComponent as defineComponent2, h as h4 } from "vue";
|
|
225
683
|
var AttentionScreen = defineComponent2({
|
|
226
684
|
name: "FonderieAttentionScreen",
|
|
227
685
|
props: { client: { type: Object, required: true } },
|
|
228
686
|
setup(props) {
|
|
229
687
|
const { attention, isLoading, error, refresh } = useAttention(props.client);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
"
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
688
|
+
const { manifest } = useManifest(props.client);
|
|
689
|
+
return () => {
|
|
690
|
+
const a = attention.value;
|
|
691
|
+
const m = manifest.value;
|
|
692
|
+
const errors = a?.items.filter((i) => i.severity === "error") ?? [];
|
|
693
|
+
const advice = a?.items.filter((i) => i.severity !== "error") ?? [];
|
|
694
|
+
const ready = m?.modules.filter((x) => x.readiness.ok).length ?? 0;
|
|
695
|
+
const total = m?.modules.length ?? 0;
|
|
696
|
+
const tiles = h4("div", { style: styles.grid }, [
|
|
697
|
+
stat(
|
|
698
|
+
"Needs action",
|
|
699
|
+
a ? errors.length : "\u2014",
|
|
700
|
+
"errors to fix",
|
|
701
|
+
errors.length ? "bad" : a ? "ok" : void 0
|
|
702
|
+
),
|
|
703
|
+
stat("Advice", a ? advice.length : "\u2014", "worth a look", advice.length ? "warn" : void 0),
|
|
704
|
+
stat(
|
|
705
|
+
"Modules ready",
|
|
706
|
+
m ? `${ready}/${total}` : "\u2014",
|
|
707
|
+
m ? `admin ${m.admin.version}` : void 0,
|
|
708
|
+
m && ready < total ? "warn" : m ? "ok" : void 0
|
|
709
|
+
),
|
|
710
|
+
stat("Routes", m ? m.routes.length : "\u2014", m ? m.env : void 0)
|
|
711
|
+
]);
|
|
712
|
+
return page(
|
|
713
|
+
"Attention",
|
|
714
|
+
// Tiles show while the checks run; only the list waits.
|
|
715
|
+
{ isLoading: { value: false }, error },
|
|
716
|
+
() => {
|
|
717
|
+
if (isLoading.value && !a)
|
|
718
|
+
return [tiles, h4("p", { style: styles.status }, "Running the checks\u2026")];
|
|
719
|
+
if (!a) return tiles;
|
|
720
|
+
if (a.items.length === 0)
|
|
721
|
+
return [
|
|
722
|
+
tiles,
|
|
723
|
+
empty(
|
|
724
|
+
"Nothing needs you",
|
|
725
|
+
`Every check passes. Checked ${new Date(a.generatedAt).toLocaleString()}.`
|
|
726
|
+
)
|
|
727
|
+
];
|
|
728
|
+
return [
|
|
729
|
+
tiles,
|
|
730
|
+
h4(
|
|
731
|
+
"ul",
|
|
732
|
+
{ style: styles.list },
|
|
733
|
+
[...errors, ...advice].map(
|
|
734
|
+
(item) => h4(
|
|
735
|
+
"li",
|
|
736
|
+
{
|
|
737
|
+
key: `${item.source}:${item.message}`,
|
|
738
|
+
style: {
|
|
739
|
+
...styles.row,
|
|
740
|
+
display: "flex",
|
|
741
|
+
gap: "12px",
|
|
742
|
+
alignItems: "flex-start"
|
|
743
|
+
}
|
|
744
|
+
},
|
|
745
|
+
[
|
|
746
|
+
icon("alert", 16, {
|
|
747
|
+
marginTop: "3px",
|
|
748
|
+
color: item.severity === "error" ? "var(--fonderie-danger,#e00)" : "var(--fonderie-warning,#f5a623)"
|
|
749
|
+
}),
|
|
750
|
+
h4("div", { style: { minWidth: 0, flex: 1 } }, [
|
|
751
|
+
h4(
|
|
752
|
+
"div",
|
|
753
|
+
{
|
|
754
|
+
style: {
|
|
755
|
+
display: "flex",
|
|
756
|
+
gap: "8px",
|
|
757
|
+
alignItems: "center",
|
|
758
|
+
flexWrap: "wrap",
|
|
759
|
+
marginBottom: "2px"
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
[
|
|
763
|
+
pill(
|
|
764
|
+
item.severity === "error" ? "bad" : "warn",
|
|
765
|
+
item.severity === "error" ? "error" : "advice"
|
|
766
|
+
),
|
|
767
|
+
h4("span", { style: styles.mono }, item.source)
|
|
768
|
+
]
|
|
769
|
+
),
|
|
770
|
+
h4("div", { style: { lineHeight: 1.55 } }, item.message)
|
|
771
|
+
])
|
|
772
|
+
]
|
|
773
|
+
)
|
|
774
|
+
)
|
|
775
|
+
)
|
|
776
|
+
];
|
|
777
|
+
},
|
|
778
|
+
[refreshButton("Refresh", isLoading.value, () => void refresh())],
|
|
779
|
+
{
|
|
780
|
+
lead: a ? `What needs you on this deployment \xB7 checked ${new Date(a.generatedAt).toLocaleTimeString()}` : "What needs you on this deployment"
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
};
|
|
262
784
|
}
|
|
263
785
|
});
|
|
264
786
|
|
|
265
787
|
// src/screens/EnvironmentScreen.ts
|
|
266
788
|
import { useAdminEnvironment } from "@fonderie/vue-admin";
|
|
267
|
-
import { defineComponent as defineComponent3, h as
|
|
789
|
+
import { defineComponent as defineComponent3, h as h5 } from "vue";
|
|
268
790
|
var EnvironmentScreen = defineComponent3({
|
|
269
791
|
name: "FonderieEnvironmentScreen",
|
|
270
792
|
props: { client: { type: Object, required: true } },
|
|
271
793
|
setup(props) {
|
|
272
794
|
const { report, isLoading, error } = useAdminEnvironment(props.client);
|
|
273
|
-
return () =>
|
|
795
|
+
return () => {
|
|
274
796
|
const r = report.value;
|
|
275
|
-
|
|
276
|
-
return
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
(
|
|
283
|
-
|
|
284
|
-
"
|
|
285
|
-
|
|
286
|
-
|
|
797
|
+
const missing = r?.env.filter((e) => !e.set).length ?? 0;
|
|
798
|
+
return page(
|
|
799
|
+
"Environment",
|
|
800
|
+
{ isLoading, error },
|
|
801
|
+
() => {
|
|
802
|
+
if (!r) return null;
|
|
803
|
+
return [
|
|
804
|
+
h5("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Variables"),
|
|
805
|
+
r.env.length === 0 ? h5(
|
|
806
|
+
"p",
|
|
807
|
+
{ style: styles.muted },
|
|
808
|
+
"No variables declared \u2014 pass `env` to AdminModule."
|
|
809
|
+
) : h5(
|
|
810
|
+
"div",
|
|
811
|
+
{
|
|
812
|
+
style: {
|
|
813
|
+
...styles.grid,
|
|
814
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
|
|
815
|
+
gap: "8px"
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
r.env.map(
|
|
819
|
+
(e) => h5(
|
|
287
820
|
"div",
|
|
288
821
|
{
|
|
289
|
-
key:
|
|
290
|
-
style:
|
|
822
|
+
key: e.name,
|
|
823
|
+
style: {
|
|
824
|
+
...styles.card,
|
|
825
|
+
padding: "10px 12px",
|
|
826
|
+
display: "flex",
|
|
827
|
+
alignItems: "center",
|
|
828
|
+
justifyContent: "space-between",
|
|
829
|
+
gap: "8px"
|
|
830
|
+
}
|
|
291
831
|
},
|
|
292
|
-
|
|
832
|
+
[
|
|
833
|
+
h5("span", { style: { ...styles.mono, wordBreak: "break-all" } }, e.name),
|
|
834
|
+
pill(e.set ? "ok" : "bad", e.set ? "set" : "missing")
|
|
835
|
+
]
|
|
293
836
|
)
|
|
294
837
|
)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
838
|
+
),
|
|
839
|
+
h5("h2", { style: styles.subtitle }, "Module readiness"),
|
|
840
|
+
h5(
|
|
841
|
+
"ul",
|
|
842
|
+
{ style: styles.list },
|
|
843
|
+
r.modules.map(
|
|
844
|
+
(m) => h5("li", { key: m.name, style: styles.row }, [
|
|
845
|
+
h5(
|
|
846
|
+
"div",
|
|
847
|
+
{
|
|
848
|
+
style: {
|
|
849
|
+
display: "flex",
|
|
850
|
+
justifyContent: "space-between",
|
|
851
|
+
gap: "12px",
|
|
852
|
+
alignItems: "center"
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
[
|
|
856
|
+
h5("span", { style: { ...styles.mono, fontWeight: 600 } }, m.name),
|
|
857
|
+
pill(
|
|
858
|
+
m.problems.length === 0 ? "ok" : m.problems.some((p) => p.severity === "error") ? "bad" : "warn",
|
|
859
|
+
m.problems.length === 0 ? "ready" : `${m.problems.length} problem${m.problems.length === 1 ? "" : "s"}`
|
|
860
|
+
)
|
|
861
|
+
]
|
|
862
|
+
),
|
|
863
|
+
...m.problems.map(
|
|
864
|
+
(p) => h5(
|
|
865
|
+
"div",
|
|
866
|
+
{
|
|
867
|
+
key: p.message,
|
|
868
|
+
style: {
|
|
869
|
+
marginTop: "6px",
|
|
870
|
+
fontSize: "13px",
|
|
871
|
+
color: p.severity === "error" ? "var(--fonderie-danger,#e00)" : "var(--fonderie-text-muted,#5c5c5c)"
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
p.message
|
|
875
|
+
)
|
|
876
|
+
)
|
|
877
|
+
])
|
|
878
|
+
)
|
|
879
|
+
)
|
|
880
|
+
];
|
|
881
|
+
},
|
|
882
|
+
[
|
|
883
|
+
r && r.env.length > 0 ? pill(missing ? "bad" : "ok", missing ? `${missing} missing` : "all set") : null
|
|
884
|
+
],
|
|
885
|
+
{
|
|
886
|
+
lead: "Whether each module is configured and each variable the app reads is set. Values are never shown."
|
|
887
|
+
}
|
|
888
|
+
);
|
|
889
|
+
};
|
|
312
890
|
}
|
|
313
891
|
});
|
|
314
892
|
|
|
315
893
|
// src/screens/DoctorScreen.ts
|
|
316
894
|
import { useDoctor } from "@fonderie/vue-admin";
|
|
317
|
-
import { defineComponent as defineComponent4, h as
|
|
895
|
+
import { defineComponent as defineComponent4, h as h6 } from "vue";
|
|
318
896
|
var DoctorScreen = defineComponent4({
|
|
319
897
|
name: "FonderieDoctorScreen",
|
|
320
898
|
props: { client: { type: Object, required: true } },
|
|
@@ -322,123 +900,193 @@ var DoctorScreen = defineComponent4({
|
|
|
322
900
|
const { report, isLoading, error, refresh } = useDoctor(props.client);
|
|
323
901
|
return () => page(
|
|
324
902
|
"Doctor",
|
|
325
|
-
{ isLoading, error },
|
|
903
|
+
{ isLoading: { value: isLoading.value && !report.value }, error },
|
|
326
904
|
() => {
|
|
327
905
|
const r = report.value;
|
|
328
906
|
if (!r) return null;
|
|
329
|
-
return
|
|
330
|
-
|
|
331
|
-
|
|
907
|
+
return table(
|
|
908
|
+
[
|
|
909
|
+
{ label: "Result", style: { width: "110px" } },
|
|
910
|
+
"Check",
|
|
911
|
+
{ label: "Time", style: { textAlign: "right", width: "90px" } }
|
|
912
|
+
],
|
|
332
913
|
r.checks.map(
|
|
333
|
-
(c) =>
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
914
|
+
(c) => h6("tr", { key: c.name }, [
|
|
915
|
+
td(
|
|
916
|
+
pill(
|
|
917
|
+
c.skipped ? "neutral" : c.ok ? c.findings.length ? "warn" : "ok" : "bad",
|
|
918
|
+
c.skipped ? "skipped" : c.ok ? c.findings.length ? "advice" : "ok" : "failed"
|
|
919
|
+
)
|
|
338
920
|
),
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
921
|
+
td([
|
|
922
|
+
h6("div", [
|
|
923
|
+
h6("span", { style: { ...styles.mono, fontWeight: 600 } }, c.name),
|
|
924
|
+
" ",
|
|
925
|
+
h6("span", { style: styles.muted }, c.module)
|
|
926
|
+
]),
|
|
927
|
+
c.skipped ? h6("div", { style: { ...styles.muted, marginTop: "4px" } }, c.skipped) : null,
|
|
928
|
+
...c.findings.map(
|
|
929
|
+
(f) => h6(
|
|
930
|
+
"div",
|
|
931
|
+
{
|
|
932
|
+
key: f,
|
|
933
|
+
style: {
|
|
934
|
+
marginTop: "4px",
|
|
935
|
+
fontSize: "13px",
|
|
936
|
+
lineHeight: 1.55,
|
|
937
|
+
color: c.ok ? "var(--fonderie-text,#171717)" : "var(--fonderie-danger,#e00)"
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
f
|
|
941
|
+
)
|
|
942
|
+
)
|
|
943
|
+
]),
|
|
944
|
+
td(`${c.durationMs} ms`, {
|
|
945
|
+
...styles.muted,
|
|
946
|
+
textAlign: "right",
|
|
947
|
+
whiteSpace: "nowrap"
|
|
948
|
+
})
|
|
347
949
|
])
|
|
348
950
|
)
|
|
349
951
|
);
|
|
350
952
|
},
|
|
351
953
|
[
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
"span",
|
|
355
|
-
{ style: report.value.ok ? styles.ok : styles.bad },
|
|
954
|
+
report.value && !isLoading.value ? pill(
|
|
955
|
+
report.value.ok ? "ok" : "bad",
|
|
356
956
|
report.value.ok ? "all checks pass" : "a check failed"
|
|
357
|
-
) :
|
|
957
|
+
) : null,
|
|
958
|
+
refreshButton("Run again", isLoading.value, () => void refresh())
|
|
358
959
|
],
|
|
359
|
-
{
|
|
960
|
+
{
|
|
961
|
+
loadingText: "Running the checks\u2026",
|
|
962
|
+
lead: "Every reconciliation check across the installed modules, run on demand."
|
|
963
|
+
}
|
|
360
964
|
);
|
|
361
965
|
}
|
|
362
966
|
});
|
|
363
967
|
|
|
364
968
|
// src/screens/ModulesScreen.ts
|
|
365
|
-
import { useManifest } from "@fonderie/vue-admin";
|
|
366
|
-
import { defineComponent as defineComponent5, h as
|
|
969
|
+
import { useManifest as useManifest2 } from "@fonderie/vue-admin";
|
|
970
|
+
import { defineComponent as defineComponent5, h as h7 } from "vue";
|
|
367
971
|
var ModulesScreen = defineComponent5({
|
|
368
972
|
name: "FonderieModulesScreen",
|
|
369
973
|
props: { client: { type: Object, required: true } },
|
|
370
974
|
setup(props) {
|
|
371
|
-
const { manifest, isLoading, error } =
|
|
372
|
-
return () =>
|
|
975
|
+
const { manifest, isLoading, error } = useManifest2(props.client);
|
|
976
|
+
return () => {
|
|
373
977
|
const m = manifest.value;
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
p.message
|
|
978
|
+
return page(
|
|
979
|
+
"Modules",
|
|
980
|
+
{ isLoading, error },
|
|
981
|
+
() => {
|
|
982
|
+
if (!m) return null;
|
|
983
|
+
return table(
|
|
984
|
+
["Module", "Version", "Readiness", "Admin"],
|
|
985
|
+
m.modules.map(
|
|
986
|
+
(mod) => h7("tr", { key: mod.name }, [
|
|
987
|
+
td(mod.name, { ...styles.mono, fontWeight: 600 }),
|
|
988
|
+
td(mod.version ?? h7("span", { style: styles.muted }, "not reported"), styles.mono),
|
|
989
|
+
td([
|
|
990
|
+
pill(mod.readiness.ok ? "ok" : "bad", mod.readiness.ok ? "ready" : "error"),
|
|
991
|
+
...mod.readiness.problems.map(
|
|
992
|
+
(p) => h7(
|
|
993
|
+
"div",
|
|
994
|
+
{
|
|
995
|
+
key: p.message,
|
|
996
|
+
style: {
|
|
997
|
+
marginTop: "6px",
|
|
998
|
+
fontSize: "13px",
|
|
999
|
+
color: p.severity === "error" ? "var(--fonderie-danger,#e00)" : "var(--fonderie-text-muted,#5c5c5c)"
|
|
1000
|
+
}
|
|
1001
|
+
},
|
|
1002
|
+
p.message
|
|
1003
|
+
)
|
|
401
1004
|
)
|
|
1005
|
+
]),
|
|
1006
|
+
td(
|
|
1007
|
+
mod.describesAdmin ? h7("span", { style: styles.badge }, "describes admin") : h7("span", { style: styles.muted }, "\u2014")
|
|
402
1008
|
)
|
|
403
|
-
])
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
1009
|
+
])
|
|
1010
|
+
)
|
|
1011
|
+
);
|
|
1012
|
+
},
|
|
1013
|
+
[],
|
|
1014
|
+
{
|
|
1015
|
+
lead: m ? `${m.env} \xB7 admin ${m.admin.version} \xB7 admin log ${m.admin.log ? "on" : "off"} \xB7 ${m.routes.length} routes` : "Every installed module, its version and whether it is ready."
|
|
1016
|
+
}
|
|
1017
|
+
);
|
|
1018
|
+
};
|
|
410
1019
|
}
|
|
411
1020
|
});
|
|
412
1021
|
|
|
413
1022
|
// src/screens/RoutesScreen.ts
|
|
414
1023
|
import { useAdminRoutes } from "@fonderie/vue-admin";
|
|
415
|
-
import { defineComponent as defineComponent6, h as
|
|
1024
|
+
import { defineComponent as defineComponent6, h as h8, ref } from "vue";
|
|
416
1025
|
var RoutesScreen = defineComponent6({
|
|
417
1026
|
name: "FonderieRoutesScreen",
|
|
418
1027
|
props: { client: { type: Object, required: true } },
|
|
419
1028
|
setup(props) {
|
|
420
1029
|
const { report, isLoading, error } = useAdminRoutes(props.client);
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
1030
|
+
const q = ref("");
|
|
1031
|
+
return () => {
|
|
1032
|
+
const needle = q.value.trim().toLowerCase();
|
|
1033
|
+
const routes = (report.value?.routes ?? []).filter(
|
|
1034
|
+
(r) => !needle || `${r.method} ${r.path} ${r.module ?? "application"} ${r.guard}`.toLowerCase().includes(needle)
|
|
1035
|
+
);
|
|
1036
|
+
return page(
|
|
1037
|
+
"Routes",
|
|
1038
|
+
{ isLoading, error },
|
|
1039
|
+
() => table(
|
|
1040
|
+
[{ label: "Method", style: { width: "80px" } }, "Path", "Guard", "Module"],
|
|
1041
|
+
[
|
|
1042
|
+
...routes.map(
|
|
1043
|
+
(r) => h8("tr", { key: `${r.method} ${r.path}` }, [
|
|
1044
|
+
td(methodChip(r.method)),
|
|
1045
|
+
td(r.path, { ...styles.mono, wordBreak: "break-all" }),
|
|
1046
|
+
td(
|
|
1047
|
+
pill(
|
|
1048
|
+
r.guard === "admin" ? "ok" : r.guard === "probe" ? "neutral" : "info",
|
|
1049
|
+
r.guard,
|
|
1050
|
+
false
|
|
1051
|
+
)
|
|
1052
|
+
),
|
|
1053
|
+
td(r.module ?? "application", styles.muted)
|
|
1054
|
+
])
|
|
1055
|
+
),
|
|
1056
|
+
...routes.length === 0 ? [
|
|
1057
|
+
h8("tr", [
|
|
1058
|
+
h8(
|
|
1059
|
+
"td",
|
|
1060
|
+
{ style: { ...styles.td, ...styles.muted }, colspan: 4 },
|
|
1061
|
+
`No route matches \u201C${q.value}\u201D.`
|
|
1062
|
+
)
|
|
1063
|
+
])
|
|
1064
|
+
] : []
|
|
1065
|
+
]
|
|
1066
|
+
),
|
|
1067
|
+
[
|
|
1068
|
+
h8("input", {
|
|
1069
|
+
type: "search",
|
|
1070
|
+
value: q.value,
|
|
1071
|
+
placeholder: "Filter by path, method, module\u2026",
|
|
1072
|
+
style: { ...styles.input, width: "280px" },
|
|
1073
|
+
"aria-label": "Filter routes",
|
|
1074
|
+
onInput: (e) => {
|
|
1075
|
+
q.value = e.target.value;
|
|
1076
|
+
}
|
|
1077
|
+
})
|
|
1078
|
+
],
|
|
1079
|
+
{
|
|
1080
|
+
lead: report.value ? `${report.value.routes.length} routes exposed by this deployment, with the guard in front of each.` : "Every exposed route, with its guard."
|
|
1081
|
+
}
|
|
1082
|
+
);
|
|
1083
|
+
};
|
|
436
1084
|
}
|
|
437
1085
|
});
|
|
438
1086
|
|
|
439
1087
|
// src/screens/MigrationsScreen.ts
|
|
440
1088
|
import { useAdminMigrations } from "@fonderie/vue-admin";
|
|
441
|
-
import { defineComponent as defineComponent7, h as
|
|
1089
|
+
import { defineComponent as defineComponent7, h as h9 } from "vue";
|
|
442
1090
|
function why(m, everApplied) {
|
|
443
1091
|
if (m.pending.length === 0) return null;
|
|
444
1092
|
if (m.blockedBy) return `Apply "${m.blockedBy}" first \u2014 it runs before this one and is behind.`;
|
|
@@ -454,31 +1102,31 @@ var MigrationsScreen = defineComponent7({
|
|
|
454
1102
|
const section = (m, everApplied) => {
|
|
455
1103
|
const blocked = why(m, everApplied);
|
|
456
1104
|
const files = m.pending.map((p) => p.file);
|
|
457
|
-
return
|
|
458
|
-
|
|
459
|
-
|
|
1105
|
+
return h9("section", { style: { ...styles.card, marginBottom: "16px" } }, [
|
|
1106
|
+
h9("h2", { style: { ...styles.subtitle, marginTop: 0 } }, [
|
|
1107
|
+
h9("span", { style: styles.mono }, m.name),
|
|
1108
|
+
pill("warn", `${m.pending.length} pending`)
|
|
1109
|
+
]),
|
|
1110
|
+
h9(
|
|
460
1111
|
"ul",
|
|
1112
|
+
{ style: { margin: "0 0 12px", paddingLeft: "18px", lineHeight: 1.9 } },
|
|
461
1113
|
m.pending.map(
|
|
462
|
-
(p) =>
|
|
463
|
-
|
|
1114
|
+
(p) => h9("li", [
|
|
1115
|
+
h9("code", { style: styles.code }, p.file),
|
|
464
1116
|
" ",
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
{ style: p.impact === "destructive" ? styles.bad : styles.advice },
|
|
468
|
-
p.impact
|
|
469
|
-
),
|
|
470
|
-
p.destructive.length > 0 ? h8(
|
|
1117
|
+
p.impact === "destructive" ? pill("bad", "destructive") : pill("neutral", "additive"),
|
|
1118
|
+
p.destructive.length > 0 ? h9(
|
|
471
1119
|
"ul",
|
|
472
|
-
p.destructive.map((s) =>
|
|
1120
|
+
p.destructive.map((s) => h9("li", [h9("code", s.slice(0, 120))]))
|
|
473
1121
|
) : null
|
|
474
1122
|
])
|
|
475
1123
|
)
|
|
476
1124
|
),
|
|
477
|
-
blocked ?
|
|
1125
|
+
blocked ? h9("p", { style: { ...styles.notice, marginBottom: 0 } }, blocked) : h9(
|
|
478
1126
|
"button",
|
|
479
1127
|
{
|
|
480
1128
|
type: "button",
|
|
481
|
-
style: styles.
|
|
1129
|
+
style: styles.buttonPrimary,
|
|
482
1130
|
disabled: isLoading.value || !m.appliable,
|
|
483
1131
|
onClick: () => {
|
|
484
1132
|
if (window.confirm(
|
|
@@ -488,81 +1136,399 @@ var MigrationsScreen = defineComponent7({
|
|
|
488
1136
|
});
|
|
489
1137
|
}
|
|
490
1138
|
},
|
|
491
|
-
`Apply ${m.pending.length} migration${m.pending.length === 1 ? "" : "s"}`
|
|
492
|
-
)
|
|
1139
|
+
`Apply ${m.pending.length} migration${m.pending.length === 1 ? "" : "s"}`
|
|
1140
|
+
)
|
|
1141
|
+
]);
|
|
1142
|
+
};
|
|
1143
|
+
return () => page(
|
|
1144
|
+
"Migrations",
|
|
1145
|
+
{ isLoading, error },
|
|
1146
|
+
() => {
|
|
1147
|
+
const r = report.value;
|
|
1148
|
+
if (!r) return null;
|
|
1149
|
+
const behind = r.modules.filter((m) => m.pending.length > 0);
|
|
1150
|
+
if (behind.length === 0)
|
|
1151
|
+
return empty("Every module is up to date", "No pending migrations.", "migrations");
|
|
1152
|
+
return [
|
|
1153
|
+
r.everApplied ? null : h9(
|
|
1154
|
+
"p",
|
|
1155
|
+
{ style: styles.notice },
|
|
1156
|
+
"This database has never been migrated \u2014 treating it as a first install, so nothing is held back."
|
|
1157
|
+
),
|
|
1158
|
+
...behind.map((m) => section(m, r.everApplied))
|
|
1159
|
+
].filter(Boolean);
|
|
1160
|
+
},
|
|
1161
|
+
[refreshButton("Refresh", isLoading.value, () => void refresh())],
|
|
1162
|
+
{
|
|
1163
|
+
lead: "Schema changes each module ships, and whether this database has them.",
|
|
1164
|
+
errorText: (e) => e.status === 403 ? "Applying migrations needs a token with the write scope." : e.explanation
|
|
1165
|
+
}
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
// src/screens/OperatorsScreen.ts
|
|
1171
|
+
import { useAdminOperators } from "@fonderie/vue-admin";
|
|
1172
|
+
import { defineComponent as defineComponent8, h as h10, ref as ref2 } from "vue";
|
|
1173
|
+
var LEVELS = [
|
|
1174
|
+
{ label: "Read only", scopes: ["read"], hint: "look around, change nothing" },
|
|
1175
|
+
{ label: "Editor", scopes: ["read", "write"], hint: "change config, templates, users" },
|
|
1176
|
+
{
|
|
1177
|
+
label: "Owner",
|
|
1178
|
+
scopes: ["read", "write", "secrets"],
|
|
1179
|
+
hint: "secrets, tokens and operators too"
|
|
1180
|
+
}
|
|
1181
|
+
];
|
|
1182
|
+
var levelOf = (scopes) => scopes.includes("secrets") ? "Owner" : scopes.includes("write") ? "Editor" : "Read only";
|
|
1183
|
+
var scopesFor = (label) => LEVELS.find((l) => l.label === label)?.scopes ?? ["read"];
|
|
1184
|
+
var small = { height: "28px" };
|
|
1185
|
+
var OperatorsScreen = defineComponent8({
|
|
1186
|
+
name: "FonderieOperatorsScreen",
|
|
1187
|
+
props: {
|
|
1188
|
+
client: { type: Object, required: true },
|
|
1189
|
+
// The signed-in operator's email: their own row offers no disable or
|
|
1190
|
+
// recovery (the server refuses both for yourself anyway).
|
|
1191
|
+
me: { type: String, default: void 0 }
|
|
1192
|
+
},
|
|
1193
|
+
setup(props) {
|
|
1194
|
+
const { report, isLoading, error, invite, recover, update, revokeLink } = useAdminOperators(
|
|
1195
|
+
props.client
|
|
1196
|
+
);
|
|
1197
|
+
const email = ref2("");
|
|
1198
|
+
const level = ref2("Editor");
|
|
1199
|
+
const minted = ref2(null);
|
|
1200
|
+
const copied = ref2(false);
|
|
1201
|
+
const status = (o) => o.disabledAt ? pill("neutral", "disabled") : o.locked ? pill("warn", "locked") : !o.enrolled ? pill("warn", "setting up") : pill("ok", "active");
|
|
1202
|
+
const mintedNotice = (link) => {
|
|
1203
|
+
const url = `${window.location.origin}${link.url}`;
|
|
1204
|
+
return h10(
|
|
1205
|
+
"div",
|
|
1206
|
+
{
|
|
1207
|
+
style: {
|
|
1208
|
+
...styles.notice,
|
|
1209
|
+
background: "color-mix(in srgb, var(--fonderie-accent,#00d294) 10%, transparent)",
|
|
1210
|
+
borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
[
|
|
1214
|
+
h10("strong", `Send this link to ${link.email}. It is shown once.`),
|
|
1215
|
+
` It works once and expires ${new Date(link.expiresAt).toLocaleString()}.`,
|
|
1216
|
+
h10(
|
|
1217
|
+
"div",
|
|
1218
|
+
{ style: { display: "flex", gap: "8px", marginTop: "8px", alignItems: "center" } },
|
|
1219
|
+
[
|
|
1220
|
+
h10(
|
|
1221
|
+
"code",
|
|
1222
|
+
{ style: { ...styles.code, flex: 1, padding: "7px 10px", wordBreak: "break-all" } },
|
|
1223
|
+
url
|
|
1224
|
+
),
|
|
1225
|
+
h10(
|
|
1226
|
+
"button",
|
|
1227
|
+
{
|
|
1228
|
+
type: "button",
|
|
1229
|
+
style: styles.button,
|
|
1230
|
+
onClick: () => {
|
|
1231
|
+
void navigator.clipboard?.writeText(url);
|
|
1232
|
+
copied.value = true;
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
copied.value ? "Copied" : "Copy"
|
|
1236
|
+
),
|
|
1237
|
+
h10(
|
|
1238
|
+
"button",
|
|
1239
|
+
{
|
|
1240
|
+
type: "button",
|
|
1241
|
+
style: styles.buttonGhost,
|
|
1242
|
+
"aria-label": "Dismiss",
|
|
1243
|
+
onClick: () => {
|
|
1244
|
+
minted.value = null;
|
|
1245
|
+
copied.value = false;
|
|
1246
|
+
}
|
|
1247
|
+
},
|
|
1248
|
+
[icon("close", 14)]
|
|
1249
|
+
)
|
|
1250
|
+
]
|
|
1251
|
+
)
|
|
1252
|
+
]
|
|
1253
|
+
);
|
|
1254
|
+
};
|
|
1255
|
+
const setMinted = (l) => {
|
|
1256
|
+
copied.value = false;
|
|
1257
|
+
minted.value = l;
|
|
1258
|
+
};
|
|
1259
|
+
return () => {
|
|
1260
|
+
const r = report.value;
|
|
1261
|
+
const e = error.value;
|
|
1262
|
+
return h10("div", { style: styles.container }, [
|
|
1263
|
+
pageHeader(
|
|
1264
|
+
"Operators",
|
|
1265
|
+
"The people who can sign in to this console. Each one uses a password and an authenticator app; there is no sign-up."
|
|
1266
|
+
),
|
|
1267
|
+
e ? h10(
|
|
1268
|
+
"p",
|
|
1269
|
+
{ style: styles.error, role: "alert" },
|
|
1270
|
+
e.status === 403 && e.reason === "FORBIDDEN" ? "Managing operators needs the Owner level." : e.explanation
|
|
1271
|
+
) : null,
|
|
1272
|
+
minted.value ? mintedNotice(minted.value) : null,
|
|
1273
|
+
h10(
|
|
1274
|
+
"form",
|
|
1275
|
+
{
|
|
1276
|
+
style: {
|
|
1277
|
+
...styles.card,
|
|
1278
|
+
display: "flex",
|
|
1279
|
+
gap: "8px",
|
|
1280
|
+
alignItems: "center",
|
|
1281
|
+
flexWrap: "wrap",
|
|
1282
|
+
marginBottom: "20px"
|
|
1283
|
+
},
|
|
1284
|
+
onSubmit: (ev) => {
|
|
1285
|
+
ev.preventDefault();
|
|
1286
|
+
void invite({ email: email.value.trim(), scopes: scopesFor(level.value) }).then((l) => {
|
|
1287
|
+
setMinted(l);
|
|
1288
|
+
email.value = "";
|
|
1289
|
+
}).catch(() => {
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
[
|
|
1294
|
+
icon("users", 16),
|
|
1295
|
+
h10("strong", { style: { fontSize: "13.5px", marginRight: "4px" } }, "Invite"),
|
|
1296
|
+
h10("input", {
|
|
1297
|
+
type: "email",
|
|
1298
|
+
required: true,
|
|
1299
|
+
value: email.value,
|
|
1300
|
+
onInput: (ev) => email.value = ev.target.value,
|
|
1301
|
+
placeholder: "teammate@company.com",
|
|
1302
|
+
style: { ...styles.input, flex: 1, minWidth: "220px" },
|
|
1303
|
+
"aria-label": "Email to invite"
|
|
1304
|
+
}),
|
|
1305
|
+
h10(
|
|
1306
|
+
"select",
|
|
1307
|
+
{
|
|
1308
|
+
value: level.value,
|
|
1309
|
+
onChange: (ev) => level.value = ev.target.value,
|
|
1310
|
+
style: styles.input,
|
|
1311
|
+
"aria-label": "Access level"
|
|
1312
|
+
},
|
|
1313
|
+
LEVELS.map(
|
|
1314
|
+
(l) => h10("option", { key: l.label, value: l.label }, `${l.label} \u2014 ${l.hint}`)
|
|
1315
|
+
)
|
|
1316
|
+
),
|
|
1317
|
+
h10(
|
|
1318
|
+
"button",
|
|
1319
|
+
{ type: "submit", style: styles.buttonPrimary, disabled: !email.value.trim() },
|
|
1320
|
+
[icon("plus", 14), "Create invite link"]
|
|
1321
|
+
)
|
|
1322
|
+
]
|
|
1323
|
+
),
|
|
1324
|
+
isLoading.value && !r ? h10("p", { style: styles.status }, "Loading\u2026") : r && r.operators.length === 0 ? empty("No operators yet", void 0, "users") : r ? table(
|
|
1325
|
+
["Operator", "Access", "Status", "Last sign-in", ""],
|
|
1326
|
+
r.operators.map((o) => {
|
|
1327
|
+
const mine = o.email === props.me;
|
|
1328
|
+
return h10("tr", { key: o.id }, [
|
|
1329
|
+
td([
|
|
1330
|
+
h10("div", { style: { fontWeight: 600 } }, o.name || o.email),
|
|
1331
|
+
o.name ? h10("div", { style: styles.muted }, o.email) : null
|
|
1332
|
+
]),
|
|
1333
|
+
td(
|
|
1334
|
+
h10(
|
|
1335
|
+
"select",
|
|
1336
|
+
{
|
|
1337
|
+
value: levelOf(o.scopes),
|
|
1338
|
+
disabled: mine,
|
|
1339
|
+
onChange: (ev) => void update(o.id, {
|
|
1340
|
+
scopes: scopesFor(ev.target.value)
|
|
1341
|
+
}).catch(() => {
|
|
1342
|
+
}),
|
|
1343
|
+
style: { ...styles.input, ...small, fontSize: "12.5px" },
|
|
1344
|
+
"aria-label": `Access level for ${o.email}`
|
|
1345
|
+
},
|
|
1346
|
+
LEVELS.map((l) => h10("option", { key: l.label, value: l.label }, l.label))
|
|
1347
|
+
)
|
|
1348
|
+
),
|
|
1349
|
+
td([
|
|
1350
|
+
status(o),
|
|
1351
|
+
o.enrolled && o.backupCodesLeft <= 2 && !o.disabledAt ? h10(
|
|
1352
|
+
"div",
|
|
1353
|
+
{ style: { ...styles.muted, marginTop: "4px" } },
|
|
1354
|
+
`${o.backupCodesLeft} backup code(s) left`
|
|
1355
|
+
) : null
|
|
1356
|
+
]),
|
|
1357
|
+
td(o.lastLoginAt ? new Date(o.lastLoginAt).toLocaleString() : "never", {
|
|
1358
|
+
...styles.muted,
|
|
1359
|
+
whiteSpace: "nowrap"
|
|
1360
|
+
}),
|
|
1361
|
+
td(
|
|
1362
|
+
mine ? h10("span", { style: styles.muted }, "you") : [
|
|
1363
|
+
h10(
|
|
1364
|
+
"button",
|
|
1365
|
+
{
|
|
1366
|
+
type: "button",
|
|
1367
|
+
style: { ...styles.button, ...small, marginRight: "6px" },
|
|
1368
|
+
onClick: () => {
|
|
1369
|
+
if (window.confirm(
|
|
1370
|
+
`Create a recovery link for ${o.email}? It signs them out everywhere; the link sets a new password and a new authenticator.`
|
|
1371
|
+
))
|
|
1372
|
+
void recover(o.id).then(setMinted).catch(() => {
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
},
|
|
1376
|
+
"Recovery link"
|
|
1377
|
+
),
|
|
1378
|
+
h10(
|
|
1379
|
+
"button",
|
|
1380
|
+
{
|
|
1381
|
+
type: "button",
|
|
1382
|
+
style: {
|
|
1383
|
+
...o.disabledAt ? styles.button : styles.buttonDanger,
|
|
1384
|
+
...small
|
|
1385
|
+
},
|
|
1386
|
+
onClick: () => {
|
|
1387
|
+
if (o.disabledAt || window.confirm(
|
|
1388
|
+
`Disable ${o.email}? They are signed out immediately.`
|
|
1389
|
+
))
|
|
1390
|
+
void update(o.id, { disabled: !o.disabledAt }).catch(
|
|
1391
|
+
() => {
|
|
1392
|
+
}
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
},
|
|
1396
|
+
o.disabledAt ? "Enable" : "Disable"
|
|
1397
|
+
)
|
|
1398
|
+
],
|
|
1399
|
+
{ textAlign: "right", whiteSpace: "nowrap" }
|
|
1400
|
+
)
|
|
1401
|
+
]);
|
|
1402
|
+
})
|
|
1403
|
+
) : null,
|
|
1404
|
+
r && r.links.length > 0 ? [
|
|
1405
|
+
h10("h2", { style: styles.subtitle }, "Pending links"),
|
|
1406
|
+
h10(
|
|
1407
|
+
"ul",
|
|
1408
|
+
{ style: styles.list },
|
|
1409
|
+
r.links.map(
|
|
1410
|
+
(l) => h10(
|
|
1411
|
+
"li",
|
|
1412
|
+
{
|
|
1413
|
+
key: l.id,
|
|
1414
|
+
style: { ...styles.row, display: "flex", gap: "12px", alignItems: "center" }
|
|
1415
|
+
},
|
|
1416
|
+
[
|
|
1417
|
+
pill(l.kind === "invite" ? "info" : "warn", l.kind, false),
|
|
1418
|
+
h10("span", { style: { flex: 1 } }, [
|
|
1419
|
+
l.email,
|
|
1420
|
+
l.kind === "invite" ? h10("span", { style: styles.muted }, ` \xB7 ${levelOf(l.scopes)}`) : null
|
|
1421
|
+
]),
|
|
1422
|
+
h10(
|
|
1423
|
+
"span",
|
|
1424
|
+
{ style: styles.muted },
|
|
1425
|
+
`expires ${new Date(l.expiresAt).toLocaleString()}`
|
|
1426
|
+
),
|
|
1427
|
+
h10(
|
|
1428
|
+
"button",
|
|
1429
|
+
{
|
|
1430
|
+
type: "button",
|
|
1431
|
+
style: { ...styles.buttonDanger, ...small },
|
|
1432
|
+
onClick: () => void revokeLink(l.id).catch(() => {
|
|
1433
|
+
})
|
|
1434
|
+
},
|
|
1435
|
+
"Revoke"
|
|
1436
|
+
)
|
|
1437
|
+
]
|
|
1438
|
+
)
|
|
1439
|
+
)
|
|
1440
|
+
)
|
|
1441
|
+
] : null
|
|
493
1442
|
]);
|
|
494
1443
|
};
|
|
495
|
-
return () => page(
|
|
496
|
-
"Migrations",
|
|
497
|
-
{ isLoading, error },
|
|
498
|
-
() => {
|
|
499
|
-
const r = report.value;
|
|
500
|
-
if (!r) return null;
|
|
501
|
-
const behind = r.modules.filter((m) => m.pending.length > 0);
|
|
502
|
-
if (behind.length === 0) return h8("p", { style: styles.ok }, "Every module is up to date.");
|
|
503
|
-
return [
|
|
504
|
-
r.everApplied ? null : h8(
|
|
505
|
-
"p",
|
|
506
|
-
{ style: styles.advice },
|
|
507
|
-
"This database has never been migrated \u2014 treating it as a first install, so nothing is held back."
|
|
508
|
-
),
|
|
509
|
-
...behind.map((m) => section(m, r.everApplied))
|
|
510
|
-
].filter(Boolean);
|
|
511
|
-
},
|
|
512
|
-
[refreshButton("Refresh", isLoading.value, () => void refresh())],
|
|
513
|
-
{
|
|
514
|
-
errorText: (e) => e.status === 403 ? "Applying migrations needs a token with the write scope." : e.explanation
|
|
515
|
-
}
|
|
516
|
-
);
|
|
517
1444
|
}
|
|
518
1445
|
});
|
|
519
1446
|
|
|
520
1447
|
// src/screens/TokensScreen.ts
|
|
521
1448
|
import { useAdminTokens } from "@fonderie/vue-admin";
|
|
522
|
-
import { defineComponent as
|
|
1449
|
+
import { defineComponent as defineComponent9, h as h11, ref as ref3 } from "vue";
|
|
523
1450
|
var ALL = ["read", "write", "secrets"];
|
|
524
|
-
var TokensScreen =
|
|
1451
|
+
var TokensScreen = defineComponent9({
|
|
525
1452
|
name: "FonderieTokensScreen",
|
|
526
1453
|
props: { client: { type: Object, required: true } },
|
|
527
1454
|
setup(props) {
|
|
528
1455
|
const { report, isLoading, error, issue, revoke } = useAdminTokens(props.client);
|
|
529
|
-
const name =
|
|
530
|
-
const scopes =
|
|
531
|
-
const days =
|
|
532
|
-
const minted =
|
|
1456
|
+
const name = ref3("");
|
|
1457
|
+
const scopes = ref3(["read"]);
|
|
1458
|
+
const days = ref3("");
|
|
1459
|
+
const minted = ref3(null);
|
|
533
1460
|
const toggle = (s) => scopes.value = scopes.value.includes(s) ? scopes.value.filter((x) => x !== s) : [...scopes.value, s];
|
|
534
1461
|
return () => {
|
|
535
1462
|
const r = report.value;
|
|
536
|
-
return
|
|
537
|
-
|
|
538
|
-
|
|
1463
|
+
return h11("div", { style: styles.container }, [
|
|
1464
|
+
pageHeader(
|
|
1465
|
+
"Tokens",
|
|
1466
|
+
"Credentials for machines: the root token's strength, scoped tokens for the CLI and CI, and any legacy per-module tokens. People sign in as operators."
|
|
1467
|
+
),
|
|
1468
|
+
error.value ? h11(
|
|
539
1469
|
"p",
|
|
540
1470
|
{ style: styles.error, role: "alert" },
|
|
541
1471
|
error.value.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.value.explanation
|
|
542
1472
|
) : null,
|
|
543
|
-
|
|
544
|
-
r ?
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
1473
|
+
h11("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Root token"),
|
|
1474
|
+
r ? h11(
|
|
1475
|
+
"div",
|
|
1476
|
+
{ style: { ...styles.card, display: "flex", gap: "12px", alignItems: "flex-start" } },
|
|
1477
|
+
[
|
|
1478
|
+
icon("lock", 18, { marginTop: "2px" }),
|
|
1479
|
+
h11("div", [
|
|
1480
|
+
pill(r.admin.ok ? "ok" : "bad", r.admin.ok ? "strong" : "weak"),
|
|
1481
|
+
...r.admin.problems.map(
|
|
1482
|
+
(p) => h11(
|
|
1483
|
+
"div",
|
|
1484
|
+
{
|
|
1485
|
+
key: p.message,
|
|
1486
|
+
style: {
|
|
1487
|
+
marginTop: "6px",
|
|
1488
|
+
color: "var(--fonderie-danger,#e00)",
|
|
1489
|
+
fontSize: "13px"
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
p.message
|
|
1493
|
+
)
|
|
1494
|
+
)
|
|
1495
|
+
])
|
|
1496
|
+
]
|
|
1497
|
+
) : null,
|
|
1498
|
+
h11("h2", { style: styles.subtitle }, "Issued tokens"),
|
|
1499
|
+
r?.issued === null ? h11(
|
|
556
1500
|
"p",
|
|
557
1501
|
{ style: styles.muted },
|
|
558
1502
|
"Issuing is off \u2014 give AdminModule a store and run its migrations."
|
|
559
1503
|
) : [
|
|
560
|
-
minted.value ?
|
|
561
|
-
"
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1504
|
+
minted.value ? h11(
|
|
1505
|
+
"div",
|
|
1506
|
+
{
|
|
1507
|
+
style: {
|
|
1508
|
+
...styles.notice,
|
|
1509
|
+
background: "color-mix(in srgb, var(--fonderie-accent,#00d294) 10%, transparent)",
|
|
1510
|
+
borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
|
|
1511
|
+
}
|
|
1512
|
+
},
|
|
1513
|
+
[
|
|
1514
|
+
h11("strong", "Copy this now \u2014 it is never shown again."),
|
|
1515
|
+
` (${minted.value.name})`,
|
|
1516
|
+
h11(
|
|
1517
|
+
"div",
|
|
1518
|
+
{
|
|
1519
|
+
style: {
|
|
1520
|
+
...styles.code,
|
|
1521
|
+
display: "block",
|
|
1522
|
+
marginTop: "8px",
|
|
1523
|
+
padding: "8px 10px",
|
|
1524
|
+
wordBreak: "break-all"
|
|
1525
|
+
}
|
|
1526
|
+
},
|
|
1527
|
+
minted.value.token
|
|
1528
|
+
)
|
|
1529
|
+
]
|
|
1530
|
+
) : null,
|
|
1531
|
+
h11(
|
|
566
1532
|
"form",
|
|
567
1533
|
{
|
|
568
1534
|
style: styles.toolbar,
|
|
@@ -582,49 +1548,60 @@ var TokensScreen = defineComponent8({
|
|
|
582
1548
|
}
|
|
583
1549
|
},
|
|
584
1550
|
[
|
|
585
|
-
|
|
1551
|
+
h11("input", {
|
|
586
1552
|
value: name.value,
|
|
587
1553
|
onInput: (e) => name.value = e.target.value,
|
|
588
1554
|
placeholder: "name",
|
|
589
|
-
style:
|
|
1555
|
+
style: styles.input,
|
|
590
1556
|
"aria-label": "Name"
|
|
591
1557
|
}),
|
|
592
1558
|
...ALL.map(
|
|
593
|
-
(s) =>
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
1559
|
+
(s) => h11(
|
|
1560
|
+
"label",
|
|
1561
|
+
{
|
|
1562
|
+
key: s,
|
|
1563
|
+
style: {
|
|
1564
|
+
...styles.badge,
|
|
1565
|
+
cursor: "pointer",
|
|
1566
|
+
height: "32px",
|
|
1567
|
+
boxSizing: "border-box",
|
|
1568
|
+
padding: "0 10px",
|
|
1569
|
+
fontSize: "13px"
|
|
1570
|
+
}
|
|
1571
|
+
},
|
|
1572
|
+
[
|
|
1573
|
+
h11("input", {
|
|
1574
|
+
type: "checkbox",
|
|
1575
|
+
checked: scopes.value.includes(s),
|
|
1576
|
+
onChange: () => toggle(s)
|
|
1577
|
+
}),
|
|
1578
|
+
` ${s}`
|
|
1579
|
+
]
|
|
1580
|
+
)
|
|
601
1581
|
),
|
|
602
|
-
|
|
1582
|
+
h11("input", {
|
|
603
1583
|
value: days.value,
|
|
604
1584
|
onInput: (e) => days.value = e.target.value,
|
|
605
1585
|
placeholder: "days (optional)",
|
|
606
|
-
style: { ...styles.
|
|
1586
|
+
style: { ...styles.input, width: "140px" },
|
|
607
1587
|
"aria-label": "Days"
|
|
608
1588
|
}),
|
|
609
|
-
|
|
1589
|
+
h11(
|
|
610
1590
|
"button",
|
|
611
1591
|
{
|
|
612
1592
|
type: "submit",
|
|
613
|
-
style: styles.
|
|
1593
|
+
style: styles.buttonPrimary,
|
|
614
1594
|
disabled: !name.value.trim() || scopes.value.length === 0 || isLoading.value
|
|
615
1595
|
},
|
|
616
|
-
"Issue"
|
|
1596
|
+
[icon("plus", 14), "Issue token"]
|
|
617
1597
|
)
|
|
618
1598
|
]
|
|
619
1599
|
),
|
|
620
1600
|
r?.issued && r.issued.length > 0 ? table(
|
|
621
1601
|
["Name", "Scopes", "Created", "Expires", "Last used", ""],
|
|
622
1602
|
r.issued.map(
|
|
623
|
-
(t2) =>
|
|
624
|
-
td([
|
|
625
|
-
t2.name,
|
|
626
|
-
t2.revokedAt ? h9("span", { style: styles.badge }, " revoked") : ""
|
|
627
|
-
]),
|
|
1603
|
+
(t2) => h11("tr", { key: t2.id }, [
|
|
1604
|
+
td([t2.name, t2.revokedAt ? [" ", pill("neutral", "revoked")] : ""]),
|
|
628
1605
|
td(t2.scopes.join(", "), styles.mono),
|
|
629
1606
|
td(
|
|
630
1607
|
`${new Date(t2.createdAt).toLocaleDateString()} \xB7 ${t2.createdBy}`,
|
|
@@ -639,11 +1616,11 @@ var TokensScreen = defineComponent8({
|
|
|
639
1616
|
styles.muted
|
|
640
1617
|
),
|
|
641
1618
|
td(
|
|
642
|
-
t2.revokedAt ? "" :
|
|
1619
|
+
t2.revokedAt ? "" : h11(
|
|
643
1620
|
"button",
|
|
644
1621
|
{
|
|
645
1622
|
type: "button",
|
|
646
|
-
style: styles.
|
|
1623
|
+
style: styles.buttonDanger,
|
|
647
1624
|
onClick: () => {
|
|
648
1625
|
if (window.confirm(
|
|
649
1626
|
`Revoke "${t2.name}"? Anything using it stops working immediately.`
|
|
@@ -657,21 +1634,25 @@ var TokensScreen = defineComponent8({
|
|
|
657
1634
|
)
|
|
658
1635
|
])
|
|
659
1636
|
)
|
|
660
|
-
) :
|
|
1637
|
+
) : empty(
|
|
1638
|
+
"No scoped tokens yet",
|
|
1639
|
+
"Issue a read-only token for a dashboard or a teammate instead of sharing the root.",
|
|
1640
|
+
"tokens"
|
|
1641
|
+
)
|
|
661
1642
|
],
|
|
662
|
-
|
|
663
|
-
r && r.legacy.length === 0 ?
|
|
1643
|
+
h11("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
|
|
1644
|
+
r && r.legacy.length === 0 ? h11(
|
|
664
1645
|
"p",
|
|
665
1646
|
{ style: styles.muted },
|
|
666
1647
|
"None \u2014 every brick's admin surface goes through this token."
|
|
667
|
-
) :
|
|
1648
|
+
) : h11(
|
|
668
1649
|
"ul",
|
|
669
1650
|
{ style: styles.list },
|
|
670
1651
|
(r?.legacy ?? []).map(
|
|
671
|
-
(l) =>
|
|
672
|
-
|
|
1652
|
+
(l) => h11("li", { key: l.module, style: styles.row }, [
|
|
1653
|
+
h11("span", { style: styles.mono }, l.module),
|
|
673
1654
|
" ",
|
|
674
|
-
|
|
1655
|
+
h11(
|
|
675
1656
|
"span",
|
|
676
1657
|
{ style: styles.muted },
|
|
677
1658
|
"still registers its standalone routes with its own token (deprecated)"
|
|
@@ -692,17 +1673,17 @@ import {
|
|
|
692
1673
|
useAdminUserSessions,
|
|
693
1674
|
useAdminUsers
|
|
694
1675
|
} from "@fonderie/vue-admin";
|
|
695
|
-
import { computed, defineComponent as
|
|
696
|
-
var UsersScreen =
|
|
1676
|
+
import { computed, defineComponent as defineComponent10, h as h12, ref as ref4 } from "vue";
|
|
1677
|
+
var UsersScreen = defineComponent10({
|
|
697
1678
|
name: "FonderieUsersScreen",
|
|
698
1679
|
props: {
|
|
699
1680
|
client: { type: Object, required: true },
|
|
700
1681
|
pageSize: { type: Number, default: 50 }
|
|
701
1682
|
},
|
|
702
1683
|
setup(props) {
|
|
703
|
-
const input =
|
|
704
|
-
const email =
|
|
705
|
-
const selectedId =
|
|
1684
|
+
const input = ref4("");
|
|
1685
|
+
const email = ref4("");
|
|
1686
|
+
const selectedId = ref4("");
|
|
706
1687
|
const list = useAdminUsers(props.client, { limit: props.pageSize });
|
|
707
1688
|
const { user, isLoading, error, suspend, unsuspend, revokeSessions } = useAdminUser(
|
|
708
1689
|
props.client,
|
|
@@ -712,8 +1693,8 @@ var UsersScreen = defineComponent9({
|
|
|
712
1693
|
const sessions = useAdminUserSessions(props.client, userId);
|
|
713
1694
|
const history = useAdminLoginHistory(props.client, userId, { limit: 20 });
|
|
714
1695
|
const showList = computed(() => !email.value && !selectedId.value);
|
|
715
|
-
const yesNo = (v) =>
|
|
716
|
-
const row = (k, v) =>
|
|
1696
|
+
const yesNo = (v) => v ? pill("ok", "yes") : pill("neutral", "no");
|
|
1697
|
+
const row = (k, v) => h12("tr", [td(k), td(v)]);
|
|
717
1698
|
const clear = () => {
|
|
718
1699
|
email.value = "";
|
|
719
1700
|
selectedId.value = "";
|
|
@@ -721,9 +1702,12 @@ var UsersScreen = defineComponent9({
|
|
|
721
1702
|
};
|
|
722
1703
|
return () => {
|
|
723
1704
|
const u = user.value;
|
|
724
|
-
return
|
|
725
|
-
|
|
726
|
-
|
|
1705
|
+
return h12("div", { style: styles.container }, [
|
|
1706
|
+
pageHeader(
|
|
1707
|
+
"Users",
|
|
1708
|
+
"Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
|
|
1709
|
+
),
|
|
1710
|
+
h12(
|
|
727
1711
|
"form",
|
|
728
1712
|
{
|
|
729
1713
|
style: styles.toolbar,
|
|
@@ -734,75 +1718,67 @@ var UsersScreen = defineComponent9({
|
|
|
734
1718
|
}
|
|
735
1719
|
},
|
|
736
1720
|
[
|
|
737
|
-
|
|
1721
|
+
h12("input", {
|
|
738
1722
|
type: "email",
|
|
739
1723
|
value: input.value,
|
|
740
1724
|
onInput: (e) => input.value = e.target.value,
|
|
741
1725
|
placeholder: "email address",
|
|
742
|
-
style: { ...styles.
|
|
1726
|
+
style: { ...styles.input, minWidth: "280px" },
|
|
743
1727
|
"aria-label": "Email"
|
|
744
1728
|
}),
|
|
745
|
-
|
|
1729
|
+
h12(
|
|
746
1730
|
"button",
|
|
747
|
-
{ type: "submit", style: styles.
|
|
748
|
-
"Look up"
|
|
1731
|
+
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1732
|
+
[icon("search", 14), "Look up"]
|
|
749
1733
|
),
|
|
750
|
-
showList.value ? refreshButton("Refresh", list.isLoading.value, () => void list.refresh()) :
|
|
1734
|
+
showList.value ? refreshButton("Refresh", list.isLoading.value, () => void list.refresh()) : actionButton([icon("back", 14), "All users"], false, clear, styles.buttonGhost)
|
|
751
1735
|
]
|
|
752
1736
|
),
|
|
753
|
-
showList.value ?
|
|
754
|
-
list.error.value ?
|
|
755
|
-
list.users.value.length === 0 && !list.isLoading.value ?
|
|
1737
|
+
showList.value ? h12("div", [
|
|
1738
|
+
list.error.value ? h12("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
1739
|
+
list.users.value.length === 0 && !list.isLoading.value ? empty("No users yet", "Sign-ups appear here as they happen.", "users") : table(
|
|
756
1740
|
["Email", "Name", "Created", "Status"],
|
|
757
1741
|
list.users.value.map(
|
|
758
|
-
(u2) =>
|
|
1742
|
+
(u2) => h12("tr", { key: u2.id }, [
|
|
759
1743
|
td(
|
|
760
|
-
|
|
1744
|
+
h12(
|
|
761
1745
|
"button",
|
|
762
1746
|
{
|
|
763
1747
|
type: "button",
|
|
764
|
-
style:
|
|
1748
|
+
style: styles.link,
|
|
765
1749
|
onClick: () => selectedId.value = u2.id
|
|
766
1750
|
},
|
|
767
1751
|
u2.email
|
|
768
1752
|
)
|
|
769
1753
|
),
|
|
770
1754
|
td(
|
|
771
|
-
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() ||
|
|
1755
|
+
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || h12("span", { style: styles.muted }, "\u2014")
|
|
772
1756
|
),
|
|
773
1757
|
td(new Date(u2.createdAt).toLocaleDateString()),
|
|
774
1758
|
td(
|
|
775
|
-
u2.suspended ?
|
|
1759
|
+
u2.suspended ? pill("warn", "suspended") : u2.deletedAt ? pill("neutral", "deleted") : pill("ok", "active")
|
|
776
1760
|
)
|
|
777
1761
|
])
|
|
778
1762
|
)
|
|
779
1763
|
),
|
|
780
|
-
list.isLoading.value ?
|
|
781
|
-
list.hasMore.value && !list.isLoading.value ?
|
|
782
|
-
"button",
|
|
783
|
-
{
|
|
784
|
-
type: "button",
|
|
785
|
-
style: { ...styles.button, marginTop: "8px" },
|
|
786
|
-
onClick: () => void list.loadMore()
|
|
787
|
-
},
|
|
788
|
-
"Load more"
|
|
789
|
-
) : null
|
|
1764
|
+
list.isLoading.value ? h12("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1765
|
+
list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
|
|
790
1766
|
]) : null,
|
|
791
|
-
error.value ?
|
|
1767
|
+
error.value ? h12(
|
|
792
1768
|
"p",
|
|
793
1769
|
{ style: styles.error, role: "alert" },
|
|
794
1770
|
error.value.status === 404 ? "No user with that email." : error.value.explanation
|
|
795
1771
|
) : null,
|
|
796
1772
|
u ? [
|
|
797
|
-
|
|
1773
|
+
h12("h2", { style: styles.subtitle }, [
|
|
798
1774
|
u.firstName || u.lastName ? `${u.firstName} ${u.lastName}`.trim() : u.email,
|
|
799
1775
|
" ",
|
|
800
|
-
u.suspended ?
|
|
801
|
-
u.deletedAt ?
|
|
1776
|
+
u.suspended ? pill("warn", "suspended") : null,
|
|
1777
|
+
u.deletedAt ? pill("neutral", "deleted") : null
|
|
802
1778
|
]),
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
row("id",
|
|
1779
|
+
h12("table", { style: styles.table }, [
|
|
1780
|
+
h12("tbody", [
|
|
1781
|
+
row("id", h12("span", { style: styles.mono }, u.id)),
|
|
806
1782
|
row("email", [u.email, " \xB7 verified ", yesNo(u.isEmailVerified)]),
|
|
807
1783
|
row("MFA", yesNo(u.mfaEnabled)),
|
|
808
1784
|
row(
|
|
@@ -811,32 +1787,37 @@ var UsersScreen = defineComponent9({
|
|
|
811
1787
|
),
|
|
812
1788
|
row(
|
|
813
1789
|
"last login",
|
|
814
|
-
u.lastLogin ? new Date(u.lastLogin).toLocaleString() :
|
|
1790
|
+
u.lastLogin ? new Date(u.lastLogin).toLocaleString() : h12("span", { style: styles.muted }, "never")
|
|
815
1791
|
),
|
|
816
1792
|
row("created", new Date(u.createdAt).toLocaleString())
|
|
817
1793
|
])
|
|
818
1794
|
]),
|
|
819
|
-
|
|
820
|
-
u.suspended ?
|
|
821
|
-
|
|
1795
|
+
h12("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
|
|
1796
|
+
u.suspended ? actionButton("Unsuspend", isLoading.value, () => void unsuspend()) : actionButton(
|
|
1797
|
+
"Suspend",
|
|
1798
|
+
isLoading.value,
|
|
1799
|
+
() => void suspend(),
|
|
1800
|
+
styles.buttonDanger
|
|
1801
|
+
),
|
|
1802
|
+
actionButton(
|
|
822
1803
|
"Sign out everywhere",
|
|
823
1804
|
isLoading.value,
|
|
824
1805
|
() => void revokeSessions().then(() => sessions.refresh())
|
|
825
1806
|
)
|
|
826
1807
|
]),
|
|
827
|
-
|
|
828
|
-
sessions.sessions.value.length === 0 ?
|
|
1808
|
+
h12("h2", { style: styles.subtitle }, "Live sessions"),
|
|
1809
|
+
sessions.sessions.value.length === 0 ? h12("p", { style: styles.muted }, "None.") : h12(
|
|
829
1810
|
"ul",
|
|
830
1811
|
{ style: styles.list },
|
|
831
1812
|
sessions.sessions.value.map(
|
|
832
|
-
(s) =>
|
|
833
|
-
|
|
1813
|
+
(s) => h12("li", { key: s.id, style: styles.row }, [
|
|
1814
|
+
h12("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
|
|
834
1815
|
" ",
|
|
835
|
-
|
|
1816
|
+
h12("span", { style: styles.muted }, describeLocation(s.location) ?? ""),
|
|
836
1817
|
" ",
|
|
837
|
-
|
|
1818
|
+
h12("span", { style: styles.muted }, s.userAgent ?? ""),
|
|
838
1819
|
" ",
|
|
839
|
-
|
|
1820
|
+
h12(
|
|
840
1821
|
"span",
|
|
841
1822
|
{ style: styles.muted },
|
|
842
1823
|
`since ${new Date(s.createdAt).toLocaleString()}`
|
|
@@ -844,38 +1825,26 @@ var UsersScreen = defineComponent9({
|
|
|
844
1825
|
])
|
|
845
1826
|
)
|
|
846
1827
|
),
|
|
847
|
-
|
|
848
|
-
history.events.value.length === 0 && !history.isLoading.value ?
|
|
1828
|
+
h12("h2", { style: styles.subtitle }, "Recent sign-ins"),
|
|
1829
|
+
history.events.value.length === 0 && !history.isLoading.value ? h12("p", { style: styles.muted }, "None recorded.") : h12(
|
|
849
1830
|
"ul",
|
|
850
1831
|
{ style: styles.list },
|
|
851
1832
|
history.events.value.map(
|
|
852
|
-
(e) =>
|
|
853
|
-
|
|
854
|
-
"span",
|
|
855
|
-
{ style: e.outcome === "success" ? styles.ok : styles.bad },
|
|
856
|
-
e.outcome
|
|
857
|
-
),
|
|
1833
|
+
(e) => h12("li", { key: e.id, style: styles.row }, [
|
|
1834
|
+
pill(e.outcome === "success" ? "ok" : "bad", e.outcome),
|
|
858
1835
|
" ",
|
|
859
|
-
|
|
1836
|
+
h12("span", { style: styles.muted }, e.method),
|
|
860
1837
|
" ",
|
|
861
|
-
|
|
1838
|
+
h12("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
|
|
862
1839
|
" ",
|
|
863
|
-
|
|
864
|
-
e.location?.proxy || e.location?.hosting ?
|
|
1840
|
+
h12("span", { style: styles.muted }, describeLocation(e.location) ?? ""),
|
|
1841
|
+
e.location?.proxy || e.location?.hosting ? [" ", pill("warn", e.location.proxy ? "proxy/VPN" : "hosting")] : "",
|
|
865
1842
|
" ",
|
|
866
|
-
|
|
1843
|
+
h12("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
|
|
867
1844
|
])
|
|
868
1845
|
)
|
|
869
1846
|
),
|
|
870
|
-
history.hasMore.value && !history.isLoading.value ?
|
|
871
|
-
"button",
|
|
872
|
-
{
|
|
873
|
-
type: "button",
|
|
874
|
-
style: { ...styles.button, marginTop: "8px" },
|
|
875
|
-
onClick: () => void history.loadMore()
|
|
876
|
-
},
|
|
877
|
-
"Load more"
|
|
878
|
-
) : null
|
|
1847
|
+
history.hasMore.value && !history.isLoading.value ? loadMoreButton(() => void history.loadMore()) : null
|
|
879
1848
|
] : null
|
|
880
1849
|
]);
|
|
881
1850
|
};
|
|
@@ -884,8 +1853,8 @@ var UsersScreen = defineComponent9({
|
|
|
884
1853
|
|
|
885
1854
|
// src/screens/CatalogScreen.ts
|
|
886
1855
|
import { useAdminCatalog } from "@fonderie/vue-admin";
|
|
887
|
-
import { defineComponent as
|
|
888
|
-
var CatalogScreen =
|
|
1856
|
+
import { defineComponent as defineComponent11, h as h13 } from "vue";
|
|
1857
|
+
var CatalogScreen = defineComponent11({
|
|
889
1858
|
name: "FonderieCatalogScreen",
|
|
890
1859
|
props: { client: { type: Object, required: true } },
|
|
891
1860
|
setup(props) {
|
|
@@ -893,35 +1862,42 @@ var CatalogScreen = defineComponent10({
|
|
|
893
1862
|
const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
|
|
894
1863
|
return () => {
|
|
895
1864
|
const c = catalog.value;
|
|
896
|
-
return
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1865
|
+
return h13("div", { style: styles.container }, [
|
|
1866
|
+
pageHeader(
|
|
1867
|
+
"Catalog",
|
|
1868
|
+
"What you sell: plans as configured in code, and as stored in the database, side by side.",
|
|
1869
|
+
[refreshButton("Refresh", isLoading.value, () => void refresh())]
|
|
1870
|
+
),
|
|
1871
|
+
error.value ? h13("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
1872
|
+
isLoading.value && !c ? h13("p", { style: styles.status }, "Loading\u2026") : null,
|
|
903
1873
|
c ? [
|
|
904
|
-
|
|
905
|
-
|
|
1874
|
+
h13("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Configured (code)"),
|
|
1875
|
+
h13(
|
|
906
1876
|
"pre",
|
|
907
1877
|
{
|
|
908
1878
|
style: {
|
|
1879
|
+
...styles.card,
|
|
909
1880
|
...styles.mono,
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1881
|
+
margin: 0,
|
|
1882
|
+
overflowX: "auto",
|
|
1883
|
+
maxHeight: "360px",
|
|
1884
|
+
lineHeight: 1.6
|
|
913
1885
|
}
|
|
914
1886
|
},
|
|
915
1887
|
JSON.stringify(c.configured, null, 2)
|
|
916
1888
|
),
|
|
917
|
-
|
|
918
|
-
c.stored.length === 0 ?
|
|
1889
|
+
h13("h2", { style: styles.subtitle }, "Stored (database)"),
|
|
1890
|
+
c.stored.length === 0 ? empty(
|
|
1891
|
+
"No stored plans",
|
|
1892
|
+
"Plans are configured in code; nothing has been written to the database.",
|
|
1893
|
+
"catalog"
|
|
1894
|
+
) : table(
|
|
919
1895
|
["Plan", "Tier", "Seats", "Monthly", "Yearly", "Trial", ""],
|
|
920
1896
|
c.stored.map(
|
|
921
|
-
(p) =>
|
|
1897
|
+
(p) => h13("tr", { key: p.id }, [
|
|
922
1898
|
td([
|
|
923
|
-
|
|
924
|
-
p.description ?
|
|
1899
|
+
h13("strong", p.name),
|
|
1900
|
+
p.description ? h13("div", { style: styles.muted }, p.description) : ""
|
|
925
1901
|
]),
|
|
926
1902
|
td(String(p.tier)),
|
|
927
1903
|
td(p.seats == null ? "\u221E" : String(p.seats)),
|
|
@@ -929,11 +1905,11 @@ var CatalogScreen = defineComponent10({
|
|
|
929
1905
|
td(money(p.pricing?.yearly)),
|
|
930
1906
|
td(p.trialDays ? `${p.trialDays} d` : "\u2014"),
|
|
931
1907
|
td(
|
|
932
|
-
|
|
1908
|
+
h13(
|
|
933
1909
|
"button",
|
|
934
1910
|
{
|
|
935
1911
|
type: "button",
|
|
936
|
-
style: styles.
|
|
1912
|
+
style: styles.buttonDanger,
|
|
937
1913
|
onClick: () => {
|
|
938
1914
|
if (window.confirm(`Delete stored plan "${p.name}"?`))
|
|
939
1915
|
void deletePlan(p.id).catch(() => {
|
|
@@ -954,17 +1930,17 @@ var CatalogScreen = defineComponent10({
|
|
|
954
1930
|
|
|
955
1931
|
// src/screens/SubscriberScreen.ts
|
|
956
1932
|
import { useAdminSubscriber, useAdminSubscribers } from "@fonderie/vue-admin";
|
|
957
|
-
import { defineComponent as
|
|
958
|
-
var SubscriberScreen =
|
|
1933
|
+
import { defineComponent as defineComponent12, h as h14, ref as ref5 } from "vue";
|
|
1934
|
+
var SubscriberScreen = defineComponent12({
|
|
959
1935
|
name: "FonderieSubscriberScreen",
|
|
960
1936
|
props: {
|
|
961
1937
|
client: { type: Object, required: true },
|
|
962
1938
|
pageSize: { type: Number, default: 50 }
|
|
963
1939
|
},
|
|
964
1940
|
setup(props) {
|
|
965
|
-
const type =
|
|
966
|
-
const idInput =
|
|
967
|
-
const subscriber =
|
|
1941
|
+
const type = ref5("user");
|
|
1942
|
+
const idInput = ref5("");
|
|
1943
|
+
const subscriber = ref5(null);
|
|
968
1944
|
const list = useAdminSubscribers(props.client, { limit: props.pageSize });
|
|
969
1945
|
const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = useAdminSubscriber(props.client, subscriber, { limit: 20 });
|
|
970
1946
|
const open = (t2, id) => {
|
|
@@ -973,22 +1949,25 @@ var SubscriberScreen = defineComponent11({
|
|
|
973
1949
|
idInput.value = id;
|
|
974
1950
|
subscriber.value = { type: t2, id };
|
|
975
1951
|
};
|
|
976
|
-
const amount =
|
|
977
|
-
const note =
|
|
978
|
-
const granted =
|
|
979
|
-
const input = (model, placeholder, extra = {}) =>
|
|
1952
|
+
const amount = ref5("");
|
|
1953
|
+
const note = ref5("");
|
|
1954
|
+
const granted = ref5(null);
|
|
1955
|
+
const input = (model, placeholder, extra = {}) => h14("input", {
|
|
980
1956
|
value: model.value,
|
|
981
1957
|
onInput: (e) => model.value = e.target.value,
|
|
982
1958
|
placeholder,
|
|
983
|
-
style: { ...styles.
|
|
1959
|
+
style: { ...styles.input, ...extra },
|
|
984
1960
|
"aria-label": placeholder
|
|
985
1961
|
});
|
|
986
1962
|
return () => {
|
|
987
1963
|
const s = subscription.value;
|
|
988
1964
|
const w = wallet.value;
|
|
989
|
-
return
|
|
990
|
-
|
|
991
|
-
|
|
1965
|
+
return h14("div", { style: styles.container }, [
|
|
1966
|
+
pageHeader(
|
|
1967
|
+
"Subscribers",
|
|
1968
|
+
"Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
|
|
1969
|
+
),
|
|
1970
|
+
h14(
|
|
992
1971
|
"form",
|
|
993
1972
|
{
|
|
994
1973
|
style: styles.toolbar,
|
|
@@ -999,26 +1978,26 @@ var SubscriberScreen = defineComponent11({
|
|
|
999
1978
|
}
|
|
1000
1979
|
},
|
|
1001
1980
|
[
|
|
1002
|
-
|
|
1981
|
+
h14(
|
|
1003
1982
|
"select",
|
|
1004
1983
|
{
|
|
1005
1984
|
value: type.value,
|
|
1006
1985
|
onChange: (e) => type.value = e.target.value,
|
|
1007
|
-
style: styles.
|
|
1986
|
+
style: styles.input,
|
|
1008
1987
|
"aria-label": "Type"
|
|
1009
1988
|
},
|
|
1010
1989
|
[
|
|
1011
|
-
|
|
1012
|
-
|
|
1990
|
+
h14("option", { value: "user" }, "user"),
|
|
1991
|
+
h14("option", { value: "workspace" }, "workspace")
|
|
1013
1992
|
]
|
|
1014
1993
|
),
|
|
1015
1994
|
input(idInput, "subscriber id", { minWidth: "280px" }),
|
|
1016
|
-
|
|
1995
|
+
h14(
|
|
1017
1996
|
"button",
|
|
1018
|
-
{ type: "submit", style: styles.
|
|
1019
|
-
"Look up"
|
|
1997
|
+
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1998
|
+
[icon("search", 14), "Look up"]
|
|
1020
1999
|
),
|
|
1021
|
-
subscriber.value ?
|
|
2000
|
+
subscriber.value ? h14(
|
|
1022
2001
|
"button",
|
|
1023
2002
|
{
|
|
1024
2003
|
type: "button",
|
|
@@ -1029,8 +2008,8 @@ var SubscriberScreen = defineComponent11({
|
|
|
1029
2008
|
granted.value = null;
|
|
1030
2009
|
}
|
|
1031
2010
|
},
|
|
1032
|
-
"
|
|
1033
|
-
) :
|
|
2011
|
+
[icon("back", 14), "All subscribers"]
|
|
2012
|
+
) : h14(
|
|
1034
2013
|
"button",
|
|
1035
2014
|
{
|
|
1036
2015
|
type: "button",
|
|
@@ -1042,35 +2021,43 @@ var SubscriberScreen = defineComponent11({
|
|
|
1042
2021
|
)
|
|
1043
2022
|
]
|
|
1044
2023
|
),
|
|
1045
|
-
!subscriber.value ?
|
|
1046
|
-
list.error.value ?
|
|
1047
|
-
list.subscriptions.value.length === 0 && !list.isLoading.value ?
|
|
2024
|
+
!subscriber.value ? h14("div", [
|
|
2025
|
+
list.error.value ? h14("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
2026
|
+
list.subscriptions.value.length === 0 && !list.isLoading.value ? empty(
|
|
2027
|
+
"No subscribers yet",
|
|
2028
|
+
"Subscriptions appear here once someone checks out.",
|
|
2029
|
+
"subscriber"
|
|
2030
|
+
) : table(
|
|
1048
2031
|
["Subscriber", "Plan", "Status", "Renews"],
|
|
1049
2032
|
list.subscriptions.value.map(
|
|
1050
|
-
(sub) =>
|
|
2033
|
+
(sub) => h14("tr", { key: sub.id }, [
|
|
1051
2034
|
td(
|
|
1052
|
-
|
|
2035
|
+
h14(
|
|
1053
2036
|
"button",
|
|
1054
2037
|
{
|
|
1055
2038
|
type: "button",
|
|
1056
|
-
style: { ...styles.
|
|
2039
|
+
style: { ...styles.link, ...styles.mono },
|
|
1057
2040
|
onClick: () => open(sub.subscriberType, sub.subscriberId)
|
|
1058
2041
|
},
|
|
1059
2042
|
`${sub.subscriberType}/${sub.subscriberId}`
|
|
1060
2043
|
)
|
|
1061
2044
|
),
|
|
1062
2045
|
td(`${sub.plan} \xB7 ${sub.interval}`),
|
|
2046
|
+
td([
|
|
2047
|
+
pill(
|
|
2048
|
+
sub.status === "active" ? "ok" : sub.status === "trialing" ? "info" : sub.status === "past_due" ? "warn" : "neutral",
|
|
2049
|
+
sub.status
|
|
2050
|
+
),
|
|
2051
|
+
sub.cancelAtPeriodEnd ? [" ", pill("warn", "cancels", false)] : null
|
|
2052
|
+
]),
|
|
1063
2053
|
td(
|
|
1064
|
-
sub.
|
|
1065
|
-
),
|
|
1066
|
-
td(
|
|
1067
|
-
sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : h12("span", { style: styles.muted }, "\u2014")
|
|
2054
|
+
sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : h14("span", { style: styles.muted }, "\u2014")
|
|
1068
2055
|
)
|
|
1069
2056
|
])
|
|
1070
2057
|
)
|
|
1071
2058
|
),
|
|
1072
|
-
list.isLoading.value ?
|
|
1073
|
-
list.hasMore.value && !list.isLoading.value ?
|
|
2059
|
+
list.isLoading.value ? h14("p", { style: styles.status }, "Loading\u2026") : null,
|
|
2060
|
+
list.hasMore.value && !list.isLoading.value ? h14(
|
|
1074
2061
|
"button",
|
|
1075
2062
|
{
|
|
1076
2063
|
type: "button",
|
|
@@ -1080,37 +2067,58 @@ var SubscriberScreen = defineComponent11({
|
|
|
1080
2067
|
"Load more"
|
|
1081
2068
|
) : null
|
|
1082
2069
|
]) : null,
|
|
1083
|
-
error.value ?
|
|
2070
|
+
error.value ? h14("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
1084
2071
|
subscriber.value && !isLoading.value ? [
|
|
1085
|
-
|
|
1086
|
-
s ?
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
2072
|
+
h14("h2", { style: styles.subtitle }, "Subscription"),
|
|
2073
|
+
s ? h14("table", { style: styles.table }, [
|
|
2074
|
+
h14("tbody", [
|
|
2075
|
+
h14("tr", [td("plan"), td([h14("strong", s.plan), ` \xB7 ${s.interval}`])]),
|
|
2076
|
+
h14("tr", [
|
|
1090
2077
|
td("status"),
|
|
1091
2078
|
td(`${s.status}${s.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""}`)
|
|
1092
2079
|
]),
|
|
1093
|
-
|
|
2080
|
+
h14("tr", [
|
|
1094
2081
|
td("period"),
|
|
1095
2082
|
td(
|
|
1096
2083
|
`${s.currentPeriodStart ? new Date(s.currentPeriodStart).toLocaleDateString() : "\u2014"} \u2192 ${s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : "\u2014"}`
|
|
1097
2084
|
)
|
|
1098
2085
|
]),
|
|
1099
|
-
|
|
2086
|
+
h14("tr", [td("provider"), td(s.providerSubscriptionId ?? "\u2014", styles.mono)])
|
|
1100
2087
|
])
|
|
1101
|
-
]) :
|
|
1102
|
-
|
|
2088
|
+
]) : h14("p", { style: styles.muted }, "No subscription."),
|
|
2089
|
+
h14("h2", { style: styles.subtitle }, "Wallet"),
|
|
1103
2090
|
w ? [
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
2091
|
+
h14(
|
|
2092
|
+
"p",
|
|
2093
|
+
{
|
|
2094
|
+
style: {
|
|
2095
|
+
...styles.card,
|
|
2096
|
+
display: "flex",
|
|
2097
|
+
alignItems: "baseline",
|
|
2098
|
+
gap: "8px",
|
|
2099
|
+
flexWrap: "wrap"
|
|
2100
|
+
}
|
|
2101
|
+
},
|
|
2102
|
+
[
|
|
2103
|
+
h14(
|
|
2104
|
+
"strong",
|
|
2105
|
+
{
|
|
2106
|
+
style: {
|
|
2107
|
+
fontSize: "22px",
|
|
2108
|
+
letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
|
|
2109
|
+
}
|
|
2110
|
+
},
|
|
2111
|
+
w.balance
|
|
2112
|
+
),
|
|
2113
|
+
` ${w.currency} `,
|
|
2114
|
+
h14(
|
|
2115
|
+
"span",
|
|
2116
|
+
{ style: styles.muted },
|
|
2117
|
+
`(granted ${w.granted ?? "0"} \xB7 purchased ${w.purchased ?? "0"}) \xB7 minor units, precision ${w.precision}`
|
|
2118
|
+
)
|
|
2119
|
+
]
|
|
2120
|
+
),
|
|
2121
|
+
h14(
|
|
1114
2122
|
"form",
|
|
1115
2123
|
{
|
|
1116
2124
|
style: styles.toolbar,
|
|
@@ -1135,23 +2143,23 @@ var SubscriberScreen = defineComponent11({
|
|
|
1135
2143
|
[
|
|
1136
2144
|
input(amount, "amount (minor units)"),
|
|
1137
2145
|
input(note, "reason (optional)", { minWidth: "220px" }),
|
|
1138
|
-
|
|
2146
|
+
h14(
|
|
1139
2147
|
"button",
|
|
1140
2148
|
{
|
|
1141
2149
|
type: "submit",
|
|
1142
|
-
style: styles.
|
|
2150
|
+
style: styles.buttonPrimary,
|
|
1143
2151
|
disabled: !/^\d+$/.test(amount.value.trim())
|
|
1144
2152
|
},
|
|
1145
|
-
"Grant"
|
|
2153
|
+
[icon("plus", 14), "Grant"]
|
|
1146
2154
|
),
|
|
1147
|
-
granted.value ?
|
|
2155
|
+
granted.value ? h14("span", { style: styles.ok }, granted.value) : null
|
|
1148
2156
|
]
|
|
1149
2157
|
),
|
|
1150
|
-
|
|
1151
|
-
ledger.value.length === 0 ?
|
|
2158
|
+
h14("h2", { style: styles.subtitle }, "Ledger"),
|
|
2159
|
+
ledger.value.length === 0 ? empty("Nothing moved yet", void 0, "subscriber") : table(
|
|
1152
2160
|
["When", "Kind", "Amount", "Note"],
|
|
1153
2161
|
ledger.value.map(
|
|
1154
|
-
(t2) =>
|
|
2162
|
+
(t2) => h14("tr", { key: t2.id }, [
|
|
1155
2163
|
td(new Date(t2.createdAt).toLocaleString(), styles.muted),
|
|
1156
2164
|
td(t2.type),
|
|
1157
2165
|
td(t2.amount, styles.mono),
|
|
@@ -1159,7 +2167,7 @@ var SubscriberScreen = defineComponent11({
|
|
|
1159
2167
|
])
|
|
1160
2168
|
)
|
|
1161
2169
|
),
|
|
1162
|
-
hasMoreLedger.value ?
|
|
2170
|
+
hasMoreLedger.value ? h14(
|
|
1163
2171
|
"button",
|
|
1164
2172
|
{
|
|
1165
2173
|
type: "button",
|
|
@@ -1168,7 +2176,7 @@ var SubscriberScreen = defineComponent11({
|
|
|
1168
2176
|
},
|
|
1169
2177
|
"Load more"
|
|
1170
2178
|
) : null
|
|
1171
|
-
] :
|
|
2179
|
+
] : h14(
|
|
1172
2180
|
"p",
|
|
1173
2181
|
{ style: styles.muted },
|
|
1174
2182
|
"No wallet \u2014 billing has no wallet configuration."
|
|
@@ -1181,30 +2189,33 @@ var SubscriberScreen = defineComponent11({
|
|
|
1181
2189
|
|
|
1182
2190
|
// src/screens/AuditScreen.ts
|
|
1183
2191
|
import { useAdminAudit } from "@fonderie/vue-admin";
|
|
1184
|
-
import { defineComponent as
|
|
1185
|
-
var AuditScreen =
|
|
2192
|
+
import { defineComponent as defineComponent13, h as h15, ref as ref6 } from "vue";
|
|
2193
|
+
var AuditScreen = defineComponent13({
|
|
1186
2194
|
name: "FonderieAuditScreen",
|
|
1187
2195
|
props: {
|
|
1188
2196
|
client: { type: Object, required: true },
|
|
1189
2197
|
pageSize: { type: Number, default: 50 }
|
|
1190
2198
|
},
|
|
1191
2199
|
setup(props) {
|
|
1192
|
-
const draft =
|
|
1193
|
-
const query =
|
|
2200
|
+
const draft = ref6({ workspaceId: "", type: "", actorId: "" });
|
|
2201
|
+
const query = ref6({ limit: props.pageSize });
|
|
1194
2202
|
const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(
|
|
1195
2203
|
props.client,
|
|
1196
2204
|
query
|
|
1197
2205
|
);
|
|
1198
|
-
const field = (key, placeholder) =>
|
|
2206
|
+
const field = (key, placeholder) => h15("input", {
|
|
1199
2207
|
value: draft.value[key],
|
|
1200
2208
|
onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
|
|
1201
2209
|
placeholder,
|
|
1202
|
-
style:
|
|
2210
|
+
style: styles.input,
|
|
1203
2211
|
"aria-label": placeholder
|
|
1204
2212
|
});
|
|
1205
|
-
return () =>
|
|
1206
|
-
|
|
1207
|
-
|
|
2213
|
+
return () => h15("div", { style: styles.container }, [
|
|
2214
|
+
pageHeader(
|
|
2215
|
+
"Audit",
|
|
2216
|
+
"What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
|
|
2217
|
+
),
|
|
2218
|
+
h15(
|
|
1208
2219
|
"form",
|
|
1209
2220
|
{
|
|
1210
2221
|
style: styles.toolbar,
|
|
@@ -1223,19 +2234,19 @@ var AuditScreen = defineComponent12({
|
|
|
1223
2234
|
field("workspaceId", "workspace id (all if empty)"),
|
|
1224
2235
|
field("type", "event type"),
|
|
1225
2236
|
field("actorId", "actor id"),
|
|
1226
|
-
|
|
2237
|
+
h15(
|
|
1227
2238
|
"button",
|
|
1228
|
-
{ type: "submit", style: styles.
|
|
1229
|
-
"Filter"
|
|
2239
|
+
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
2240
|
+
[icon("search", 14), "Filter"]
|
|
1230
2241
|
),
|
|
1231
2242
|
refreshButton("Refresh", isLoading.value, () => void refresh())
|
|
1232
2243
|
]
|
|
1233
2244
|
),
|
|
1234
|
-
error.value ?
|
|
1235
|
-
events.value.length === 0 && !isLoading.value ?
|
|
2245
|
+
error.value ? h15("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
2246
|
+
events.value.length === 0 && !isLoading.value ? empty("Nothing recorded for this filter", void 0, "audit") : table(
|
|
1236
2247
|
["When", "Type", "Workspace", "Actor", "Request"],
|
|
1237
2248
|
events.value.map(
|
|
1238
|
-
(e) =>
|
|
2249
|
+
(e) => h15("tr", { key: e.id }, [
|
|
1239
2250
|
td(new Date(e.createdAt).toLocaleString(), styles.muted),
|
|
1240
2251
|
td(e.type, styles.mono),
|
|
1241
2252
|
td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
|
|
@@ -1244,16 +2255,8 @@ var AuditScreen = defineComponent12({
|
|
|
1244
2255
|
])
|
|
1245
2256
|
)
|
|
1246
2257
|
),
|
|
1247
|
-
isLoading.value ?
|
|
1248
|
-
hasMore.value && !isLoading.value ?
|
|
1249
|
-
"button",
|
|
1250
|
-
{
|
|
1251
|
-
type: "button",
|
|
1252
|
-
style: { ...styles.button, marginTop: "8px" },
|
|
1253
|
-
onClick: () => void loadMore()
|
|
1254
|
-
},
|
|
1255
|
-
"Load more"
|
|
1256
|
-
) : null
|
|
2258
|
+
isLoading.value ? h15("p", { style: styles.status }, "Loading\u2026") : null,
|
|
2259
|
+
hasMore.value && !isLoading.value ? loadMoreButton(() => void loadMore()) : null
|
|
1257
2260
|
]);
|
|
1258
2261
|
}
|
|
1259
2262
|
});
|
|
@@ -1285,12 +2288,31 @@ var NAV = [
|
|
|
1285
2288
|
items: [
|
|
1286
2289
|
{ page: "audit", label: "Audit", needs: "audit" },
|
|
1287
2290
|
{ page: "log", label: "Admin log" },
|
|
1288
|
-
{ page: "
|
|
2291
|
+
{ page: "operators", label: "Operators", needs: "operators" },
|
|
2292
|
+
{ page: "tokens", label: "Tokens" },
|
|
1289
2293
|
{ page: "migrations", label: "Migrations" }
|
|
1290
2294
|
]
|
|
1291
2295
|
}
|
|
1292
2296
|
];
|
|
1293
|
-
var
|
|
2297
|
+
var ICON = {
|
|
2298
|
+
attention: "attention",
|
|
2299
|
+
modules: "modules",
|
|
2300
|
+
environment: "environment",
|
|
2301
|
+
doctor: "doctor",
|
|
2302
|
+
routes: "routes",
|
|
2303
|
+
users: "users",
|
|
2304
|
+
catalog: "catalog",
|
|
2305
|
+
subscriber: "subscriber",
|
|
2306
|
+
settings: "settings",
|
|
2307
|
+
templates: "templates",
|
|
2308
|
+
audit: "audit",
|
|
2309
|
+
log: "log",
|
|
2310
|
+
tokens: "tokens",
|
|
2311
|
+
operators: "users",
|
|
2312
|
+
migrations: "migrations"
|
|
2313
|
+
};
|
|
2314
|
+
var NARROW = "(max-width: 820px)";
|
|
2315
|
+
var AdminShell = defineComponent14({
|
|
1294
2316
|
name: "FonderieAdminShell",
|
|
1295
2317
|
props: {
|
|
1296
2318
|
client: { type: Object, required: true },
|
|
@@ -1307,75 +2329,85 @@ var AdminShell = defineComponent13({
|
|
|
1307
2329
|
environment: { type: String, default: void 0 },
|
|
1308
2330
|
// Controlled navigation: pass `page` and listen to `navigate` to own the
|
|
1309
2331
|
// URL. Omit `page` and the shell keeps it itself.
|
|
1310
|
-
page: { type: String, default: void 0 }
|
|
2332
|
+
page: { type: String, default: void 0 },
|
|
2333
|
+
// Shown at the top of the sidebar. Defaults to "Admin".
|
|
2334
|
+
appName: { type: String, default: "Admin" },
|
|
2335
|
+
// A badge beside the name — the deployment's environment ("production").
|
|
2336
|
+
// Production is tinted so an operator always knows where their clicks land.
|
|
2337
|
+
envLabel: { type: String, default: void 0 },
|
|
2338
|
+
// The deployment has operator accounts (@fonderie/admin with a store):
|
|
2339
|
+
// shows the Operators page.
|
|
2340
|
+
operators: { type: Boolean, default: false },
|
|
2341
|
+
// The signed-in operator's email, when a person (not a token) is signed in.
|
|
2342
|
+
currentOperator: { type: String, default: void 0 }
|
|
1311
2343
|
},
|
|
2344
|
+
// `footer` slot: pinned to the bottom of the sidebar — session controls
|
|
2345
|
+
// (theme, sign out). The React shell takes the same thing as a `footer` prop.
|
|
1312
2346
|
emits: { navigate: (_page) => true },
|
|
1313
|
-
setup(props, { emit }) {
|
|
1314
|
-
const
|
|
2347
|
+
setup(props, { emit, slots }) {
|
|
2348
|
+
const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
|
|
2349
|
+
const narrow = ref7(query?.matches ?? false);
|
|
2350
|
+
const drawer = ref7(false);
|
|
2351
|
+
const onMedia = (e) => narrow.value = e.matches;
|
|
2352
|
+
onMounted(() => query?.addEventListener("change", onMedia));
|
|
2353
|
+
onBeforeUnmount(() => query?.removeEventListener("change", onMedia));
|
|
2354
|
+
const own = ref7("attention");
|
|
1315
2355
|
const current = computed2(() => props.page ?? own.value);
|
|
1316
|
-
const editing =
|
|
2356
|
+
const editing = ref7(null);
|
|
1317
2357
|
const go = (p) => {
|
|
1318
2358
|
editing.value = null;
|
|
1319
2359
|
emit("navigate", p);
|
|
1320
2360
|
if (props.page === void 0) own.value = p;
|
|
2361
|
+
drawer.value = false;
|
|
1321
2362
|
};
|
|
1322
|
-
const back = () =>
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
2363
|
+
const back = (label) => h16("div", { style: { padding: "24px 40px 0" } }, [
|
|
2364
|
+
h16(
|
|
2365
|
+
"button",
|
|
2366
|
+
{ type: "button", style: styles.buttonGhost, onClick: () => editing.value = null },
|
|
2367
|
+
[icon("back", 14), label]
|
|
2368
|
+
)
|
|
2369
|
+
]);
|
|
2370
|
+
const missing = (text) => h16("p", { style: { ...styles.status, padding: "32px 40px" } }, text);
|
|
1327
2371
|
const body = () => {
|
|
1328
2372
|
const c = props.client;
|
|
1329
2373
|
switch (current.value) {
|
|
1330
2374
|
case "attention":
|
|
1331
|
-
return
|
|
2375
|
+
return h16(AttentionScreen, { client: c });
|
|
1332
2376
|
case "modules":
|
|
1333
|
-
return
|
|
2377
|
+
return h16(ModulesScreen, { client: c });
|
|
1334
2378
|
case "doctor":
|
|
1335
|
-
return
|
|
2379
|
+
return h16(DoctorScreen, { client: c });
|
|
1336
2380
|
case "environment":
|
|
1337
|
-
return
|
|
2381
|
+
return h16(EnvironmentScreen, { client: c });
|
|
1338
2382
|
case "routes":
|
|
1339
|
-
return
|
|
2383
|
+
return h16(RoutesScreen, { client: c });
|
|
1340
2384
|
case "tokens":
|
|
1341
|
-
return
|
|
2385
|
+
return h16(TokensScreen, { client: c });
|
|
2386
|
+
case "operators":
|
|
2387
|
+
return h16(OperatorsScreen, {
|
|
2388
|
+
client: c,
|
|
2389
|
+
...props.currentOperator ? { me: props.currentOperator } : {}
|
|
2390
|
+
});
|
|
1342
2391
|
case "migrations":
|
|
1343
|
-
return
|
|
2392
|
+
return h16(MigrationsScreen, { client: c });
|
|
1344
2393
|
case "log":
|
|
1345
|
-
return
|
|
2394
|
+
return h16(AdminLogScreen, { client: c });
|
|
1346
2395
|
case "catalog":
|
|
1347
|
-
return props.billingClient ?
|
|
1348
|
-
"p",
|
|
1349
|
-
{ style: styles.status },
|
|
1350
|
-
"Pass a BillingAdminClient to see the catalog here."
|
|
1351
|
-
);
|
|
2396
|
+
return props.billingClient ? h16(CatalogScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to see the catalog here.");
|
|
1352
2397
|
case "subscriber":
|
|
1353
|
-
return props.billingClient ?
|
|
1354
|
-
"p",
|
|
1355
|
-
{ style: styles.status },
|
|
1356
|
-
"Pass a BillingAdminClient to look up subscribers here."
|
|
1357
|
-
);
|
|
2398
|
+
return props.billingClient ? h16(SubscriberScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to look up subscribers here.");
|
|
1358
2399
|
case "audit":
|
|
1359
|
-
return props.auditClient ?
|
|
1360
|
-
"p",
|
|
1361
|
-
{ style: styles.status },
|
|
1362
|
-
"Pass an AuditAdminClient to see the audit trail here."
|
|
1363
|
-
);
|
|
2400
|
+
return props.auditClient ? h16(AuditScreen, { client: props.auditClient }) : missing("Pass an AuditAdminClient to see the audit trail here.");
|
|
1364
2401
|
case "users":
|
|
1365
|
-
return props.authClient ?
|
|
2402
|
+
return props.authClient ? h16(UsersScreen, { client: props.authClient }) : missing("Pass an AuthAdminClient to look up users here.");
|
|
1366
2403
|
case "settings": {
|
|
1367
2404
|
const cc = props.configClient;
|
|
1368
|
-
if (!cc)
|
|
1369
|
-
return h14(
|
|
1370
|
-
"p",
|
|
1371
|
-
{ style: styles.status },
|
|
1372
|
-
"Pass a ConfigAdminClient to manage config and secrets here."
|
|
1373
|
-
);
|
|
2405
|
+
if (!cc) return missing("Pass a ConfigAdminClient to manage config and secrets here.");
|
|
1374
2406
|
const e = editing.value;
|
|
1375
2407
|
if (e && e.kind !== "template")
|
|
1376
2408
|
return [
|
|
1377
|
-
back(),
|
|
1378
|
-
|
|
2409
|
+
back("Config & secrets"),
|
|
2410
|
+
h16(ConfigEditorScreen, {
|
|
1379
2411
|
client: cc,
|
|
1380
2412
|
kind: e.kind,
|
|
1381
2413
|
configKey: e.key,
|
|
@@ -1383,7 +2415,7 @@ var AdminShell = defineComponent13({
|
|
|
1383
2415
|
onSaved: () => editing.value = null
|
|
1384
2416
|
})
|
|
1385
2417
|
];
|
|
1386
|
-
return
|
|
2418
|
+
return h16(ConfigListScreen, {
|
|
1387
2419
|
client: cc,
|
|
1388
2420
|
...props.environment !== void 0 ? { environment: props.environment } : {},
|
|
1389
2421
|
"onSelect-config": (key) => editing.value = { kind: "config", key },
|
|
@@ -1395,61 +2427,135 @@ var AdminShell = defineComponent13({
|
|
|
1395
2427
|
}
|
|
1396
2428
|
case "templates": {
|
|
1397
2429
|
const kc = props.courierClient;
|
|
1398
|
-
if (!kc)
|
|
1399
|
-
return h14(
|
|
1400
|
-
"p",
|
|
1401
|
-
{ style: styles.status },
|
|
1402
|
-
"Pass a CourierAdminClient to manage templates here."
|
|
1403
|
-
);
|
|
2430
|
+
if (!kc) return missing("Pass a CourierAdminClient to manage templates here.");
|
|
1404
2431
|
const e = editing.value;
|
|
1405
2432
|
if (e?.kind === "template")
|
|
1406
2433
|
return [
|
|
1407
|
-
back(),
|
|
1408
|
-
|
|
2434
|
+
back("Templates"),
|
|
2435
|
+
h16(TemplateEditorScreen, {
|
|
1409
2436
|
client: kc,
|
|
1410
2437
|
type: e.type,
|
|
1411
2438
|
onSaved: () => editing.value = null
|
|
1412
2439
|
})
|
|
1413
2440
|
];
|
|
1414
|
-
return
|
|
2441
|
+
return h16(TemplateListScreen, {
|
|
1415
2442
|
client: kc,
|
|
1416
2443
|
"onSelect-template": (t2) => editing.value = { kind: "template", type: t2.type }
|
|
1417
2444
|
});
|
|
1418
2445
|
}
|
|
1419
2446
|
}
|
|
1420
2447
|
};
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
2448
|
+
const visible = (i) => !i.needs || (i.needs === "operators" ? props.operators : i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : i.needs === "billing" ? props.billingClient : props.auditClient);
|
|
2449
|
+
const brand = () => {
|
|
2450
|
+
const production = props.envLabel ? /^prod/i.test(props.envLabel) : false;
|
|
2451
|
+
return h16("div", { style: styles.navBrand }, [
|
|
2452
|
+
h16("div", { style: styles.navMark }, [icon("mark", 15)]),
|
|
2453
|
+
h16("div", { style: { minWidth: 0, flex: 1 } }, [
|
|
2454
|
+
h16("div", { style: styles.navBrandTitle }, props.appName),
|
|
2455
|
+
props.envLabel ? h16(
|
|
2456
|
+
"span",
|
|
2457
|
+
{
|
|
2458
|
+
style: {
|
|
2459
|
+
...styles.badge,
|
|
2460
|
+
marginTop: "3px",
|
|
2461
|
+
padding: "0 7px",
|
|
2462
|
+
fontSize: "11px",
|
|
2463
|
+
...production ? {
|
|
2464
|
+
color: "var(--fonderie-danger,#e00)",
|
|
2465
|
+
borderColor: "color-mix(in srgb, var(--fonderie-danger,#e00) 35%, transparent)"
|
|
2466
|
+
} : {}
|
|
2467
|
+
}
|
|
2468
|
+
},
|
|
2469
|
+
props.envLabel
|
|
2470
|
+
) : null
|
|
2471
|
+
]),
|
|
2472
|
+
narrow.value ? h16(
|
|
2473
|
+
"button",
|
|
2474
|
+
{
|
|
2475
|
+
type: "button",
|
|
2476
|
+
style: styles.buttonGhost,
|
|
2477
|
+
"aria-label": "Close menu",
|
|
2478
|
+
onClick: () => drawer.value = false
|
|
2479
|
+
},
|
|
2480
|
+
[icon("close")]
|
|
2481
|
+
) : null
|
|
2482
|
+
]);
|
|
2483
|
+
};
|
|
2484
|
+
const nav = () => h16(
|
|
2485
|
+
"nav",
|
|
2486
|
+
{
|
|
2487
|
+
style: {
|
|
2488
|
+
...styles.nav,
|
|
2489
|
+
...narrow.value ? { width: "280px", maxWidth: "85vw", boxShadow: "0 10px 40px rgba(0,0,0,.25)" } : {}
|
|
2490
|
+
},
|
|
2491
|
+
"aria-label": "Admin"
|
|
2492
|
+
},
|
|
2493
|
+
[
|
|
2494
|
+
brand(),
|
|
2495
|
+
h16(
|
|
2496
|
+
"div",
|
|
2497
|
+
{ style: styles.navScroll },
|
|
2498
|
+
NAV.map((g) => {
|
|
2499
|
+
const items = g.items.filter(visible);
|
|
2500
|
+
if (items.length === 0) return null;
|
|
2501
|
+
return h16("div", { key: g.group }, [
|
|
2502
|
+
h16("div", { style: styles.navGroup }, g.group),
|
|
2503
|
+
...items.map((i) => {
|
|
2504
|
+
const active = current.value === i.page;
|
|
2505
|
+
return h16(
|
|
2506
|
+
"button",
|
|
2507
|
+
{
|
|
2508
|
+
key: i.page,
|
|
2509
|
+
type: "button",
|
|
2510
|
+
class: "fonderie-admin-nav-item",
|
|
2511
|
+
style: { ...styles.navItem, ...active ? styles.navItemActive : {} },
|
|
2512
|
+
"aria-current": active ? "page" : void 0,
|
|
2513
|
+
onClick: () => go(i.page)
|
|
2514
|
+
},
|
|
2515
|
+
[icon(ICON[i.page], 16, active ? styles.navIconActive : void 0), i.label]
|
|
2516
|
+
);
|
|
2517
|
+
})
|
|
2518
|
+
]);
|
|
2519
|
+
})
|
|
2520
|
+
),
|
|
2521
|
+
slots["footer"] ? h16("div", { style: styles.navFooter }, slots["footer"]()) : null
|
|
2522
|
+
]
|
|
2523
|
+
);
|
|
2524
|
+
return () => {
|
|
2525
|
+
const title = NAV.flatMap((g) => g.items).find((i) => i.page === current.value)?.label ?? "";
|
|
2526
|
+
return h16(
|
|
2527
|
+
"div",
|
|
2528
|
+
{ style: { ...styles.shell, ...narrow.value ? { flexDirection: "column" } : {} } },
|
|
2529
|
+
[
|
|
2530
|
+
...narrow.value ? [
|
|
2531
|
+
h16("div", { style: styles.topbar }, [
|
|
2532
|
+
h16(
|
|
1434
2533
|
"button",
|
|
1435
2534
|
{
|
|
1436
|
-
key: i.page,
|
|
1437
2535
|
type: "button",
|
|
1438
|
-
style:
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
"aria-current": current.value === i.page ? "page" : void 0,
|
|
1443
|
-
onClick: () => go(i.page)
|
|
2536
|
+
style: styles.buttonGhost,
|
|
2537
|
+
"aria-label": "Open menu",
|
|
2538
|
+
"aria-expanded": drawer.value,
|
|
2539
|
+
onClick: () => drawer.value = true
|
|
1444
2540
|
},
|
|
1445
|
-
|
|
1446
|
-
)
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
2541
|
+
[icon("menu")]
|
|
2542
|
+
),
|
|
2543
|
+
h16("span", { style: { fontWeight: 600, fontSize: "14px" } }, title),
|
|
2544
|
+
props.envLabel ? h16("span", { style: { ...styles.badge, marginLeft: "auto" } }, props.envLabel) : null
|
|
2545
|
+
]),
|
|
2546
|
+
drawer.value ? h16("div", { style: styles.drawer }, [
|
|
2547
|
+
nav(),
|
|
2548
|
+
h16("div", {
|
|
2549
|
+
style: styles.scrim,
|
|
2550
|
+
"aria-hidden": "true",
|
|
2551
|
+
onClick: () => drawer.value = false
|
|
2552
|
+
})
|
|
2553
|
+
]) : null
|
|
2554
|
+
] : [nav()],
|
|
2555
|
+
h16("main", { class: "fonderie-admin-main", style: styles.main }, body())
|
|
2556
|
+
]
|
|
2557
|
+
);
|
|
2558
|
+
};
|
|
1453
2559
|
}
|
|
1454
2560
|
});
|
|
1455
2561
|
export {
|
|
@@ -1462,6 +2568,7 @@ export {
|
|
|
1462
2568
|
EnvironmentScreen,
|
|
1463
2569
|
MigrationsScreen,
|
|
1464
2570
|
ModulesScreen,
|
|
2571
|
+
OperatorsScreen,
|
|
1465
2572
|
RoutesScreen,
|
|
1466
2573
|
SubscriberScreen,
|
|
1467
2574
|
TokensScreen,
|