/** * Proxmox Module API client (260923-dhh). Konsumiert `/modules/proxmox/*`. * Vorbild `dkv-api.ts`: `credentials: 'include'` fuer Cookie-Auth, * `NEXT_PUBLIC_API_URL` als Basis. * * Sicherheit (T-DHH-01): keine Antwort dieses Clients enthaelt jemals ein * Geheimnisfeld — der Server waehlt `encryptedTokenSecret`/`encryptedPassword` * per `select` gar nicht erst aus. */ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'; export type ProxmoxProductType = 'pve' | 'pbs' | 'pmg'; export type ProxmoxAuthMethod = 'token' | 'password'; export type ProxmoxErrorKind = | 'netz' | 'zugang' | 'rechte' | 'zertifikat' | 'antwortform' | 'server' | 'unbekannt'; export interface ProxmoxPveNodeMetric { node: string; cpu: number | null; maxcpu: number | null; mem: number | null; maxmem: number | null; } export interface ProxmoxPveStorageMetric { storage: string; node: string; disk: number | null; maxdisk: number | null; } /** Gast, der nicht laeuft, obwohl Autostart aktiv ist (quick-261005). */ export interface ProxmoxPveAutostartGuest { vmid: number; name: string | null; type: 'qemu' | 'lxc'; node: string; status: string; } export interface ProxmoxPveMetrics { productType: 'pve'; nodeCount: number; guestsRunning: number; guestsStopped: number; nodes: ProxmoxPveNodeMetric[]; storages: ProxmoxPveStorageMetric[]; /** Fehlt in Zwischenlagerstaenden von vor quick-261005. */ autostartStopped?: ProxmoxPveAutostartGuest[]; /** Gestoppte Gaeste, deren Autostart nicht gelesen werden konnte. */ autostartUnchecked?: number; } export interface ProxmoxPbsDatastoreMetric { name: string; total: number | null; used: number | null; free: number | null; lastBackupAt: number | null; lastVerifyState: string | null; } export interface ProxmoxPbsMetrics { productType: 'pbs'; datastores: ProxmoxPbsDatastoreMetric[]; } export interface ProxmoxPmgMetrics { productType: 'pmg'; countIn: number | null; countOut: number | null; spamCount: number | null; virusCount: number | null; } export type ProxmoxMetrics = ProxmoxPveMetrics | ProxmoxPbsMetrics | ProxmoxPmgMetrics; export interface ProxmoxServerStatus { id: string; serverId: string; lastPolledAt: string | null; lastOkAt: string | null; reachable: boolean; errorKind: ProxmoxErrorKind | null; errorDetail: string | null; metrics: ProxmoxMetrics | null; rawSample: unknown; updatedAt: string; } export interface ProxmoxServer { id: string; tenantId: string; name: string; productType: ProxmoxProductType; baseUrl: string; authMethod: ProxmoxAuthMethod; tokenId: string | null; username: string | null; tlsRejectUnauthorized: boolean; isActive: boolean; pollIntervalMin: number; position: number; createdAt: string; updatedAt: string; status: ProxmoxServerStatus | null; } export interface CreateProxmoxServerPayload { name: string; productType: ProxmoxProductType; baseUrl: string; authMethod: ProxmoxAuthMethod; tokenId?: string; tokenSecret?: string; username?: string; password?: string; tlsRejectUnauthorized?: boolean; pollIntervalMin?: number; isActive?: boolean; } export type UpdateProxmoxServerPayload = Partial; export interface ProxmoxTestResult { reachable: boolean; errorKind: ProxmoxErrorKind | null; errorDetail: string | null; metrics: ProxmoxMetrics | null; rawSample: unknown; } /** Liest die NestJS-Fehlermeldung aus dem Antwortkoerper, faellt sonst auf einen Standardtext zurueck. */ async function readErrorMessage(res: Response, fallback: string): Promise { try { const body = await res.json(); if (typeof body?.message === 'string') return body.message; if (Array.isArray(body?.message) && body.message.length > 0) return String(body.message[0]); } catch { /* Antwort war kein JSON — Standardtext bleibt */ } return fallback; } /** GET /modules/proxmox/servers — Serverliste samt Zwischenlager. */ export async function listServers(): Promise { const res = await fetch(`${API_URL}/modules/proxmox/servers`, { credentials: 'include', }); if (!res.ok) throw new Error('Failed to fetch proxmox servers'); return res.json(); } /** POST /modules/proxmox/servers — Server anlegen (ADMIN/SUPER_ADMIN). */ export async function createServer( payload: CreateProxmoxServerPayload, ): Promise { const res = await fetch(`${API_URL}/modules/proxmox/servers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload), }); if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to create proxmox server')); return res.json(); } /** PUT /modules/proxmox/servers/:id — Server bearbeiten (ADMIN/SUPER_ADMIN). */ export async function updateServer( id: string, payload: UpdateProxmoxServerPayload, ): Promise { const res = await fetch(`${API_URL}/modules/proxmox/servers/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload), }); if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to update proxmox server')); return res.json(); } /** DELETE /modules/proxmox/servers/:id — Server samt Zwischenlagerzeile loeschen (ADMIN/SUPER_ADMIN). */ export async function deleteServer(id: string): Promise { const res = await fetch(`${API_URL}/modules/proxmox/servers/${id}`, { method: 'DELETE', credentials: 'include', }); if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to delete proxmox server')); } /** POST /modules/proxmox/servers/:id/poll — sofortige Abfrage (ADMIN/SUPER_ADMIN). */ export async function pollServer(id: string): Promise { const res = await fetch(`${API_URL}/modules/proxmox/servers/${id}/poll`, { method: 'POST', credentials: 'include', }); if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to poll proxmox server')); return res.json(); } /** * POST /modules/proxmox/servers/:id/test — Verbindungstest fuer einen * gespeicherten Server, schreibt NICHT ins Zwischenlager. `payload` traegt * den aktuellen Formularstand (Nachbesserung Befund 1): der Test prueft * damit, was im Formular steht, statt blind den gespeicherten Stand — ein * leer gelassenes Geheimnisfeld (`tokenSecret`/`password: undefined`) laesst * den Server serverseitig auf den gespeicherten Wert zurueckfallen. */ export async function testServer( id: string, payload: UpdateProxmoxServerPayload, ): Promise { const res = await fetch(`${API_URL}/modules/proxmox/servers/${id}/test`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload), }); if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to test proxmox server')); return res.json(); } /** * POST /modules/proxmox/servers/test — Verbindungstest waehrend der * Neuanlage (Nachbesserung Befund 1): es gibt noch keinen gespeicherten * Server, `payload` ist deshalb die einzige Quelle. */ export async function testDraftServer( payload: UpdateProxmoxServerPayload, ): Promise { const res = await fetch(`${API_URL}/modules/proxmox/servers/test`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(payload), }); if (!res.ok) throw new Error(await readErrorMessage(res, 'Failed to test proxmox server')); return res.json(); }