功能:完善云管助手与GPU故障恢复核对

This commit is contained in:
songyiming committed 2026-11-13 19:08:40 +08:00
1 parent b6026f9759
commit 9f7b01e4db
42 files changed
+3576 -120

No files matched your search

+25 -5
View File
@@ -17,6 +17,8 @@ const schemas = {
MFARecovery: object({ recoveryCodes: array(string) }),
LoginResult: object({ accessToken: string, expiresIn: integer, user: ref('Administrator') }),
Desktop: object({ id: string, name: string, os: { type: 'string', enum: ['Windows', 'Ubuntu', '麒麟'] }, node: string, vmid: integer, pool: string, powerState: string, serviceState: string, provider: { type: 'string', enum: ['demo', 'proxmox'] }, endpoint: string, app: string, width: integer, height: integer, fps: integer, updatedAt: date }),
DesktopReadinessCheck: object({ name: string, status: { type: 'string', enum: ['pass', 'fail', 'unknown'] }, message: string }),
DesktopReadiness: object({ desktopId: string, observedAt: date, ready: boolean, powerState: string, serviceState: string, setupRequired: boolean, checks: array(ref('DesktopReadinessCheck')) }),
Terminal: object({ id: string, sn: string, name: string, group: string, status: string, agentVersion: string, lastSeen: { ...date, nullable: true }, sessionDesktopId: { ...string, nullable: true }, configVersion: integer, fps: { ...number, nullable: true }, rtt: { ...number, nullable: true } }),
User: object({ id: string, username: string, displayName: string, enabled: boolean, terminalId: { ...string, nullable: true }, desktopIds: array(string) }),
Resource: object({ id: string, name: string, status: string, cpu: number, memory: number, storage: number, provider: string, updatedAt: date }),
@@ -25,6 +27,9 @@ const schemas = {
GPUItem: object({ id: string, address: string, name: string, vendor: string, vendorId: string, deviceId: string, class: string, iommuGroup: { ...integer, nullable: true }, state: { type: 'string', enum: ['unreferenced', 'unknown', 'referenced', 'multiple'] }, health: ref('GPUHealth'), references: array(ref('GPUReference')) }),
GPUIssue: object({ code: string, vmid: integer, message: string }),
GPUInventory: object({ node: string, observedAt: date, complete: boolean, visibleVmCount: integer, scannedVmCount: integer, items: array(ref('GPUItem')), issues: array(ref('GPUIssue')), health: object({ status: string, source: string, temperature: string, utilization: string, memory: string, driverBinding: string }) }),
GPUAgentSample: object({ address: string, observedAt: { ...date, nullable: true }, temperatureC: { ...number, nullable: true }, utilizationPercent: { ...number, nullable: true }, memoryUsedBytes: { ...integer, nullable: true }, memoryTotalBytes: { ...integer, nullable: true }, driver: string, driverBinding: string }, ['address']),
GPUAgentReport: object({ node: string, samples: { ...array(ref('GPUAgentSample')), minItems: 1, maxItems: 256 } }, ['node', 'samples']),
GPUAgentAccepted: object({ accepted: integer, node: string }),
Task: object({ id: string, kind: string, action: string, targetId: string, targetName: string, actor: string, status: { type: 'string', enum: ['queued', 'running', 'success', 'failed', 'timeout'] }, message: string, providerRef: string, createdAt: date, updatedAt: date }),
Audit: object({ id: string, actor: string, action: string, target: string, result: string, requestId: string, createdAt: date }),
Overview: object({ terminalCount: integer, onlineTerminals: integer, desktopCount: integer, runningDesktops: integer, activeSessions: integer, failedTasks: integer, mode: string }),
@@ -58,6 +63,13 @@ const schemas = {
DeviceDiagnostics: object({ agentVersion: string, bootId: string, uptimeSeconds: number, kioskState: string, logTail: string }),
SessionInput: object({ sessionId: string, state: { type: 'string', enum: ['connected', 'ended'] } }),
AckInput: object({ status: { type: 'string', enum: ['success', 'failed'] }, message: string, diagnostics: ref('DeviceDiagnostics') }, ['status']),
AssistantInput: object({ text: { ...string, maxLength: 500 } }, ['text']),
AssistantCandidate: object({ id: string, label: string, kind: string }),
AssistantPlan: object({ action: string, kind: { type: 'string', enum: ['action', 'route', 'blocked'] }, title: string, summary: string, userId: string, userName: string, desktopId: string, desktopName: string, desktopVmid: integer, missing: array(string), candidates: array(ref('AssistantCandidate')), risk: string, confirmationRequired: boolean, path: string, reason: string }),
AssistantPlanResult: object({ runId: string, status: string, input: string, plan: ref('AssistantPlan'), createdAt: date, expiresAt: date }),
AssistantExecuteInput: object({ runId: string }, ['runId']),
AssistantExecuteResult: object({ runId: string, status: string, result: object({ userId: string, desktopId: string, action: string, message: string }) }),
AssistantRun: object({ runId: string, input: string, action: string, status: string, plan: ref('AssistantPlan'), createdAt: date, expiresAt: date, executedAt: { ...date, nullable: true }, result: object({}, []) }),
}
Object.assign(schemas, {
PveConnectionInput: object({ url: string, tokenId: string, tokenSecret: { ...string, format: 'password', writeOnly: true }, caPem: string, revision: string }, ['url', 'tokenId', 'revision']),
@@ -98,9 +110,9 @@ Object.assign(schemas, {
UpdateAdministrator: object({ displayName: string, role: { type: 'string', enum: ['super_admin', 'operator', 'viewer'] }, enabled: boolean }),
ResetAdministratorPassword: object({ password: { type: 'string', format: 'password', minLength: 12, maxLength: 72 } }),
ChangePassword: object({ currentPassword: { type: 'string', format: 'password' }, newPassword: { type: 'string', format: 'password', minLength: 12, maxLength: 72 } }),
ImageTemplate: object({ id: string, name: string, version: string, os: { type: 'string', enum: ['Windows', 'Ubuntu', '麒麟'] }, node: string, vmid: integer, enabled: boolean, validatedAt: date }),
TemplateInput: object({ name: string, version: string, os: { type: 'string', enum: ['Windows', 'Ubuntu', '麒麟'] }, node: string, vmid: integer }),
TemplateUpdate: object({ enabled: boolean }),
ImageTemplate: object({ id: string, name: string, version: string, os: { type: 'string', enum: ['Windows', 'Ubuntu', '麒麟'] }, node: string, vmid: integer, enabled: boolean, hardwareEncodingVerified: boolean, validatedAt: date }),
TemplateInput: object({ name: string, version: string, os: { type: 'string', enum: ['Windows', 'Ubuntu', '麒麟'] }, node: string, vmid: integer, hardwareEncodingVerified: boolean }, ['name', 'version', 'os', 'node', 'vmid']),
TemplateUpdate: object({ enabled: boolean, hardwareEncodingVerified: boolean }, []),
DesktopPool: object({ id: string, name: string, type: { type: 'string', enum: ['dedicated', 'shared'] }, maxCount: integer, templateId: { ...string, nullable: true }, enabled: boolean, createdAt: date, desktopCount: integer, reservedCount: integer, assignedCount: integer, activeCount: integer }),
PoolInput: object({ name: string, type: { type: 'string', enum: ['dedicated', 'shared'] }, maxCount: integer, templateId: { ...string, nullable: true }, enabled: boolean }),
PoolMembers: object({ desktopIds: array(string) }),
@@ -119,8 +131,10 @@ schemas.GPUReconcileResult = object({ reconciled: boolean, vmid: string })
schemas.DiagnosticSubject = object({ kind: { type: 'string', enum: ['terminal', 'desktop'] }, id: string, name: string, state: string, node: string, vmid: integer, agentVersion: string, lastSeen: { ...date, nullable: true } })
schemas.DiagnosticEvent = object({ id: string, source: { type: 'string', enum: ['tasks', 'audits', 'sessions', 'alerts', 'logs'] }, time: date, action: string, status: string, targetId: string, targetName: string, actor: string, message: string, details: string })
schemas.DiagnosticReport = object({ subject: ref('DiagnosticSubject'), from: date, to: date, observedAt: date, items: array(ref('DiagnosticEvent')), counts: object({ tasks: integer, audits: integer, sessions: integer, alerts: integer, logs: integer }), total: integer, page: integer, pageSize: integer, source: string, notes: array(string) })
schemas.TerminalLogArchive = object({ id: string, taskId: string, terminalId: string, terminalName: string, capturedAt: date, bootId: string, agentVersion: string, kioskState: string, logText: string })
schemas.TerminalLogArchive = object({ id: string, taskId: string, terminalId: string, terminalName: string, capturedAt: date, bootId: string, agentVersion: string, kioskState: string, logText: string, source: { type: 'string', enum: ['snapshot', 'stream'] } })
schemas.TerminalLogArchivePage = object({ items: array(ref('TerminalLogArchive')), page: integer, pageSize: integer, total: integer, observedAt: date, retentionDays: integer })
schemas.TerminalLogEntry = object({ capturedAt: { ...date, nullable: true }, level: { type: 'string', enum: ['debug', 'info', 'warn', 'error'] }, message: { ...string, maxLength: 16000 } }, ['message'])
schemas.TerminalLogBatch = object({ bootId: { ...string, maxLength: 120 }, entries: { ...array(ref('TerminalLogEntry')), minItems: 1, maxItems: 100 } }, ['entries'])
schemas.GPUExclusiveMember = object({ desktopId: string, name: string, vmid: integer, powerState: string, powerObserved: boolean, activeSessions: integer, activeTasks: integer, conflicts: array(string), eligible: boolean })
schemas.GPUExclusiveStatus = object({ enabled: boolean, node: string, vmids: array(integer), observedAt: date, lockAvailable: boolean, groupComplete: boolean, members: array(ref('GPUExclusiveMember')), conflicts: array(string), allocationReady: boolean, releaseReady: boolean, releasePolicy: string, activeTasks: array(ref('Task')) })
schemas.Administrator.properties.enabled = boolean
@@ -195,10 +209,14 @@ const paths = {
'/administrators/{id}/sessions': { get: operation('administratorSessions', 'AdministratorSessionList', { list: true, param: true }) },
'/administrators/{id}/sessions/{sessionKey}': { delete: { ...operation('revokeAdministratorSession', null, { param: true, status: 204 }), parameters: [{ name: 'id', in: 'path', required: true, schema: string }, { name: 'sessionKey', in: 'path', required: true, schema: { ...string, minLength: 64, maxLength: 64 } }] } },
'/overview': { get: operation('overview', 'Overview') },
'/assistant/plan': { post: operation('assistantPlan', 'AssistantPlanResult', { input: 'AssistantInput' }) },
'/assistant/execute': { post: operation('assistantExecute', 'AssistantExecuteResult', { input: 'AssistantExecuteInput', command: true }) },
'/assistant/runs/{id}': { get: operation('assistantRun', 'AssistantRun', { param: true }) },
'/settings': { get: operation('settings', 'Settings') },
'/resources': { get: operation('resources', 'ResourceList', { list: true }) },
'/resources/gpus': { get: operation('gpuInventory', 'GPUInventory', { node: true }) },
'/resources/gpu-exclusive': { get: operation('gpuExclusiveStatus', 'GPUExclusiveStatus', { node: true }) },
'/gpu-agent/health': { post: operation('ingestGPUHealth', 'GPUAgentAccepted', { input: 'GPUAgentReport', security: 'gpuAgent', status: 202 }) },
'/resources/vms': { get: operation('discoverVMs', 'DiscoveredVMList', { list: true }) },
'/resources/templates': { get: operation('discoverTemplates', 'DiscoveredVMList', { list: true }) },
'/resources/storage': { get: operation('storages', 'StorageList', { list: true, node: true }) },
@@ -211,6 +229,7 @@ const paths = {
'/desktops/clone/check': { post: operation('checkClone', 'CloneCheck', { input: 'CloneInput' }) },
'/desktops/clone': { post: operation('cloneDesktop', 'Task', { input: 'CloneInput', command: true, status: 202 }) },
'/desktops/{id}/connection': { put: operation('configureDesktop', 'Desktop', { param: true, input: 'ConnectionInput' }) },
'/desktops/{id}/readiness/check': { post: operation('checkDesktopReadiness', 'DesktopReadiness', { param: true }) },
'/resources/sync': { post: operation('syncResources', 'Task', { status: 202, command: true }) },
'/desktops': { get: operation('desktops', 'DesktopList', { list: true }) },
'/desktops/import': { post: operation('importDesktop', 'Desktop', { input: 'ImportInput' }) },
@@ -234,6 +253,7 @@ const paths = {
'/diagnostics': { get: operation('diagnostics', 'DiagnosticReport') },
'/diagnostics/export': { get: operation('exportDiagnostics', null) },
'/terminal-logs': { get: operation('terminalLogArchives', 'TerminalLogArchivePage') },
'/terminal/logs': { post: operation('appendTerminalLogs', 'Accepted', { input: 'TerminalLogBatch', security: 'deviceBearer', status: 202 }) },
'/audit-logs': { get: operation('audits', 'AuditList', { list: true, period: true }) },
'/audit-logs/export': { get: operation('exportAudits', null, { period: true }) },
'/terminal/register': { post: operation('registerDevice', 'RegisterResult', { input: 'RegisterInput', security: null }) },
@@ -274,7 +294,7 @@ paths['/audit-logs/export'].get.parameters.push(...auditFilters, { name: 'keywor
paths['/audit-logs/export'].get.description = 'Authenticated administrators of all roles can export matching audit fields (maximum 10000 rows). No pagination or silent truncation. CSV uses UTF-8 BOM and formula-safe cells. Successful preparation is audited.'
paths['/audit-logs/export'].get.responses[200] = { description: 'Complete filtered CSV, with UTC timestamps and raw action/result codes', headers: { 'X-Export-Count': { schema: integer } }, content: { 'text/csv': { schema: { type: 'string', format: 'binary' } } } }
paths['/audit-logs/export'].get.responses[413] = { description: 'Over 10000 matching records; narrow the filter', content: json(ref('Error')) }
const output = { openapi: '3.0.3', info: { title: 'VDI 管理平台 API', version: '0.1.0', description: '第一阶段接口。平台票据不替代 Sunshine 配对;HTTP 202 只代表已提交。' }, servers: [{ url: '/api/v1' }], paths, components: { schemas, securitySchemes: { adminBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, deviceBearer: { type: 'http', scheme: 'bearer', description: '设备激活后获得的独立令牌,不是管理员 JWT' } } } }
const output = { openapi: '3.0.3', info: { title: 'VDI 管理平台 API', version: '0.1.0', description: '第一阶段接口。平台票据不替代 Sunshine 配对;HTTP 202 只代表已提交。' }, servers: [{ url: '/api/v1' }], paths, components: { schemas, securitySchemes: { adminBearer: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, deviceBearer: { type: 'http', scheme: 'bearer', description: '设备激活后获得的独立令牌,不是管理员 JWT' }, gpuAgent: { type: 'apiKey', in: 'header', name: 'X-GPU-Agent-Token', description: '仅供受信任计算节点采集器使用的独立令牌' } } } }
const directory = fileURLToPath(new URL('../docs/openapi/', import.meta.url))
mkdirSync(directory, { recursive: true })
writeFileSync(`${directory}/platform.v1.json`, `${JSON.stringify(output, null, 2)}\n`)