@clef-sh/ui 0.1.13-beta.88

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.
Files changed (70) hide show
  1. package/README.md +38 -0
  2. package/dist/client/assets/index-CVpAmirt.js +26 -0
  3. package/dist/client/favicon-96x96.png +0 -0
  4. package/dist/client/favicon.ico +0 -0
  5. package/dist/client/favicon.svg +16 -0
  6. package/dist/client/index.html +50 -0
  7. package/dist/client-lib/api.d.ts +3 -0
  8. package/dist/client-lib/api.d.ts.map +1 -0
  9. package/dist/client-lib/components/Button.d.ts +10 -0
  10. package/dist/client-lib/components/Button.d.ts.map +1 -0
  11. package/dist/client-lib/components/CopyButton.d.ts +6 -0
  12. package/dist/client-lib/components/CopyButton.d.ts.map +1 -0
  13. package/dist/client-lib/components/EnvBadge.d.ts +7 -0
  14. package/dist/client-lib/components/EnvBadge.d.ts.map +1 -0
  15. package/dist/client-lib/components/MatrixGrid.d.ts +13 -0
  16. package/dist/client-lib/components/MatrixGrid.d.ts.map +1 -0
  17. package/dist/client-lib/components/Sidebar.d.ts +16 -0
  18. package/dist/client-lib/components/Sidebar.d.ts.map +1 -0
  19. package/dist/client-lib/components/StatusDot.d.ts +6 -0
  20. package/dist/client-lib/components/StatusDot.d.ts.map +1 -0
  21. package/dist/client-lib/components/TopBar.d.ts +9 -0
  22. package/dist/client-lib/components/TopBar.d.ts.map +1 -0
  23. package/dist/client-lib/index.d.ts +12 -0
  24. package/dist/client-lib/index.d.ts.map +1 -0
  25. package/dist/client-lib/theme.d.ts +42 -0
  26. package/dist/client-lib/theme.d.ts.map +1 -0
  27. package/dist/server/api.d.ts +11 -0
  28. package/dist/server/api.d.ts.map +1 -0
  29. package/dist/server/api.js +1020 -0
  30. package/dist/server/api.js.map +1 -0
  31. package/dist/server/index.d.ts +12 -0
  32. package/dist/server/index.d.ts.map +1 -0
  33. package/dist/server/index.js +231 -0
  34. package/dist/server/index.js.map +1 -0
  35. package/package.json +74 -0
  36. package/src/client/App.tsx +205 -0
  37. package/src/client/api.test.tsx +94 -0
  38. package/src/client/api.ts +30 -0
  39. package/src/client/components/Button.tsx +52 -0
  40. package/src/client/components/CopyButton.test.tsx +43 -0
  41. package/src/client/components/CopyButton.tsx +36 -0
  42. package/src/client/components/EnvBadge.tsx +32 -0
  43. package/src/client/components/MatrixGrid.tsx +265 -0
  44. package/src/client/components/Sidebar.tsx +337 -0
  45. package/src/client/components/StatusDot.tsx +30 -0
  46. package/src/client/components/TopBar.tsx +50 -0
  47. package/src/client/index.html +50 -0
  48. package/src/client/index.ts +18 -0
  49. package/src/client/main.tsx +15 -0
  50. package/src/client/public/favicon-96x96.png +0 -0
  51. package/src/client/public/favicon.ico +0 -0
  52. package/src/client/public/favicon.svg +16 -0
  53. package/src/client/screens/BackendScreen.test.tsx +611 -0
  54. package/src/client/screens/BackendScreen.tsx +836 -0
  55. package/src/client/screens/DiffView.test.tsx +130 -0
  56. package/src/client/screens/DiffView.tsx +547 -0
  57. package/src/client/screens/GitLogView.test.tsx +113 -0
  58. package/src/client/screens/GitLogView.tsx +192 -0
  59. package/src/client/screens/ImportScreen.tsx +710 -0
  60. package/src/client/screens/LintView.test.tsx +143 -0
  61. package/src/client/screens/LintView.tsx +589 -0
  62. package/src/client/screens/MatrixView.test.tsx +138 -0
  63. package/src/client/screens/MatrixView.tsx +143 -0
  64. package/src/client/screens/NamespaceEditor.test.tsx +694 -0
  65. package/src/client/screens/NamespaceEditor.tsx +1122 -0
  66. package/src/client/screens/RecipientsScreen.tsx +696 -0
  67. package/src/client/screens/ScanScreen.test.tsx +323 -0
  68. package/src/client/screens/ScanScreen.tsx +523 -0
  69. package/src/client/screens/ServiceIdentitiesScreen.tsx +1398 -0
  70. package/src/client/theme.ts +48 -0
@@ -0,0 +1,138 @@
1
+ import React from "react";
2
+ import { render, screen, fireEvent } from "@testing-library/react";
3
+ import "@testing-library/jest-dom";
4
+ import { MatrixView } from "./MatrixView";
5
+ import type { ClefManifest, MatrixStatus } from "@clef-sh/core";
6
+
7
+ const manifest: ClefManifest = {
8
+ version: 1,
9
+ environments: [
10
+ { name: "dev", description: "Dev" },
11
+ { name: "production", description: "Prod", protected: true },
12
+ ],
13
+ namespaces: [
14
+ { name: "database", description: "DB" },
15
+ { name: "auth", description: "Auth" },
16
+ ],
17
+ sops: { default_backend: "age" },
18
+ file_pattern: "{namespace}/{environment}.enc.yaml",
19
+ };
20
+
21
+ const healthyStatuses: MatrixStatus[] = [
22
+ {
23
+ cell: {
24
+ namespace: "database",
25
+ environment: "dev",
26
+ filePath: "database/dev.enc.yaml",
27
+ exists: true,
28
+ },
29
+ keyCount: 4,
30
+ pendingCount: 0,
31
+ lastModified: new Date(),
32
+ issues: [],
33
+ },
34
+ {
35
+ cell: {
36
+ namespace: "database",
37
+ environment: "production",
38
+ filePath: "database/production.enc.yaml",
39
+ exists: true,
40
+ },
41
+ keyCount: 3,
42
+ pendingCount: 0,
43
+ lastModified: new Date(),
44
+ issues: [{ type: "missing_keys", message: "Key DB_REPLICA missing", key: "DB_REPLICA" }],
45
+ },
46
+ {
47
+ cell: { namespace: "auth", environment: "dev", filePath: "auth/dev.enc.yaml", exists: true },
48
+ keyCount: 6,
49
+ pendingCount: 0,
50
+ lastModified: new Date(),
51
+ issues: [],
52
+ },
53
+ {
54
+ cell: {
55
+ namespace: "auth",
56
+ environment: "production",
57
+ filePath: "auth/production.enc.yaml",
58
+ exists: true,
59
+ },
60
+ keyCount: 6,
61
+ pendingCount: 0,
62
+ lastModified: new Date(),
63
+ issues: [],
64
+ },
65
+ ];
66
+
67
+ describe("MatrixView", () => {
68
+ it("renders healthy state with summary pills", () => {
69
+ const setView = jest.fn();
70
+ const setNs = jest.fn();
71
+
72
+ render(
73
+ <MatrixView
74
+ setView={setView}
75
+ setNs={setNs}
76
+ manifest={manifest}
77
+ matrixStatuses={healthyStatuses}
78
+ />,
79
+ );
80
+
81
+ expect(screen.getByText("Secret Matrix")).toBeInTheDocument();
82
+ expect(screen.getByText(/healthy/)).toBeInTheDocument();
83
+ expect(screen.getByText(/missing keys/)).toBeInTheDocument();
84
+ expect(screen.getByTestId("matrix-table")).toBeInTheDocument();
85
+ });
86
+
87
+ it("renders loading state when manifest is null", () => {
88
+ render(
89
+ <MatrixView setView={jest.fn()} setNs={jest.fn()} manifest={null} matrixStatuses={[]} />,
90
+ );
91
+
92
+ expect(screen.getByText("Loading manifest...")).toBeInTheDocument();
93
+ });
94
+
95
+ it("renders empty state with zero counts", () => {
96
+ render(
97
+ <MatrixView setView={jest.fn()} setNs={jest.fn()} manifest={manifest} matrixStatuses={[]} />,
98
+ );
99
+
100
+ expect(screen.getByText("0 healthy")).toBeInTheDocument();
101
+ });
102
+
103
+ it("navigates to editor when row is clicked", () => {
104
+ const setView = jest.fn();
105
+ const setNs = jest.fn();
106
+
107
+ render(
108
+ <MatrixView
109
+ setView={setView}
110
+ setNs={setNs}
111
+ manifest={manifest}
112
+ matrixStatuses={healthyStatuses}
113
+ />,
114
+ );
115
+
116
+ const row = screen.getByTestId("matrix-row-database");
117
+ fireEvent.click(row);
118
+
119
+ expect(setNs).toHaveBeenCalledWith("database");
120
+ expect(setView).toHaveBeenCalledWith("editor");
121
+ });
122
+
123
+ it("navigates to diff view when button is clicked", () => {
124
+ const setView = jest.fn();
125
+
126
+ render(
127
+ <MatrixView
128
+ setView={setView}
129
+ setNs={jest.fn()}
130
+ manifest={manifest}
131
+ matrixStatuses={healthyStatuses}
132
+ />,
133
+ );
134
+
135
+ fireEvent.click(screen.getByTestId("diff-environments-btn"));
136
+ expect(setView).toHaveBeenCalledWith("diff");
137
+ });
138
+ });
@@ -0,0 +1,143 @@
1
+ import React from "react";
2
+ import { theme } from "../theme";
3
+ import { TopBar } from "../components/TopBar";
4
+ import { Button } from "../components/Button";
5
+ import { MatrixGrid } from "../components/MatrixGrid";
6
+ import type { ViewName } from "../components/Sidebar";
7
+ import type { ClefManifest, MatrixStatus } from "@clef-sh/core";
8
+
9
+ interface MatrixViewProps {
10
+ setView: (view: ViewName) => void;
11
+ setNs: (ns: string) => void;
12
+ manifest: ClefManifest | null;
13
+ matrixStatuses: MatrixStatus[];
14
+ }
15
+
16
+ export function MatrixView({ setView, setNs, manifest, matrixStatuses }: MatrixViewProps) {
17
+ if (!manifest) {
18
+ return (
19
+ <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
20
+ <TopBar title="Secret Matrix" subtitle="Loading..." />
21
+ <div style={{ flex: 1, padding: 28 }}>
22
+ <p style={{ color: theme.textMuted, fontFamily: theme.sans, fontSize: 13 }}>
23
+ Loading manifest...
24
+ </p>
25
+ </div>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ const environments = manifest.environments;
31
+ const namespaces = manifest.namespaces;
32
+ const matrixLoading = matrixStatuses.length === 0 && namespaces.length > 0;
33
+
34
+ const healthyCount = matrixStatuses.filter((s) => s.cell.exists && s.issues.length === 0).length;
35
+ const missingCount = matrixStatuses.filter(
36
+ (s) => !s.cell.exists || s.issues.some((i) => i.type === "missing_keys"),
37
+ ).length;
38
+ const warnCount = matrixStatuses.filter((s) =>
39
+ s.issues.some((i) => i.type === "schema_warning"),
40
+ ).length;
41
+ const totalPending = matrixStatuses.reduce((sum, s) => sum + (s.pendingCount ?? 0), 0);
42
+
43
+ const fileCount = namespaces.length * environments.length;
44
+
45
+ return (
46
+ <div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
47
+ <TopBar
48
+ title="Secret Matrix"
49
+ subtitle={`${namespaces.length} namespaces \u00B7 ${environments.length} environments \u00B7 ${fileCount} files`}
50
+ actions={
51
+ <>
52
+ <Button onClick={() => setView("lint")}>Lint All</Button>
53
+ <Button variant="primary">+ Namespace</Button>
54
+ </>
55
+ }
56
+ />
57
+
58
+ <div style={{ flex: 1, overflow: "auto", padding: 28, position: "relative" }}>
59
+ {matrixLoading && (
60
+ <div
61
+ style={{
62
+ position: "absolute",
63
+ inset: 0,
64
+ display: "flex",
65
+ alignItems: "center",
66
+ justifyContent: "center",
67
+ background: `${theme.bg}dd`,
68
+ zIndex: 10,
69
+ borderRadius: 8,
70
+ }}
71
+ >
72
+ <div
73
+ style={{
74
+ background: theme.surface,
75
+ border: `1px solid ${theme.border}`,
76
+ borderRadius: 10,
77
+ padding: "24px 36px",
78
+ textAlign: "center",
79
+ }}
80
+ >
81
+ <div style={{ fontSize: 20, color: theme.accent, marginBottom: 8 }}>{"\u266A"}</div>
82
+ <div style={{ fontFamily: theme.sans, fontSize: 13, color: theme.textMuted }}>
83
+ Loading...
84
+ </div>
85
+ </div>
86
+ </div>
87
+ )}
88
+ {/* Summary pills */}
89
+ <div style={{ display: "flex", gap: 10, marginBottom: 28 }}>
90
+ {[
91
+ { label: `${healthyCount} healthy`, color: theme.green },
92
+ { label: `${missingCount} missing keys`, color: theme.red },
93
+ {
94
+ label: `${warnCount} schema warning${warnCount !== 1 ? "s" : ""}`,
95
+ color: theme.yellow,
96
+ },
97
+ ...(totalPending > 0
98
+ ? [
99
+ {
100
+ label: `${totalPending} pending value${totalPending !== 1 ? "s" : ""}`,
101
+ color: theme.accent,
102
+ },
103
+ ]
104
+ : []),
105
+ ].map((p) => (
106
+ <div
107
+ key={p.label}
108
+ style={{
109
+ fontFamily: theme.sans,
110
+ fontSize: 12,
111
+ fontWeight: 500,
112
+ color: p.color,
113
+ background: `${p.color}14`,
114
+ border: `1px solid ${p.color}33`,
115
+ borderRadius: 20,
116
+ padding: "4px 14px",
117
+ }}
118
+ >
119
+ {p.label}
120
+ </div>
121
+ ))}
122
+ </div>
123
+
124
+ <MatrixGrid
125
+ namespaces={namespaces}
126
+ environments={environments}
127
+ matrixStatuses={matrixStatuses}
128
+ onNamespaceClick={(nsName) => {
129
+ setNs(nsName);
130
+ setView("editor");
131
+ }}
132
+ />
133
+
134
+ {/* Quick actions */}
135
+ <div style={{ marginTop: 20, display: "flex", gap: 10 }}>
136
+ <Button data-testid="diff-environments-btn" onClick={() => setView("diff")}>
137
+ Diff environments
138
+ </Button>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ );
143
+ }