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