const API_BASE = '/admin/api'; type ApiError = { error?: string }; async function request(path: string, options: RequestInit = {}): Promise { const headers = new Headers(options.headers); if (options.body && !headers.has('Content-Type')) { headers.set('Content-Type', 'application/json'); } const response = await fetch(`${API_BASE}${path}`, { ...options, headers, credentials: 'include', }); const text = await response.text(); const data = text ? (JSON.parse(text) as T | ApiError) : undefined; if (!response.ok) { const err = (data as ApiError | undefined)?.error; throw new Error(err || `Request failed (${response.status})`); } return data as T; } export type UserSummary = { id: string; email: string; display_name?: string | null; disabled: boolean; super_admin: boolean; created_at: string; updated_at: string; }; export type AuthUserSummary = { id: string; email: string; display_name?: string | null; super_admin: boolean; }; export type AuthSession = { user: AuthUserSummary; permissions: string[]; console_access: boolean; }; export type MembershipSummary = { id: string; role_id: string; role_name: string; scope_type: string; scope_id: string; created_at: string; }; export type UserDetail = { user: UserSummary; memberships: MembershipSummary[]; }; export type RoleSummary = { id: string; name: string; description?: string | null; permissions: string[]; }; export type ControlPlaneSummary = { id: string; name: string; base_url: string; region?: string | null; has_admin_token: boolean; created_at: string; updated_at: string; }; export type NetworkSummary = { id: string; control_plane_id: string; control_plane_name: string; network_id: string; name: string; dns_domain?: string | null; overlay_v4?: string | null; overlay_v6?: string | null; requires_approval: boolean; created_at: string; updated_at: string; }; export type RouteInfo = { prefix: string; kind: string; enabled: boolean; mapped_prefix?: string | null; }; export type NodeInfo = { id: string; name: string; dns_name: string; ipv4: string; ipv6: string; wg_public_key: string; machine_public_key: string; endpoints: string[]; tags: string[]; owner_user_id?: string | null; owner_email?: string | null; owner_is_admin?: boolean; routes: RouteInfo[]; last_seen: number; approved: boolean; key_rotation_required?: boolean; revoked?: boolean; }; export type AdminNodesResponse = { nodes: NodeInfo[]; }; export type EnrollmentToken = { token: string; expires_at: number; uses_left: number; tags: string[]; owner_user_id?: string | null; owner_email?: string | null; owner_is_admin?: boolean; revoked_at?: number | null; }; export type CreateNetworkResult = { network: NetworkSummary; bootstrap_token?: EnrollmentToken | null; }; export type CreateTokenResponse = { token: EnrollmentToken; }; export type KeyPolicyResponse = { policy: { max_age_seconds?: number | null; }; }; export type KeyHistoryResponse = { node_id: string; keys: { key_type: string; public_key: string; created_at: number; revoked_at?: number | null; }[]; }; export type ApproveNodeResponse = { node_id: string; approved: boolean; approved_at?: number | null; }; export type RevokeNodeResponse = { node_id: string; revoked_at?: number | null; }; export type KeyRotationResponse = { node_id: string; machine_public_key: string; wg_public_key: string; }; export type AuditEntry = { id: string; network_id?: string | null; node_id?: string | null; action: string; timestamp: number; detail?: unknown; }; export type AuditLogResponse = { entries: AuditEntry[]; }; export type AdminAuditEntry = { id: string; actor_user_id?: string | null; actor_email?: string | null; action: string; target_type?: string | null; target_id?: string | null; metadata?: unknown; created_at: string; }; export type ProviderSummary = { id: string; name: string; }; export async function getMe() { return request('/auth/me'); } export async function login(email: string, password: string) { return request('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }), }); } export async function logout() { return request<{ ok: boolean }>('/auth/logout', { method: 'POST' }); } export async function listProviders() { return request('/auth/providers'); } export async function listControlPlanes() { return request('/control-planes'); } export async function createControlPlane(payload: { name: string; base_url: string; admin_token?: string; region?: string; }) { return request('/control-planes', { method: 'POST', body: JSON.stringify(payload), }); } export async function updateControlPlane(id: string, payload: { name?: string; base_url?: string; admin_token?: string; clear_admin_token?: boolean; region?: string; }) { return request(`/control-planes/${id}`, { method: 'PUT', body: JSON.stringify(payload), }); } export async function verifyControlPlane(id: string) { return request<{ ok: boolean; status?: number; body?: string }>( `/control-planes/${id}/verify`, { method: 'POST' }, ); } export async function listNetworks() { return request('/networks'); } export async function createNetwork(payload: { control_plane_id: string; name: string; overlay_v4?: string; overlay_v6?: string; dns_domain?: string; requires_approval?: boolean; key_rotation_max_age_seconds?: number; bootstrap_token_ttl_seconds?: number; bootstrap_token_uses?: number; bootstrap_token_tags?: string[]; }) { return request('/networks', { method: 'POST', body: JSON.stringify(payload), }); } export async function listNodes(networkId: string) { return request(`/networks/${networkId}/nodes`); } export async function approveNode(networkId: string, nodeId: string) { return request(`/networks/${networkId}/nodes/${nodeId}/approve`, { method: 'POST', }); } export async function revokeNode(networkId: string, nodeId: string) { return request(`/networks/${networkId}/nodes/${nodeId}/revoke`, { method: 'POST', }); } export async function rotateNodeKeys( networkId: string, nodeId: string, payload: { machine_public_key?: string; wg_public_key?: string }, ) { return request(`/networks/${networkId}/nodes/${nodeId}/rotate-keys`, { method: 'POST', body: JSON.stringify(payload), }); } export async function nodeKeys(networkId: string, nodeId: string) { return request(`/networks/${networkId}/nodes/${nodeId}/keys`); } export async function createToken(networkId: string, payload: { ttl_seconds: number; uses: number; tags: string[]; }) { return request(`/networks/${networkId}/tokens`, { method: 'POST', body: JSON.stringify(payload), }); } export async function revokeToken(networkId: string, token_id: string) { return request(`/networks/${networkId}/tokens/revoke`, { method: 'POST', body: JSON.stringify({ token_id }), }); } export async function getAcl(networkId: string) { return request(`/networks/${networkId}/acl`); } export async function updateAcl(networkId: string, policy: unknown) { return request(`/networks/${networkId}/acl`, { method: 'PUT', body: JSON.stringify({ policy }), }); } export async function getKeyPolicy(networkId: string) { return request(`/networks/${networkId}/key-policy`); } export async function updateKeyPolicy(networkId: string, payload: { max_age_seconds?: number | null }) { return request(`/networks/${networkId}/key-policy`, { method: 'PUT', body: JSON.stringify(payload), }); } export async function listUsers() { return request('/users'); } export async function getUser(userId: string) { return request(`/users/${userId}`); } export async function createUser(payload: { email: string; display_name?: string; password: string; role_id?: string; super_admin?: boolean; }) { return request('/users', { method: 'POST', body: JSON.stringify(payload), }); } export async function updateUser(userId: string, payload: { display_name?: string | null; disabled?: boolean; super_admin?: boolean; }) { return request(`/users/${userId}`, { method: 'PUT', body: JSON.stringify(payload), }); } export async function setUserPassword(userId: string, password: string) { return request<{ ok: boolean }>(`/users/${userId}/password`, { method: 'POST', body: JSON.stringify({ password }), }); } export async function addMembership(userId: string, payload: { role_id: string; scope_type: string; scope_id: string; }) { return request(`/users/${userId}/memberships`, { method: 'POST', body: JSON.stringify(payload), }); } export async function deleteMembership(userId: string, membershipId: string) { return request<{ ok: boolean }>(`/users/${userId}/memberships/${membershipId}`, { method: 'DELETE', }); } export async function listRoles() { return request('/users/roles'); } export async function listAdminAudit(params: { actor_user_id?: string; action?: string; limit?: number; }) { const search = new URLSearchParams(); if (params.actor_user_id) search.set('actor_user_id', params.actor_user_id); if (params.action) search.set('action', params.action); if (params.limit) search.set('limit', String(params.limit)); const query = search.toString(); return request(`/audit${query ? `?${query}` : ''}`); } export async function listControlPlaneAudit( controlPlaneId: string, params: { network_id?: string; node_id?: string; limit?: number }, ) { const search = new URLSearchParams(); if (params.network_id) search.set('network_id', params.network_id); if (params.node_id) search.set('node_id', params.node_id); if (params.limit) search.set('limit', String(params.limit)); const query = search.toString(); return request( `/audit/control-planes/${controlPlaneId}${query ? `?${query}` : ''}`, ); } export async function deleteNetwork(id: string): Promise { const res = await fetch(`${API_BASE}/networks/${id}`, { method: 'DELETE', credentials: 'include', }); if (!res.ok) { throw new Error(`Failed to delete network: ${res.statusText}`); } } export async function deleteControlPlane(id: string): Promise { const res = await fetch(`${API_BASE}/control-planes/${id}`, { method: 'DELETE', credentials: 'include', }); if (!res.ok) { throw new Error(`Failed to delete control plane: ${res.statusText}`); } }