const BASE = import.meta.env.VITE_API_BASE_URL ?? '/api' export interface DeploymentStatus { mode: 'PUBLIC' | 'PRIVATE' tenantRegisterEnabled: boolean services: Record } export async function getDeploymentStatus(): Promise { const res = await fetch(`${BASE}/private/deployment/status`) const json = await res.json() return json.data as DeploymentStatus } async function streamOperation( path: string, onLine: (line: string) => void, signal?: AbortSignal, ): Promise { const token = localStorage.getItem('token') ?? '' const res = await fetch(`${BASE}${path}`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, signal, }) if (!res.ok) { const text = await res.text() throw new Error(text || `HTTP ${res.status}`) } const reader = res.body!.getReader() const decoder = new TextDecoder() let buf = '' try { while (true) { const { done, value } = await reader.read() if (done) break buf += decoder.decode(value, { stream: true }) const lines = buf.split('\n') buf = lines.pop() ?? '' for (const line of lines) { onLine(line) } } if (buf) onLine(buf) } catch (e: any) { // Connection drop during RESTART_SELF is expected - don't throw if (e?.name === 'TypeError' || e?.message?.includes('network') || e?.message?.includes('fetch')) { onLine('>>> 连接已中断(服务正在重启)') return } throw e } } export async function getRunningServices(): Promise { const token = localStorage.getItem('token') ?? '' const res = await fetch(`${BASE}/system/services`, { headers: { Authorization: `Bearer ${token}` }, }) if (!res.ok) throw new Error(`HTTP ${res.status}`) const json = await res.json() return json.data as string[] } export async function fetchServiceLogs(service: string, lines: number): Promise { const token = localStorage.getItem('token') ?? '' const res = await fetch( `${BASE}/system/logs/${encodeURIComponent(service)}?lines=${lines}`, { headers: { Authorization: `Bearer ${token}` } }, ) if (!res.ok) throw new Error(`HTTP ${res.status}`) return res.text() } export async function getSystemVersion(): Promise<{ currentVersion: string }> { const token = localStorage.getItem('token') ?? '' const res = await fetch(`${BASE}/system/version`, { headers: { Authorization: `Bearer ${token}` }, }) if (!res.ok) throw new Error(`HTTP ${res.status}`) const json = await res.json() return json.data as { currentVersion: string } } export function streamSystemUpdate(onLine: (line: string) => void, signal?: AbortSignal) { return streamOperation('/system/update', onLine, signal) } export function streamSelectiveUpdate(services: string[], onLine: (line: string) => void, signal?: AbortSignal) { const qs = services.map(s => `services=${encodeURIComponent(s)}`).join('&') return streamOperation(`/system/update-selective${qs ? '?' + qs : ''}`, onLine, signal) } export function streamSystemReset(onLine: (line: string) => void, signal?: AbortSignal) { return streamOperation('/system/reset', onLine, signal) } export interface UpdateCheckResult { currentVersion: string latestVersion: string hasUpdate: boolean releasedAt: string changelog: string services: Record } export async function checkForUpdates(): Promise { const json = await authFetch('/system/check-update') return json as UpdateCheckResult } // ── Database Management (PRIVATE mode only) ────────────────────────────── export interface TableInfo { name: string comment: string } export interface ColumnInfo { name: string type: string size: number nullable: boolean comment: string } export interface TableDataPage { columns: string[] rows: Record[] total: number totalPages: number page: number size: number } async function authFetch(path: string): Promise { const token = localStorage.getItem('token') ?? '' const res = await fetch(`${BASE}${path}`, { headers: { Authorization: `Bearer ${token}` }, }) if (!res.ok) { const json = await res.json().catch(() => null) throw new Error(json?.message ?? `HTTP ${res.status}`) } return res.json() } export async function listDatabaseTables(): Promise { const json = await authFetch('/system/database/tables') return json.data as TableInfo[] } export async function getTableColumns(tableName: string): Promise { const json = await authFetch(`/system/database/tables/${encodeURIComponent(tableName)}/columns`) return json.data as ColumnInfo[] } export async function getTableData( tableName: string, params: { page?: number; size?: number; keyword?: string; sortColumn?: string; sortDirection?: string }, ): Promise { const query = new URLSearchParams() if (params.page != null) query.set('page', String(params.page)) if (params.size != null) query.set('size', String(params.size)) if (params.keyword) query.set('keyword', params.keyword) if (params.sortColumn) query.set('sortColumn', params.sortColumn) if (params.sortDirection) query.set('sortDirection', params.sortDirection) const qs = query.toString() const json = await authFetch(`/system/database/tables/${encodeURIComponent(tableName)}/data${qs ? '?' + qs : ''}`) return json.data as TableDataPage }