@fonderie/vue-admin-screens 1.0.2 → 1.1.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 +1402 -585
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +1389 -572
- 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 defineComponent13, h as
|
|
4
|
+
import { computed as computed2, defineComponent as defineComponent13, h as h15, onBeforeUnmount, onMounted, ref as ref6 } 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"
|
|
42
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"
|
|
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",
|
|
237
|
+
fontWeight: 600,
|
|
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,
|
|
93
277
|
fontWeight: 600,
|
|
94
|
-
|
|
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"
|
|
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"
|
|
107
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(
|
|
@@ -499,11 +1147,12 @@ var MigrationsScreen = defineComponent7({
|
|
|
499
1147
|
const r = report.value;
|
|
500
1148
|
if (!r) return null;
|
|
501
1149
|
const behind = r.modules.filter((m) => m.pending.length > 0);
|
|
502
|
-
if (behind.length === 0)
|
|
1150
|
+
if (behind.length === 0)
|
|
1151
|
+
return empty("Every module is up to date", "No pending migrations.", "migrations");
|
|
503
1152
|
return [
|
|
504
|
-
r.everApplied ? null :
|
|
1153
|
+
r.everApplied ? null : h9(
|
|
505
1154
|
"p",
|
|
506
|
-
{ style: styles.
|
|
1155
|
+
{ style: styles.notice },
|
|
507
1156
|
"This database has never been migrated \u2014 treating it as a first install, so nothing is held back."
|
|
508
1157
|
),
|
|
509
1158
|
...behind.map((m) => section(m, r.everApplied))
|
|
@@ -511,6 +1160,7 @@ var MigrationsScreen = defineComponent7({
|
|
|
511
1160
|
},
|
|
512
1161
|
[refreshButton("Refresh", isLoading.value, () => void refresh())],
|
|
513
1162
|
{
|
|
1163
|
+
lead: "Schema changes each module ships, and whether this database has them.",
|
|
514
1164
|
errorText: (e) => e.status === 403 ? "Applying migrations needs a token with the write scope." : e.explanation
|
|
515
1165
|
}
|
|
516
1166
|
);
|
|
@@ -519,50 +1169,89 @@ var MigrationsScreen = defineComponent7({
|
|
|
519
1169
|
|
|
520
1170
|
// src/screens/TokensScreen.ts
|
|
521
1171
|
import { useAdminTokens } from "@fonderie/vue-admin";
|
|
522
|
-
import { defineComponent as defineComponent8, h as
|
|
1172
|
+
import { defineComponent as defineComponent8, h as h10, ref as ref2 } from "vue";
|
|
523
1173
|
var ALL = ["read", "write", "secrets"];
|
|
524
1174
|
var TokensScreen = defineComponent8({
|
|
525
1175
|
name: "FonderieTokensScreen",
|
|
526
1176
|
props: { client: { type: Object, required: true } },
|
|
527
1177
|
setup(props) {
|
|
528
1178
|
const { report, isLoading, error, issue, revoke } = useAdminTokens(props.client);
|
|
529
|
-
const name =
|
|
530
|
-
const scopes =
|
|
531
|
-
const days =
|
|
532
|
-
const minted =
|
|
1179
|
+
const name = ref2("");
|
|
1180
|
+
const scopes = ref2(["read"]);
|
|
1181
|
+
const days = ref2("");
|
|
1182
|
+
const minted = ref2(null);
|
|
533
1183
|
const toggle = (s) => scopes.value = scopes.value.includes(s) ? scopes.value.filter((x) => x !== s) : [...scopes.value, s];
|
|
534
1184
|
return () => {
|
|
535
1185
|
const r = report.value;
|
|
536
|
-
return
|
|
537
|
-
|
|
538
|
-
|
|
1186
|
+
return h10("div", { style: styles.container }, [
|
|
1187
|
+
pageHeader(
|
|
1188
|
+
"Access",
|
|
1189
|
+
"Who can open this surface: the root token's strength, scoped tokens you have issued, and any legacy per-module tokens."
|
|
1190
|
+
),
|
|
1191
|
+
error.value ? h10(
|
|
539
1192
|
"p",
|
|
540
1193
|
{ style: styles.error, role: "alert" },
|
|
541
1194
|
error.value.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.value.explanation
|
|
542
1195
|
) : null,
|
|
543
|
-
|
|
544
|
-
r ?
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
1196
|
+
h10("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Root token"),
|
|
1197
|
+
r ? h10(
|
|
1198
|
+
"div",
|
|
1199
|
+
{ style: { ...styles.card, display: "flex", gap: "12px", alignItems: "flex-start" } },
|
|
1200
|
+
[
|
|
1201
|
+
icon("lock", 18, { marginTop: "2px" }),
|
|
1202
|
+
h10("div", [
|
|
1203
|
+
pill(r.admin.ok ? "ok" : "bad", r.admin.ok ? "strong" : "weak"),
|
|
1204
|
+
...r.admin.problems.map(
|
|
1205
|
+
(p) => h10(
|
|
1206
|
+
"div",
|
|
1207
|
+
{
|
|
1208
|
+
key: p.message,
|
|
1209
|
+
style: {
|
|
1210
|
+
marginTop: "6px",
|
|
1211
|
+
color: "var(--fonderie-danger,#e00)",
|
|
1212
|
+
fontSize: "13px"
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1215
|
+
p.message
|
|
1216
|
+
)
|
|
1217
|
+
)
|
|
1218
|
+
])
|
|
1219
|
+
]
|
|
1220
|
+
) : null,
|
|
1221
|
+
h10("h2", { style: styles.subtitle }, "Issued tokens"),
|
|
1222
|
+
r?.issued === null ? h10(
|
|
556
1223
|
"p",
|
|
557
1224
|
{ style: styles.muted },
|
|
558
1225
|
"Issuing is off \u2014 give AdminModule a store and run its migrations."
|
|
559
1226
|
) : [
|
|
560
|
-
minted.value ?
|
|
561
|
-
"
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1227
|
+
minted.value ? h10(
|
|
1228
|
+
"div",
|
|
1229
|
+
{
|
|
1230
|
+
style: {
|
|
1231
|
+
...styles.notice,
|
|
1232
|
+
background: "color-mix(in srgb, var(--fonderie-accent,#00d294) 10%, transparent)",
|
|
1233
|
+
borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
[
|
|
1237
|
+
h10("strong", "Copy this now \u2014 it is never shown again."),
|
|
1238
|
+
` (${minted.value.name})`,
|
|
1239
|
+
h10(
|
|
1240
|
+
"div",
|
|
1241
|
+
{
|
|
1242
|
+
style: {
|
|
1243
|
+
...styles.code,
|
|
1244
|
+
display: "block",
|
|
1245
|
+
marginTop: "8px",
|
|
1246
|
+
padding: "8px 10px",
|
|
1247
|
+
wordBreak: "break-all"
|
|
1248
|
+
}
|
|
1249
|
+
},
|
|
1250
|
+
minted.value.token
|
|
1251
|
+
)
|
|
1252
|
+
]
|
|
1253
|
+
) : null,
|
|
1254
|
+
h10(
|
|
566
1255
|
"form",
|
|
567
1256
|
{
|
|
568
1257
|
style: styles.toolbar,
|
|
@@ -582,49 +1271,60 @@ var TokensScreen = defineComponent8({
|
|
|
582
1271
|
}
|
|
583
1272
|
},
|
|
584
1273
|
[
|
|
585
|
-
|
|
1274
|
+
h10("input", {
|
|
586
1275
|
value: name.value,
|
|
587
1276
|
onInput: (e) => name.value = e.target.value,
|
|
588
1277
|
placeholder: "name",
|
|
589
|
-
style:
|
|
1278
|
+
style: styles.input,
|
|
590
1279
|
"aria-label": "Name"
|
|
591
1280
|
}),
|
|
592
1281
|
...ALL.map(
|
|
593
|
-
(s) =>
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
1282
|
+
(s) => h10(
|
|
1283
|
+
"label",
|
|
1284
|
+
{
|
|
1285
|
+
key: s,
|
|
1286
|
+
style: {
|
|
1287
|
+
...styles.badge,
|
|
1288
|
+
cursor: "pointer",
|
|
1289
|
+
height: "32px",
|
|
1290
|
+
boxSizing: "border-box",
|
|
1291
|
+
padding: "0 10px",
|
|
1292
|
+
fontSize: "13px"
|
|
1293
|
+
}
|
|
1294
|
+
},
|
|
1295
|
+
[
|
|
1296
|
+
h10("input", {
|
|
1297
|
+
type: "checkbox",
|
|
1298
|
+
checked: scopes.value.includes(s),
|
|
1299
|
+
onChange: () => toggle(s)
|
|
1300
|
+
}),
|
|
1301
|
+
` ${s}`
|
|
1302
|
+
]
|
|
1303
|
+
)
|
|
601
1304
|
),
|
|
602
|
-
|
|
1305
|
+
h10("input", {
|
|
603
1306
|
value: days.value,
|
|
604
1307
|
onInput: (e) => days.value = e.target.value,
|
|
605
1308
|
placeholder: "days (optional)",
|
|
606
|
-
style: { ...styles.
|
|
1309
|
+
style: { ...styles.input, width: "140px" },
|
|
607
1310
|
"aria-label": "Days"
|
|
608
1311
|
}),
|
|
609
|
-
|
|
1312
|
+
h10(
|
|
610
1313
|
"button",
|
|
611
1314
|
{
|
|
612
1315
|
type: "submit",
|
|
613
|
-
style: styles.
|
|
1316
|
+
style: styles.buttonPrimary,
|
|
614
1317
|
disabled: !name.value.trim() || scopes.value.length === 0 || isLoading.value
|
|
615
1318
|
},
|
|
616
|
-
"Issue"
|
|
1319
|
+
[icon("plus", 14), "Issue token"]
|
|
617
1320
|
)
|
|
618
1321
|
]
|
|
619
1322
|
),
|
|
620
1323
|
r?.issued && r.issued.length > 0 ? table(
|
|
621
1324
|
["Name", "Scopes", "Created", "Expires", "Last used", ""],
|
|
622
1325
|
r.issued.map(
|
|
623
|
-
(t2) =>
|
|
624
|
-
td([
|
|
625
|
-
t2.name,
|
|
626
|
-
t2.revokedAt ? h9("span", { style: styles.badge }, " revoked") : ""
|
|
627
|
-
]),
|
|
1326
|
+
(t2) => h10("tr", { key: t2.id }, [
|
|
1327
|
+
td([t2.name, t2.revokedAt ? [" ", pill("neutral", "revoked")] : ""]),
|
|
628
1328
|
td(t2.scopes.join(", "), styles.mono),
|
|
629
1329
|
td(
|
|
630
1330
|
`${new Date(t2.createdAt).toLocaleDateString()} \xB7 ${t2.createdBy}`,
|
|
@@ -639,11 +1339,11 @@ var TokensScreen = defineComponent8({
|
|
|
639
1339
|
styles.muted
|
|
640
1340
|
),
|
|
641
1341
|
td(
|
|
642
|
-
t2.revokedAt ? "" :
|
|
1342
|
+
t2.revokedAt ? "" : h10(
|
|
643
1343
|
"button",
|
|
644
1344
|
{
|
|
645
1345
|
type: "button",
|
|
646
|
-
style: styles.
|
|
1346
|
+
style: styles.buttonDanger,
|
|
647
1347
|
onClick: () => {
|
|
648
1348
|
if (window.confirm(
|
|
649
1349
|
`Revoke "${t2.name}"? Anything using it stops working immediately.`
|
|
@@ -657,21 +1357,25 @@ var TokensScreen = defineComponent8({
|
|
|
657
1357
|
)
|
|
658
1358
|
])
|
|
659
1359
|
)
|
|
660
|
-
) :
|
|
1360
|
+
) : empty(
|
|
1361
|
+
"No scoped tokens yet",
|
|
1362
|
+
"Issue a read-only token for a dashboard or a teammate instead of sharing the root.",
|
|
1363
|
+
"tokens"
|
|
1364
|
+
)
|
|
661
1365
|
],
|
|
662
|
-
|
|
663
|
-
r && r.legacy.length === 0 ?
|
|
1366
|
+
h10("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
|
|
1367
|
+
r && r.legacy.length === 0 ? h10(
|
|
664
1368
|
"p",
|
|
665
1369
|
{ style: styles.muted },
|
|
666
1370
|
"None \u2014 every brick's admin surface goes through this token."
|
|
667
|
-
) :
|
|
1371
|
+
) : h10(
|
|
668
1372
|
"ul",
|
|
669
1373
|
{ style: styles.list },
|
|
670
1374
|
(r?.legacy ?? []).map(
|
|
671
|
-
(l) =>
|
|
672
|
-
|
|
1375
|
+
(l) => h10("li", { key: l.module, style: styles.row }, [
|
|
1376
|
+
h10("span", { style: styles.mono }, l.module),
|
|
673
1377
|
" ",
|
|
674
|
-
|
|
1378
|
+
h10(
|
|
675
1379
|
"span",
|
|
676
1380
|
{ style: styles.muted },
|
|
677
1381
|
"still registers its standalone routes with its own token (deprecated)"
|
|
@@ -692,7 +1396,7 @@ import {
|
|
|
692
1396
|
useAdminUserSessions,
|
|
693
1397
|
useAdminUsers
|
|
694
1398
|
} from "@fonderie/vue-admin";
|
|
695
|
-
import { computed, defineComponent as defineComponent9, h as
|
|
1399
|
+
import { computed, defineComponent as defineComponent9, h as h11, ref as ref3 } from "vue";
|
|
696
1400
|
var UsersScreen = defineComponent9({
|
|
697
1401
|
name: "FonderieUsersScreen",
|
|
698
1402
|
props: {
|
|
@@ -700,9 +1404,9 @@ var UsersScreen = defineComponent9({
|
|
|
700
1404
|
pageSize: { type: Number, default: 50 }
|
|
701
1405
|
},
|
|
702
1406
|
setup(props) {
|
|
703
|
-
const input =
|
|
704
|
-
const email =
|
|
705
|
-
const selectedId =
|
|
1407
|
+
const input = ref3("");
|
|
1408
|
+
const email = ref3("");
|
|
1409
|
+
const selectedId = ref3("");
|
|
706
1410
|
const list = useAdminUsers(props.client, { limit: props.pageSize });
|
|
707
1411
|
const { user, isLoading, error, suspend, unsuspend, revokeSessions } = useAdminUser(
|
|
708
1412
|
props.client,
|
|
@@ -712,8 +1416,8 @@ var UsersScreen = defineComponent9({
|
|
|
712
1416
|
const sessions = useAdminUserSessions(props.client, userId);
|
|
713
1417
|
const history = useAdminLoginHistory(props.client, userId, { limit: 20 });
|
|
714
1418
|
const showList = computed(() => !email.value && !selectedId.value);
|
|
715
|
-
const yesNo = (v) =>
|
|
716
|
-
const row = (k, v) =>
|
|
1419
|
+
const yesNo = (v) => v ? pill("ok", "yes") : pill("neutral", "no");
|
|
1420
|
+
const row = (k, v) => h11("tr", [td(k), td(v)]);
|
|
717
1421
|
const clear = () => {
|
|
718
1422
|
email.value = "";
|
|
719
1423
|
selectedId.value = "";
|
|
@@ -721,9 +1425,12 @@ var UsersScreen = defineComponent9({
|
|
|
721
1425
|
};
|
|
722
1426
|
return () => {
|
|
723
1427
|
const u = user.value;
|
|
724
|
-
return
|
|
725
|
-
|
|
726
|
-
|
|
1428
|
+
return h11("div", { style: styles.container }, [
|
|
1429
|
+
pageHeader(
|
|
1430
|
+
"Users",
|
|
1431
|
+
"Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
|
|
1432
|
+
),
|
|
1433
|
+
h11(
|
|
727
1434
|
"form",
|
|
728
1435
|
{
|
|
729
1436
|
style: styles.toolbar,
|
|
@@ -734,75 +1441,67 @@ var UsersScreen = defineComponent9({
|
|
|
734
1441
|
}
|
|
735
1442
|
},
|
|
736
1443
|
[
|
|
737
|
-
|
|
1444
|
+
h11("input", {
|
|
738
1445
|
type: "email",
|
|
739
1446
|
value: input.value,
|
|
740
1447
|
onInput: (e) => input.value = e.target.value,
|
|
741
1448
|
placeholder: "email address",
|
|
742
|
-
style: { ...styles.
|
|
1449
|
+
style: { ...styles.input, minWidth: "280px" },
|
|
743
1450
|
"aria-label": "Email"
|
|
744
1451
|
}),
|
|
745
|
-
|
|
1452
|
+
h11(
|
|
746
1453
|
"button",
|
|
747
|
-
{ type: "submit", style: styles.
|
|
748
|
-
"Look up"
|
|
1454
|
+
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1455
|
+
[icon("search", 14), "Look up"]
|
|
749
1456
|
),
|
|
750
|
-
showList.value ? refreshButton("Refresh", list.isLoading.value, () => void list.refresh()) :
|
|
1457
|
+
showList.value ? refreshButton("Refresh", list.isLoading.value, () => void list.refresh()) : actionButton([icon("back", 14), "All users"], false, clear, styles.buttonGhost)
|
|
751
1458
|
]
|
|
752
1459
|
),
|
|
753
|
-
showList.value ?
|
|
754
|
-
list.error.value ?
|
|
755
|
-
list.users.value.length === 0 && !list.isLoading.value ?
|
|
1460
|
+
showList.value ? h11("div", [
|
|
1461
|
+
list.error.value ? h11("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
1462
|
+
list.users.value.length === 0 && !list.isLoading.value ? empty("No users yet", "Sign-ups appear here as they happen.", "users") : table(
|
|
756
1463
|
["Email", "Name", "Created", "Status"],
|
|
757
1464
|
list.users.value.map(
|
|
758
|
-
(u2) =>
|
|
1465
|
+
(u2) => h11("tr", { key: u2.id }, [
|
|
759
1466
|
td(
|
|
760
|
-
|
|
1467
|
+
h11(
|
|
761
1468
|
"button",
|
|
762
1469
|
{
|
|
763
1470
|
type: "button",
|
|
764
|
-
style:
|
|
1471
|
+
style: styles.link,
|
|
765
1472
|
onClick: () => selectedId.value = u2.id
|
|
766
1473
|
},
|
|
767
1474
|
u2.email
|
|
768
1475
|
)
|
|
769
1476
|
),
|
|
770
1477
|
td(
|
|
771
|
-
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() ||
|
|
1478
|
+
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || h11("span", { style: styles.muted }, "\u2014")
|
|
772
1479
|
),
|
|
773
1480
|
td(new Date(u2.createdAt).toLocaleDateString()),
|
|
774
1481
|
td(
|
|
775
|
-
u2.suspended ?
|
|
1482
|
+
u2.suspended ? pill("warn", "suspended") : u2.deletedAt ? pill("neutral", "deleted") : pill("ok", "active")
|
|
776
1483
|
)
|
|
777
1484
|
])
|
|
778
1485
|
)
|
|
779
1486
|
),
|
|
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
|
|
1487
|
+
list.isLoading.value ? h11("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1488
|
+
list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
|
|
790
1489
|
]) : null,
|
|
791
|
-
error.value ?
|
|
1490
|
+
error.value ? h11(
|
|
792
1491
|
"p",
|
|
793
1492
|
{ style: styles.error, role: "alert" },
|
|
794
1493
|
error.value.status === 404 ? "No user with that email." : error.value.explanation
|
|
795
1494
|
) : null,
|
|
796
1495
|
u ? [
|
|
797
|
-
|
|
1496
|
+
h11("h2", { style: styles.subtitle }, [
|
|
798
1497
|
u.firstName || u.lastName ? `${u.firstName} ${u.lastName}`.trim() : u.email,
|
|
799
1498
|
" ",
|
|
800
|
-
u.suspended ?
|
|
801
|
-
u.deletedAt ?
|
|
1499
|
+
u.suspended ? pill("warn", "suspended") : null,
|
|
1500
|
+
u.deletedAt ? pill("neutral", "deleted") : null
|
|
802
1501
|
]),
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
row("id",
|
|
1502
|
+
h11("table", { style: styles.table }, [
|
|
1503
|
+
h11("tbody", [
|
|
1504
|
+
row("id", h11("span", { style: styles.mono }, u.id)),
|
|
806
1505
|
row("email", [u.email, " \xB7 verified ", yesNo(u.isEmailVerified)]),
|
|
807
1506
|
row("MFA", yesNo(u.mfaEnabled)),
|
|
808
1507
|
row(
|
|
@@ -811,32 +1510,37 @@ var UsersScreen = defineComponent9({
|
|
|
811
1510
|
),
|
|
812
1511
|
row(
|
|
813
1512
|
"last login",
|
|
814
|
-
u.lastLogin ? new Date(u.lastLogin).toLocaleString() :
|
|
1513
|
+
u.lastLogin ? new Date(u.lastLogin).toLocaleString() : h11("span", { style: styles.muted }, "never")
|
|
815
1514
|
),
|
|
816
1515
|
row("created", new Date(u.createdAt).toLocaleString())
|
|
817
1516
|
])
|
|
818
1517
|
]),
|
|
819
|
-
|
|
820
|
-
u.suspended ?
|
|
821
|
-
|
|
1518
|
+
h11("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
|
|
1519
|
+
u.suspended ? actionButton("Unsuspend", isLoading.value, () => void unsuspend()) : actionButton(
|
|
1520
|
+
"Suspend",
|
|
1521
|
+
isLoading.value,
|
|
1522
|
+
() => void suspend(),
|
|
1523
|
+
styles.buttonDanger
|
|
1524
|
+
),
|
|
1525
|
+
actionButton(
|
|
822
1526
|
"Sign out everywhere",
|
|
823
1527
|
isLoading.value,
|
|
824
1528
|
() => void revokeSessions().then(() => sessions.refresh())
|
|
825
1529
|
)
|
|
826
1530
|
]),
|
|
827
|
-
|
|
828
|
-
sessions.sessions.value.length === 0 ?
|
|
1531
|
+
h11("h2", { style: styles.subtitle }, "Live sessions"),
|
|
1532
|
+
sessions.sessions.value.length === 0 ? h11("p", { style: styles.muted }, "None.") : h11(
|
|
829
1533
|
"ul",
|
|
830
1534
|
{ style: styles.list },
|
|
831
1535
|
sessions.sessions.value.map(
|
|
832
|
-
(s) =>
|
|
833
|
-
|
|
1536
|
+
(s) => h11("li", { key: s.id, style: styles.row }, [
|
|
1537
|
+
h11("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
|
|
834
1538
|
" ",
|
|
835
|
-
|
|
1539
|
+
h11("span", { style: styles.muted }, describeLocation(s.location) ?? ""),
|
|
836
1540
|
" ",
|
|
837
|
-
|
|
1541
|
+
h11("span", { style: styles.muted }, s.userAgent ?? ""),
|
|
838
1542
|
" ",
|
|
839
|
-
|
|
1543
|
+
h11(
|
|
840
1544
|
"span",
|
|
841
1545
|
{ style: styles.muted },
|
|
842
1546
|
`since ${new Date(s.createdAt).toLocaleString()}`
|
|
@@ -844,38 +1548,26 @@ var UsersScreen = defineComponent9({
|
|
|
844
1548
|
])
|
|
845
1549
|
)
|
|
846
1550
|
),
|
|
847
|
-
|
|
848
|
-
history.events.value.length === 0 && !history.isLoading.value ?
|
|
1551
|
+
h11("h2", { style: styles.subtitle }, "Recent sign-ins"),
|
|
1552
|
+
history.events.value.length === 0 && !history.isLoading.value ? h11("p", { style: styles.muted }, "None recorded.") : h11(
|
|
849
1553
|
"ul",
|
|
850
1554
|
{ style: styles.list },
|
|
851
1555
|
history.events.value.map(
|
|
852
|
-
(e) =>
|
|
853
|
-
|
|
854
|
-
"span",
|
|
855
|
-
{ style: e.outcome === "success" ? styles.ok : styles.bad },
|
|
856
|
-
e.outcome
|
|
857
|
-
),
|
|
1556
|
+
(e) => h11("li", { key: e.id, style: styles.row }, [
|
|
1557
|
+
pill(e.outcome === "success" ? "ok" : "bad", e.outcome),
|
|
858
1558
|
" ",
|
|
859
|
-
|
|
1559
|
+
h11("span", { style: styles.muted }, e.method),
|
|
860
1560
|
" ",
|
|
861
|
-
|
|
1561
|
+
h11("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
|
|
862
1562
|
" ",
|
|
863
|
-
|
|
864
|
-
e.location?.proxy || e.location?.hosting ?
|
|
1563
|
+
h11("span", { style: styles.muted }, describeLocation(e.location) ?? ""),
|
|
1564
|
+
e.location?.proxy || e.location?.hosting ? [" ", pill("warn", e.location.proxy ? "proxy/VPN" : "hosting")] : "",
|
|
865
1565
|
" ",
|
|
866
|
-
|
|
1566
|
+
h11("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
|
|
867
1567
|
])
|
|
868
1568
|
)
|
|
869
1569
|
),
|
|
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
|
|
1570
|
+
history.hasMore.value && !history.isLoading.value ? loadMoreButton(() => void history.loadMore()) : null
|
|
879
1571
|
] : null
|
|
880
1572
|
]);
|
|
881
1573
|
};
|
|
@@ -884,7 +1576,7 @@ var UsersScreen = defineComponent9({
|
|
|
884
1576
|
|
|
885
1577
|
// src/screens/CatalogScreen.ts
|
|
886
1578
|
import { useAdminCatalog } from "@fonderie/vue-admin";
|
|
887
|
-
import { defineComponent as defineComponent10, h as
|
|
1579
|
+
import { defineComponent as defineComponent10, h as h12 } from "vue";
|
|
888
1580
|
var CatalogScreen = defineComponent10({
|
|
889
1581
|
name: "FonderieCatalogScreen",
|
|
890
1582
|
props: { client: { type: Object, required: true } },
|
|
@@ -893,35 +1585,42 @@ var CatalogScreen = defineComponent10({
|
|
|
893
1585
|
const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
|
|
894
1586
|
return () => {
|
|
895
1587
|
const c = catalog.value;
|
|
896
|
-
return
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1588
|
+
return h12("div", { style: styles.container }, [
|
|
1589
|
+
pageHeader(
|
|
1590
|
+
"Catalog",
|
|
1591
|
+
"What you sell: plans as configured in code, and as stored in the database, side by side.",
|
|
1592
|
+
[refreshButton("Refresh", isLoading.value, () => void refresh())]
|
|
1593
|
+
),
|
|
1594
|
+
error.value ? h12("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
1595
|
+
isLoading.value && !c ? h12("p", { style: styles.status }, "Loading\u2026") : null,
|
|
903
1596
|
c ? [
|
|
904
|
-
|
|
905
|
-
|
|
1597
|
+
h12("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Configured (code)"),
|
|
1598
|
+
h12(
|
|
906
1599
|
"pre",
|
|
907
1600
|
{
|
|
908
1601
|
style: {
|
|
1602
|
+
...styles.card,
|
|
909
1603
|
...styles.mono,
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1604
|
+
margin: 0,
|
|
1605
|
+
overflowX: "auto",
|
|
1606
|
+
maxHeight: "360px",
|
|
1607
|
+
lineHeight: 1.6
|
|
913
1608
|
}
|
|
914
1609
|
},
|
|
915
1610
|
JSON.stringify(c.configured, null, 2)
|
|
916
1611
|
),
|
|
917
|
-
|
|
918
|
-
c.stored.length === 0 ?
|
|
1612
|
+
h12("h2", { style: styles.subtitle }, "Stored (database)"),
|
|
1613
|
+
c.stored.length === 0 ? empty(
|
|
1614
|
+
"No stored plans",
|
|
1615
|
+
"Plans are configured in code; nothing has been written to the database.",
|
|
1616
|
+
"catalog"
|
|
1617
|
+
) : table(
|
|
919
1618
|
["Plan", "Tier", "Seats", "Monthly", "Yearly", "Trial", ""],
|
|
920
1619
|
c.stored.map(
|
|
921
|
-
(p) =>
|
|
1620
|
+
(p) => h12("tr", { key: p.id }, [
|
|
922
1621
|
td([
|
|
923
|
-
|
|
924
|
-
p.description ?
|
|
1622
|
+
h12("strong", p.name),
|
|
1623
|
+
p.description ? h12("div", { style: styles.muted }, p.description) : ""
|
|
925
1624
|
]),
|
|
926
1625
|
td(String(p.tier)),
|
|
927
1626
|
td(p.seats == null ? "\u221E" : String(p.seats)),
|
|
@@ -929,11 +1628,11 @@ var CatalogScreen = defineComponent10({
|
|
|
929
1628
|
td(money(p.pricing?.yearly)),
|
|
930
1629
|
td(p.trialDays ? `${p.trialDays} d` : "\u2014"),
|
|
931
1630
|
td(
|
|
932
|
-
|
|
1631
|
+
h12(
|
|
933
1632
|
"button",
|
|
934
1633
|
{
|
|
935
1634
|
type: "button",
|
|
936
|
-
style: styles.
|
|
1635
|
+
style: styles.buttonDanger,
|
|
937
1636
|
onClick: () => {
|
|
938
1637
|
if (window.confirm(`Delete stored plan "${p.name}"?`))
|
|
939
1638
|
void deletePlan(p.id).catch(() => {
|
|
@@ -954,7 +1653,7 @@ var CatalogScreen = defineComponent10({
|
|
|
954
1653
|
|
|
955
1654
|
// src/screens/SubscriberScreen.ts
|
|
956
1655
|
import { useAdminSubscriber, useAdminSubscribers } from "@fonderie/vue-admin";
|
|
957
|
-
import { defineComponent as defineComponent11, h as
|
|
1656
|
+
import { defineComponent as defineComponent11, h as h13, ref as ref4 } from "vue";
|
|
958
1657
|
var SubscriberScreen = defineComponent11({
|
|
959
1658
|
name: "FonderieSubscriberScreen",
|
|
960
1659
|
props: {
|
|
@@ -962,9 +1661,9 @@ var SubscriberScreen = defineComponent11({
|
|
|
962
1661
|
pageSize: { type: Number, default: 50 }
|
|
963
1662
|
},
|
|
964
1663
|
setup(props) {
|
|
965
|
-
const type =
|
|
966
|
-
const idInput =
|
|
967
|
-
const subscriber =
|
|
1664
|
+
const type = ref4("user");
|
|
1665
|
+
const idInput = ref4("");
|
|
1666
|
+
const subscriber = ref4(null);
|
|
968
1667
|
const list = useAdminSubscribers(props.client, { limit: props.pageSize });
|
|
969
1668
|
const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = useAdminSubscriber(props.client, subscriber, { limit: 20 });
|
|
970
1669
|
const open = (t2, id) => {
|
|
@@ -973,22 +1672,25 @@ var SubscriberScreen = defineComponent11({
|
|
|
973
1672
|
idInput.value = id;
|
|
974
1673
|
subscriber.value = { type: t2, id };
|
|
975
1674
|
};
|
|
976
|
-
const amount =
|
|
977
|
-
const note =
|
|
978
|
-
const granted =
|
|
979
|
-
const input = (model, placeholder, extra = {}) =>
|
|
1675
|
+
const amount = ref4("");
|
|
1676
|
+
const note = ref4("");
|
|
1677
|
+
const granted = ref4(null);
|
|
1678
|
+
const input = (model, placeholder, extra = {}) => h13("input", {
|
|
980
1679
|
value: model.value,
|
|
981
1680
|
onInput: (e) => model.value = e.target.value,
|
|
982
1681
|
placeholder,
|
|
983
|
-
style: { ...styles.
|
|
1682
|
+
style: { ...styles.input, ...extra },
|
|
984
1683
|
"aria-label": placeholder
|
|
985
1684
|
});
|
|
986
1685
|
return () => {
|
|
987
1686
|
const s = subscription.value;
|
|
988
1687
|
const w = wallet.value;
|
|
989
|
-
return
|
|
990
|
-
|
|
991
|
-
|
|
1688
|
+
return h13("div", { style: styles.container }, [
|
|
1689
|
+
pageHeader(
|
|
1690
|
+
"Subscribers",
|
|
1691
|
+
"Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
|
|
1692
|
+
),
|
|
1693
|
+
h13(
|
|
992
1694
|
"form",
|
|
993
1695
|
{
|
|
994
1696
|
style: styles.toolbar,
|
|
@@ -999,26 +1701,26 @@ var SubscriberScreen = defineComponent11({
|
|
|
999
1701
|
}
|
|
1000
1702
|
},
|
|
1001
1703
|
[
|
|
1002
|
-
|
|
1704
|
+
h13(
|
|
1003
1705
|
"select",
|
|
1004
1706
|
{
|
|
1005
1707
|
value: type.value,
|
|
1006
1708
|
onChange: (e) => type.value = e.target.value,
|
|
1007
|
-
style: styles.
|
|
1709
|
+
style: styles.input,
|
|
1008
1710
|
"aria-label": "Type"
|
|
1009
1711
|
},
|
|
1010
1712
|
[
|
|
1011
|
-
|
|
1012
|
-
|
|
1713
|
+
h13("option", { value: "user" }, "user"),
|
|
1714
|
+
h13("option", { value: "workspace" }, "workspace")
|
|
1013
1715
|
]
|
|
1014
1716
|
),
|
|
1015
1717
|
input(idInput, "subscriber id", { minWidth: "280px" }),
|
|
1016
|
-
|
|
1718
|
+
h13(
|
|
1017
1719
|
"button",
|
|
1018
|
-
{ type: "submit", style: styles.
|
|
1019
|
-
"Look up"
|
|
1720
|
+
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1721
|
+
[icon("search", 14), "Look up"]
|
|
1020
1722
|
),
|
|
1021
|
-
subscriber.value ?
|
|
1723
|
+
subscriber.value ? h13(
|
|
1022
1724
|
"button",
|
|
1023
1725
|
{
|
|
1024
1726
|
type: "button",
|
|
@@ -1029,8 +1731,8 @@ var SubscriberScreen = defineComponent11({
|
|
|
1029
1731
|
granted.value = null;
|
|
1030
1732
|
}
|
|
1031
1733
|
},
|
|
1032
|
-
"
|
|
1033
|
-
) :
|
|
1734
|
+
[icon("back", 14), "All subscribers"]
|
|
1735
|
+
) : h13(
|
|
1034
1736
|
"button",
|
|
1035
1737
|
{
|
|
1036
1738
|
type: "button",
|
|
@@ -1042,35 +1744,43 @@ var SubscriberScreen = defineComponent11({
|
|
|
1042
1744
|
)
|
|
1043
1745
|
]
|
|
1044
1746
|
),
|
|
1045
|
-
!subscriber.value ?
|
|
1046
|
-
list.error.value ?
|
|
1047
|
-
list.subscriptions.value.length === 0 && !list.isLoading.value ?
|
|
1747
|
+
!subscriber.value ? h13("div", [
|
|
1748
|
+
list.error.value ? h13("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
1749
|
+
list.subscriptions.value.length === 0 && !list.isLoading.value ? empty(
|
|
1750
|
+
"No subscribers yet",
|
|
1751
|
+
"Subscriptions appear here once someone checks out.",
|
|
1752
|
+
"subscriber"
|
|
1753
|
+
) : table(
|
|
1048
1754
|
["Subscriber", "Plan", "Status", "Renews"],
|
|
1049
1755
|
list.subscriptions.value.map(
|
|
1050
|
-
(sub) =>
|
|
1756
|
+
(sub) => h13("tr", { key: sub.id }, [
|
|
1051
1757
|
td(
|
|
1052
|
-
|
|
1758
|
+
h13(
|
|
1053
1759
|
"button",
|
|
1054
1760
|
{
|
|
1055
1761
|
type: "button",
|
|
1056
|
-
style: { ...styles.
|
|
1762
|
+
style: { ...styles.link, ...styles.mono },
|
|
1057
1763
|
onClick: () => open(sub.subscriberType, sub.subscriberId)
|
|
1058
1764
|
},
|
|
1059
1765
|
`${sub.subscriberType}/${sub.subscriberId}`
|
|
1060
1766
|
)
|
|
1061
1767
|
),
|
|
1062
1768
|
td(`${sub.plan} \xB7 ${sub.interval}`),
|
|
1769
|
+
td([
|
|
1770
|
+
pill(
|
|
1771
|
+
sub.status === "active" ? "ok" : sub.status === "trialing" ? "info" : sub.status === "past_due" ? "warn" : "neutral",
|
|
1772
|
+
sub.status
|
|
1773
|
+
),
|
|
1774
|
+
sub.cancelAtPeriodEnd ? [" ", pill("warn", "cancels", false)] : null
|
|
1775
|
+
]),
|
|
1063
1776
|
td(
|
|
1064
|
-
sub.
|
|
1065
|
-
),
|
|
1066
|
-
td(
|
|
1067
|
-
sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : h12("span", { style: styles.muted }, "\u2014")
|
|
1777
|
+
sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : h13("span", { style: styles.muted }, "\u2014")
|
|
1068
1778
|
)
|
|
1069
1779
|
])
|
|
1070
1780
|
)
|
|
1071
1781
|
),
|
|
1072
|
-
list.isLoading.value ?
|
|
1073
|
-
list.hasMore.value && !list.isLoading.value ?
|
|
1782
|
+
list.isLoading.value ? h13("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1783
|
+
list.hasMore.value && !list.isLoading.value ? h13(
|
|
1074
1784
|
"button",
|
|
1075
1785
|
{
|
|
1076
1786
|
type: "button",
|
|
@@ -1080,37 +1790,58 @@ var SubscriberScreen = defineComponent11({
|
|
|
1080
1790
|
"Load more"
|
|
1081
1791
|
) : null
|
|
1082
1792
|
]) : null,
|
|
1083
|
-
error.value ?
|
|
1793
|
+
error.value ? h13("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
1084
1794
|
subscriber.value && !isLoading.value ? [
|
|
1085
|
-
|
|
1086
|
-
s ?
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1795
|
+
h13("h2", { style: styles.subtitle }, "Subscription"),
|
|
1796
|
+
s ? h13("table", { style: styles.table }, [
|
|
1797
|
+
h13("tbody", [
|
|
1798
|
+
h13("tr", [td("plan"), td([h13("strong", s.plan), ` \xB7 ${s.interval}`])]),
|
|
1799
|
+
h13("tr", [
|
|
1090
1800
|
td("status"),
|
|
1091
1801
|
td(`${s.status}${s.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""}`)
|
|
1092
1802
|
]),
|
|
1093
|
-
|
|
1803
|
+
h13("tr", [
|
|
1094
1804
|
td("period"),
|
|
1095
1805
|
td(
|
|
1096
1806
|
`${s.currentPeriodStart ? new Date(s.currentPeriodStart).toLocaleDateString() : "\u2014"} \u2192 ${s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : "\u2014"}`
|
|
1097
1807
|
)
|
|
1098
1808
|
]),
|
|
1099
|
-
|
|
1809
|
+
h13("tr", [td("provider"), td(s.providerSubscriptionId ?? "\u2014", styles.mono)])
|
|
1100
1810
|
])
|
|
1101
|
-
]) :
|
|
1102
|
-
|
|
1811
|
+
]) : h13("p", { style: styles.muted }, "No subscription."),
|
|
1812
|
+
h13("h2", { style: styles.subtitle }, "Wallet"),
|
|
1103
1813
|
w ? [
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1814
|
+
h13(
|
|
1815
|
+
"p",
|
|
1816
|
+
{
|
|
1817
|
+
style: {
|
|
1818
|
+
...styles.card,
|
|
1819
|
+
display: "flex",
|
|
1820
|
+
alignItems: "baseline",
|
|
1821
|
+
gap: "8px",
|
|
1822
|
+
flexWrap: "wrap"
|
|
1823
|
+
}
|
|
1824
|
+
},
|
|
1825
|
+
[
|
|
1826
|
+
h13(
|
|
1827
|
+
"strong",
|
|
1828
|
+
{
|
|
1829
|
+
style: {
|
|
1830
|
+
fontSize: "22px",
|
|
1831
|
+
letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
w.balance
|
|
1835
|
+
),
|
|
1836
|
+
` ${w.currency} `,
|
|
1837
|
+
h13(
|
|
1838
|
+
"span",
|
|
1839
|
+
{ style: styles.muted },
|
|
1840
|
+
`(granted ${w.granted ?? "0"} \xB7 purchased ${w.purchased ?? "0"}) \xB7 minor units, precision ${w.precision}`
|
|
1841
|
+
)
|
|
1842
|
+
]
|
|
1843
|
+
),
|
|
1844
|
+
h13(
|
|
1114
1845
|
"form",
|
|
1115
1846
|
{
|
|
1116
1847
|
style: styles.toolbar,
|
|
@@ -1135,23 +1866,23 @@ var SubscriberScreen = defineComponent11({
|
|
|
1135
1866
|
[
|
|
1136
1867
|
input(amount, "amount (minor units)"),
|
|
1137
1868
|
input(note, "reason (optional)", { minWidth: "220px" }),
|
|
1138
|
-
|
|
1869
|
+
h13(
|
|
1139
1870
|
"button",
|
|
1140
1871
|
{
|
|
1141
1872
|
type: "submit",
|
|
1142
|
-
style: styles.
|
|
1873
|
+
style: styles.buttonPrimary,
|
|
1143
1874
|
disabled: !/^\d+$/.test(amount.value.trim())
|
|
1144
1875
|
},
|
|
1145
|
-
"Grant"
|
|
1876
|
+
[icon("plus", 14), "Grant"]
|
|
1146
1877
|
),
|
|
1147
|
-
granted.value ?
|
|
1878
|
+
granted.value ? h13("span", { style: styles.ok }, granted.value) : null
|
|
1148
1879
|
]
|
|
1149
1880
|
),
|
|
1150
|
-
|
|
1151
|
-
ledger.value.length === 0 ?
|
|
1881
|
+
h13("h2", { style: styles.subtitle }, "Ledger"),
|
|
1882
|
+
ledger.value.length === 0 ? empty("Nothing moved yet", void 0, "subscriber") : table(
|
|
1152
1883
|
["When", "Kind", "Amount", "Note"],
|
|
1153
1884
|
ledger.value.map(
|
|
1154
|
-
(t2) =>
|
|
1885
|
+
(t2) => h13("tr", { key: t2.id }, [
|
|
1155
1886
|
td(new Date(t2.createdAt).toLocaleString(), styles.muted),
|
|
1156
1887
|
td(t2.type),
|
|
1157
1888
|
td(t2.amount, styles.mono),
|
|
@@ -1159,7 +1890,7 @@ var SubscriberScreen = defineComponent11({
|
|
|
1159
1890
|
])
|
|
1160
1891
|
)
|
|
1161
1892
|
),
|
|
1162
|
-
hasMoreLedger.value ?
|
|
1893
|
+
hasMoreLedger.value ? h13(
|
|
1163
1894
|
"button",
|
|
1164
1895
|
{
|
|
1165
1896
|
type: "button",
|
|
@@ -1168,7 +1899,7 @@ var SubscriberScreen = defineComponent11({
|
|
|
1168
1899
|
},
|
|
1169
1900
|
"Load more"
|
|
1170
1901
|
) : null
|
|
1171
|
-
] :
|
|
1902
|
+
] : h13(
|
|
1172
1903
|
"p",
|
|
1173
1904
|
{ style: styles.muted },
|
|
1174
1905
|
"No wallet \u2014 billing has no wallet configuration."
|
|
@@ -1181,7 +1912,7 @@ var SubscriberScreen = defineComponent11({
|
|
|
1181
1912
|
|
|
1182
1913
|
// src/screens/AuditScreen.ts
|
|
1183
1914
|
import { useAdminAudit } from "@fonderie/vue-admin";
|
|
1184
|
-
import { defineComponent as defineComponent12, h as
|
|
1915
|
+
import { defineComponent as defineComponent12, h as h14, ref as ref5 } from "vue";
|
|
1185
1916
|
var AuditScreen = defineComponent12({
|
|
1186
1917
|
name: "FonderieAuditScreen",
|
|
1187
1918
|
props: {
|
|
@@ -1189,22 +1920,25 @@ var AuditScreen = defineComponent12({
|
|
|
1189
1920
|
pageSize: { type: Number, default: 50 }
|
|
1190
1921
|
},
|
|
1191
1922
|
setup(props) {
|
|
1192
|
-
const draft =
|
|
1193
|
-
const query =
|
|
1923
|
+
const draft = ref5({ workspaceId: "", type: "", actorId: "" });
|
|
1924
|
+
const query = ref5({ limit: props.pageSize });
|
|
1194
1925
|
const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(
|
|
1195
1926
|
props.client,
|
|
1196
1927
|
query
|
|
1197
1928
|
);
|
|
1198
|
-
const field = (key, placeholder) =>
|
|
1929
|
+
const field = (key, placeholder) => h14("input", {
|
|
1199
1930
|
value: draft.value[key],
|
|
1200
1931
|
onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
|
|
1201
1932
|
placeholder,
|
|
1202
|
-
style:
|
|
1933
|
+
style: styles.input,
|
|
1203
1934
|
"aria-label": placeholder
|
|
1204
1935
|
});
|
|
1205
|
-
return () =>
|
|
1206
|
-
|
|
1207
|
-
|
|
1936
|
+
return () => h14("div", { style: styles.container }, [
|
|
1937
|
+
pageHeader(
|
|
1938
|
+
"Audit",
|
|
1939
|
+
"What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
|
|
1940
|
+
),
|
|
1941
|
+
h14(
|
|
1208
1942
|
"form",
|
|
1209
1943
|
{
|
|
1210
1944
|
style: styles.toolbar,
|
|
@@ -1223,19 +1957,19 @@ var AuditScreen = defineComponent12({
|
|
|
1223
1957
|
field("workspaceId", "workspace id (all if empty)"),
|
|
1224
1958
|
field("type", "event type"),
|
|
1225
1959
|
field("actorId", "actor id"),
|
|
1226
|
-
|
|
1960
|
+
h14(
|
|
1227
1961
|
"button",
|
|
1228
|
-
{ type: "submit", style: styles.
|
|
1229
|
-
"Filter"
|
|
1962
|
+
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1963
|
+
[icon("search", 14), "Filter"]
|
|
1230
1964
|
),
|
|
1231
1965
|
refreshButton("Refresh", isLoading.value, () => void refresh())
|
|
1232
1966
|
]
|
|
1233
1967
|
),
|
|
1234
|
-
error.value ?
|
|
1235
|
-
events.value.length === 0 && !isLoading.value ?
|
|
1968
|
+
error.value ? h14("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
1969
|
+
events.value.length === 0 && !isLoading.value ? empty("Nothing recorded for this filter", void 0, "audit") : table(
|
|
1236
1970
|
["When", "Type", "Workspace", "Actor", "Request"],
|
|
1237
1971
|
events.value.map(
|
|
1238
|
-
(e) =>
|
|
1972
|
+
(e) => h14("tr", { key: e.id }, [
|
|
1239
1973
|
td(new Date(e.createdAt).toLocaleString(), styles.muted),
|
|
1240
1974
|
td(e.type, styles.mono),
|
|
1241
1975
|
td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
|
|
@@ -1244,16 +1978,8 @@ var AuditScreen = defineComponent12({
|
|
|
1244
1978
|
])
|
|
1245
1979
|
)
|
|
1246
1980
|
),
|
|
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
|
|
1981
|
+
isLoading.value ? h14("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1982
|
+
hasMore.value && !isLoading.value ? loadMoreButton(() => void loadMore()) : null
|
|
1257
1983
|
]);
|
|
1258
1984
|
}
|
|
1259
1985
|
});
|
|
@@ -1290,6 +2016,23 @@ var NAV = [
|
|
|
1290
2016
|
]
|
|
1291
2017
|
}
|
|
1292
2018
|
];
|
|
2019
|
+
var ICON = {
|
|
2020
|
+
attention: "attention",
|
|
2021
|
+
modules: "modules",
|
|
2022
|
+
environment: "environment",
|
|
2023
|
+
doctor: "doctor",
|
|
2024
|
+
routes: "routes",
|
|
2025
|
+
users: "users",
|
|
2026
|
+
catalog: "catalog",
|
|
2027
|
+
subscriber: "subscriber",
|
|
2028
|
+
settings: "settings",
|
|
2029
|
+
templates: "templates",
|
|
2030
|
+
audit: "audit",
|
|
2031
|
+
log: "log",
|
|
2032
|
+
tokens: "tokens",
|
|
2033
|
+
migrations: "migrations"
|
|
2034
|
+
};
|
|
2035
|
+
var NARROW = "(max-width: 820px)";
|
|
1293
2036
|
var AdminShell = defineComponent13({
|
|
1294
2037
|
name: "FonderieAdminShell",
|
|
1295
2038
|
props: {
|
|
@@ -1307,75 +2050,75 @@ var AdminShell = defineComponent13({
|
|
|
1307
2050
|
environment: { type: String, default: void 0 },
|
|
1308
2051
|
// Controlled navigation: pass `page` and listen to `navigate` to own the
|
|
1309
2052
|
// URL. Omit `page` and the shell keeps it itself.
|
|
1310
|
-
page: { type: String, default: void 0 }
|
|
2053
|
+
page: { type: String, default: void 0 },
|
|
2054
|
+
// Shown at the top of the sidebar. Defaults to "Admin".
|
|
2055
|
+
appName: { type: String, default: "Admin" },
|
|
2056
|
+
// A badge beside the name — the deployment's environment ("production").
|
|
2057
|
+
// Production is tinted so an operator always knows where their clicks land.
|
|
2058
|
+
envLabel: { type: String, default: void 0 }
|
|
1311
2059
|
},
|
|
2060
|
+
// `footer` slot: pinned to the bottom of the sidebar — session controls
|
|
2061
|
+
// (theme, sign out). The React shell takes the same thing as a `footer` prop.
|
|
1312
2062
|
emits: { navigate: (_page) => true },
|
|
1313
|
-
setup(props, { emit }) {
|
|
1314
|
-
const
|
|
2063
|
+
setup(props, { emit, slots }) {
|
|
2064
|
+
const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
|
|
2065
|
+
const narrow = ref6(query?.matches ?? false);
|
|
2066
|
+
const drawer = ref6(false);
|
|
2067
|
+
const onMedia = (e) => narrow.value = e.matches;
|
|
2068
|
+
onMounted(() => query?.addEventListener("change", onMedia));
|
|
2069
|
+
onBeforeUnmount(() => query?.removeEventListener("change", onMedia));
|
|
2070
|
+
const own = ref6("attention");
|
|
1315
2071
|
const current = computed2(() => props.page ?? own.value);
|
|
1316
|
-
const editing =
|
|
2072
|
+
const editing = ref6(null);
|
|
1317
2073
|
const go = (p) => {
|
|
1318
2074
|
editing.value = null;
|
|
1319
2075
|
emit("navigate", p);
|
|
1320
2076
|
if (props.page === void 0) own.value = p;
|
|
2077
|
+
drawer.value = false;
|
|
1321
2078
|
};
|
|
1322
|
-
const back = () =>
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
2079
|
+
const back = (label) => h15("div", { style: { padding: "24px 40px 0" } }, [
|
|
2080
|
+
h15(
|
|
2081
|
+
"button",
|
|
2082
|
+
{ type: "button", style: styles.buttonGhost, onClick: () => editing.value = null },
|
|
2083
|
+
[icon("back", 14), label]
|
|
2084
|
+
)
|
|
2085
|
+
]);
|
|
2086
|
+
const missing = (text) => h15("p", { style: { ...styles.status, padding: "32px 40px" } }, text);
|
|
1327
2087
|
const body = () => {
|
|
1328
2088
|
const c = props.client;
|
|
1329
2089
|
switch (current.value) {
|
|
1330
2090
|
case "attention":
|
|
1331
|
-
return
|
|
2091
|
+
return h15(AttentionScreen, { client: c });
|
|
1332
2092
|
case "modules":
|
|
1333
|
-
return
|
|
2093
|
+
return h15(ModulesScreen, { client: c });
|
|
1334
2094
|
case "doctor":
|
|
1335
|
-
return
|
|
2095
|
+
return h15(DoctorScreen, { client: c });
|
|
1336
2096
|
case "environment":
|
|
1337
|
-
return
|
|
2097
|
+
return h15(EnvironmentScreen, { client: c });
|
|
1338
2098
|
case "routes":
|
|
1339
|
-
return
|
|
2099
|
+
return h15(RoutesScreen, { client: c });
|
|
1340
2100
|
case "tokens":
|
|
1341
|
-
return
|
|
2101
|
+
return h15(TokensScreen, { client: c });
|
|
1342
2102
|
case "migrations":
|
|
1343
|
-
return
|
|
2103
|
+
return h15(MigrationsScreen, { client: c });
|
|
1344
2104
|
case "log":
|
|
1345
|
-
return
|
|
2105
|
+
return h15(AdminLogScreen, { client: c });
|
|
1346
2106
|
case "catalog":
|
|
1347
|
-
return props.billingClient ?
|
|
1348
|
-
"p",
|
|
1349
|
-
{ style: styles.status },
|
|
1350
|
-
"Pass a BillingAdminClient to see the catalog here."
|
|
1351
|
-
);
|
|
2107
|
+
return props.billingClient ? h15(CatalogScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to see the catalog here.");
|
|
1352
2108
|
case "subscriber":
|
|
1353
|
-
return props.billingClient ?
|
|
1354
|
-
"p",
|
|
1355
|
-
{ style: styles.status },
|
|
1356
|
-
"Pass a BillingAdminClient to look up subscribers here."
|
|
1357
|
-
);
|
|
2109
|
+
return props.billingClient ? h15(SubscriberScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to look up subscribers here.");
|
|
1358
2110
|
case "audit":
|
|
1359
|
-
return props.auditClient ?
|
|
1360
|
-
"p",
|
|
1361
|
-
{ style: styles.status },
|
|
1362
|
-
"Pass an AuditAdminClient to see the audit trail here."
|
|
1363
|
-
);
|
|
2111
|
+
return props.auditClient ? h15(AuditScreen, { client: props.auditClient }) : missing("Pass an AuditAdminClient to see the audit trail here.");
|
|
1364
2112
|
case "users":
|
|
1365
|
-
return props.authClient ?
|
|
2113
|
+
return props.authClient ? h15(UsersScreen, { client: props.authClient }) : missing("Pass an AuthAdminClient to look up users here.");
|
|
1366
2114
|
case "settings": {
|
|
1367
2115
|
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
|
-
);
|
|
2116
|
+
if (!cc) return missing("Pass a ConfigAdminClient to manage config and secrets here.");
|
|
1374
2117
|
const e = editing.value;
|
|
1375
2118
|
if (e && e.kind !== "template")
|
|
1376
2119
|
return [
|
|
1377
|
-
back(),
|
|
1378
|
-
|
|
2120
|
+
back("Config & secrets"),
|
|
2121
|
+
h15(ConfigEditorScreen, {
|
|
1379
2122
|
client: cc,
|
|
1380
2123
|
kind: e.kind,
|
|
1381
2124
|
configKey: e.key,
|
|
@@ -1383,7 +2126,7 @@ var AdminShell = defineComponent13({
|
|
|
1383
2126
|
onSaved: () => editing.value = null
|
|
1384
2127
|
})
|
|
1385
2128
|
];
|
|
1386
|
-
return
|
|
2129
|
+
return h15(ConfigListScreen, {
|
|
1387
2130
|
client: cc,
|
|
1388
2131
|
...props.environment !== void 0 ? { environment: props.environment } : {},
|
|
1389
2132
|
"onSelect-config": (key) => editing.value = { kind: "config", key },
|
|
@@ -1395,61 +2138,135 @@ var AdminShell = defineComponent13({
|
|
|
1395
2138
|
}
|
|
1396
2139
|
case "templates": {
|
|
1397
2140
|
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
|
-
);
|
|
2141
|
+
if (!kc) return missing("Pass a CourierAdminClient to manage templates here.");
|
|
1404
2142
|
const e = editing.value;
|
|
1405
2143
|
if (e?.kind === "template")
|
|
1406
2144
|
return [
|
|
1407
|
-
back(),
|
|
1408
|
-
|
|
2145
|
+
back("Templates"),
|
|
2146
|
+
h15(TemplateEditorScreen, {
|
|
1409
2147
|
client: kc,
|
|
1410
2148
|
type: e.type,
|
|
1411
2149
|
onSaved: () => editing.value = null
|
|
1412
2150
|
})
|
|
1413
2151
|
];
|
|
1414
|
-
return
|
|
2152
|
+
return h15(TemplateListScreen, {
|
|
1415
2153
|
client: kc,
|
|
1416
2154
|
"onSelect-template": (t2) => editing.value = { kind: "template", type: t2.type }
|
|
1417
2155
|
});
|
|
1418
2156
|
}
|
|
1419
2157
|
}
|
|
1420
2158
|
};
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
2159
|
+
const visible = (i) => !i.needs || (i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : i.needs === "billing" ? props.billingClient : props.auditClient);
|
|
2160
|
+
const brand = () => {
|
|
2161
|
+
const production = props.envLabel ? /^prod/i.test(props.envLabel) : false;
|
|
2162
|
+
return h15("div", { style: styles.navBrand }, [
|
|
2163
|
+
h15("div", { style: styles.navMark }, [icon("mark", 15)]),
|
|
2164
|
+
h15("div", { style: { minWidth: 0, flex: 1 } }, [
|
|
2165
|
+
h15("div", { style: styles.navBrandTitle }, props.appName),
|
|
2166
|
+
props.envLabel ? h15(
|
|
2167
|
+
"span",
|
|
2168
|
+
{
|
|
2169
|
+
style: {
|
|
2170
|
+
...styles.badge,
|
|
2171
|
+
marginTop: "3px",
|
|
2172
|
+
padding: "0 7px",
|
|
2173
|
+
fontSize: "11px",
|
|
2174
|
+
...production ? {
|
|
2175
|
+
color: "var(--fonderie-danger,#e00)",
|
|
2176
|
+
borderColor: "color-mix(in srgb, var(--fonderie-danger,#e00) 35%, transparent)"
|
|
2177
|
+
} : {}
|
|
2178
|
+
}
|
|
2179
|
+
},
|
|
2180
|
+
props.envLabel
|
|
2181
|
+
) : null
|
|
2182
|
+
]),
|
|
2183
|
+
narrow.value ? h15(
|
|
2184
|
+
"button",
|
|
2185
|
+
{
|
|
2186
|
+
type: "button",
|
|
2187
|
+
style: styles.buttonGhost,
|
|
2188
|
+
"aria-label": "Close menu",
|
|
2189
|
+
onClick: () => drawer.value = false
|
|
2190
|
+
},
|
|
2191
|
+
[icon("close")]
|
|
2192
|
+
) : null
|
|
2193
|
+
]);
|
|
2194
|
+
};
|
|
2195
|
+
const nav = () => h15(
|
|
2196
|
+
"nav",
|
|
2197
|
+
{
|
|
2198
|
+
style: {
|
|
2199
|
+
...styles.nav,
|
|
2200
|
+
...narrow.value ? { width: "280px", maxWidth: "85vw", boxShadow: "0 10px 40px rgba(0,0,0,.25)" } : {}
|
|
2201
|
+
},
|
|
2202
|
+
"aria-label": "Admin"
|
|
2203
|
+
},
|
|
2204
|
+
[
|
|
2205
|
+
brand(),
|
|
2206
|
+
h15(
|
|
2207
|
+
"div",
|
|
2208
|
+
{ style: styles.navScroll },
|
|
2209
|
+
NAV.map((g) => {
|
|
2210
|
+
const items = g.items.filter(visible);
|
|
2211
|
+
if (items.length === 0) return null;
|
|
2212
|
+
return h15("div", { key: g.group }, [
|
|
2213
|
+
h15("div", { style: styles.navGroup }, g.group),
|
|
2214
|
+
...items.map((i) => {
|
|
2215
|
+
const active = current.value === i.page;
|
|
2216
|
+
return h15(
|
|
2217
|
+
"button",
|
|
2218
|
+
{
|
|
2219
|
+
key: i.page,
|
|
2220
|
+
type: "button",
|
|
2221
|
+
class: "fonderie-admin-nav-item",
|
|
2222
|
+
style: { ...styles.navItem, ...active ? styles.navItemActive : {} },
|
|
2223
|
+
"aria-current": active ? "page" : void 0,
|
|
2224
|
+
onClick: () => go(i.page)
|
|
2225
|
+
},
|
|
2226
|
+
[icon(ICON[i.page], 16, active ? styles.navIconActive : void 0), i.label]
|
|
2227
|
+
);
|
|
2228
|
+
})
|
|
2229
|
+
]);
|
|
2230
|
+
})
|
|
2231
|
+
),
|
|
2232
|
+
slots["footer"] ? h15("div", { style: styles.navFooter }, slots["footer"]()) : null
|
|
2233
|
+
]
|
|
2234
|
+
);
|
|
2235
|
+
return () => {
|
|
2236
|
+
const title = NAV.flatMap((g) => g.items).find((i) => i.page === current.value)?.label ?? "";
|
|
2237
|
+
return h15(
|
|
2238
|
+
"div",
|
|
2239
|
+
{ style: { ...styles.shell, ...narrow.value ? { flexDirection: "column" } : {} } },
|
|
2240
|
+
[
|
|
2241
|
+
...narrow.value ? [
|
|
2242
|
+
h15("div", { style: styles.topbar }, [
|
|
2243
|
+
h15(
|
|
1434
2244
|
"button",
|
|
1435
2245
|
{
|
|
1436
|
-
key: i.page,
|
|
1437
2246
|
type: "button",
|
|
1438
|
-
style:
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
"aria-current": current.value === i.page ? "page" : void 0,
|
|
1443
|
-
onClick: () => go(i.page)
|
|
2247
|
+
style: styles.buttonGhost,
|
|
2248
|
+
"aria-label": "Open menu",
|
|
2249
|
+
"aria-expanded": drawer.value,
|
|
2250
|
+
onClick: () => drawer.value = true
|
|
1444
2251
|
},
|
|
1445
|
-
|
|
1446
|
-
)
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
2252
|
+
[icon("menu")]
|
|
2253
|
+
),
|
|
2254
|
+
h15("span", { style: { fontWeight: 600, fontSize: "14px" } }, title),
|
|
2255
|
+
props.envLabel ? h15("span", { style: { ...styles.badge, marginLeft: "auto" } }, props.envLabel) : null
|
|
2256
|
+
]),
|
|
2257
|
+
drawer.value ? h15("div", { style: styles.drawer }, [
|
|
2258
|
+
nav(),
|
|
2259
|
+
h15("div", {
|
|
2260
|
+
style: styles.scrim,
|
|
2261
|
+
"aria-hidden": "true",
|
|
2262
|
+
onClick: () => drawer.value = false
|
|
2263
|
+
})
|
|
2264
|
+
]) : null
|
|
2265
|
+
] : [nav()],
|
|
2266
|
+
h15("main", { class: "fonderie-admin-main", style: styles.main }, body())
|
|
2267
|
+
]
|
|
2268
|
+
);
|
|
2269
|
+
};
|
|
1453
2270
|
}
|
|
1454
2271
|
});
|
|
1455
2272
|
export {
|