Skip to content

Commit ce26b9c

Browse files
committed
Add exchange config, ZK proofs, and full settings pages
1 parent ee4fc23 commit ce26b9c

26 files changed

+1857
-0
lines changed

dashboard/src/App.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const SettingsPage = lazy(() => import("@/pages/settings/SettingsPage"));
1414
const AlertsPage = lazy(() => import("@/pages/alerts/AlertsPage"));
1515
const SpendPage = lazy(() => import("@/pages/spend/SpendPage"));
1616
const OnchainPage = lazy(() => import("@/pages/onchain/OnchainPage"));
17+
const ExchangeConfigPage = lazy(() => import("@/pages/exchange-config/ExchangeConfigPage"));
18+
const ZkProofsPage = lazy(() => import("@/pages/zk-proofs/ZkProofsPage"));
1719
const LoginPage = lazy(() => import("@/pages/login/LoginPage"));
1820
const LandingPage = lazy(() => import("@/pages/landing/LandingPage"));
1921
const DocsLayout = lazy(() => import("@/pages/docs/DocsLayout"));
@@ -178,6 +180,22 @@ export default function App() {
178180
</Suspense>
179181
}
180182
/>
183+
<Route
184+
path={ROUTES.EXCHANGE_CONFIG}
185+
element={
186+
<Suspense fallback={<PageLoader />}>
187+
<ExchangeConfigPage />
188+
</Suspense>
189+
}
190+
/>
191+
<Route
192+
path={ROUTES.ZK_PROOFS}
193+
element={
194+
<Suspense fallback={<PageLoader />}>
195+
<ZkProofsPage />
196+
</Suspense>
197+
}
198+
/>
181199
<Route
182200
path={ROUTES.SETTINGS}
183201
element={
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { apiFetch } from "../client";
2+
import type {
3+
ExchangeConfigResponse,
4+
AddExchangePayload,
5+
UpdateEndpointPayload,
6+
UpdateExchangeLimitsPayload,
7+
} from "../types";
8+
9+
export function fetchExchangeConfig(): Promise<ExchangeConfigResponse> {
10+
return apiFetch<ExchangeConfigResponse>("/exchange-config");
11+
}
12+
13+
export function addExchange(
14+
payload: AddExchangePayload,
15+
): Promise<{ success: boolean }> {
16+
return apiFetch("/exchange-config", {
17+
method: "POST",
18+
body: JSON.stringify(payload),
19+
});
20+
}
21+
22+
export function removeExchange(
23+
id: string,
24+
): Promise<{ success: boolean }> {
25+
return apiFetch(`/exchange-config/${id}`, { method: "DELETE" });
26+
}
27+
28+
export function updateEndpointToggle(
29+
payload: UpdateEndpointPayload,
30+
): Promise<{ success: boolean }> {
31+
return apiFetch("/exchange-config/endpoint", {
32+
method: "PUT",
33+
body: JSON.stringify(payload),
34+
});
35+
}
36+
37+
export function updateExchangeLimits(
38+
payload: UpdateExchangeLimitsPayload,
39+
): Promise<{ success: boolean }> {
40+
return apiFetch(`/exchange-config/${payload.exchange_id}/limits`, {
41+
method: "PUT",
42+
body: JSON.stringify(payload),
43+
});
44+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { apiFetch } from "../client";
2+
import { API_BASE } from "@/lib/constants";
3+
import type {
4+
ProofGeneratePayload,
5+
ProofGenerateResponse,
6+
ProofJobStatusResponse,
7+
ProofHistoryResponse,
8+
} from "../types";
9+
10+
export function generateProof(
11+
payload: ProofGeneratePayload,
12+
): Promise<ProofGenerateResponse> {
13+
return apiFetch<ProofGenerateResponse>(
14+
`/proof/generate?from=${payload.from_date}&to=${payload.to_date}`,
15+
{ method: "POST" },
16+
);
17+
}
18+
19+
export function fetchProofJobStatus(
20+
jobId: string,
21+
): Promise<ProofJobStatusResponse> {
22+
return apiFetch<ProofJobStatusResponse>(`/proof/${jobId}`);
23+
}
24+
25+
export function fetchProofHistory(): Promise<ProofHistoryResponse> {
26+
return apiFetch<ProofHistoryResponse>("/proof/history");
27+
}
28+
29+
export function getProofDownloadUrl(proofId: string): string {
30+
return `${API_BASE}/proof/${proofId}/download`;
31+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { apiFetch, TOKEN_KEY } from "../client";
2+
import { API_BASE } from "@/lib/constants";
3+
import type {
4+
ChangePasswordPayload,
5+
VaultBackupResponse,
6+
NetworkIsolationResponse,
7+
SignerModeResponse,
8+
SignerModeType,
9+
FactoryResetResponse,
10+
} from "../types";
11+
12+
export function changePassword(
13+
payload: ChangePasswordPayload,
14+
): Promise<{ success: boolean }> {
15+
return apiFetch("/settings/password", {
16+
method: "POST",
17+
body: JSON.stringify(payload),
18+
});
19+
}
20+
21+
export function fetchVaultBackupUrl(): Promise<VaultBackupResponse> {
22+
return apiFetch<VaultBackupResponse>("/settings/vault/backup");
23+
}
24+
25+
export async function restoreVault(
26+
file: File,
27+
): Promise<{ success: boolean }> {
28+
const token = localStorage.getItem(TOKEN_KEY);
29+
const form = new FormData();
30+
form.append("file", file);
31+
32+
const res = await fetch(`${API_BASE}/settings/vault/restore`, {
33+
method: "POST",
34+
headers: token ? { Authorization: `Bearer ${token}` } : {},
35+
body: form,
36+
});
37+
38+
if (!res.ok) {
39+
const body = await res.json().catch(() => ({ error: res.statusText }));
40+
throw new Error(body.error ?? "Restore failed");
41+
}
42+
43+
return res.json();
44+
}
45+
46+
export function fetchNetworkIsolation(): Promise<NetworkIsolationResponse> {
47+
return apiFetch<NetworkIsolationResponse>("/settings/network-isolation");
48+
}
49+
50+
export function updateNetworkIsolation(
51+
enabled: boolean,
52+
): Promise<NetworkIsolationResponse> {
53+
return apiFetch<NetworkIsolationResponse>("/settings/network-isolation", {
54+
method: "PUT",
55+
body: JSON.stringify({ enabled }),
56+
});
57+
}
58+
59+
export function fetchSignerMode(): Promise<SignerModeResponse> {
60+
return apiFetch<SignerModeResponse>("/settings/signer-mode");
61+
}
62+
63+
export function updateSignerMode(
64+
mode: SignerModeType,
65+
): Promise<SignerModeResponse> {
66+
return apiFetch<SignerModeResponse>("/settings/signer-mode", {
67+
method: "PUT",
68+
body: JSON.stringify({ mode }),
69+
});
70+
}
71+
72+
export function factoryReset(
73+
confirmToken: string,
74+
): Promise<FactoryResetResponse> {
75+
return apiFetch<FactoryResetResponse>("/settings/factory-reset", {
76+
method: "POST",
77+
body: JSON.stringify({ confirm: confirmToken }),
78+
});
79+
}

dashboard/src/api/types.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,119 @@ export interface PolicyQuickConfigPayload {
253253
rate_limit_rpm: number;
254254
}
255255

256+
// --- Exchange Config ---
257+
export type EndpointPermission = "always_allowed" | "toggleable" | "permanently_blocked";
258+
259+
export interface ExchangeEndpoint {
260+
pattern: string;
261+
method: "GET" | "POST" | "PUT" | "DELETE";
262+
permission: EndpointPermission;
263+
enabled: boolean;
264+
description: string;
265+
max_order_value?: number;
266+
daily_volume_cap?: number;
267+
}
268+
269+
export interface Exchange {
270+
id: string;
271+
name: string;
272+
base_url: string;
273+
auth_pattern: string;
274+
status: "connected" | "disconnected" | "error";
275+
endpoints: ExchangeEndpoint[];
276+
volume: { today_volume_usd: number; daily_cap_usd: number };
277+
limits: { max_order_value_usd: number; daily_volume_cap_usd: number };
278+
}
279+
280+
export interface ExchangeConfigResponse {
281+
exchanges: Exchange[];
282+
}
283+
284+
export interface AddExchangePayload {
285+
name: string;
286+
base_url: string;
287+
auth_pattern: string;
288+
blocked_endpoints: string[];
289+
}
290+
291+
export interface UpdateEndpointPayload {
292+
exchange_id: string;
293+
endpoint_pattern: string;
294+
enabled: boolean;
295+
max_order_value?: number;
296+
daily_volume_cap?: number;
297+
}
298+
299+
export interface UpdateExchangeLimitsPayload {
300+
exchange_id: string;
301+
max_order_value_usd: number;
302+
daily_volume_cap_usd: number;
303+
}
304+
305+
// --- ZK Proofs ---
306+
export type ProofJobStatus = "pending" | "generating" | "completed" | "failed";
307+
308+
export interface ProofGeneratePayload {
309+
from_date: string;
310+
to_date: string;
311+
}
312+
313+
export interface ProofGenerateResponse {
314+
job_id: string;
315+
}
316+
317+
export interface ProofJobStatusResponse {
318+
job_id: string;
319+
status: ProofJobStatus;
320+
progress_pct: number;
321+
error?: string;
322+
}
323+
324+
export interface ProofResult {
325+
id: string;
326+
job_id: string;
327+
generated_at: number;
328+
from_date: string;
329+
to_date: string;
330+
entries_covered: number;
331+
merkle_root: string;
332+
policy_hash: string;
333+
spend_status: "within_budget" | "over_budget" | "no_data";
334+
download_url: string;
335+
}
336+
337+
export interface ProofHistoryResponse {
338+
proofs: ProofResult[];
339+
}
340+
341+
// --- Settings (extended) ---
342+
export interface ChangePasswordPayload {
343+
current_password: string;
344+
new_password: string;
345+
confirm_password: string;
346+
}
347+
348+
export interface VaultBackupResponse {
349+
download_url: string;
350+
filename: string;
351+
}
352+
353+
export interface NetworkIsolationResponse {
354+
enabled: boolean;
355+
status: "active" | "inactive" | "error";
356+
}
357+
358+
export type SignerModeType = "secure_enclave" | "encrypted_keyfile" | "threshold";
359+
360+
export interface SignerModeResponse {
361+
current: SignerModeType;
362+
available: SignerModeType[];
363+
}
364+
365+
export interface FactoryResetResponse {
366+
success: boolean;
367+
}
368+
256369
// --- Generic ---
257370
export interface ApiError {
258371
error: string;

dashboard/src/components/layout/Shell.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const routeTitles: Record<string, string> = {
1313
[ROUTES.ALERTS]: "Alerts",
1414
[ROUTES.SPEND]: "Spend Analytics",
1515
[ROUTES.ONCHAIN]: "Onchain Permits",
16+
[ROUTES.EXCHANGE_CONFIG]: "Exchange Config",
17+
[ROUTES.ZK_PROOFS]: "ZK Proofs",
1618
};
1719

1820
const routeSubtitles: Record<string, string> = {
@@ -21,6 +23,8 @@ const routeSubtitles: Record<string, string> = {
2123
[ROUTES.ALERTS]: "Monitor and manage security and budget alerts",
2224
[ROUTES.SPEND]: "Budget tracking and daily spend breakdown",
2325
[ROUTES.ONCHAIN]: "Contract whitelist, permit history, and signer status",
26+
[ROUTES.EXCHANGE_CONFIG]: "Configure exchange connections, endpoints, and volume limits",
27+
[ROUTES.ZK_PROOFS]: "Generate and manage zero-knowledge compliance proofs",
2428
};
2529

2630
export function Shell() {

dashboard/src/components/layout/Sidebar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ROUTES } from "@/lib/constants";
66
import {
77
LayoutDashboard,
88
Key,
9+
ArrowLeftRight,
910
Sliders,
1011
FileText,
1112
BarChart3,
@@ -15,6 +16,7 @@ import {
1516
ChevronRight,
1617
LogOut,
1718
Shield,
19+
ShieldCheck,
1820
} from "lucide-react";
1921
interface SidebarProps {
2022
collapsed: boolean;
@@ -35,10 +37,12 @@ interface NavItemData {
3537
const mainNavItems: NavItemData[] = [
3638
{ to: ROUTES.HOME, label: "Dashboard", icon: <LayoutDashboard size={18} /> },
3739
{ label: "Credentials", icon: <Key size={18} />, disabled: true },
40+
{ to: ROUTES.EXCHANGE_CONFIG, label: "Exchange Config", icon: <ArrowLeftRight size={18} /> },
3841
{ label: "Policies", icon: <Sliders size={18} />, disabled: true },
3942
{ label: "Audit Log", icon: <FileText size={18} />, disabled: true },
4043
{ to: ROUTES.SPEND, label: "Spend", icon: <BarChart3 size={18} /> },
4144
{ to: ROUTES.ONCHAIN, label: "Onchain", icon: <Shield size={18} /> },
45+
{ to: ROUTES.ZK_PROOFS, label: "ZK Proofs", icon: <ShieldCheck size={18} /> },
4246
];
4347

4448
function getSecondaryNavItems(alertCount: number): NavItemData[] {

0 commit comments

Comments
 (0)