export type ServerStatus = "pending" | "active" | "offline"; export type KeySource = "uploaded" | "generated"; export interface PackageUpdate { name: string; current_version?: string; new_version: string; } export interface Server { id: string; server_id: string; hostname: string; ip_address: string; os_info: string; status: ServerStatus; agent_version?: string; last_seen: string; created_at: string; available_updates?: PackageUpdate[]; updates_checked_at?: string; } export interface Key { id: string; key_id: string; label: string; public_key: string; fingerprint: string; source: KeySource; generated_by_server_id?: string; has_private_key: boolean; created_at: string; assigned_count?: number; } export interface Assignment { id: string; key_id: string; server_id: string; assigned_at: string; revoked_at: string | null; } export interface AuditEvent { id: string; event_type: string; actor: string; server_id?: string; key_id?: string; details: string; created_at: string; } export interface AlertSettings { enabled: boolean; webhook_url: string; offline_threshold_minutes: number; } export interface EmailSettings { enabled: boolean; smtp_host: string; smtp_port: number; username: string; password: string; from_addr: string; to_addrs: string[]; use_tls: boolean; } export interface SecretsSettings { read_token_set: boolean; rotated_at?: string; } export interface Settings { alerts: AlertSettings; email: EmailSettings; secrets: SecretsSettings; } export interface SecretGroupSummary { group: string; key_count: number; updated_at: string; } export interface Secret { group: string; key: string; updated_at: string; } export interface NewServerResponse { server_id: string; pre_reg_token: string; install_command: string; } export interface GenerateKeyOptions { label: string; key_type: "ed25519" | "rsa" | "ecdsa"; key_size?: number; passphrase?: string; comment?: string; } export interface KeyWithAssignments extends Key { assignments: (Assignment & { server: Server })[]; } export interface ServerWithKeys extends Server { keys: (Assignment & { key: Key })[]; } class ApiError extends Error { constructor( public status: number, message: string ) { super(message); this.name = "ApiError"; } } async function request(path: string, options?: RequestInit): Promise { const res = await fetch(`/api${path}`, { credentials: "include", headers: { "Content-Type": "application/json", ...options?.headers, }, ...options, }); if (!res.ok) { const text = await res.text().catch(() => res.statusText); throw new ApiError(res.status, text || `HTTP ${res.status}`); } if (res.status === 204) { return undefined as T; } return res.json(); } export const api = { // Servers listServers(): Promise { return request("/servers"); }, getServer(serverId: string): Promise { return request(`/servers/${serverId}`); }, createServer(): Promise { return request("/servers/new", { method: "POST" }); }, deleteServer(serverId: string): Promise { return request(`/servers/${serverId}`, { method: "DELETE" }); }, generateKeyForServer(serverId: string, opts: GenerateKeyOptions): Promise<{ command_id: string }> { return request<{ command_id: string }>(`/servers/${serverId}/generate-key`, { method: "POST", body: JSON.stringify(opts), }); }, getUpdateCommand(): string { return `curl -fsSL "${window.location.origin}/update" | bash`; }, getLatestAgentVersion(): Promise<{ version: string }> { return request<{ version: string }>("/agent/latest-version"); }, updateAgent(serverId: string): Promise<{ message: string; version: string }> { return request<{ message: string; version: string }>(`/servers/${serverId}/update-agent`, { method: "POST", }); }, applyUpdates(serverId: string): Promise<{ message: string }> { return request<{ message: string }>(`/servers/${serverId}/apply-updates`, { method: "POST", }); }, // Audit listAuditEvents(limit?: number): Promise { const qs = limit ? `?limit=${limit}` : ""; return request(`/audit${qs}`); }, // Settings getSettings(): Promise { return request("/settings"); }, saveSettings(settings: { alerts: AlertSettings; email: EmailSettings }): Promise<{ saved: boolean }> { return request<{ saved: boolean }>("/settings", { method: "PUT", body: JSON.stringify(settings), }); }, rotateSecretsToken(): Promise<{ token: string }> { return request<{ token: string }>("/settings/secrets-token", { method: "POST" }); }, // Secrets listSecretGroups(): Promise { return request("/secrets"); }, createSecretGroup(group: string, values: Record): Promise<{ group: string }> { return request<{ group: string }>("/secrets", { method: "POST", body: JSON.stringify({ group, values }), }); }, getSecretGroup(group: string): Promise<{ group: string; secrets: Secret[] }> { return request<{ group: string; secrets: Secret[] }>(`/secrets/${encodeURIComponent(group)}`); }, putSecrets(group: string, values: Record): Promise<{ saved: boolean }> { return request<{ saved: boolean }>(`/secrets/${encodeURIComponent(group)}`, { method: "PUT", body: JSON.stringify(values), }); }, revealSecret(group: string, key: string): Promise<{ value: string }> { return request<{ value: string }>(`/secrets/${encodeURIComponent(group)}/reveal`, { method: "POST", body: JSON.stringify({ key }), }); }, deleteSecret(group: string, key: string): Promise { return request(`/secrets/${encodeURIComponent(group)}/${encodeURIComponent(key)}`, { method: "DELETE", }); }, deleteSecretGroup(group: string): Promise { return request(`/secrets/${encodeURIComponent(group)}`, { method: "DELETE" }); }, // Keys listKeys(): Promise { return request("/keys"); }, getKey(keyId: string): Promise { return request(`/keys/${keyId}`); }, uploadKey(label: string, public_key: string, private_key?: string): Promise { return request("/keys", { method: "POST", body: JSON.stringify({ label, public_key, private_key: private_key || undefined }), }); }, getPrivateKey(keyId: string): Promise<{ private_key: string }> { return request<{ private_key: string }>(`/keys/${keyId}/private-key`); }, deleteKey(keyId: string): Promise { return request(`/keys/${keyId}`, { method: "DELETE" }); }, // Assignments assignKey(keyId: string, serverId: string): Promise { return request(`/keys/${keyId}/assign`, { method: "POST", body: JSON.stringify({ server_id: serverId }), }); }, revokeKey(keyId: string, serverId: string): Promise { return request(`/keys/${keyId}/assign/${serverId}`, { method: "DELETE", }); }, };