@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,10 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback } 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";
|
|
6
4
|
import { EnvBadge } from "../components/EnvBadge";
|
|
7
5
|
import { CopyButton } from "../components/CopyButton";
|
|
6
|
+
import { Toolbar, Table, EmptyState } from "../primitives";
|
|
8
7
|
import type { ClefManifest, DiffResult } from "@clef-sh/core";
|
|
9
8
|
|
|
10
9
|
interface DiffViewProps {
|
|
@@ -12,9 +11,14 @@ interface DiffViewProps {
|
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
function truncate(s: string, max: number): string {
|
|
15
|
-
return s.length > max ? s.slice(0, max) + "
|
|
14
|
+
return s.length > max ? s.slice(0, max) + "…" : s;
|
|
16
15
|
}
|
|
17
16
|
|
|
17
|
+
const SELECT_CLASSES =
|
|
18
|
+
"rounded-md border border-edge bg-ink-850 px-2.5 py-1 font-mono text-[12px] text-bone cursor-pointer focus:outline-none focus:border-edge-strong";
|
|
19
|
+
|
|
20
|
+
type DiffStatus = "changed" | "identical" | "missing_a" | "missing_b";
|
|
21
|
+
|
|
18
22
|
export function DiffView({ manifest }: DiffViewProps) {
|
|
19
23
|
const environments = manifest?.environments ?? [];
|
|
20
24
|
const namespaces = manifest?.namespaces ?? [];
|
|
@@ -65,19 +69,79 @@ export function DiffView({ manifest }: DiffViewProps) {
|
|
|
65
69
|
const identicalCount = rows.filter((r) => r.status === "identical").length;
|
|
66
70
|
const missingRows = rows.filter((r) => r.status === "missing_a" || r.status === "missing_b");
|
|
67
71
|
|
|
68
|
-
const statusMeta: Record<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
const statusMeta: Record<
|
|
73
|
+
DiffStatus,
|
|
74
|
+
{ label: string; text: string; bg: string; border: string }
|
|
75
|
+
> = {
|
|
76
|
+
changed: {
|
|
77
|
+
label: "Changed",
|
|
78
|
+
text: "text-warn-500",
|
|
79
|
+
bg: "bg-warn-500/10",
|
|
80
|
+
border: "border-warn-500/30",
|
|
81
|
+
},
|
|
82
|
+
identical: {
|
|
83
|
+
label: "Identical",
|
|
84
|
+
text: "text-ash",
|
|
85
|
+
bg: "bg-ash/10",
|
|
86
|
+
border: "border-ash/20",
|
|
87
|
+
},
|
|
88
|
+
missing_a: {
|
|
89
|
+
label: `Missing in ${envA}`,
|
|
90
|
+
text: "text-stop-500",
|
|
91
|
+
bg: "bg-stop-500/10",
|
|
92
|
+
border: "border-stop-500/30",
|
|
93
|
+
},
|
|
94
|
+
missing_b: {
|
|
95
|
+
label: `Missing in ${envB}`,
|
|
96
|
+
text: "text-stop-500",
|
|
97
|
+
bg: "bg-stop-500/10",
|
|
98
|
+
border: "border-stop-500/30",
|
|
99
|
+
},
|
|
73
100
|
};
|
|
74
101
|
|
|
102
|
+
const summaryPills: Array<{ label: string; text: string; bg: string; border: string }> = [
|
|
103
|
+
{
|
|
104
|
+
label: `${changedCount} changed`,
|
|
105
|
+
text: "text-warn-500",
|
|
106
|
+
bg: "bg-warn-500/10",
|
|
107
|
+
border: "border-warn-500/30",
|
|
108
|
+
},
|
|
109
|
+
...(missingACount > 0
|
|
110
|
+
? [
|
|
111
|
+
{
|
|
112
|
+
label: `${missingACount} missing in ${envA}`,
|
|
113
|
+
text: "text-stop-500",
|
|
114
|
+
bg: "bg-stop-500/10",
|
|
115
|
+
border: "border-stop-500/30",
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
: []),
|
|
119
|
+
...(missingBCount > 0
|
|
120
|
+
? [
|
|
121
|
+
{
|
|
122
|
+
label: `${missingBCount} missing in ${envB}`,
|
|
123
|
+
text: "text-stop-500",
|
|
124
|
+
bg: "bg-stop-500/10",
|
|
125
|
+
border: "border-stop-500/30",
|
|
126
|
+
},
|
|
127
|
+
]
|
|
128
|
+
: []),
|
|
129
|
+
{
|
|
130
|
+
label: `${identicalCount} identical`,
|
|
131
|
+
text: "text-ash",
|
|
132
|
+
bg: "bg-ash/10",
|
|
133
|
+
border: "border-ash/20",
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
75
137
|
return (
|
|
76
|
-
<div
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
138
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
139
|
+
<Toolbar>
|
|
140
|
+
<div>
|
|
141
|
+
<Toolbar.Title>Environment Diff</Toolbar.Title>
|
|
142
|
+
<Toolbar.Subtitle>Compare secrets across environments</Toolbar.Subtitle>
|
|
143
|
+
</div>
|
|
144
|
+
<Toolbar.Actions>
|
|
81
145
|
<Button
|
|
82
146
|
variant="primary"
|
|
83
147
|
data-testid="sync-missing-btn"
|
|
@@ -86,69 +150,26 @@ export function DiffView({ manifest }: DiffViewProps) {
|
|
|
86
150
|
setTimeout(() => setToastVisible(false), 2000);
|
|
87
151
|
}}
|
|
88
152
|
>
|
|
89
|
-
Sync missing keys {"
|
|
153
|
+
Sync missing keys {"→"}
|
|
90
154
|
</Button>
|
|
91
|
-
|
|
92
|
-
|
|
155
|
+
</Toolbar.Actions>
|
|
156
|
+
</Toolbar>
|
|
93
157
|
|
|
94
158
|
{/* Toast */}
|
|
95
159
|
{toastVisible && (
|
|
96
160
|
<div
|
|
97
161
|
data-testid="coming-soon-toast"
|
|
98
|
-
|
|
99
|
-
position: "fixed",
|
|
100
|
-
top: 20,
|
|
101
|
-
right: 20,
|
|
102
|
-
padding: "10px 18px",
|
|
103
|
-
background: theme.surface,
|
|
104
|
-
border: `1px solid ${theme.accent}44`,
|
|
105
|
-
borderRadius: 8,
|
|
106
|
-
fontFamily: theme.sans,
|
|
107
|
-
fontSize: 12,
|
|
108
|
-
color: theme.accent,
|
|
109
|
-
zIndex: 1000,
|
|
110
|
-
}}
|
|
162
|
+
className="fixed right-5 top-5 z-[1000] rounded-md border border-gold-500/30 bg-ink-850 px-4 py-2.5 font-sans text-[12px] text-gold-500"
|
|
111
163
|
>
|
|
112
164
|
Coming soon
|
|
113
165
|
</div>
|
|
114
166
|
)}
|
|
115
167
|
|
|
116
168
|
{/* Controls */}
|
|
117
|
-
<div
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
borderBottom: `1px solid ${theme.border}`,
|
|
122
|
-
display: "flex",
|
|
123
|
-
alignItems: "center",
|
|
124
|
-
gap: 12,
|
|
125
|
-
flexWrap: "wrap",
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
128
|
-
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
129
|
-
<span
|
|
130
|
-
style={{
|
|
131
|
-
fontFamily: theme.sans,
|
|
132
|
-
fontSize: 12,
|
|
133
|
-
color: theme.textMuted,
|
|
134
|
-
}}
|
|
135
|
-
>
|
|
136
|
-
Namespace
|
|
137
|
-
</span>
|
|
138
|
-
<select
|
|
139
|
-
value={ns}
|
|
140
|
-
onChange={(e) => setNs(e.target.value)}
|
|
141
|
-
style={{
|
|
142
|
-
background: theme.surface,
|
|
143
|
-
border: `1px solid ${theme.border}`,
|
|
144
|
-
borderRadius: 6,
|
|
145
|
-
padding: "5px 10px",
|
|
146
|
-
fontFamily: theme.mono,
|
|
147
|
-
fontSize: 12,
|
|
148
|
-
color: theme.text,
|
|
149
|
-
cursor: "pointer",
|
|
150
|
-
}}
|
|
151
|
-
>
|
|
169
|
+
<div className="flex flex-wrap items-center gap-3 border-b border-edge bg-ink-800 px-6 py-3.5">
|
|
170
|
+
<div className="flex items-center gap-2">
|
|
171
|
+
<span className="font-sans text-[12px] text-ash">Namespace</span>
|
|
172
|
+
<select value={ns} onChange={(e) => setNs(e.target.value)} className={SELECT_CLASSES}>
|
|
152
173
|
{namespaces.map((n) => (
|
|
153
174
|
<option key={n.name} value={n.name}>
|
|
154
175
|
{n.name}
|
|
@@ -157,59 +178,17 @@ export function DiffView({ manifest }: DiffViewProps) {
|
|
|
157
178
|
</select>
|
|
158
179
|
</div>
|
|
159
180
|
|
|
160
|
-
<div
|
|
161
|
-
<span
|
|
162
|
-
|
|
163
|
-
fontFamily: theme.sans,
|
|
164
|
-
fontSize: 12,
|
|
165
|
-
color: theme.textMuted,
|
|
166
|
-
}}
|
|
167
|
-
>
|
|
168
|
-
Compare
|
|
169
|
-
</span>
|
|
170
|
-
<select
|
|
171
|
-
value={envA}
|
|
172
|
-
onChange={(e) => setEnvA(e.target.value)}
|
|
173
|
-
style={{
|
|
174
|
-
background: theme.surface,
|
|
175
|
-
border: `1px solid ${theme.border}`,
|
|
176
|
-
borderRadius: 6,
|
|
177
|
-
padding: "5px 10px",
|
|
178
|
-
fontFamily: theme.mono,
|
|
179
|
-
fontSize: 12,
|
|
180
|
-
color: theme.text,
|
|
181
|
-
cursor: "pointer",
|
|
182
|
-
}}
|
|
183
|
-
>
|
|
181
|
+
<div className="flex items-center gap-2">
|
|
182
|
+
<span className="font-sans text-[12px] text-ash">Compare</span>
|
|
183
|
+
<select value={envA} onChange={(e) => setEnvA(e.target.value)} className={SELECT_CLASSES}>
|
|
184
184
|
{environments.map((e) => (
|
|
185
185
|
<option key={e.name} value={e.name}>
|
|
186
186
|
{e.name}
|
|
187
187
|
</option>
|
|
188
188
|
))}
|
|
189
189
|
</select>
|
|
190
|
-
<span
|
|
191
|
-
|
|
192
|
-
fontFamily: theme.mono,
|
|
193
|
-
fontSize: 12,
|
|
194
|
-
color: theme.textDim,
|
|
195
|
-
}}
|
|
196
|
-
>
|
|
197
|
-
{"\u2192"}
|
|
198
|
-
</span>
|
|
199
|
-
<select
|
|
200
|
-
value={envB}
|
|
201
|
-
onChange={(e) => setEnvB(e.target.value)}
|
|
202
|
-
style={{
|
|
203
|
-
background: theme.surface,
|
|
204
|
-
border: `1px solid ${theme.border}`,
|
|
205
|
-
borderRadius: 6,
|
|
206
|
-
padding: "5px 10px",
|
|
207
|
-
fontFamily: theme.mono,
|
|
208
|
-
fontSize: 12,
|
|
209
|
-
color: theme.text,
|
|
210
|
-
cursor: "pointer",
|
|
211
|
-
}}
|
|
212
|
-
>
|
|
190
|
+
<span className="font-mono text-[12px] text-ash-dim">{"→"}</span>
|
|
191
|
+
<select value={envB} onChange={(e) => setEnvB(e.target.value)} className={SELECT_CLASSES}>
|
|
213
192
|
{environments.map((e) => (
|
|
214
193
|
<option key={e.name} value={e.name}>
|
|
215
194
|
{e.name}
|
|
@@ -218,317 +197,147 @@ export function DiffView({ manifest }: DiffViewProps) {
|
|
|
218
197
|
</select>
|
|
219
198
|
</div>
|
|
220
199
|
|
|
221
|
-
<div
|
|
200
|
+
<div className="flex-1" />
|
|
222
201
|
|
|
223
|
-
<label
|
|
202
|
+
<label className="flex cursor-pointer items-center gap-1.5">
|
|
224
203
|
<input
|
|
225
204
|
type="checkbox"
|
|
226
205
|
checked={showValues}
|
|
227
206
|
onChange={(e) => setShowValues(e.target.checked)}
|
|
228
207
|
data-testid="show-values-toggle"
|
|
229
|
-
|
|
208
|
+
className="accent-gold-500"
|
|
230
209
|
/>
|
|
231
|
-
<span
|
|
232
|
-
style={{
|
|
233
|
-
fontFamily: theme.sans,
|
|
234
|
-
fontSize: 12,
|
|
235
|
-
color: theme.textMuted,
|
|
236
|
-
}}
|
|
237
|
-
>
|
|
238
|
-
Show values
|
|
239
|
-
</span>
|
|
210
|
+
<span className="font-sans text-[12px] text-ash">Show values</span>
|
|
240
211
|
</label>
|
|
241
212
|
|
|
242
|
-
<label
|
|
213
|
+
<label className="flex cursor-pointer items-center gap-1.5">
|
|
243
214
|
<input
|
|
244
215
|
type="checkbox"
|
|
245
216
|
checked={showSame}
|
|
246
217
|
onChange={(e) => setShowSame(e.target.checked)}
|
|
247
|
-
|
|
218
|
+
className="accent-gold-500"
|
|
248
219
|
/>
|
|
249
|
-
<span
|
|
250
|
-
style={{
|
|
251
|
-
fontFamily: theme.sans,
|
|
252
|
-
fontSize: 12,
|
|
253
|
-
color: theme.textMuted,
|
|
254
|
-
}}
|
|
255
|
-
>
|
|
256
|
-
Show identical
|
|
257
|
-
</span>
|
|
220
|
+
<span className="font-sans text-[12px] text-ash">Show identical</span>
|
|
258
221
|
</label>
|
|
259
222
|
</div>
|
|
260
223
|
|
|
261
224
|
{/* Summary strip */}
|
|
262
|
-
<div
|
|
263
|
-
|
|
264
|
-
padding: "10px 24px",
|
|
265
|
-
display: "flex",
|
|
266
|
-
gap: 10,
|
|
267
|
-
borderBottom: `1px solid ${theme.border}`,
|
|
268
|
-
}}
|
|
269
|
-
>
|
|
270
|
-
{[
|
|
271
|
-
{ label: `${changedCount} changed`, color: theme.yellow },
|
|
272
|
-
...(missingACount > 0
|
|
273
|
-
? [{ label: `${missingACount} missing in ${envA}`, color: theme.red }]
|
|
274
|
-
: []),
|
|
275
|
-
...(missingBCount > 0
|
|
276
|
-
? [{ label: `${missingBCount} missing in ${envB}`, color: theme.red }]
|
|
277
|
-
: []),
|
|
278
|
-
{ label: `${identicalCount} identical`, color: theme.textMuted },
|
|
279
|
-
].map((p) => (
|
|
225
|
+
<div className="flex gap-2.5 border-b border-edge px-6 py-2.5">
|
|
226
|
+
{summaryPills.map((p) => (
|
|
280
227
|
<span
|
|
281
228
|
key={p.label}
|
|
282
|
-
|
|
283
|
-
fontFamily: theme.mono,
|
|
284
|
-
fontSize: 11,
|
|
285
|
-
color: p.color,
|
|
286
|
-
background: `${p.color}14`,
|
|
287
|
-
border: `1px solid ${p.color}33`,
|
|
288
|
-
borderRadius: 20,
|
|
289
|
-
padding: "2px 10px",
|
|
290
|
-
}}
|
|
229
|
+
className={`rounded-pill border px-2.5 py-px font-mono text-[11px] ${p.text} ${p.bg} ${p.border}`}
|
|
291
230
|
>
|
|
292
231
|
{p.label}
|
|
293
232
|
</span>
|
|
294
233
|
))}
|
|
295
234
|
</div>
|
|
296
235
|
|
|
297
|
-
<div
|
|
298
|
-
{loading && <
|
|
236
|
+
<div className="flex-1 overflow-auto p-6">
|
|
237
|
+
{loading && <EmptyState title="Loading..." body="Computing diff between environments" />}
|
|
299
238
|
|
|
300
239
|
{!loading && (
|
|
301
240
|
<>
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
>
|
|
311
|
-
{/* Header */}
|
|
312
|
-
<div
|
|
313
|
-
style={{
|
|
314
|
-
display: "grid",
|
|
315
|
-
gridTemplateColumns: "220px 1fr 1fr 100px",
|
|
316
|
-
background: "#0D0F14",
|
|
317
|
-
padding: "10px 20px",
|
|
318
|
-
borderBottom: `1px solid ${theme.border}`,
|
|
319
|
-
}}
|
|
320
|
-
>
|
|
321
|
-
<span
|
|
322
|
-
style={{
|
|
323
|
-
fontFamily: theme.sans,
|
|
324
|
-
fontSize: 11,
|
|
325
|
-
fontWeight: 600,
|
|
326
|
-
color: theme.textMuted,
|
|
327
|
-
textTransform: "uppercase",
|
|
328
|
-
letterSpacing: "0.07em",
|
|
329
|
-
}}
|
|
330
|
-
>
|
|
331
|
-
Key
|
|
332
|
-
</span>
|
|
333
|
-
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
334
|
-
<EnvBadge env={envA} small />
|
|
335
|
-
<span
|
|
336
|
-
style={{
|
|
337
|
-
fontFamily: theme.sans,
|
|
338
|
-
fontSize: 11,
|
|
339
|
-
fontWeight: 600,
|
|
340
|
-
color: theme.textMuted,
|
|
341
|
-
textTransform: "uppercase",
|
|
342
|
-
}}
|
|
343
|
-
>
|
|
344
|
-
{envA}
|
|
345
|
-
</span>
|
|
346
|
-
</div>
|
|
347
|
-
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
348
|
-
<EnvBadge env={envB} small />
|
|
349
|
-
<span
|
|
350
|
-
style={{
|
|
351
|
-
fontFamily: theme.sans,
|
|
352
|
-
fontSize: 11,
|
|
353
|
-
fontWeight: 600,
|
|
354
|
-
color: theme.textMuted,
|
|
355
|
-
textTransform: "uppercase",
|
|
356
|
-
}}
|
|
357
|
-
>
|
|
358
|
-
{envB}
|
|
359
|
-
</span>
|
|
360
|
-
</div>
|
|
361
|
-
<span
|
|
362
|
-
style={{
|
|
363
|
-
fontFamily: theme.sans,
|
|
364
|
-
fontSize: 11,
|
|
365
|
-
fontWeight: 600,
|
|
366
|
-
color: theme.textMuted,
|
|
367
|
-
textTransform: "uppercase",
|
|
368
|
-
}}
|
|
369
|
-
>
|
|
370
|
-
Status
|
|
371
|
-
</span>
|
|
372
|
-
</div>
|
|
373
|
-
|
|
374
|
-
{filtered.map((row, i) => {
|
|
375
|
-
const meta = statusMeta[row.status];
|
|
376
|
-
return (
|
|
377
|
-
<div
|
|
378
|
-
key={row.key}
|
|
379
|
-
style={{
|
|
380
|
-
display: "grid",
|
|
381
|
-
gridTemplateColumns: "220px 1fr 1fr 100px",
|
|
382
|
-
padding: "0 20px",
|
|
383
|
-
minHeight: 48,
|
|
384
|
-
alignItems: "center",
|
|
385
|
-
borderBottom: i < filtered.length - 1 ? `1px solid ${theme.border}` : "none",
|
|
386
|
-
background:
|
|
387
|
-
row.status === "changed"
|
|
388
|
-
? `${theme.yellow}06`
|
|
389
|
-
: row.status.startsWith("missing")
|
|
390
|
-
? `${theme.red}06`
|
|
391
|
-
: "transparent",
|
|
392
|
-
}}
|
|
393
|
-
>
|
|
394
|
-
<span
|
|
395
|
-
style={{
|
|
396
|
-
fontFamily: theme.mono,
|
|
397
|
-
fontSize: 12,
|
|
398
|
-
color: theme.text,
|
|
399
|
-
paddingRight: 16,
|
|
400
|
-
}}
|
|
401
|
-
>
|
|
402
|
-
{row.key}
|
|
403
|
-
</span>
|
|
404
|
-
|
|
405
|
-
{/* Env A value */}
|
|
406
|
-
<div style={{ paddingRight: 16 }}>
|
|
407
|
-
{row.valueA !== null ? (
|
|
408
|
-
<span
|
|
409
|
-
style={{
|
|
410
|
-
fontFamily: theme.mono,
|
|
411
|
-
fontSize: 11,
|
|
412
|
-
color: row.status === "changed" ? theme.yellow : theme.textMuted,
|
|
413
|
-
background: row.status === "changed" ? theme.yellowDim : "transparent",
|
|
414
|
-
padding: row.status === "changed" ? "2px 6px" : "0",
|
|
415
|
-
borderRadius: 3,
|
|
416
|
-
}}
|
|
417
|
-
>
|
|
418
|
-
{truncate(row.valueA, 36)}
|
|
419
|
-
</span>
|
|
420
|
-
) : (
|
|
421
|
-
<span
|
|
422
|
-
style={{
|
|
423
|
-
fontFamily: theme.mono,
|
|
424
|
-
fontSize: 11,
|
|
425
|
-
color: theme.textDim,
|
|
426
|
-
fontStyle: "italic",
|
|
427
|
-
}}
|
|
428
|
-
>
|
|
429
|
-
{"\u2014"} not set {"\u2014"}
|
|
430
|
-
</span>
|
|
431
|
-
)}
|
|
241
|
+
<Table data-testid="diff-table">
|
|
242
|
+
<Table.Header>
|
|
243
|
+
<tr>
|
|
244
|
+
<Table.HeaderCell className="w-[220px]">Key</Table.HeaderCell>
|
|
245
|
+
<Table.HeaderCell>
|
|
246
|
+
<div className="flex items-center gap-2">
|
|
247
|
+
<EnvBadge env={envA} small />
|
|
248
|
+
<span>{envA}</span>
|
|
432
249
|
</div>
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
<div
|
|
436
|
-
{
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
250
|
+
</Table.HeaderCell>
|
|
251
|
+
<Table.HeaderCell>
|
|
252
|
+
<div className="flex items-center gap-2">
|
|
253
|
+
<EnvBadge env={envB} small />
|
|
254
|
+
<span>{envB}</span>
|
|
255
|
+
</div>
|
|
256
|
+
</Table.HeaderCell>
|
|
257
|
+
<Table.HeaderCell className="w-[120px]">Status</Table.HeaderCell>
|
|
258
|
+
</tr>
|
|
259
|
+
</Table.Header>
|
|
260
|
+
<tbody>
|
|
261
|
+
{filtered.map((row) => {
|
|
262
|
+
const status = row.status as DiffStatus;
|
|
263
|
+
const meta = statusMeta[status];
|
|
264
|
+
const isChanged = status === "changed";
|
|
265
|
+
const isMissing = status === "missing_a" || status === "missing_b";
|
|
266
|
+
const rowBg = isChanged
|
|
267
|
+
? "bg-warn-500/[0.025]"
|
|
268
|
+
: isMissing
|
|
269
|
+
? "bg-stop-500/[0.025]"
|
|
270
|
+
: "";
|
|
271
|
+
return (
|
|
272
|
+
<Table.Row
|
|
273
|
+
key={row.key}
|
|
274
|
+
tone={isMissing ? "drift" : undefined}
|
|
275
|
+
className={rowBg}
|
|
276
|
+
>
|
|
277
|
+
<Table.Cell className="font-mono text-[12px] text-bone">{row.key}</Table.Cell>
|
|
278
|
+
<Table.Cell>
|
|
279
|
+
{row.valueA !== null ? (
|
|
280
|
+
<span
|
|
281
|
+
className={`font-mono text-[11px] ${
|
|
282
|
+
isChanged
|
|
283
|
+
? "rounded-sm bg-warn-500/15 px-1.5 py-0.5 text-warn-500"
|
|
284
|
+
: "text-ash"
|
|
285
|
+
}`}
|
|
286
|
+
>
|
|
287
|
+
{truncate(row.valueA, 36)}
|
|
288
|
+
</span>
|
|
289
|
+
) : (
|
|
290
|
+
<span className="font-mono text-[11px] italic text-ash-dim">
|
|
291
|
+
{"—"} not set {"—"}
|
|
292
|
+
</span>
|
|
293
|
+
)}
|
|
294
|
+
</Table.Cell>
|
|
295
|
+
<Table.Cell>
|
|
296
|
+
{row.valueB !== null ? (
|
|
297
|
+
<span
|
|
298
|
+
className={`font-mono text-[11px] ${
|
|
299
|
+
isChanged
|
|
300
|
+
? "rounded-sm bg-blue-400/15 px-1.5 py-0.5 text-blue-400"
|
|
301
|
+
: "text-ash"
|
|
302
|
+
}`}
|
|
303
|
+
>
|
|
304
|
+
{truncate(row.valueB, 36)}
|
|
305
|
+
</span>
|
|
306
|
+
) : (
|
|
307
|
+
<span className="font-mono text-[11px] italic text-ash-dim">
|
|
308
|
+
{"—"} not set {"—"}
|
|
309
|
+
</span>
|
|
310
|
+
)}
|
|
311
|
+
</Table.Cell>
|
|
312
|
+
<Table.Cell>
|
|
450
313
|
<span
|
|
451
|
-
|
|
452
|
-
fontFamily: theme.mono,
|
|
453
|
-
fontSize: 11,
|
|
454
|
-
color: theme.textDim,
|
|
455
|
-
fontStyle: "italic",
|
|
456
|
-
}}
|
|
314
|
+
className={`inline-block rounded-sm border px-2 py-0.5 font-mono text-[10px] font-semibold ${meta.text} ${meta.bg} ${meta.border}`}
|
|
457
315
|
>
|
|
458
|
-
{
|
|
316
|
+
{meta.label}
|
|
459
317
|
</span>
|
|
460
|
-
|
|
461
|
-
</
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
fontFamily: theme.mono,
|
|
467
|
-
fontSize: 10,
|
|
468
|
-
fontWeight: 600,
|
|
469
|
-
color: meta.color,
|
|
470
|
-
background: `${meta.color}18`,
|
|
471
|
-
border: `1px solid ${meta.color}33`,
|
|
472
|
-
borderRadius: 3,
|
|
473
|
-
padding: "2px 8px",
|
|
474
|
-
display: "inline-block",
|
|
475
|
-
}}
|
|
476
|
-
>
|
|
477
|
-
{meta.label}
|
|
478
|
-
</span>
|
|
479
|
-
</div>
|
|
480
|
-
);
|
|
481
|
-
})}
|
|
482
|
-
</div>
|
|
318
|
+
</Table.Cell>
|
|
319
|
+
</Table.Row>
|
|
320
|
+
);
|
|
321
|
+
})}
|
|
322
|
+
</tbody>
|
|
323
|
+
</Table>
|
|
483
324
|
|
|
484
325
|
{/* Inline fix hint */}
|
|
485
326
|
{missingRows.length > 0 && (
|
|
486
327
|
<div
|
|
487
328
|
data-testid="fix-hint"
|
|
488
|
-
|
|
489
|
-
marginTop: 20,
|
|
490
|
-
padding: "14px 18px",
|
|
491
|
-
background: theme.surface,
|
|
492
|
-
border: `1px solid ${theme.border}`,
|
|
493
|
-
borderRadius: 8,
|
|
494
|
-
display: "flex",
|
|
495
|
-
flexDirection: "column",
|
|
496
|
-
gap: 10,
|
|
497
|
-
}}
|
|
329
|
+
className="mt-5 flex flex-col gap-2.5 rounded-md border border-edge bg-ink-850 px-[18px] py-3.5"
|
|
498
330
|
>
|
|
499
331
|
{missingRows.map((row) => {
|
|
500
332
|
const missingEnv = row.status === "missing_a" ? envA : envB;
|
|
501
333
|
const cmd = `clef set ${ns}/${missingEnv} ${row.key}`;
|
|
502
334
|
return (
|
|
503
|
-
<div
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
alignItems: "center",
|
|
508
|
-
gap: 12,
|
|
509
|
-
}}
|
|
510
|
-
>
|
|
511
|
-
<span style={{ fontSize: 16 }}>{"\uD83D\uDCA1"}</span>
|
|
512
|
-
<span
|
|
513
|
-
style={{
|
|
514
|
-
fontFamily: theme.sans,
|
|
515
|
-
fontSize: 12,
|
|
516
|
-
color: theme.textMuted,
|
|
517
|
-
flex: 1,
|
|
518
|
-
}}
|
|
519
|
-
>
|
|
520
|
-
<strong style={{ color: theme.text }}>{row.key}</strong> is missing in{" "}
|
|
335
|
+
<div key={row.key} className="flex items-center gap-3">
|
|
336
|
+
<span className="text-[16px]">{"💡"}</span>
|
|
337
|
+
<span className="flex-1 font-sans text-[12px] text-ash">
|
|
338
|
+
<strong className="text-bone">{row.key}</strong> is missing in{" "}
|
|
521
339
|
<EnvBadge env={missingEnv} small />. Run{" "}
|
|
522
|
-
<code
|
|
523
|
-
style={{
|
|
524
|
-
fontFamily: theme.mono,
|
|
525
|
-
fontSize: 11,
|
|
526
|
-
color: theme.accent,
|
|
527
|
-
background: theme.accentDim,
|
|
528
|
-
padding: "1px 6px",
|
|
529
|
-
borderRadius: 3,
|
|
530
|
-
}}
|
|
531
|
-
>
|
|
340
|
+
<code className="rounded-sm bg-gold-500/15 px-1.5 py-px font-mono text-[11px] text-gold-500">
|
|
532
341
|
{cmd}
|
|
533
342
|
</code>{" "}
|
|
534
343
|
to add it.
|