Skip to content

Commit dcde617

Browse files
committed
feat(ui): credential vault page — list, add, delete with type-specific forms
- ui/src/api/credentials.ts: typed API client for vault CRUD (listCredentials, createCredential, updateCredential, deleteCredential) - ui/src/pages/Credentials.tsx: full-featured Credentials page - Table listing all credentials (label, type badge, device_id, created_by, timestamp) - Filter by label substring (debounced) and credential type - Add modal with type-specific field forms (SSH / SNMP / API key / password / custom JSON) - Delete button (admin-only) with confirm dialog - Encrypted-at-rest info banner - ui/src/App.tsx: /credentials route - ui/src/components/Layout.tsx: 🔑 Credentials nav entry Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AbfhpQR6qzZAXcTacEcZvn
1 parent 17d5ed6 commit dcde617

4 files changed

Lines changed: 502 additions & 0 deletions

File tree

ui/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Networks from "./pages/Networks";
1111
import Topology from "./pages/Topology";
1212
import Devices from "./pages/Devices";
1313
import Teams from "./pages/Teams";
14+
import Credentials from "./pages/Credentials";
1415

1516
export default function App() {
1617
const auth = useAuthProvider();
@@ -36,6 +37,7 @@ export default function App() {
3637
<Route path="devices" element={<Devices />} />
3738
<Route path="vulns" element={<Vulns />} />
3839
<Route path="teams" element={<Teams />} />
40+
<Route path="credentials" element={<Credentials />} />
3941
</Route>
4042
<Route path="*" element={<Navigate to="/" replace />} />
4143
</Routes>

ui/src/api/credentials.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { apiFetch } from "./client";
2+
3+
export interface Credential {
4+
id: string;
5+
label: string;
6+
credential_type: string;
7+
device_id: string | null;
8+
created_by: string;
9+
updated_by: string | null;
10+
created_at: string;
11+
updated_at: string;
12+
}
13+
14+
export interface CredentialCreate {
15+
label: string;
16+
credential_type: string;
17+
device_id?: string | null;
18+
data: Record<string, string>;
19+
}
20+
21+
export interface CredentialUpdate {
22+
label?: string;
23+
device_id?: string | null;
24+
data?: Record<string, string>;
25+
}
26+
27+
export const listCredentials = (params?: {
28+
credential_type?: string;
29+
label?: string;
30+
skip?: number;
31+
limit?: number;
32+
}) => {
33+
const qs = new URLSearchParams();
34+
if (params?.credential_type) qs.set("credential_type", params.credential_type);
35+
if (params?.label) qs.set("label", params.label);
36+
if (params?.skip != null) qs.set("skip", String(params.skip));
37+
if (params?.limit != null) qs.set("limit", String(params.limit));
38+
const q = qs.toString();
39+
return apiFetch<Credential[]>(`/api/v1/vault/credentials${q ? `?${q}` : ""}`);
40+
};
41+
42+
export const createCredential = (body: CredentialCreate) =>
43+
apiFetch<Credential>("/api/v1/vault/credentials", {
44+
method: "POST",
45+
body: JSON.stringify(body),
46+
});
47+
48+
export const updateCredential = (id: string, body: CredentialUpdate) =>
49+
apiFetch<Credential>(`/api/v1/vault/credentials/${id}`, {
50+
method: "PATCH",
51+
body: JSON.stringify(body),
52+
});
53+
54+
export const deleteCredential = (id: string) =>
55+
apiFetch<void>(`/api/v1/vault/credentials/${id}`, { method: "DELETE" });

ui/src/components/Layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const NAV_ITEMS = [
1010
{ to: "/devices", label: "Devices", icon: "⊞" },
1111
{ to: "/vulns", label: "Vulnerabilities", icon: "🛡" },
1212
{ to: "/teams", label: "Teams", icon: "👥" },
13+
{ to: "/credentials", label: "Credentials", icon: "🔑" },
1314
];
1415

1516
export default function Layout() {

0 commit comments

Comments
 (0)