Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const SettingsPage = lazy(() => import("@/pages/settings/SettingsPage"));
const AlertsPage = lazy(() => import("@/pages/alerts/AlertsPage"));
const SpendPage = lazy(() => import("@/pages/spend/SpendPage"));
const OnchainPage = lazy(() => import("@/pages/onchain/OnchainPage"));
const ExchangeConfigPage = lazy(() => import("@/pages/exchange-config/ExchangeConfigPage"));
const ZkProofsPage = lazy(() => import("@/pages/zk-proofs/ZkProofsPage"));
const LoginPage = lazy(() => import("@/pages/login/LoginPage"));
const LandingPage = lazy(() => import("@/pages/landing/LandingPage"));
const DocsLayout = lazy(() => import("@/pages/docs/DocsLayout"));
Expand Down Expand Up @@ -178,6 +180,22 @@ export default function App() {
</Suspense>
}
/>
<Route
path={ROUTES.EXCHANGE_CONFIG}
element={
<Suspense fallback={<PageLoader />}>
<ExchangeConfigPage />
</Suspense>
}
/>
<Route
path={ROUTES.ZK_PROOFS}
element={
<Suspense fallback={<PageLoader />}>
<ZkProofsPage />
</Suspense>
}
/>
<Route
path={ROUTES.SETTINGS}
element={
Expand Down
44 changes: 44 additions & 0 deletions dashboard/src/api/endpoints/exchange-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { apiFetch } from "../client";
import type {
ExchangeConfigResponse,
AddExchangePayload,
UpdateEndpointPayload,
UpdateExchangeLimitsPayload,
} from "../types";

export function fetchExchangeConfig(): Promise<ExchangeConfigResponse> {
return apiFetch<ExchangeConfigResponse>("/exchange-config");
}

export function addExchange(
payload: AddExchangePayload,
): Promise<{ success: boolean }> {
return apiFetch("/exchange-config", {
method: "POST",
body: JSON.stringify(payload),
});
}

export function removeExchange(
id: string,
): Promise<{ success: boolean }> {
return apiFetch(`/exchange-config/${id}`, { method: "DELETE" });
}

export function updateEndpointToggle(
payload: UpdateEndpointPayload,
): Promise<{ success: boolean }> {
return apiFetch("/exchange-config/endpoint", {
method: "PUT",
body: JSON.stringify(payload),
});
}

export function updateExchangeLimits(
payload: UpdateExchangeLimitsPayload,
): Promise<{ success: boolean }> {
return apiFetch(`/exchange-config/${payload.exchange_id}/limits`, {
method: "PUT",
body: JSON.stringify(payload),
});
}
31 changes: 31 additions & 0 deletions dashboard/src/api/endpoints/proofs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { apiFetch } from "../client";
import { API_BASE } from "@/lib/constants";
import type {
ProofGeneratePayload,
ProofGenerateResponse,
ProofJobStatusResponse,
ProofHistoryResponse,
} from "../types";

export function generateProof(
payload: ProofGeneratePayload,
): Promise<ProofGenerateResponse> {
return apiFetch<ProofGenerateResponse>(
`/proof/generate?from=${payload.from_date}&to=${payload.to_date}`,
{ method: "POST" },
);
}

export function fetchProofJobStatus(
jobId: string,
): Promise<ProofJobStatusResponse> {
return apiFetch<ProofJobStatusResponse>(`/proof/${jobId}`);
}

export function fetchProofHistory(): Promise<ProofHistoryResponse> {
return apiFetch<ProofHistoryResponse>("/proof/history");
}

export function getProofDownloadUrl(proofId: string): string {
return `${API_BASE}/proof/${proofId}/download`;
}
79 changes: 79 additions & 0 deletions dashboard/src/api/endpoints/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { apiFetch, TOKEN_KEY } from "../client";
import { API_BASE } from "@/lib/constants";
import type {
ChangePasswordPayload,
VaultBackupResponse,
NetworkIsolationResponse,
SignerModeResponse,
SignerModeType,
FactoryResetResponse,
} from "../types";

export function changePassword(
payload: ChangePasswordPayload,
): Promise<{ success: boolean }> {
return apiFetch("/settings/password", {
method: "POST",
body: JSON.stringify(payload),
});
}

export function fetchVaultBackupUrl(): Promise<VaultBackupResponse> {
return apiFetch<VaultBackupResponse>("/settings/vault/backup");
}

export async function restoreVault(
file: File,
): Promise<{ success: boolean }> {
const token = localStorage.getItem(TOKEN_KEY);
const form = new FormData();
form.append("file", file);

const res = await fetch(`${API_BASE}/settings/vault/restore`, {
method: "POST",
headers: token ? { Authorization: `Bearer ${token}` } : {},
body: form,
});

if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }));
throw new Error(body.error ?? "Restore failed");
}

return res.json();
}

export function fetchNetworkIsolation(): Promise<NetworkIsolationResponse> {
return apiFetch<NetworkIsolationResponse>("/settings/network-isolation");
}

export function updateNetworkIsolation(
enabled: boolean,
): Promise<NetworkIsolationResponse> {
return apiFetch<NetworkIsolationResponse>("/settings/network-isolation", {
method: "PUT",
body: JSON.stringify({ enabled }),
});
}

export function fetchSignerMode(): Promise<SignerModeResponse> {
return apiFetch<SignerModeResponse>("/settings/signer-mode");
}

export function updateSignerMode(
mode: SignerModeType,
): Promise<SignerModeResponse> {
return apiFetch<SignerModeResponse>("/settings/signer-mode", {
method: "PUT",
body: JSON.stringify({ mode }),
});
}

export function factoryReset(
confirmToken: string,
): Promise<FactoryResetResponse> {
return apiFetch<FactoryResetResponse>("/settings/factory-reset", {
method: "POST",
body: JSON.stringify({ confirm: confirmToken }),
});
}
113 changes: 113 additions & 0 deletions dashboard/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,119 @@ export interface PolicyQuickConfigPayload {
rate_limit_rpm: number;
}

// --- Exchange Config ---
export type EndpointPermission = "always_allowed" | "toggleable" | "permanently_blocked";

export interface ExchangeEndpoint {
pattern: string;
method: "GET" | "POST" | "PUT" | "DELETE";
permission: EndpointPermission;
enabled: boolean;
description: string;
max_order_value?: number;
daily_volume_cap?: number;
}

export interface Exchange {
id: string;
name: string;
base_url: string;
auth_pattern: string;
status: "connected" | "disconnected" | "error";
endpoints: ExchangeEndpoint[];
volume: { today_volume_usd: number; daily_cap_usd: number };
limits: { max_order_value_usd: number; daily_volume_cap_usd: number };
}

export interface ExchangeConfigResponse {
exchanges: Exchange[];
}

export interface AddExchangePayload {
name: string;
base_url: string;
auth_pattern: string;
blocked_endpoints: string[];
}

export interface UpdateEndpointPayload {
exchange_id: string;
endpoint_pattern: string;
enabled: boolean;
max_order_value?: number;
daily_volume_cap?: number;
}

export interface UpdateExchangeLimitsPayload {
exchange_id: string;
max_order_value_usd: number;
daily_volume_cap_usd: number;
}

// --- ZK Proofs ---
export type ProofJobStatus = "pending" | "generating" | "completed" | "failed";

export interface ProofGeneratePayload {
from_date: string;
to_date: string;
}

export interface ProofGenerateResponse {
job_id: string;
}

export interface ProofJobStatusResponse {
job_id: string;
status: ProofJobStatus;
progress_pct: number;
error?: string;
}

export interface ProofResult {
id: string;
job_id: string;
generated_at: number;
from_date: string;
to_date: string;
entries_covered: number;
merkle_root: string;
policy_hash: string;
spend_status: "within_budget" | "over_budget" | "no_data";
download_url: string;
}

export interface ProofHistoryResponse {
proofs: ProofResult[];
}

// --- Settings (extended) ---
export interface ChangePasswordPayload {
current_password: string;
new_password: string;
confirm_password: string;
}

export interface VaultBackupResponse {
download_url: string;
filename: string;
}

export interface NetworkIsolationResponse {
enabled: boolean;
status: "active" | "inactive" | "error";
}

export type SignerModeType = "secure_enclave" | "encrypted_keyfile" | "threshold";

export interface SignerModeResponse {
current: SignerModeType;
available: SignerModeType[];
}

export interface FactoryResetResponse {
success: boolean;
}

// --- Generic ---
export interface ApiError {
error: string;
Expand Down
4 changes: 4 additions & 0 deletions dashboard/src/components/layout/Shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const routeTitles: Record<string, string> = {
[ROUTES.ALERTS]: "Alerts",
[ROUTES.SPEND]: "Spend Analytics",
[ROUTES.ONCHAIN]: "Onchain Permits",
[ROUTES.EXCHANGE_CONFIG]: "Exchange Config",
[ROUTES.ZK_PROOFS]: "ZK Proofs",
};

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

export function Shell() {
Expand Down
4 changes: 4 additions & 0 deletions dashboard/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ROUTES } from "@/lib/constants";
import {
LayoutDashboard,
Key,
ArrowLeftRight,
Sliders,
FileText,
BarChart3,
Expand All @@ -15,6 +16,7 @@ import {
ChevronRight,
LogOut,
Shield,
ShieldCheck,
} from "lucide-react";
interface SidebarProps {
collapsed: boolean;
Expand All @@ -35,10 +37,12 @@ interface NavItemData {
const mainNavItems: NavItemData[] = [
{ to: ROUTES.HOME, label: "Dashboard", icon: <LayoutDashboard size={18} /> },
{ label: "Credentials", icon: <Key size={18} />, disabled: true },
{ to: ROUTES.EXCHANGE_CONFIG, label: "Exchange Config", icon: <ArrowLeftRight size={18} /> },
{ label: "Policies", icon: <Sliders size={18} />, disabled: true },
{ label: "Audit Log", icon: <FileText size={18} />, disabled: true },
{ to: ROUTES.SPEND, label: "Spend", icon: <BarChart3 size={18} /> },
{ to: ROUTES.ONCHAIN, label: "Onchain", icon: <Shield size={18} /> },
{ to: ROUTES.ZK_PROOFS, label: "ZK Proofs", icon: <ShieldCheck size={18} /> },
];

function getSecondaryNavItems(alertCount: number): NavItemData[] {
Expand Down
Loading