@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,9 +1,8 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
-
import { theme, ENV_COLORS } from "../theme";
|
|
3
2
|
import { apiFetch } from "../api";
|
|
4
|
-
import { TopBar } from "../components/TopBar";
|
|
5
3
|
import { Button } from "../components/Button";
|
|
6
4
|
import { EnvBadge } from "../components/EnvBadge";
|
|
5
|
+
import { Toolbar } from "../primitives";
|
|
7
6
|
import type { ClefManifest, DecryptedFile, LintIssue } from "@clef-sh/core";
|
|
8
7
|
|
|
9
8
|
interface EditorRow {
|
|
@@ -23,6 +22,22 @@ interface NamespaceEditorProps {
|
|
|
23
22
|
manifest: ClefManifest | null;
|
|
24
23
|
}
|
|
25
24
|
|
|
25
|
+
// Per-env underline colors for the tab bar. Mirrors ENV_COLORS but as Tailwind classes.
|
|
26
|
+
const ENV_TAB_BORDER: Record<string, string> = {
|
|
27
|
+
dev: "border-go-500",
|
|
28
|
+
staging: "border-warn-500",
|
|
29
|
+
production: "border-stop-500",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const VALUE_INPUT =
|
|
33
|
+
"flex-1 rounded border border-edge-strong bg-ink-800 px-2.5 py-1 font-mono text-[12px] text-bone outline-none focus-visible:border-gold-500";
|
|
34
|
+
|
|
35
|
+
const NEW_KEY_INPUT =
|
|
36
|
+
"shrink-0 basis-[240px] rounded border border-gold-500/40 bg-ink-800 px-2.5 py-1.5 font-mono text-[12px] text-bone outline-none focus-visible:border-gold-500";
|
|
37
|
+
|
|
38
|
+
const NEW_VALUE_INPUT =
|
|
39
|
+
"flex-1 rounded border border-edge bg-ink-800 px-2.5 py-1.5 font-mono text-[12px] text-bone outline-none focus-visible:border-gold-500";
|
|
40
|
+
|
|
26
41
|
export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorProps) {
|
|
27
42
|
const [env, setEnv] = useState(initialEnv ?? "");
|
|
28
43
|
const [rows, setRows] = useState<EditorRow[]>([]);
|
|
@@ -44,7 +59,6 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
44
59
|
|
|
45
60
|
const REVEAL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
46
61
|
|
|
47
|
-
// Clear timeout on unmount
|
|
48
62
|
useEffect(() => () => clearTimeout(revealTimeoutRef.current), []);
|
|
49
63
|
|
|
50
64
|
const environments = manifest?.environments ?? [];
|
|
@@ -84,7 +98,7 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
84
98
|
const backend = data.metadata?.backend ?? "age";
|
|
85
99
|
const recipientCount = data.metadata?.recipients?.length ?? 0;
|
|
86
100
|
setSopsInfo(
|
|
87
|
-
`encrypted with ${backend}
|
|
101
|
+
`encrypted with ${backend} ยท ${recipientCount} recipient${recipientCount !== 1 ? "s" : ""}`,
|
|
88
102
|
);
|
|
89
103
|
} catch {
|
|
90
104
|
setError("Failed to load namespace data");
|
|
@@ -109,7 +123,6 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
109
123
|
const toggleVisible = (key: string) => {
|
|
110
124
|
setRows((r) => r.map((row) => (row.key === key ? { ...row, visible: !row.visible } : row)));
|
|
111
125
|
|
|
112
|
-
// Reset idle timeout on any reveal action
|
|
113
126
|
clearTimeout(revealTimeoutRef.current);
|
|
114
127
|
revealTimeoutRef.current = setTimeout(() => {
|
|
115
128
|
setRows((r) =>
|
|
@@ -132,9 +145,6 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
132
145
|
setSaving(true);
|
|
133
146
|
setError(null);
|
|
134
147
|
try {
|
|
135
|
-
// Each PUT auto-commits via the transaction manager, matching CLI
|
|
136
|
-
// behavior where each `clef set` is its own commit. Serialize so
|
|
137
|
-
// each transaction completes before the next starts.
|
|
138
148
|
let failure: string | null = null;
|
|
139
149
|
for (const row of dirtyRows) {
|
|
140
150
|
const payload: Record<string, unknown> = { value: row.value };
|
|
@@ -145,18 +155,11 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
145
155
|
body: JSON.stringify(payload),
|
|
146
156
|
});
|
|
147
157
|
if (!res.ok) {
|
|
148
|
-
// Bail on first failure โ for dirty-tree / recipient errors the
|
|
149
|
-
// remaining PUTs will fail the same way, and piling errors on top
|
|
150
|
-
// of each other just obscures the root cause.
|
|
151
158
|
const data = await res.json().catch(() => ({}));
|
|
152
159
|
failure = data.error || `Failed to save ${row.key}`;
|
|
153
160
|
break;
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
|
-
// Always reload from disk โ on success to pick up server-side state
|
|
157
|
-
// (lastModified, metadata), on failure to snap the UI back to the
|
|
158
|
-
// current on-disk values and re-mask the inputs rather than leaving
|
|
159
|
-
// unsaved plaintext edits visible in an illusory "pending" state.
|
|
160
163
|
await loadData();
|
|
161
164
|
if (failure) setError(failure);
|
|
162
165
|
} catch {
|
|
@@ -185,10 +188,7 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
185
188
|
return;
|
|
186
189
|
}
|
|
187
190
|
const data = await res.json();
|
|
188
|
-
if (data.warning)
|
|
189
|
-
setError(data.warning);
|
|
190
|
-
}
|
|
191
|
-
// Update local state directly โ avoids an extra decrypt round-trip
|
|
191
|
+
if (data.warning) setError(data.warning);
|
|
192
192
|
setRows((prev) => [
|
|
193
193
|
...prev,
|
|
194
194
|
{
|
|
@@ -224,7 +224,6 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
224
224
|
setError(data.error || "Failed to reset key");
|
|
225
225
|
return;
|
|
226
226
|
}
|
|
227
|
-
// Update local state directly โ avoids an extra decrypt round-trip
|
|
228
227
|
setRows((prev) =>
|
|
229
228
|
prev.map((row) =>
|
|
230
229
|
row.key === key ? { ...row, pending: true, value: "", edited: false } : row,
|
|
@@ -282,47 +281,37 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
282
281
|
const hasChanges = rows.some((r) => r.edited);
|
|
283
282
|
|
|
284
283
|
return (
|
|
285
|
-
<div
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
>
|
|
303
|
-
{saving ? "Saving..." : "Save"}
|
|
304
|
-
</Button>
|
|
305
|
-
)}
|
|
306
|
-
<Button variant="primary" data-testid="add-key-btn" onClick={() => setAdding(true)}>
|
|
307
|
-
+ Add key
|
|
284
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
285
|
+
<Toolbar>
|
|
286
|
+
<div>
|
|
287
|
+
<Toolbar.Title>{`/${ns}`}</Toolbar.Title>
|
|
288
|
+
<Toolbar.Subtitle>Namespace ยท {rows.length} keys</Toolbar.Subtitle>
|
|
289
|
+
</div>
|
|
290
|
+
<Toolbar.Actions>
|
|
291
|
+
{hasChanges && (
|
|
292
|
+
<Button
|
|
293
|
+
variant="primary"
|
|
294
|
+
disabled={saving}
|
|
295
|
+
onClick={() => {
|
|
296
|
+
if (isProduction) setProtectedConfirm("save");
|
|
297
|
+
else handleSave();
|
|
298
|
+
}}
|
|
299
|
+
>
|
|
300
|
+
{saving ? "Saving..." : "Save"}
|
|
308
301
|
</Button>
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
302
|
+
)}
|
|
303
|
+
<Button variant="primary" data-testid="add-key-btn" onClick={() => setAdding(true)}>
|
|
304
|
+
+ Add key
|
|
305
|
+
</Button>
|
|
306
|
+
</Toolbar.Actions>
|
|
307
|
+
</Toolbar>
|
|
312
308
|
|
|
313
309
|
{/* Env tabs */}
|
|
314
|
-
<div
|
|
315
|
-
style={{
|
|
316
|
-
display: "flex",
|
|
317
|
-
gap: 0,
|
|
318
|
-
borderBottom: `1px solid ${theme.border}`,
|
|
319
|
-
padding: "0 24px",
|
|
320
|
-
background: "#0D0F14",
|
|
321
|
-
}}
|
|
322
|
-
>
|
|
310
|
+
<div className="flex border-b border-edge bg-ink-800 px-6">
|
|
323
311
|
{environments.map((e) => {
|
|
324
312
|
const isActive = env === e.name;
|
|
325
|
-
const
|
|
313
|
+
const activeBorder = ENV_TAB_BORDER[e.name] ?? "border-ash";
|
|
314
|
+
const borderClass = isActive ? activeBorder : "border-transparent";
|
|
326
315
|
return (
|
|
327
316
|
<div
|
|
328
317
|
key={e.name}
|
|
@@ -332,480 +321,214 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
332
321
|
onKeyDown={(ev) => {
|
|
333
322
|
if (ev.key === "Enter") setEnv(e.name);
|
|
334
323
|
}}
|
|
335
|
-
|
|
336
|
-
padding: "10px 18px",
|
|
337
|
-
cursor: "pointer",
|
|
338
|
-
borderBottom: isActive ? `2px solid ${c.color}` : "2px solid transparent",
|
|
339
|
-
display: "flex",
|
|
340
|
-
alignItems: "center",
|
|
341
|
-
gap: 7,
|
|
342
|
-
marginBottom: -1,
|
|
343
|
-
}}
|
|
324
|
+
className={`-mb-px flex cursor-pointer items-center gap-1.5 border-b-2 px-4 py-2.5 ${borderClass}`}
|
|
344
325
|
>
|
|
345
326
|
<EnvBadge env={e.name} small />
|
|
346
327
|
<span
|
|
347
|
-
|
|
348
|
-
fontFamily: theme.sans,
|
|
349
|
-
fontSize: 13,
|
|
350
|
-
fontWeight: isActive ? 600 : 400,
|
|
351
|
-
color: isActive ? theme.text : theme.textMuted,
|
|
352
|
-
}}
|
|
328
|
+
className={`font-sans text-[13px] ${isActive ? "font-semibold text-bone" : "font-normal text-ash"}`}
|
|
353
329
|
>
|
|
354
330
|
{e.name}
|
|
355
331
|
</span>
|
|
356
332
|
</div>
|
|
357
333
|
);
|
|
358
334
|
})}
|
|
359
|
-
<div
|
|
360
|
-
<div
|
|
361
|
-
|
|
362
|
-
padding: "10px 0",
|
|
363
|
-
display: "flex",
|
|
364
|
-
alignItems: "center",
|
|
365
|
-
}}
|
|
366
|
-
>
|
|
367
|
-
<span
|
|
368
|
-
style={{
|
|
369
|
-
fontFamily: theme.mono,
|
|
370
|
-
fontSize: 10,
|
|
371
|
-
color: theme.textMuted,
|
|
372
|
-
}}
|
|
373
|
-
>
|
|
374
|
-
{sopsInfo}
|
|
375
|
-
</span>
|
|
335
|
+
<div className="flex-1" />
|
|
336
|
+
<div className="flex items-center py-2.5">
|
|
337
|
+
<span className="font-mono text-[10px] text-ash">{sopsInfo}</span>
|
|
376
338
|
</div>
|
|
377
339
|
</div>
|
|
378
340
|
|
|
379
|
-
<div
|
|
341
|
+
<div className="flex-1 overflow-auto p-6">
|
|
380
342
|
{/* Production warning */}
|
|
381
343
|
{isProduction && (
|
|
382
344
|
<div
|
|
383
345
|
data-testid="production-warning"
|
|
384
|
-
|
|
385
|
-
marginBottom: 20,
|
|
386
|
-
padding: "10px 16px",
|
|
387
|
-
background: theme.redDim,
|
|
388
|
-
border: `1px solid ${theme.red}44`,
|
|
389
|
-
borderRadius: 8,
|
|
390
|
-
display: "flex",
|
|
391
|
-
alignItems: "center",
|
|
392
|
-
gap: 10,
|
|
393
|
-
}}
|
|
346
|
+
className="mb-5 flex items-center gap-2.5 rounded-lg border border-stop-500/30 bg-stop-500/10 px-4 py-2.5"
|
|
394
347
|
>
|
|
395
|
-
<span
|
|
396
|
-
<span
|
|
348
|
+
<span className="text-[14px]">{"๐"}</span>
|
|
349
|
+
<span className="font-sans text-[12px] text-stop-500">
|
|
397
350
|
<strong>Production environment.</strong> Changes will require confirmation before
|
|
398
351
|
committing.
|
|
399
352
|
</span>
|
|
400
353
|
</div>
|
|
401
354
|
)}
|
|
402
355
|
|
|
403
|
-
{loading && <p
|
|
356
|
+
{loading && <p className="font-sans text-ash">Loading...</p>}
|
|
404
357
|
|
|
405
358
|
{error && (
|
|
406
|
-
<div
|
|
407
|
-
style={{
|
|
408
|
-
padding: "12px 16px",
|
|
409
|
-
background: theme.redDim,
|
|
410
|
-
border: `1px solid ${theme.red}44`,
|
|
411
|
-
borderRadius: 8,
|
|
412
|
-
fontFamily: theme.sans,
|
|
413
|
-
fontSize: 12,
|
|
414
|
-
color: theme.red,
|
|
415
|
-
marginBottom: 20,
|
|
416
|
-
}}
|
|
417
|
-
>
|
|
359
|
+
<div className="mb-5 rounded-lg border border-stop-500/30 bg-stop-500/10 px-4 py-3 font-sans text-[12px] text-stop-500">
|
|
418
360
|
{error}
|
|
419
361
|
</div>
|
|
420
362
|
)}
|
|
421
363
|
|
|
422
364
|
{!loading && (
|
|
423
365
|
<>
|
|
424
|
-
{/* Keys table
|
|
425
|
-
|
|
426
|
-
On load failure `rows` is empty, so the table renders
|
|
427
|
-
gracefully with no row body. */}
|
|
428
|
-
<div
|
|
429
|
-
style={{
|
|
430
|
-
background: theme.surface,
|
|
431
|
-
border: `1px solid ${theme.border}`,
|
|
432
|
-
borderRadius: 10,
|
|
433
|
-
}}
|
|
434
|
-
>
|
|
366
|
+
{/* Keys table */}
|
|
367
|
+
<div className="rounded-card border border-edge bg-ink-850">
|
|
435
368
|
{/* Header */}
|
|
436
|
-
<div
|
|
437
|
-
style={{
|
|
438
|
-
display: "grid",
|
|
439
|
-
gridTemplateColumns: "260px 1fr 90px 36px",
|
|
440
|
-
background: "#0D0F14",
|
|
441
|
-
padding: "10px 20px",
|
|
442
|
-
borderBottom: `1px solid ${theme.border}`,
|
|
443
|
-
borderRadius: "10px 10px 0 0",
|
|
444
|
-
}}
|
|
445
|
-
>
|
|
369
|
+
<div className="grid grid-cols-[260px_1fr_90px_36px] items-center rounded-t-card border-b border-edge bg-ink-800 px-5 py-2.5">
|
|
446
370
|
{["Key", "Value", "Type", ""].map((h) => (
|
|
447
371
|
<span
|
|
448
372
|
key={h || "actions"}
|
|
449
|
-
|
|
450
|
-
fontFamily: theme.sans,
|
|
451
|
-
fontSize: 11,
|
|
452
|
-
fontWeight: 600,
|
|
453
|
-
color: theme.textMuted,
|
|
454
|
-
textTransform: "uppercase",
|
|
455
|
-
letterSpacing: "0.07em",
|
|
456
|
-
}}
|
|
373
|
+
className="font-sans text-[11px] font-semibold uppercase tracking-[0.07em] text-ash"
|
|
457
374
|
>
|
|
458
375
|
{h}
|
|
459
376
|
</span>
|
|
460
377
|
))}
|
|
461
378
|
</div>
|
|
462
379
|
|
|
463
|
-
{rows.map((row, i) =>
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
borderLeft: row.pending
|
|
477
|
-
? "3px solid #F0A50088"
|
|
478
|
-
: row.edited
|
|
479
|
-
? `2px solid ${theme.accent}`
|
|
480
|
-
: "2px solid transparent",
|
|
481
|
-
alignItems: "center",
|
|
482
|
-
minHeight: 48,
|
|
483
|
-
}}
|
|
484
|
-
>
|
|
485
|
-
{/* Key */}
|
|
380
|
+
{rows.map((row, i) => {
|
|
381
|
+
const rowBg = row.pending
|
|
382
|
+
? "bg-gold-500/[0.07]"
|
|
383
|
+
: row.edited
|
|
384
|
+
? "bg-gold-500/[0.03]"
|
|
385
|
+
: "bg-transparent";
|
|
386
|
+
const rowBorderLeft = row.pending
|
|
387
|
+
? "border-l-[3px] border-gold-500/55"
|
|
388
|
+
: row.edited
|
|
389
|
+
? "border-l-2 border-gold-500"
|
|
390
|
+
: "border-l-2 border-transparent";
|
|
391
|
+
const rowBorderBottom = i < rows.length - 1 ? "border-b border-edge" : "border-b-0";
|
|
392
|
+
return (
|
|
486
393
|
<div
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
alignItems: "center",
|
|
490
|
-
gap: 8,
|
|
491
|
-
paddingRight: 16,
|
|
492
|
-
}}
|
|
394
|
+
key={row.key}
|
|
395
|
+
className={`grid min-h-[48px] grid-cols-[260px_1fr_90px_36px] items-center px-5 ${rowBg} ${rowBorderLeft} ${rowBorderBottom}`}
|
|
493
396
|
>
|
|
494
|
-
{
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
fontFamily: theme.mono,
|
|
508
|
-
fontSize: 12,
|
|
509
|
-
color: theme.text,
|
|
510
|
-
}}
|
|
511
|
-
>
|
|
512
|
-
{row.key}
|
|
513
|
-
</span>
|
|
514
|
-
{row.edited && (
|
|
515
|
-
<span
|
|
516
|
-
data-testid="dirty-dot"
|
|
517
|
-
style={{
|
|
518
|
-
width: 6,
|
|
519
|
-
height: 6,
|
|
520
|
-
borderRadius: "50%",
|
|
521
|
-
background: theme.accent,
|
|
522
|
-
flexShrink: 0,
|
|
523
|
-
display: "inline-block",
|
|
524
|
-
}}
|
|
525
|
-
/>
|
|
526
|
-
)}
|
|
527
|
-
</div>
|
|
397
|
+
{/* Key */}
|
|
398
|
+
<div className="flex items-center gap-2 pr-4">
|
|
399
|
+
{row.required && (
|
|
400
|
+
<span className="text-[14px] leading-none text-gold-500">*</span>
|
|
401
|
+
)}
|
|
402
|
+
<span className="font-mono text-[12px] text-bone">{row.key}</span>
|
|
403
|
+
{row.edited && (
|
|
404
|
+
<span
|
|
405
|
+
data-testid="dirty-dot"
|
|
406
|
+
className="inline-block h-1.5 w-1.5 shrink-0 rounded-full bg-gold-500"
|
|
407
|
+
/>
|
|
408
|
+
)}
|
|
409
|
+
</div>
|
|
528
410
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
) : (
|
|
570
|
-
<span
|
|
571
|
-
style={{
|
|
572
|
-
fontFamily: theme.mono,
|
|
573
|
-
fontSize: 13,
|
|
574
|
-
color: theme.textMuted,
|
|
575
|
-
letterSpacing: "0.15em",
|
|
576
|
-
}}
|
|
577
|
-
>
|
|
578
|
-
{
|
|
579
|
-
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"
|
|
580
|
-
}
|
|
581
|
-
</span>
|
|
582
|
-
)}
|
|
583
|
-
{row.pending && !row.visible ? (
|
|
584
|
-
<div style={{ display: "flex", gap: 6 }}>
|
|
411
|
+
{/* Value */}
|
|
412
|
+
<div className="flex items-center gap-2 pr-4">
|
|
413
|
+
{row.pending && !row.visible ? (
|
|
414
|
+
<span className="font-mono text-[11px] italic text-gold-500">
|
|
415
|
+
PENDING {"โ"} not yet set
|
|
416
|
+
</span>
|
|
417
|
+
) : row.visible ? (
|
|
418
|
+
<input
|
|
419
|
+
type="text"
|
|
420
|
+
data-testid={`value-input-${row.key}`}
|
|
421
|
+
value={row.value}
|
|
422
|
+
onChange={(e) => handleEdit(row.key, e.target.value)}
|
|
423
|
+
autoComplete="off"
|
|
424
|
+
placeholder={row.pending ? "Enter real value..." : undefined}
|
|
425
|
+
className={VALUE_INPUT}
|
|
426
|
+
/>
|
|
427
|
+
) : (
|
|
428
|
+
<span className="font-mono text-[13px] tracking-[0.15em] text-ash">
|
|
429
|
+
{"โขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโขโข"}
|
|
430
|
+
</span>
|
|
431
|
+
)}
|
|
432
|
+
{row.pending && !row.visible ? (
|
|
433
|
+
<div className="flex gap-1.5">
|
|
434
|
+
<button
|
|
435
|
+
data-testid={`set-value-${row.key}`}
|
|
436
|
+
onClick={() => toggleVisible(row.key)}
|
|
437
|
+
className="cursor-pointer rounded border border-gold-500/40 bg-gold-500/10 px-2.5 py-0.5 font-sans text-[11px] font-semibold text-gold-500 hover:bg-gold-500/20"
|
|
438
|
+
>
|
|
439
|
+
Set value
|
|
440
|
+
</button>
|
|
441
|
+
<button
|
|
442
|
+
data-testid={`accept-value-${row.key}`}
|
|
443
|
+
onClick={() => handleAccept(row.key)}
|
|
444
|
+
title="Accept the random value as the final secret"
|
|
445
|
+
className="cursor-pointer rounded border border-go-500/40 bg-go-500/10 px-2.5 py-0.5 font-sans text-[11px] font-semibold text-go-500 hover:bg-go-500/20"
|
|
446
|
+
>
|
|
447
|
+
Accept random
|
|
448
|
+
</button>
|
|
449
|
+
</div>
|
|
450
|
+
) : (
|
|
585
451
|
<button
|
|
586
|
-
data-testid={`
|
|
452
|
+
data-testid={`eye-${row.key}`}
|
|
587
453
|
onClick={() => toggleVisible(row.key)}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
border: `1px solid ${theme.accent}55`,
|
|
591
|
-
borderRadius: 5,
|
|
592
|
-
cursor: "pointer",
|
|
593
|
-
color: theme.accent,
|
|
594
|
-
padding: "3px 10px",
|
|
595
|
-
fontFamily: theme.sans,
|
|
596
|
-
fontSize: 11,
|
|
597
|
-
fontWeight: 600,
|
|
598
|
-
}}
|
|
454
|
+
aria-label={row.visible ? "Hide value" : "Reveal value"}
|
|
455
|
+
className={`flex cursor-pointer items-center bg-transparent p-1 text-[13px] ${row.visible ? "text-gold-500" : "text-ash-dim"}`}
|
|
599
456
|
>
|
|
600
|
-
|
|
457
|
+
{"๐"}
|
|
601
458
|
</button>
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
onClick={() => handleAccept(row.key)}
|
|
605
|
-
title="Accept the random value as the final secret"
|
|
606
|
-
style={{
|
|
607
|
-
background: `${theme.green}18`,
|
|
608
|
-
border: `1px solid ${theme.green}55`,
|
|
609
|
-
borderRadius: 5,
|
|
610
|
-
cursor: "pointer",
|
|
611
|
-
color: theme.green,
|
|
612
|
-
padding: "3px 10px",
|
|
613
|
-
fontFamily: theme.sans,
|
|
614
|
-
fontSize: 11,
|
|
615
|
-
fontWeight: 600,
|
|
616
|
-
}}
|
|
617
|
-
>
|
|
618
|
-
Accept random
|
|
619
|
-
</button>
|
|
620
|
-
</div>
|
|
621
|
-
) : (
|
|
622
|
-
<button
|
|
623
|
-
data-testid={`eye-${row.key}`}
|
|
624
|
-
onClick={() => toggleVisible(row.key)}
|
|
625
|
-
aria-label={row.visible ? "Hide value" : "Reveal value"}
|
|
626
|
-
style={{
|
|
627
|
-
background: "none",
|
|
628
|
-
border: "none",
|
|
629
|
-
cursor: "pointer",
|
|
630
|
-
color: row.visible ? theme.accent : theme.textDim,
|
|
631
|
-
padding: 4,
|
|
632
|
-
display: "flex",
|
|
633
|
-
alignItems: "center",
|
|
634
|
-
fontSize: 13,
|
|
635
|
-
}}
|
|
636
|
-
>
|
|
637
|
-
{"\uD83D\uDC41"}
|
|
638
|
-
</button>
|
|
639
|
-
)}
|
|
640
|
-
</div>
|
|
459
|
+
)}
|
|
460
|
+
</div>
|
|
641
461
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
padding: "2px 7px",
|
|
655
|
-
}}
|
|
656
|
-
>
|
|
657
|
-
PENDING
|
|
658
|
-
</span>
|
|
659
|
-
) : (
|
|
660
|
-
<span
|
|
661
|
-
style={{
|
|
662
|
-
fontFamily: theme.mono,
|
|
663
|
-
fontSize: 10,
|
|
664
|
-
color: theme.blue,
|
|
665
|
-
background: theme.blueDim,
|
|
666
|
-
border: `1px solid ${theme.blue}33`,
|
|
667
|
-
borderRadius: 3,
|
|
668
|
-
padding: "2px 7px",
|
|
669
|
-
}}
|
|
670
|
-
>
|
|
671
|
-
{row.type}
|
|
672
|
-
</span>
|
|
673
|
-
)}
|
|
674
|
-
</div>
|
|
462
|
+
{/* Type */}
|
|
463
|
+
<div>
|
|
464
|
+
{row.pending ? (
|
|
465
|
+
<span className="rounded-sm border border-gold-500/20 bg-gold-500/10 px-1.5 py-0.5 font-mono text-[10px] font-bold text-gold-500">
|
|
466
|
+
PENDING
|
|
467
|
+
</span>
|
|
468
|
+
) : (
|
|
469
|
+
<span className="rounded-sm border border-blue-400/20 bg-blue-400/10 px-1.5 py-0.5 font-mono text-[10px] text-blue-400">
|
|
470
|
+
{row.type}
|
|
471
|
+
</span>
|
|
472
|
+
)}
|
|
473
|
+
</div>
|
|
675
474
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
background: "none",
|
|
683
|
-
border: "none",
|
|
684
|
-
cursor: "pointer",
|
|
685
|
-
color: theme.textDim,
|
|
686
|
-
fontSize: 16,
|
|
687
|
-
padding: 4,
|
|
688
|
-
}}
|
|
689
|
-
>
|
|
690
|
-
{"\u22EF"}
|
|
691
|
-
</button>
|
|
692
|
-
{overflowKey === row.key && (
|
|
693
|
-
<div
|
|
694
|
-
data-testid={`overflow-menu-${row.key}`}
|
|
695
|
-
style={{
|
|
696
|
-
position: "absolute",
|
|
697
|
-
top: "100%",
|
|
698
|
-
right: 0,
|
|
699
|
-
zIndex: 10,
|
|
700
|
-
background: theme.surface,
|
|
701
|
-
border: `1px solid ${theme.border}`,
|
|
702
|
-
borderRadius: 6,
|
|
703
|
-
padding: 4,
|
|
704
|
-
minWidth: 200,
|
|
705
|
-
boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
|
|
706
|
-
}}
|
|
475
|
+
{/* Actions */}
|
|
476
|
+
<div className="relative flex justify-center">
|
|
477
|
+
<button
|
|
478
|
+
data-testid={`overflow-${row.key}`}
|
|
479
|
+
onClick={() => setOverflowKey(overflowKey === row.key ? null : row.key)}
|
|
480
|
+
className="cursor-pointer bg-transparent p-1 text-[16px] text-ash-dim"
|
|
707
481
|
>
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
title="Use this to immediately invalidate a compromised secret while you arrange a replacement."
|
|
715
|
-
style={{
|
|
716
|
-
display: "block",
|
|
717
|
-
width: "100%",
|
|
718
|
-
textAlign: "left",
|
|
719
|
-
background: "none",
|
|
720
|
-
border: "none",
|
|
721
|
-
cursor: "pointer",
|
|
722
|
-
fontFamily: theme.sans,
|
|
723
|
-
fontSize: 12,
|
|
724
|
-
color: theme.accent,
|
|
725
|
-
padding: "6px 10px",
|
|
726
|
-
borderRadius: 4,
|
|
727
|
-
}}
|
|
728
|
-
onMouseEnter={(e) => {
|
|
729
|
-
(e.currentTarget as HTMLElement).style.background = theme.surfaceHover;
|
|
730
|
-
}}
|
|
731
|
-
onMouseLeave={(e) => {
|
|
732
|
-
(e.currentTarget as HTMLElement).style.background = "none";
|
|
733
|
-
}}
|
|
734
|
-
>
|
|
735
|
-
Reset to random (pending)
|
|
736
|
-
</button>
|
|
737
|
-
<button
|
|
738
|
-
data-testid={`delete-key-${row.key}`}
|
|
739
|
-
onClick={() => {
|
|
740
|
-
setOverflowKey(null);
|
|
741
|
-
setConfirmDelete(row.key);
|
|
742
|
-
}}
|
|
743
|
-
style={{
|
|
744
|
-
display: "block",
|
|
745
|
-
width: "100%",
|
|
746
|
-
textAlign: "left",
|
|
747
|
-
background: "none",
|
|
748
|
-
border: "none",
|
|
749
|
-
cursor: "pointer",
|
|
750
|
-
fontFamily: theme.sans,
|
|
751
|
-
fontSize: 12,
|
|
752
|
-
color: theme.red,
|
|
753
|
-
padding: "6px 10px",
|
|
754
|
-
borderRadius: 4,
|
|
755
|
-
}}
|
|
756
|
-
onMouseEnter={(e) => {
|
|
757
|
-
(e.currentTarget as HTMLElement).style.background = theme.surfaceHover;
|
|
758
|
-
}}
|
|
759
|
-
onMouseLeave={(e) => {
|
|
760
|
-
(e.currentTarget as HTMLElement).style.background = "none";
|
|
761
|
-
}}
|
|
482
|
+
{"โฏ"}
|
|
483
|
+
</button>
|
|
484
|
+
{overflowKey === row.key && (
|
|
485
|
+
<div
|
|
486
|
+
data-testid={`overflow-menu-${row.key}`}
|
|
487
|
+
className="absolute right-0 top-full z-10 min-w-[200px] rounded-md border border-edge bg-ink-850 p-1 shadow-soft-drop"
|
|
762
488
|
>
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
489
|
+
<button
|
|
490
|
+
data-testid={`reset-random-${row.key}`}
|
|
491
|
+
onClick={() => {
|
|
492
|
+
setOverflowKey(null);
|
|
493
|
+
setConfirmReset(row.key);
|
|
494
|
+
}}
|
|
495
|
+
title="Use this to immediately invalidate a compromised secret while you arrange a replacement."
|
|
496
|
+
className="block w-full cursor-pointer rounded bg-transparent px-2.5 py-1.5 text-left font-sans text-[12px] text-gold-500 hover:bg-ink-800"
|
|
497
|
+
>
|
|
498
|
+
Reset to random (pending)
|
|
499
|
+
</button>
|
|
500
|
+
<button
|
|
501
|
+
data-testid={`delete-key-${row.key}`}
|
|
502
|
+
onClick={() => {
|
|
503
|
+
setOverflowKey(null);
|
|
504
|
+
setConfirmDelete(row.key);
|
|
505
|
+
}}
|
|
506
|
+
className="block w-full cursor-pointer rounded bg-transparent px-2.5 py-1.5 text-left font-sans text-[12px] text-stop-500 hover:bg-ink-800"
|
|
507
|
+
>
|
|
508
|
+
Delete key
|
|
509
|
+
</button>
|
|
510
|
+
</div>
|
|
511
|
+
)}
|
|
512
|
+
</div>
|
|
767
513
|
</div>
|
|
768
|
-
|
|
769
|
-
)
|
|
514
|
+
);
|
|
515
|
+
})}
|
|
770
516
|
|
|
771
517
|
{/* Add key row */}
|
|
772
518
|
{adding && (
|
|
773
|
-
<div
|
|
774
|
-
|
|
775
|
-
padding: "12px 20px",
|
|
776
|
-
borderTop: `1px solid ${theme.border}`,
|
|
777
|
-
display: "flex",
|
|
778
|
-
flexDirection: "column",
|
|
779
|
-
gap: 10,
|
|
780
|
-
}}
|
|
781
|
-
>
|
|
782
|
-
<div style={{ display: "flex", gap: 10, alignItems: "center" }}>
|
|
519
|
+
<div className="flex flex-col gap-2.5 border-t border-edge px-5 py-3">
|
|
520
|
+
<div className="flex items-center gap-2.5">
|
|
783
521
|
<input
|
|
784
522
|
data-testid="new-key-input"
|
|
785
523
|
placeholder="KEY_NAME"
|
|
786
524
|
value={newKey}
|
|
787
525
|
onChange={(e) => setNewKey(e.target.value)}
|
|
788
|
-
|
|
789
|
-
flex: "0 0 240px",
|
|
790
|
-
background: "#0D0F14",
|
|
791
|
-
border: `1px solid ${theme.accent}66`,
|
|
792
|
-
borderRadius: 5,
|
|
793
|
-
padding: "6px 10px",
|
|
794
|
-
fontFamily: theme.mono,
|
|
795
|
-
fontSize: 12,
|
|
796
|
-
color: theme.text,
|
|
797
|
-
outline: "none",
|
|
798
|
-
}}
|
|
526
|
+
className={NEW_KEY_INPUT}
|
|
799
527
|
/>
|
|
800
528
|
{/* Mode toggle */}
|
|
801
529
|
<div
|
|
802
530
|
role="radiogroup"
|
|
803
|
-
|
|
804
|
-
display: "flex",
|
|
805
|
-
border: `1px solid ${theme.border}`,
|
|
806
|
-
borderRadius: 6,
|
|
807
|
-
overflow: "hidden",
|
|
808
|
-
}}
|
|
531
|
+
className="flex overflow-hidden rounded-md border border-edge"
|
|
809
532
|
>
|
|
810
533
|
<button
|
|
811
534
|
data-testid="mode-set-value"
|
|
@@ -815,17 +538,11 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
815
538
|
setAddMode("value");
|
|
816
539
|
setNewValue("");
|
|
817
540
|
}}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
border: "none",
|
|
824
|
-
borderRight: `1px solid ${theme.border}`,
|
|
825
|
-
background: addMode === "value" ? `${theme.accent}22` : "transparent",
|
|
826
|
-
color: addMode === "value" ? theme.accent : theme.textMuted,
|
|
827
|
-
cursor: "pointer",
|
|
828
|
-
}}
|
|
541
|
+
className={`cursor-pointer border-r border-edge px-3.5 py-1 font-sans text-[11px] font-semibold ${
|
|
542
|
+
addMode === "value"
|
|
543
|
+
? "bg-gold-500/15 text-gold-500"
|
|
544
|
+
: "bg-transparent text-ash"
|
|
545
|
+
}`}
|
|
829
546
|
>
|
|
830
547
|
Set value
|
|
831
548
|
</button>
|
|
@@ -837,22 +554,17 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
837
554
|
setAddMode("random");
|
|
838
555
|
setNewValue("");
|
|
839
556
|
}}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
border: "none",
|
|
846
|
-
background: addMode === "random" ? `${theme.accent}22` : "transparent",
|
|
847
|
-
color: addMode === "random" ? theme.accent : theme.textMuted,
|
|
848
|
-
cursor: "pointer",
|
|
849
|
-
}}
|
|
557
|
+
className={`cursor-pointer px-3.5 py-1 font-sans text-[11px] font-semibold ${
|
|
558
|
+
addMode === "random"
|
|
559
|
+
? "bg-gold-500/15 text-gold-500"
|
|
560
|
+
: "bg-transparent text-ash"
|
|
561
|
+
}`}
|
|
850
562
|
>
|
|
851
563
|
Random (pending)
|
|
852
564
|
</button>
|
|
853
565
|
</div>
|
|
854
566
|
</div>
|
|
855
|
-
<div
|
|
567
|
+
<div className="flex items-center gap-2.5">
|
|
856
568
|
{addMode === "value" ? (
|
|
857
569
|
<input
|
|
858
570
|
data-testid="new-value-input"
|
|
@@ -861,29 +573,10 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
861
573
|
value={newValue}
|
|
862
574
|
onChange={(e) => setNewValue(e.target.value)}
|
|
863
575
|
autoComplete="off"
|
|
864
|
-
|
|
865
|
-
flex: 1,
|
|
866
|
-
background: "#0D0F14",
|
|
867
|
-
border: `1px solid ${theme.border}`,
|
|
868
|
-
borderRadius: 5,
|
|
869
|
-
padding: "6px 10px",
|
|
870
|
-
fontFamily: theme.mono,
|
|
871
|
-
fontSize: 12,
|
|
872
|
-
color: theme.text,
|
|
873
|
-
outline: "none",
|
|
874
|
-
}}
|
|
576
|
+
className={NEW_VALUE_INPUT}
|
|
875
577
|
/>
|
|
876
578
|
) : (
|
|
877
|
-
<div
|
|
878
|
-
style={{
|
|
879
|
-
flex: 1,
|
|
880
|
-
fontFamily: theme.mono,
|
|
881
|
-
fontSize: 11,
|
|
882
|
-
fontStyle: "italic",
|
|
883
|
-
color: theme.accent,
|
|
884
|
-
padding: "6px 10px",
|
|
885
|
-
}}
|
|
886
|
-
>
|
|
579
|
+
<div className="flex-1 px-2.5 py-1.5 font-mono text-[11px] italic text-gold-500">
|
|
887
580
|
A cryptographically random placeholder will be generated server-side.
|
|
888
581
|
</div>
|
|
889
582
|
)}
|
|
@@ -891,11 +584,8 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
891
584
|
variant="primary"
|
|
892
585
|
data-testid="add-key-submit"
|
|
893
586
|
onClick={() => {
|
|
894
|
-
if (isProduction)
|
|
895
|
-
|
|
896
|
-
} else {
|
|
897
|
-
handleAdd();
|
|
898
|
-
}
|
|
587
|
+
if (isProduction) setProtectedConfirm("add");
|
|
588
|
+
else handleAdd();
|
|
899
589
|
}}
|
|
900
590
|
>
|
|
901
591
|
{addMode === "random" ? "Generate random value" : "Add"}
|
|
@@ -919,33 +609,18 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
919
609
|
{confirmReset && (
|
|
920
610
|
<div
|
|
921
611
|
data-testid="confirm-reset-dialog"
|
|
922
|
-
|
|
923
|
-
marginTop: 16,
|
|
924
|
-
padding: "14px 18px",
|
|
925
|
-
background: `${theme.accent}0A`,
|
|
926
|
-
border: `1px solid ${theme.accent}33`,
|
|
927
|
-
borderRadius: 8,
|
|
928
|
-
fontFamily: theme.sans,
|
|
929
|
-
fontSize: 12,
|
|
930
|
-
}}
|
|
612
|
+
className="mt-4 rounded-lg border border-gold-500/30 bg-gold-500/[0.04] px-4 py-3.5 font-sans text-[12px]"
|
|
931
613
|
>
|
|
932
|
-
<p
|
|
933
|
-
Reset <strong
|
|
934
|
-
|
|
614
|
+
<p className="m-0 mb-2.5 text-bone">
|
|
615
|
+
Reset <strong className="font-mono">{confirmReset}</strong> to a random
|
|
616
|
+
placeholder? The current value will be overwritten.
|
|
935
617
|
</p>
|
|
936
618
|
{isProduction && (
|
|
937
|
-
<p
|
|
938
|
-
|
|
939
|
-
color: theme.red,
|
|
940
|
-
margin: "0 0 10px 0",
|
|
941
|
-
fontSize: 12,
|
|
942
|
-
fontWeight: 600,
|
|
943
|
-
}}
|
|
944
|
-
>
|
|
945
|
-
{"\uD83D\uDD12"} This is a protected environment.
|
|
619
|
+
<p className="m-0 mb-2.5 text-[12px] font-semibold text-stop-500">
|
|
620
|
+
{"๐"} This is a protected environment.
|
|
946
621
|
</p>
|
|
947
622
|
)}
|
|
948
|
-
<div
|
|
623
|
+
<div className="flex gap-2">
|
|
949
624
|
<Button
|
|
950
625
|
variant="primary"
|
|
951
626
|
data-testid="confirm-reset-yes"
|
|
@@ -964,34 +639,18 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
964
639
|
{confirmDelete && (
|
|
965
640
|
<div
|
|
966
641
|
data-testid="confirm-delete-dialog"
|
|
967
|
-
|
|
968
|
-
marginTop: 16,
|
|
969
|
-
padding: "14px 18px",
|
|
970
|
-
background: theme.redDim,
|
|
971
|
-
border: `1px solid ${theme.red}44`,
|
|
972
|
-
borderRadius: 8,
|
|
973
|
-
fontFamily: theme.sans,
|
|
974
|
-
fontSize: 12,
|
|
975
|
-
}}
|
|
642
|
+
className="mt-4 rounded-lg border border-stop-500/30 bg-stop-500/10 px-4 py-3.5 font-sans text-[12px]"
|
|
976
643
|
>
|
|
977
|
-
<p
|
|
978
|
-
Permanently delete{" "}
|
|
979
|
-
<strong style={{ fontFamily: theme.mono }}>{confirmDelete}</strong> from{" "}
|
|
644
|
+
<p className="m-0 mb-2.5 text-bone">
|
|
645
|
+
Permanently delete <strong className="font-mono">{confirmDelete}</strong> from{" "}
|
|
980
646
|
<strong>{env}</strong>? This cannot be undone.
|
|
981
647
|
</p>
|
|
982
648
|
{isProduction && (
|
|
983
|
-
<p
|
|
984
|
-
|
|
985
|
-
color: theme.red,
|
|
986
|
-
margin: "0 0 10px 0",
|
|
987
|
-
fontSize: 12,
|
|
988
|
-
fontWeight: 600,
|
|
989
|
-
}}
|
|
990
|
-
>
|
|
991
|
-
{"\uD83D\uDD12"} This is a protected environment.
|
|
649
|
+
<p className="m-0 mb-2.5 text-[12px] font-semibold text-stop-500">
|
|
650
|
+
{"๐"} This is a protected environment.
|
|
992
651
|
</p>
|
|
993
652
|
)}
|
|
994
|
-
<div
|
|
653
|
+
<div className="flex gap-2">
|
|
995
654
|
<Button
|
|
996
655
|
variant="danger"
|
|
997
656
|
data-testid="confirm-delete-yes"
|
|
@@ -1010,34 +669,22 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
1010
669
|
{protectedConfirm && (
|
|
1011
670
|
<div
|
|
1012
671
|
data-testid="confirm-protected-dialog"
|
|
1013
|
-
|
|
1014
|
-
marginTop: 16,
|
|
1015
|
-
padding: "14px 18px",
|
|
1016
|
-
background: theme.redDim,
|
|
1017
|
-
border: `1px solid ${theme.red}44`,
|
|
1018
|
-
borderRadius: 8,
|
|
1019
|
-
fontFamily: theme.sans,
|
|
1020
|
-
fontSize: 12,
|
|
1021
|
-
}}
|
|
672
|
+
className="mt-4 rounded-lg border border-stop-500/30 bg-stop-500/10 px-4 py-3.5 font-sans text-[12px]"
|
|
1022
673
|
>
|
|
1023
|
-
<p
|
|
1024
|
-
{"
|
|
1025
|
-
|
|
1026
|
-
to {protectedConfirm === "save" ? "commit changes to" : "add a key to"}{" "}
|
|
674
|
+
<p className="m-0 mb-2.5 text-bone">
|
|
675
|
+
{"๐"} <strong className="text-stop-500">Protected environment.</strong> You are
|
|
676
|
+
about to {protectedConfirm === "save" ? "commit changes to" : "add a key to"}{" "}
|
|
1027
677
|
<strong>{env}</strong>. Are you sure?
|
|
1028
678
|
</p>
|
|
1029
|
-
<div
|
|
679
|
+
<div className="flex gap-2">
|
|
1030
680
|
<Button
|
|
1031
681
|
variant="primary"
|
|
1032
682
|
data-testid="confirm-protected-yes"
|
|
1033
683
|
onClick={async () => {
|
|
1034
684
|
const action = protectedConfirm;
|
|
1035
685
|
setProtectedConfirm(null);
|
|
1036
|
-
if (action === "save")
|
|
1037
|
-
|
|
1038
|
-
} else {
|
|
1039
|
-
await handleAdd(true);
|
|
1040
|
-
}
|
|
686
|
+
if (action === "save") await handleSave(true);
|
|
687
|
+
else await handleAdd(true);
|
|
1041
688
|
}}
|
|
1042
689
|
>
|
|
1043
690
|
Confirm
|
|
@@ -1053,32 +700,13 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
1053
700
|
)}
|
|
1054
701
|
|
|
1055
702
|
{/* Schema section */}
|
|
1056
|
-
<div
|
|
1057
|
-
<div
|
|
1058
|
-
|
|
1059
|
-
fontFamily: theme.sans,
|
|
1060
|
-
fontSize: 11,
|
|
1061
|
-
fontWeight: 600,
|
|
1062
|
-
color: theme.textMuted,
|
|
1063
|
-
textTransform: "uppercase",
|
|
1064
|
-
letterSpacing: "0.08em",
|
|
1065
|
-
marginBottom: 10,
|
|
1066
|
-
}}
|
|
1067
|
-
>
|
|
1068
|
-
Schema {"\u00B7"} schemas/{ns}.yaml
|
|
703
|
+
<div className="mt-6">
|
|
704
|
+
<div className="mb-2.5 font-sans text-[11px] font-semibold uppercase tracking-[0.08em] text-ash">
|
|
705
|
+
Schema ยท schemas/{ns}.yaml
|
|
1069
706
|
</div>
|
|
1070
707
|
<div
|
|
1071
708
|
data-testid="schema-summary"
|
|
1072
|
-
|
|
1073
|
-
background: theme.surface,
|
|
1074
|
-
border: `1px solid ${theme.border}`,
|
|
1075
|
-
borderRadius: 8,
|
|
1076
|
-
padding: "12px 16px",
|
|
1077
|
-
fontFamily: theme.mono,
|
|
1078
|
-
fontSize: 11,
|
|
1079
|
-
color: theme.textMuted,
|
|
1080
|
-
lineHeight: 1.7,
|
|
1081
|
-
}}
|
|
709
|
+
className="rounded-lg border border-edge bg-ink-850 px-4 py-3 font-mono text-[11px] leading-relaxed text-ash"
|
|
1082
710
|
>
|
|
1083
711
|
{(() => {
|
|
1084
712
|
const errors = lintIssues.filter((i) => i.severity === "error");
|
|
@@ -1086,26 +714,23 @@ export function NamespaceEditor({ ns, initialEnv, manifest }: NamespaceEditorPro
|
|
|
1086
714
|
if (errors.length === 0 && warnings.length === 0) {
|
|
1087
715
|
return (
|
|
1088
716
|
<>
|
|
1089
|
-
<span
|
|
1090
|
-
|
|
1091
|
-
<span
|
|
1092
|
-
|
|
1093
|
-
<span style={{ color: theme.textDim }}>0 warnings</span>
|
|
717
|
+
<span className="text-go-500">{"โ"}</span> All required keys present
|
|
718
|
+
ยท
|
|
719
|
+
<span className="text-go-500">{"โ"}</span> All types valid ยท
|
|
720
|
+
<span className="text-ash-dim">0 warnings</span>
|
|
1094
721
|
</>
|
|
1095
722
|
);
|
|
1096
723
|
}
|
|
1097
724
|
return (
|
|
1098
725
|
<>
|
|
1099
726
|
{errors.length > 0 && (
|
|
1100
|
-
<span
|
|
727
|
+
<span className="text-stop-500">
|
|
1101
728
|
{errors.length} error{errors.length !== 1 ? "s" : ""}
|
|
1102
729
|
</span>
|
|
1103
730
|
)}
|
|
1104
|
-
{errors.length > 0 && warnings.length > 0 &&
|
|
1105
|
-
<span> {"\u00B7"} </span>
|
|
1106
|
-
)}
|
|
731
|
+
{errors.length > 0 && warnings.length > 0 && <span> ยท </span>}
|
|
1107
732
|
{warnings.length > 0 && (
|
|
1108
|
-
<span
|
|
733
|
+
<span className="text-warn-500">
|
|
1109
734
|
{warnings.length} warning{warnings.length !== 1 ? "s" : ""}
|
|
1110
735
|
</span>
|
|
1111
736
|
)}
|