@clef-sh/ui 0.1.20 → 0.1.21
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/client/assets/index-DPWHjBbB.js +34 -0
- package/dist/client/assets/index-qsLTYpc9.css +2 -0
- package/dist/client/clef.svg +2 -0
- package/dist/client/index.html +3 -31
- package/dist/client-lib/components/Button.d.ts +1 -1
- package/dist/client-lib/components/Button.d.ts.map +1 -1
- package/dist/client-lib/components/CopyButton.d.ts.map +1 -1
- package/dist/client-lib/components/EnvBadge.d.ts.map +1 -1
- package/dist/client-lib/components/MatrixGrid.d.ts.map +1 -1
- package/dist/client-lib/components/Sidebar.d.ts +1 -1
- package/dist/client-lib/components/Sidebar.d.ts.map +1 -1
- package/dist/client-lib/components/StatusDot.d.ts.map +1 -1
- package/dist/client-lib/components/SyncPanel.d.ts.map +1 -1
- package/dist/client-lib/components/TopBar.d.ts +6 -0
- package/dist/client-lib/components/TopBar.d.ts.map +1 -1
- package/dist/client-lib/primitives/Badge.d.ts +11 -0
- package/dist/client-lib/primitives/Badge.d.ts.map +1 -0
- package/dist/client-lib/primitives/Card.d.ts +28 -0
- package/dist/client-lib/primitives/Card.d.ts.map +1 -0
- package/dist/client-lib/primitives/Dialog.d.ts +30 -0
- package/dist/client-lib/primitives/Dialog.d.ts.map +1 -0
- package/dist/client-lib/primitives/EmptyState.d.ts +10 -0
- package/dist/client-lib/primitives/EmptyState.d.ts.map +1 -0
- package/dist/client-lib/primitives/Field.d.ts +36 -0
- package/dist/client-lib/primitives/Field.d.ts.map +1 -0
- package/dist/client-lib/primitives/Input.d.ts +6 -0
- package/dist/client-lib/primitives/Input.d.ts.map +1 -0
- package/dist/client-lib/primitives/Stat.d.ts +11 -0
- package/dist/client-lib/primitives/Stat.d.ts.map +1 -0
- package/dist/client-lib/primitives/Table.d.ts +37 -0
- package/dist/client-lib/primitives/Table.d.ts.map +1 -0
- package/dist/client-lib/primitives/Tabs.d.ts +29 -0
- package/dist/client-lib/primitives/Tabs.d.ts.map +1 -0
- package/dist/client-lib/primitives/Toast.d.ts +16 -0
- package/dist/client-lib/primitives/Toast.d.ts.map +1 -0
- package/dist/client-lib/primitives/Toolbar.d.ts +29 -0
- package/dist/client-lib/primitives/Toolbar.d.ts.map +1 -0
- package/dist/client-lib/primitives/index.d.ts +23 -0
- package/dist/client-lib/primitives/index.d.ts.map +1 -0
- package/dist/client-lib/theme.d.ts +18 -41
- package/dist/client-lib/theme.d.ts.map +1 -1
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +215 -0
- package/dist/server/api.js.map +1 -1
- package/dist/server/envelope.d.ts +15 -0
- package/dist/server/envelope.d.ts.map +1 -0
- package/dist/server/envelope.js +310 -0
- package/dist/server/envelope.js.map +1 -0
- package/package.json +7 -2
- package/src/client/App.tsx +16 -41
- package/src/client/components/Button.tsx +13 -22
- package/src/client/components/CopyButton.tsx +5 -12
- package/src/client/components/EnvBadge.tsx +30 -15
- package/src/client/components/MatrixGrid.tsx +108 -252
- package/src/client/components/Sidebar.tsx +123 -199
- package/src/client/components/StatusDot.tsx +10 -15
- package/src/client/components/SyncPanel.tsx +14 -62
- package/src/client/components/TopBar.tsx +11 -36
- package/src/client/index.html +1 -30
- package/src/client/main.tsx +1 -0
- package/src/client/primitives/Badge.test.tsx +47 -0
- package/src/client/primitives/Badge.tsx +64 -0
- package/src/client/primitives/Card.test.tsx +50 -0
- package/src/client/primitives/Card.tsx +85 -0
- package/src/client/primitives/Dialog.test.tsx +55 -0
- package/src/client/primitives/Dialog.tsx +96 -0
- package/src/client/primitives/EmptyState.test.tsx +25 -0
- package/src/client/primitives/EmptyState.tsx +38 -0
- package/src/client/primitives/Field.test.tsx +46 -0
- package/src/client/primitives/Field.tsx +95 -0
- package/src/client/primitives/Input.tsx +26 -0
- package/src/client/primitives/Stat.test.tsx +32 -0
- package/src/client/primitives/Stat.tsx +52 -0
- package/src/client/primitives/Table.test.tsx +58 -0
- package/src/client/primitives/Table.tsx +113 -0
- package/src/client/primitives/Tabs.test.tsx +44 -0
- package/src/client/primitives/Tabs.tsx +100 -0
- package/src/client/primitives/Toast.test.tsx +77 -0
- package/src/client/primitives/Toast.tsx +89 -0
- package/src/client/primitives/Toolbar.test.tsx +50 -0
- package/src/client/primitives/Toolbar.tsx +86 -0
- package/src/client/primitives/index.ts +43 -0
- package/src/client/public/clef.svg +2 -0
- package/src/client/screens/BackendScreen.tsx +104 -363
- package/src/client/screens/DiffView.tsx +187 -378
- package/src/client/screens/EnvelopeScreen.test.tsx +542 -0
- package/src/client/screens/EnvelopeScreen.tsx +948 -0
- package/src/client/screens/GitLogView.tsx +48 -106
- package/src/client/screens/ImportScreen.tsx +105 -308
- package/src/client/screens/LintView.tsx +184 -379
- package/src/client/screens/ManifestScreen.tsx +283 -445
- package/src/client/screens/MatrixView.tsx +75 -91
- package/src/client/screens/NamespaceEditor.tsx +234 -609
- package/src/client/screens/PolicyView.tsx +183 -453
- package/src/client/screens/RecipientsScreen.tsx +71 -350
- package/src/client/screens/ResetScreen.tsx +67 -237
- package/src/client/screens/ScanScreen.tsx +85 -249
- package/src/client/screens/SchemaEditor.test.tsx +237 -0
- package/src/client/screens/SchemaEditor.tsx +435 -0
- package/src/client/screens/ServiceIdentitiesScreen.tsx +251 -788
- package/src/client/styles.css +77 -0
- package/src/client/theme.ts +27 -48
- package/dist/client/assets/index-Db6WgHgY.js +0 -38
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { theme } from "../theme";
|
|
3
2
|
import { apiFetch } from "../api";
|
|
4
|
-
import { TopBar } from "../components/TopBar";
|
|
5
3
|
import { Button } from "../components/Button";
|
|
4
|
+
import { Toolbar } from "../primitives";
|
|
6
5
|
import type {
|
|
7
6
|
ClefManifest,
|
|
8
7
|
BackendType,
|
|
@@ -57,22 +56,26 @@ const KEY_PLACEHOLDERS: Record<string, string> = {
|
|
|
57
56
|
};
|
|
58
57
|
|
|
59
58
|
const ALL_BACKENDS: BackendType[] = ["age", "awskms", "gcpkms", "azurekv", "pgp", "hsm"];
|
|
59
|
+
|
|
60
|
+
const TEXT_INPUT_BASE =
|
|
61
|
+
"w-full box-border rounded-md border border-edge bg-ink-850 px-3 py-2 font-mono text-[12px] text-bone outline-none focus-visible:border-gold-500 placeholder:text-ash-dim";
|
|
62
|
+
|
|
63
|
+
const SELECT_CLASSES =
|
|
64
|
+
"w-full rounded-md border border-edge bg-ink-850 px-2.5 py-1.5 font-sans text-[13px] text-bone cursor-pointer outline-none focus-visible:border-gold-500";
|
|
65
|
+
|
|
60
66
|
export function BackendScreen({ manifest, setView, reloadManifest }: BackendScreenProps) {
|
|
61
67
|
const [step, setStep] = useState<1 | 2 | 3 | 4>(1);
|
|
62
68
|
const [config, setConfig] = useState<BackendConfigResponse | null>(null);
|
|
63
69
|
|
|
64
|
-
// Step 1 state
|
|
65
70
|
const [targetBackend, setTargetBackend] = useState<BackendType>("age");
|
|
66
71
|
const [targetKey, setTargetKey] = useState("");
|
|
67
72
|
const [scope, setScope] = useState<"all" | "single">("all");
|
|
68
73
|
const [selectedEnv, setSelectedEnv] = useState("");
|
|
69
74
|
|
|
70
|
-
// Step 2 state
|
|
71
75
|
const [previewResult, setPreviewResult] = useState<MigrationResponse | null>(null);
|
|
72
76
|
const [needsConfirmation, setNeedsConfirmation] = useState(false);
|
|
73
77
|
const [confirmed, setConfirmed] = useState(false);
|
|
74
78
|
|
|
75
|
-
// Step 3/4 state
|
|
76
79
|
const [applyResult, setApplyResult] = useState<MigrationResponse | null>(null);
|
|
77
80
|
|
|
78
81
|
const [loading, setLoading] = useState(false);
|
|
@@ -197,61 +200,43 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
197
200
|
.length ?? 0;
|
|
198
201
|
|
|
199
202
|
return (
|
|
200
|
-
<div
|
|
201
|
-
<
|
|
203
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
204
|
+
<Toolbar>
|
|
205
|
+
<div>
|
|
206
|
+
<Toolbar.Title>Backend</Toolbar.Title>
|
|
207
|
+
<Toolbar.Subtitle>clef migrate-backend — change encryption backend</Toolbar.Subtitle>
|
|
208
|
+
</div>
|
|
209
|
+
</Toolbar>
|
|
202
210
|
|
|
203
|
-
<div
|
|
204
|
-
<div
|
|
211
|
+
<div className="flex-1 overflow-auto p-6">
|
|
212
|
+
<div className="mx-auto max-w-[620px]">
|
|
205
213
|
{/* Step indicator */}
|
|
206
|
-
<div
|
|
207
|
-
style={{
|
|
208
|
-
display: "flex",
|
|
209
|
-
alignItems: "center",
|
|
210
|
-
gap: 0,
|
|
211
|
-
marginBottom: 32,
|
|
212
|
-
}}
|
|
213
|
-
>
|
|
214
|
+
<div className="mb-8 flex items-center">
|
|
214
215
|
{([1, 2, 3, 4] as const).map((s, i) => (
|
|
215
216
|
<React.Fragment key={s}>
|
|
216
|
-
<div
|
|
217
|
+
<div className="flex items-center gap-2">
|
|
217
218
|
<div
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
border: `1px solid ${step >= s ? theme.accent : theme.border}`,
|
|
224
|
-
display: "flex",
|
|
225
|
-
alignItems: "center",
|
|
226
|
-
justifyContent: "center",
|
|
227
|
-
fontFamily: theme.mono,
|
|
228
|
-
fontSize: 11,
|
|
229
|
-
fontWeight: 700,
|
|
230
|
-
color: step >= s ? "#000" : theme.textDim,
|
|
231
|
-
}}
|
|
219
|
+
className={`flex h-6 w-6 items-center justify-center rounded-full font-mono text-[11px] font-bold ${
|
|
220
|
+
step >= s
|
|
221
|
+
? "bg-gold-500 border border-gold-500 text-ink-950"
|
|
222
|
+
: "bg-ink-850 border border-edge text-ash-dim"
|
|
223
|
+
}`}
|
|
232
224
|
>
|
|
233
225
|
{s}
|
|
234
226
|
</div>
|
|
235
227
|
<span
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
color: step >= s ? theme.text : theme.textDim,
|
|
240
|
-
fontWeight: step === s ? 600 : 400,
|
|
241
|
-
}}
|
|
228
|
+
className={`font-sans text-[12px] ${
|
|
229
|
+
step >= s ? "text-bone" : "text-ash-dim"
|
|
230
|
+
} ${step === s ? "font-semibold" : "font-normal"}`}
|
|
242
231
|
>
|
|
243
232
|
{s === 1 ? "Configure" : s === 2 ? "Preview" : s === 3 ? "Migrate" : "Done"}
|
|
244
233
|
</span>
|
|
245
234
|
</div>
|
|
246
235
|
{i < 3 && (
|
|
247
236
|
<div
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
background: step > s ? theme.accent : theme.border,
|
|
252
|
-
margin: "0 12px",
|
|
253
|
-
minWidth: 20,
|
|
254
|
-
}}
|
|
237
|
+
className={`mx-3 h-px min-w-[20px] flex-1 ${
|
|
238
|
+
step > s ? "bg-gold-500" : "bg-edge"
|
|
239
|
+
}`}
|
|
255
240
|
/>
|
|
256
241
|
)}
|
|
257
242
|
</React.Fragment>
|
|
@@ -259,18 +244,7 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
259
244
|
</div>
|
|
260
245
|
|
|
261
246
|
{error && (
|
|
262
|
-
<div
|
|
263
|
-
style={{
|
|
264
|
-
background: theme.redDim,
|
|
265
|
-
border: `1px solid ${theme.red}44`,
|
|
266
|
-
borderRadius: 8,
|
|
267
|
-
padding: "12px 16px",
|
|
268
|
-
marginBottom: 16,
|
|
269
|
-
fontFamily: theme.sans,
|
|
270
|
-
fontSize: 13,
|
|
271
|
-
color: theme.red,
|
|
272
|
-
}}
|
|
273
|
-
>
|
|
247
|
+
<div className="mb-4 rounded-lg border border-stop-500/30 bg-stop-500/10 px-4 py-3 font-sans text-[13px] text-stop-500">
|
|
274
248
|
{error}
|
|
275
249
|
</div>
|
|
276
250
|
)}
|
|
@@ -278,50 +252,27 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
278
252
|
{/* ── Step 1: Configure ─────────────────────────────────────── */}
|
|
279
253
|
{step === 1 && (
|
|
280
254
|
<div>
|
|
281
|
-
{/* Current config */}
|
|
282
255
|
{config && (
|
|
283
|
-
<div
|
|
256
|
+
<div className="mb-6">
|
|
284
257
|
<Label>Current Configuration</Label>
|
|
285
|
-
<div
|
|
286
|
-
|
|
287
|
-
background: theme.surface,
|
|
288
|
-
border: `1px solid ${theme.border}`,
|
|
289
|
-
borderRadius: 8,
|
|
290
|
-
padding: 14,
|
|
291
|
-
}}
|
|
292
|
-
>
|
|
293
|
-
<div
|
|
294
|
-
style={{
|
|
295
|
-
fontFamily: theme.mono,
|
|
296
|
-
fontSize: 12,
|
|
297
|
-
color: theme.text,
|
|
298
|
-
marginBottom: 8,
|
|
299
|
-
}}
|
|
300
|
-
>
|
|
258
|
+
<div className="rounded-lg border border-edge bg-ink-850 p-3.5">
|
|
259
|
+
<div className="mb-2 font-mono text-[12px] text-bone">
|
|
301
260
|
Default backend:{" "}
|
|
302
|
-
<span
|
|
261
|
+
<span className="font-semibold text-gold-500">
|
|
303
262
|
{BACKEND_LABELS[config.global.default_backend]}
|
|
304
263
|
</span>
|
|
305
264
|
</div>
|
|
306
265
|
{config.environments.map((env) => (
|
|
307
266
|
<div
|
|
308
267
|
key={env.name}
|
|
309
|
-
|
|
310
|
-
display: "flex",
|
|
311
|
-
alignItems: "center",
|
|
312
|
-
gap: 8,
|
|
313
|
-
fontFamily: theme.mono,
|
|
314
|
-
fontSize: 11,
|
|
315
|
-
color: theme.textMuted,
|
|
316
|
-
marginBottom: 2,
|
|
317
|
-
}}
|
|
268
|
+
className="mb-px flex items-center gap-2 font-mono text-[11px] text-ash"
|
|
318
269
|
>
|
|
319
270
|
<span>
|
|
320
|
-
{env.protected ? "
|
|
271
|
+
{env.protected ? "🔒 " : ""}
|
|
321
272
|
{env.name}
|
|
322
273
|
</span>
|
|
323
|
-
<span
|
|
324
|
-
<span
|
|
274
|
+
<span className="text-ash-dim">→</span>
|
|
275
|
+
<span className={env.hasOverride ? "text-warn-500" : "text-ash"}>
|
|
325
276
|
{BACKEND_LABELS[env.effective.backend]}
|
|
326
277
|
{env.hasOverride ? " (override)" : ""}
|
|
327
278
|
</span>
|
|
@@ -331,22 +282,15 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
331
282
|
</div>
|
|
332
283
|
)}
|
|
333
284
|
|
|
334
|
-
|
|
335
|
-
<div style={{ marginBottom: 20 }}>
|
|
285
|
+
<div className="mb-5">
|
|
336
286
|
<Label>Target Backend</Label>
|
|
337
|
-
<div
|
|
287
|
+
<div className="flex flex-col gap-1.5">
|
|
338
288
|
{ALL_BACKENDS.map((b) => (
|
|
339
289
|
<label
|
|
340
290
|
key={b}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
gap: 8,
|
|
345
|
-
cursor: "pointer",
|
|
346
|
-
fontFamily: theme.sans,
|
|
347
|
-
fontSize: 13,
|
|
348
|
-
color: targetBackend === b ? theme.text : theme.textMuted,
|
|
349
|
-
}}
|
|
291
|
+
className={`flex cursor-pointer items-center gap-2 font-sans text-[13px] ${
|
|
292
|
+
targetBackend === b ? "text-bone" : "text-ash"
|
|
293
|
+
}`}
|
|
350
294
|
>
|
|
351
295
|
<input
|
|
352
296
|
type="radio"
|
|
@@ -357,7 +301,7 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
357
301
|
setTargetBackend(b);
|
|
358
302
|
setTargetKey("");
|
|
359
303
|
}}
|
|
360
|
-
|
|
304
|
+
className="accent-gold-500"
|
|
361
305
|
data-testid={`backend-radio-${b}`}
|
|
362
306
|
/>
|
|
363
307
|
{BACKEND_LABELS[b]}
|
|
@@ -366,9 +310,8 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
366
310
|
</div>
|
|
367
311
|
</div>
|
|
368
312
|
|
|
369
|
-
{/* Key input (non-age) */}
|
|
370
313
|
{targetBackend !== "age" && (
|
|
371
|
-
<div
|
|
314
|
+
<div className="mb-5">
|
|
372
315
|
<Label>Key Identifier</Label>
|
|
373
316
|
<input
|
|
374
317
|
type="text"
|
|
@@ -376,63 +319,39 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
376
319
|
onChange={(e) => setTargetKey(e.target.value)}
|
|
377
320
|
placeholder={KEY_PLACEHOLDERS[targetBackend]}
|
|
378
321
|
data-testid="backend-key-input"
|
|
379
|
-
|
|
380
|
-
width: "100%",
|
|
381
|
-
background: theme.surface,
|
|
382
|
-
border: `1px solid ${theme.border}`,
|
|
383
|
-
borderRadius: 6,
|
|
384
|
-
padding: "8px 12px",
|
|
385
|
-
fontFamily: theme.mono,
|
|
386
|
-
fontSize: 12,
|
|
387
|
-
color: theme.text,
|
|
388
|
-
outline: "none",
|
|
389
|
-
boxSizing: "border-box",
|
|
390
|
-
}}
|
|
322
|
+
className={TEXT_INPUT_BASE}
|
|
391
323
|
/>
|
|
392
324
|
</div>
|
|
393
325
|
)}
|
|
394
326
|
|
|
395
|
-
|
|
396
|
-
<div style={{ marginBottom: 24 }}>
|
|
327
|
+
<div className="mb-6">
|
|
397
328
|
<Label>Scope</Label>
|
|
398
|
-
<div
|
|
329
|
+
<div className="mb-2 flex gap-4">
|
|
399
330
|
<label
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
gap: 6,
|
|
404
|
-
cursor: "pointer",
|
|
405
|
-
fontFamily: theme.sans,
|
|
406
|
-
fontSize: 13,
|
|
407
|
-
color: scope === "all" ? theme.text : theme.textMuted,
|
|
408
|
-
}}
|
|
331
|
+
className={`flex cursor-pointer items-center gap-1.5 font-sans text-[13px] ${
|
|
332
|
+
scope === "all" ? "text-bone" : "text-ash"
|
|
333
|
+
}`}
|
|
409
334
|
>
|
|
410
335
|
<input
|
|
411
336
|
type="radio"
|
|
412
337
|
name="scope"
|
|
413
338
|
checked={scope === "all"}
|
|
414
339
|
onChange={() => setScope("all")}
|
|
415
|
-
|
|
340
|
+
className="accent-gold-500"
|
|
416
341
|
/>
|
|
417
342
|
All environments
|
|
418
343
|
</label>
|
|
419
344
|
<label
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
gap: 6,
|
|
424
|
-
cursor: "pointer",
|
|
425
|
-
fontFamily: theme.sans,
|
|
426
|
-
fontSize: 13,
|
|
427
|
-
color: scope === "single" ? theme.text : theme.textMuted,
|
|
428
|
-
}}
|
|
345
|
+
className={`flex cursor-pointer items-center gap-1.5 font-sans text-[13px] ${
|
|
346
|
+
scope === "single" ? "text-bone" : "text-ash"
|
|
347
|
+
}`}
|
|
429
348
|
>
|
|
430
349
|
<input
|
|
431
350
|
type="radio"
|
|
432
351
|
name="scope"
|
|
433
352
|
checked={scope === "single"}
|
|
434
353
|
onChange={() => setScope("single")}
|
|
435
|
-
|
|
354
|
+
className="accent-gold-500"
|
|
436
355
|
/>
|
|
437
356
|
Single environment
|
|
438
357
|
</label>
|
|
@@ -442,18 +361,7 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
442
361
|
value={selectedEnv}
|
|
443
362
|
onChange={(e) => setSelectedEnv(e.target.value)}
|
|
444
363
|
data-testid="env-select"
|
|
445
|
-
|
|
446
|
-
width: "100%",
|
|
447
|
-
background: theme.surface,
|
|
448
|
-
border: `1px solid ${theme.border}`,
|
|
449
|
-
borderRadius: 6,
|
|
450
|
-
padding: "7px 10px",
|
|
451
|
-
fontFamily: theme.sans,
|
|
452
|
-
fontSize: 13,
|
|
453
|
-
color: theme.text,
|
|
454
|
-
outline: "none",
|
|
455
|
-
cursor: "pointer",
|
|
456
|
-
}}
|
|
364
|
+
className={SELECT_CLASSES}
|
|
457
365
|
>
|
|
458
366
|
{environments.map((env) => (
|
|
459
367
|
<option key={env.name} value={env.name}>
|
|
@@ -465,43 +373,17 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
465
373
|
)}
|
|
466
374
|
</div>
|
|
467
375
|
|
|
468
|
-
{/* Protected env confirmation */}
|
|
469
376
|
{needsConfirmation && (
|
|
470
|
-
<div
|
|
471
|
-
|
|
472
|
-
background: theme.yellowDim,
|
|
473
|
-
border: `1px solid ${theme.yellow}44`,
|
|
474
|
-
borderRadius: 8,
|
|
475
|
-
padding: "12px 16px",
|
|
476
|
-
marginBottom: 16,
|
|
477
|
-
}}
|
|
478
|
-
>
|
|
479
|
-
<div
|
|
480
|
-
style={{
|
|
481
|
-
fontFamily: theme.sans,
|
|
482
|
-
fontSize: 13,
|
|
483
|
-
color: theme.yellow,
|
|
484
|
-
marginBottom: 8,
|
|
485
|
-
}}
|
|
486
|
-
>
|
|
377
|
+
<div className="mb-4 rounded-lg border border-warn-500/30 bg-warn-500/10 px-4 py-3">
|
|
378
|
+
<div className="mb-2 font-sans text-[13px] text-warn-500">
|
|
487
379
|
This migration affects protected environments.
|
|
488
380
|
</div>
|
|
489
|
-
<label
|
|
490
|
-
style={{
|
|
491
|
-
display: "flex",
|
|
492
|
-
alignItems: "center",
|
|
493
|
-
gap: 8,
|
|
494
|
-
cursor: "pointer",
|
|
495
|
-
fontFamily: theme.sans,
|
|
496
|
-
fontSize: 12,
|
|
497
|
-
color: theme.text,
|
|
498
|
-
}}
|
|
499
|
-
>
|
|
381
|
+
<label className="flex cursor-pointer items-center gap-2 font-sans text-[12px] text-bone">
|
|
500
382
|
<input
|
|
501
383
|
type="checkbox"
|
|
502
384
|
checked={confirmed}
|
|
503
385
|
onChange={(e) => setConfirmed(e.target.checked)}
|
|
504
|
-
|
|
386
|
+
className="accent-warn-500"
|
|
505
387
|
data-testid="protected-confirm"
|
|
506
388
|
/>
|
|
507
389
|
I understand and want to proceed
|
|
@@ -526,76 +408,51 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
526
408
|
{/* ── Step 2: Preview ───────────────────────────────────────── */}
|
|
527
409
|
{step === 2 && previewResult && (
|
|
528
410
|
<div>
|
|
529
|
-
<div
|
|
530
|
-
style={{
|
|
531
|
-
fontFamily: theme.sans,
|
|
532
|
-
fontSize: 13,
|
|
533
|
-
color: theme.textMuted,
|
|
534
|
-
marginBottom: 20,
|
|
535
|
-
}}
|
|
536
|
-
>
|
|
411
|
+
<div className="mb-5 font-sans text-[13px] text-ash">
|
|
537
412
|
Migrating to{" "}
|
|
538
|
-
<span
|
|
539
|
-
{BACKEND_LABELS[targetBackend]}
|
|
540
|
-
</span>
|
|
413
|
+
<span className="font-semibold text-gold-500">{BACKEND_LABELS[targetBackend]}</span>
|
|
541
414
|
{scope === "single" ? ` (${selectedEnv} only)` : " (all environments)"}
|
|
542
415
|
</div>
|
|
543
416
|
|
|
544
|
-
{/* Events / files to migrate */}
|
|
545
417
|
{previewResult.events.filter((e) => e.type === "info").length > 0 && (
|
|
546
|
-
<div
|
|
547
|
-
<SectionLabel
|
|
418
|
+
<div className="mb-4">
|
|
419
|
+
<SectionLabel toneClass="text-go-500">
|
|
548
420
|
Files to migrate ({previewResult.events.filter((e) => e.type === "info").length}
|
|
549
421
|
)
|
|
550
422
|
</SectionLabel>
|
|
551
423
|
{previewResult.events
|
|
552
424
|
.filter((e) => e.type === "info")
|
|
553
425
|
.map((e, i) => (
|
|
554
|
-
<FileRow key={i} icon=
|
|
426
|
+
<FileRow key={i} icon="→" iconClass="text-go-500" label={e.message} />
|
|
555
427
|
))}
|
|
556
428
|
</div>
|
|
557
429
|
)}
|
|
558
430
|
|
|
559
|
-
{/* Skipped files */}
|
|
560
431
|
{previewResult.events.filter((e) => e.type === "skip").length > 0 && (
|
|
561
|
-
<div
|
|
562
|
-
<SectionLabel
|
|
432
|
+
<div className="mb-4">
|
|
433
|
+
<SectionLabel toneClass="text-ash-dim">
|
|
563
434
|
Already on target (
|
|
564
435
|
{previewResult.events.filter((e) => e.type === "skip").length})
|
|
565
436
|
</SectionLabel>
|
|
566
437
|
{previewResult.events
|
|
567
438
|
.filter((e) => e.type === "skip")
|
|
568
439
|
.map((e, i) => (
|
|
569
|
-
<FileRow
|
|
570
|
-
key={i}
|
|
571
|
-
icon={"\u21B7"}
|
|
572
|
-
iconColor={theme.textDim}
|
|
573
|
-
label={e.message}
|
|
574
|
-
/>
|
|
440
|
+
<FileRow key={i} icon="↷" iconClass="text-ash-dim" label={e.message} />
|
|
575
441
|
))}
|
|
576
442
|
</div>
|
|
577
443
|
)}
|
|
578
444
|
|
|
579
|
-
{/* Warnings */}
|
|
580
445
|
{previewResult.result.warnings.length > 0 && (
|
|
581
|
-
<div
|
|
446
|
+
<div className="mb-4">
|
|
582
447
|
{previewResult.result.warnings.map((w, i) => (
|
|
583
|
-
<div
|
|
584
|
-
|
|
585
|
-
style={{
|
|
586
|
-
fontFamily: theme.mono,
|
|
587
|
-
fontSize: 11,
|
|
588
|
-
color: theme.yellow,
|
|
589
|
-
marginBottom: 4,
|
|
590
|
-
}}
|
|
591
|
-
>
|
|
592
|
-
{"\u26A0"} {w}
|
|
448
|
+
<div key={i} className="mb-1 font-mono text-[11px] text-warn-500">
|
|
449
|
+
⚠ {w}
|
|
593
450
|
</div>
|
|
594
451
|
))}
|
|
595
452
|
</div>
|
|
596
453
|
)}
|
|
597
454
|
|
|
598
|
-
<div
|
|
455
|
+
<div className="mt-6 flex gap-2.5">
|
|
599
456
|
<Button variant="ghost" onClick={handleReset}>
|
|
600
457
|
Back
|
|
601
458
|
</Button>
|
|
@@ -613,115 +470,46 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
613
470
|
|
|
614
471
|
{/* ── Step 3: Executing ─────────────────────────────────────── */}
|
|
615
472
|
{step === 3 && (
|
|
616
|
-
<div
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
flexDirection: "column",
|
|
620
|
-
alignItems: "center",
|
|
621
|
-
paddingTop: 40,
|
|
622
|
-
}}
|
|
623
|
-
>
|
|
624
|
-
<div
|
|
625
|
-
style={{
|
|
626
|
-
width: 40,
|
|
627
|
-
height: 40,
|
|
628
|
-
border: `3px solid ${theme.border}`,
|
|
629
|
-
borderTopColor: theme.accent,
|
|
630
|
-
borderRadius: "50%",
|
|
631
|
-
animation: "spin 1s linear infinite",
|
|
632
|
-
marginBottom: 16,
|
|
633
|
-
}}
|
|
634
|
-
/>
|
|
635
|
-
<div
|
|
636
|
-
style={{
|
|
637
|
-
fontFamily: theme.sans,
|
|
638
|
-
fontSize: 14,
|
|
639
|
-
color: theme.textMuted,
|
|
640
|
-
}}
|
|
641
|
-
>
|
|
473
|
+
<div className="flex flex-col items-center pt-10">
|
|
474
|
+
<div className="mb-4 h-10 w-10 animate-spin rounded-full border-[3px] border-edge border-t-gold-500" />
|
|
475
|
+
<div className="font-sans text-[14px] text-ash">
|
|
642
476
|
Migrating... this may take a moment
|
|
643
477
|
</div>
|
|
644
|
-
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
|
645
478
|
</div>
|
|
646
479
|
)}
|
|
647
480
|
|
|
648
481
|
{/* ── Step 4: Result ────────────────────────────────────────── */}
|
|
649
482
|
{step === 4 && applyResult && (
|
|
650
483
|
<div>
|
|
651
|
-
<div
|
|
652
|
-
style={{
|
|
653
|
-
display: "flex",
|
|
654
|
-
flexDirection: "column",
|
|
655
|
-
alignItems: "center",
|
|
656
|
-
paddingTop: 20,
|
|
657
|
-
paddingBottom: 32,
|
|
658
|
-
}}
|
|
659
|
-
>
|
|
484
|
+
<div className="flex flex-col items-center pt-5 pb-8">
|
|
660
485
|
<div
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
border: `1px solid ${applyResult.result.rolledBack ? theme.red + "44" : theme.green + "44"}`,
|
|
667
|
-
display: "flex",
|
|
668
|
-
alignItems: "center",
|
|
669
|
-
justifyContent: "center",
|
|
670
|
-
fontSize: 24,
|
|
671
|
-
color: applyResult.result.rolledBack ? theme.red : theme.green,
|
|
672
|
-
marginBottom: 16,
|
|
673
|
-
}}
|
|
486
|
+
className={`mb-4 flex h-14 w-14 items-center justify-center rounded-full text-[24px] ${
|
|
487
|
+
applyResult.result.rolledBack
|
|
488
|
+
? "border border-stop-500/30 bg-stop-500/10 text-stop-500"
|
|
489
|
+
: "border border-go-500/30 bg-go-500/10 text-go-500"
|
|
490
|
+
}`}
|
|
674
491
|
>
|
|
675
|
-
{applyResult.result.rolledBack ? "
|
|
492
|
+
{applyResult.result.rolledBack ? "⚠" : "✓"}
|
|
676
493
|
</div>
|
|
677
|
-
|
|
678
494
|
<div
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
fontSize: 16,
|
|
683
|
-
color: applyResult.result.rolledBack ? theme.red : theme.green,
|
|
684
|
-
marginBottom: 8,
|
|
685
|
-
}}
|
|
495
|
+
className={`mb-2 font-sans text-[16px] font-semibold ${
|
|
496
|
+
applyResult.result.rolledBack ? "text-stop-500" : "text-go-500"
|
|
497
|
+
}`}
|
|
686
498
|
>
|
|
687
499
|
{applyResult.result.rolledBack ? "Migration failed" : "Migration complete"}
|
|
688
500
|
</div>
|
|
689
|
-
|
|
690
501
|
{applyResult.result.rolledBack && applyResult.result.error && (
|
|
691
|
-
<div
|
|
692
|
-
style={{
|
|
693
|
-
fontFamily: theme.mono,
|
|
694
|
-
fontSize: 12,
|
|
695
|
-
color: theme.red,
|
|
696
|
-
marginBottom: 8,
|
|
697
|
-
textAlign: "center",
|
|
698
|
-
}}
|
|
699
|
-
>
|
|
502
|
+
<div className="mb-2 text-center font-mono text-[12px] text-stop-500">
|
|
700
503
|
{applyResult.result.error}
|
|
701
504
|
</div>
|
|
702
505
|
)}
|
|
703
|
-
|
|
704
506
|
{applyResult.result.rolledBack && (
|
|
705
|
-
<div
|
|
706
|
-
style={{
|
|
707
|
-
fontFamily: theme.sans,
|
|
708
|
-
fontSize: 12,
|
|
709
|
-
color: theme.textMuted,
|
|
710
|
-
marginBottom: 8,
|
|
711
|
-
}}
|
|
712
|
-
>
|
|
507
|
+
<div className="mb-2 font-sans text-[12px] text-ash">
|
|
713
508
|
All changes have been rolled back.
|
|
714
509
|
</div>
|
|
715
510
|
)}
|
|
716
|
-
|
|
717
511
|
{!applyResult.result.rolledBack && (
|
|
718
|
-
<div
|
|
719
|
-
style={{
|
|
720
|
-
fontFamily: theme.mono,
|
|
721
|
-
fontSize: 12,
|
|
722
|
-
color: theme.textMuted,
|
|
723
|
-
}}
|
|
724
|
-
>
|
|
512
|
+
<div className="font-mono text-[12px] text-ash">
|
|
725
513
|
{applyResult.result.migratedFiles.length} migrated,{" "}
|
|
726
514
|
{applyResult.result.skippedFiles.length} skipped,{" "}
|
|
727
515
|
{applyResult.result.verifiedFiles.length} verified
|
|
@@ -729,44 +517,24 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
729
517
|
)}
|
|
730
518
|
</div>
|
|
731
519
|
|
|
732
|
-
{/* Warnings */}
|
|
733
520
|
{applyResult.result.warnings.length > 0 && (
|
|
734
|
-
<div
|
|
521
|
+
<div className="mb-4">
|
|
735
522
|
{applyResult.result.warnings.map((w, i) => (
|
|
736
|
-
<div
|
|
737
|
-
|
|
738
|
-
style={{
|
|
739
|
-
fontFamily: theme.mono,
|
|
740
|
-
fontSize: 11,
|
|
741
|
-
color: theme.yellow,
|
|
742
|
-
marginBottom: 4,
|
|
743
|
-
}}
|
|
744
|
-
>
|
|
745
|
-
{"\u26A0"} {w}
|
|
523
|
+
<div key={i} className="mb-1 font-mono text-[11px] text-warn-500">
|
|
524
|
+
⚠ {w}
|
|
746
525
|
</div>
|
|
747
526
|
))}
|
|
748
527
|
</div>
|
|
749
528
|
)}
|
|
750
529
|
|
|
751
530
|
{!applyResult.result.rolledBack && (
|
|
752
|
-
<div
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
border: `1px solid ${theme.border}`,
|
|
756
|
-
borderRadius: 6,
|
|
757
|
-
padding: "10px 14px",
|
|
758
|
-
marginBottom: 24,
|
|
759
|
-
fontFamily: theme.mono,
|
|
760
|
-
fontSize: 11,
|
|
761
|
-
color: theme.textMuted,
|
|
762
|
-
}}
|
|
763
|
-
>
|
|
764
|
-
git add clef.yaml .sops.yaml secrets/ && git commit -m "chore: migrate backend to{" "}
|
|
765
|
-
{targetBackend}"
|
|
531
|
+
<div className="mb-6 rounded-md border border-edge bg-ink-850 px-3.5 py-2.5 font-mono text-[11px] text-ash">
|
|
532
|
+
git add clef.yaml .sops.yaml secrets/ && git commit -m "chore: migrate
|
|
533
|
+
backend to {targetBackend}"
|
|
766
534
|
</div>
|
|
767
535
|
)}
|
|
768
536
|
|
|
769
|
-
<div
|
|
537
|
+
<div className="flex gap-2.5">
|
|
770
538
|
<Button variant="primary" onClick={() => setView("matrix")}>
|
|
771
539
|
View in Matrix
|
|
772
540
|
</Button>
|
|
@@ -784,54 +552,27 @@ export function BackendScreen({ manifest, setView, reloadManifest }: BackendScre
|
|
|
784
552
|
|
|
785
553
|
function Label({ children }: { children: React.ReactNode }) {
|
|
786
554
|
return (
|
|
787
|
-
<div
|
|
788
|
-
style={{
|
|
789
|
-
fontFamily: theme.sans,
|
|
790
|
-
fontSize: 12,
|
|
791
|
-
fontWeight: 600,
|
|
792
|
-
color: theme.textMuted,
|
|
793
|
-
marginBottom: 8,
|
|
794
|
-
letterSpacing: "0.05em",
|
|
795
|
-
textTransform: "uppercase",
|
|
796
|
-
}}
|
|
797
|
-
>
|
|
555
|
+
<div className="mb-2 font-sans text-[12px] font-semibold uppercase tracking-[0.05em] text-ash">
|
|
798
556
|
{children}
|
|
799
557
|
</div>
|
|
800
558
|
);
|
|
801
559
|
}
|
|
802
560
|
|
|
803
|
-
function SectionLabel({ children,
|
|
561
|
+
function SectionLabel({ children, toneClass }: { children: React.ReactNode; toneClass: string }) {
|
|
804
562
|
return (
|
|
805
563
|
<div
|
|
806
|
-
|
|
807
|
-
fontFamily: theme.sans,
|
|
808
|
-
fontSize: 11,
|
|
809
|
-
fontWeight: 600,
|
|
810
|
-
color,
|
|
811
|
-
letterSpacing: "0.06em",
|
|
812
|
-
textTransform: "uppercase",
|
|
813
|
-
marginBottom: 8,
|
|
814
|
-
}}
|
|
564
|
+
className={`mb-2 font-sans text-[11px] font-semibold uppercase tracking-[0.06em] ${toneClass}`}
|
|
815
565
|
>
|
|
816
566
|
{children}
|
|
817
567
|
</div>
|
|
818
568
|
);
|
|
819
569
|
}
|
|
820
570
|
|
|
821
|
-
function FileRow({ icon,
|
|
571
|
+
function FileRow({ icon, iconClass, label }: { icon: string; iconClass: string; label: string }) {
|
|
822
572
|
return (
|
|
823
|
-
<div
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
alignItems: "center",
|
|
827
|
-
gap: 10,
|
|
828
|
-
padding: "5px 10px",
|
|
829
|
-
borderRadius: 6,
|
|
830
|
-
marginBottom: 3,
|
|
831
|
-
}}
|
|
832
|
-
>
|
|
833
|
-
<span style={{ color: iconColor, fontFamily: theme.mono, fontSize: 13 }}>{icon}</span>
|
|
834
|
-
<span style={{ fontFamily: theme.mono, fontSize: 12, color: theme.text }}>{label}</span>
|
|
573
|
+
<div className="mb-px flex items-center gap-2.5 rounded-md px-2.5 py-1">
|
|
574
|
+
<span className={`font-mono text-[13px] ${iconClass}`}>{icon}</span>
|
|
575
|
+
<span className="font-mono text-[12px] text-bone">{label}</span>
|
|
835
576
|
</div>
|
|
836
577
|
);
|
|
837
578
|
}
|