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