2026-11-09 04:54:52 -05:00
import { mkdirSync , writeFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
const string = { type : 'string' } , integer = { type : 'integer' } , number = { type : 'number' } , boolean = { type : 'boolean' }
const date = { type : 'string' , format : 'date-time' }
const ref = name => ( { $ref : ` #/components/schemas/ ${ name } ` } )
const array = items => ( { type : 'array' , items } )
const object = ( properties , required = Object . keys ( properties ) ) => ( { type : 'object' , properties , required , additionalProperties : false } )
const schemas = {
Error : object ( { code : string , message : string , requestId : string } ) ,
Administrator : object ( { id : string , username : string , displayName : string , role : { type : 'string' , enum : [ 'super_admin' , 'operator' , 'viewer' ] } } ) ,
2026-11-11 05:46:16 -05:00
LoginInput : object ( { username : string , password : string , code : { ... string , maxLength : 80 } } , [ 'username' , 'password' ] ) ,
MFAStatus : object ( { enabled : boolean , recoveryRemaining : integer } ) ,
MFASetupInput : object ( { password : { ... string , format : 'password' , maxLength : 200 } } ) ,
MFASetup : object ( { secret : string , provisioningUri : string , expiresAt : date } ) ,
MFAConfirm : object ( { code : { ... string , minLength : 6 , maxLength : 6 } } ) ,
MFACredential : object ( { password : { ... string , format : 'password' , maxLength : 200 } , code : { ... string , maxLength : 80 } } ) ,
MFARecovery : object ( { recoveryCodes : array ( string ) } ) ,
2026-11-09 04:54:52 -05:00
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 } ) ,
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 } ) ,
2026-11-10 04:52:52 -05:00
GPUReference : object ( { vmid : integer , vmName : string , vmStatus : string , template : boolean , desktopId : string , desktopName : string , slot : string , phase : { type : 'string' , enum : [ 'current' , 'pending' ] } , source : { type : 'string' , enum : [ 'direct' , 'mapping' ] } , mapping : string , mediated : boolean , pendingRemoval : boolean } ) ,
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' ] } , 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' ) ) } ) ,
2026-11-09 04:54:52 -05:00
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 } ) ,
Settings : object ( { mode : { type : 'string' , enum : [ 'demo' , 'production' ] } , computeProvider : string , pveConfigured : boolean , terminalTransport : string , capabilities : array ( string ) } ) ,
ImportInput : object ( { name : string , os : { type : 'string' , enum : [ 'Windows' , 'Ubuntu' , '麒麟' ] } , node : string , vmid : integer , endpoint : string , width : integer , height : integer , fps : integer } , [ 'name' , 'os' , 'node' , 'vmid' ] ) ,
DiscoveredVM : object ( { name : string , node : string , vmid : integer , status : string } ) ,
EnrollmentInput : object ( { sn : string , name : string , group : string } ) ,
Enrollment : object ( { id : string , activationSecret : string } ) ,
2026-11-10 04:52:52 -05:00
TerminalUpdate : object ( { name : string , group : string } ) ,
UserUpdate : object ( { displayName : string , enabled : boolean } ) ,
BatchBindings : object ( { userIds : { ... array ( string ) , minItems : 1 , maxItems : 100 } , desktopIds : { ... array ( string ) , minItems : 1 , maxItems : 100 } , action : { type : 'string' , enum : [ 'grant' , 'revoke' ] } } ) ,
BatchBindingsResult : object ( { accepted : boolean , userCount : integer } ) ,
2026-11-09 04:54:52 -05:00
CreateUserInput : object ( { username : string , displayName : string } ) ,
BindingInput : object ( { terminalId : { ... string , nullable : true } , desktopIds : array ( string ) , enabled : boolean } ) ,
DesktopAction : object ( { action : { type : 'string' , enum : [ 'start' , 'shutdown' , 'reboot' ] } } ) ,
TerminalAction : object ( { action : { type : 'string' , enum : [ 'reboot' , 'reset-session' , 'collect-logs' ] } } ) ,
RegisterInput : object ( { sn : string , activationSecret : string , agentVersion : string } ) ,
RegisterResult : object ( { terminalId : string , accessToken : string , heartbeatInterval : integer } ) ,
HeartbeatInput : object ( { agentVersion : string , fps : { ... number , nullable : true } , rtt : { ... number , nullable : true } } , [ 'agentVersion' ] ) ,
2026-11-10 04:52:52 -05:00
TerminalSystemMetrics : object ( { cpuPercent : { ... number , nullable : true , minimum : 0 , maximum : 100 } , memoryPercent : { ... number , nullable : true , minimum : 0 , maximum : 100 } , socTemperatureC : { ... number , nullable : true , minimum : - 40 , maximum : 150 } , uptimeSeconds : { ... number , nullable : true , minimum : 0 , maximum : 3155760000 } } , [ ] ) ,
TerminalMetricLatest : object ( { cpuPercent : { ... number , nullable : true } , memoryPercent : { ... number , nullable : true } , socTemperatureC : { ... number , nullable : true } , uptimeSeconds : { ... number , nullable : true } , sampledAt : { ... date , nullable : true } , supported : boolean , online : boolean , fresh : boolean } ) ,
TerminalMetricPoint : object ( { time : date , cpuPercent : { ... number , nullable : true } , memoryPercent : { ... number , nullable : true } , socTemperatureC : { ... number , nullable : true } , uptimeSeconds : { ... number , nullable : true } , samples : integer } ) ,
TerminalMetrics : object ( { latest : ref ( 'TerminalMetricLatest' ) , items : array ( ref ( 'TerminalMetricPoint' ) ) , from : date , to : date , bucketSeconds : integer , retentionDays : integer } ) ,
2026-11-09 04:54:52 -05:00
Accepted : object ( { accepted : boolean } ) ,
HeartbeatResult : object ( { accepted : boolean , serverTime : date } ) ,
DeviceConfig : object ( { configVersion : integer , identityMode : string , commandTransport : string , heartbeatInterval : integer } ) ,
TicketInput : object ( { desktopId : string } ) ,
Ticket : object ( { ticket : string , desktopId : string , expiresAt : date , credentialMode : string } ) ,
ConsumeInput : object ( { ticket : string } ) ,
Connection : object ( { desktopId : string , sessionId : string , protocol : string , endpoint : string , app : string , width : integer , height : integer , fps : integer , credentialMode : string } ) ,
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' ] ) ,
}
2026-11-10 04:52:52 -05:00
Object . assign ( schemas , {
PveConnectionInput : object ( { url : string , tokenId : string , tokenSecret : { ... string , format : 'password' , writeOnly : true } , caPem : string , revision : string } , [ 'url' , 'tokenId' , 'revision' ] ) ,
PveConnection : object ( { url : string , tokenId : string , caPem : string , revision : string , tokenConfigured : boolean , source : { type : 'string' , enum : [ 'environment' , 'database' ] } } ) ,
PveConnectionCheck : object ( { available : boolean , nodes : integer , vms : integer , message : string } ) ,
VMDisk : object ( { key : string , storage : string , sizeGiB : number } ) ,
VMHardware : object ( { cores : integer , sockets : integer , memoryMiB : integer , digest : string , disks : array ( ref ( 'VMDisk' ) ) , powerState : string , blockers : array ( string ) } ) ,
HardwareInput : object ( { kind : { type : 'string' , enum : [ 'compute' , 'disk' ] } , digest : string , cores : integer , sockets : integer , memoryMiB : integer , disk : string , sizeGiB : integer } , [ 'kind' , 'digest' ] ) ,
PowerBatchInput : object ( { action : { type : 'string' , enum : [ 'start' , 'shutdown' , 'reboot' ] } , desktopIds : { ... array ( string ) , minItems : 1 , maxItems : 50 } , previewToken : string } , [ 'action' , 'desktopIds' ] ) ,
PowerBatchCheckItem : object ( { desktopId : string , name : string , vmid : integer , powerState : string , available : boolean , code : string , message : string } ) ,
PowerBatchPreview : object ( { items : array ( ref ( 'PowerBatchCheckItem' ) ) , eligibleCount : integer , skippedCount : integer , previewToken : string , expiresAt : date } ) ,
PowerBatch : object ( { id : string , action : { type : 'string' , enum : [ 'start' , 'shutdown' , 'reboot' ] } , actor : string , createdAt : date , totalCount : integer , submittedCount : integer , skippedCount : integer , activeCount : integer , successCount : integer , failedCount : integer , uncertainCount : integer } ) ,
PowerBatchItem : object ( { desktopId : string , name : string , vmid : integer , taskId : string , status : string , code : string , message : string } ) ,
PowerBatchDetail : object ( { batch : ref ( 'PowerBatch' ) , items : array ( ref ( 'PowerBatchItem' ) ) } ) ,
SnapshotItem : object ( { name : string , description : string , createdAt : integer , parent : string , includesMemory : boolean , state : string , revision : string } ) ,
SnapshotView : object ( { items : array ( ref ( 'SnapshotItem' ) ) , digest : string , powerState : string , blockers : array ( string ) , disks : array ( string ) , canCreate : boolean , canDelete : boolean , canRestore : boolean } ) ,
SnapshotInput : object ( { action : { type : 'string' , enum : [ 'create' , 'delete' , 'rollback' ] } , name : { ... string , maxLength : 40 } , description : { ... string , maxLength : 500 } , digest : string , revision : string , confirmVmid : integer } , [ 'action' , 'name' , 'digest' ] ) ,
HardwareCheck : object ( { available : boolean , message : string } ) ,
CsvImportInput : object ( { kind : { type : 'string' , enum : [ 'users' , 'terminals' , 'bindings' ] } , csv : { ... string , maxLength : 524288 } , previewToken : string } , [ 'kind' , 'csv' ] ) ,
CsvImportRow : object ( { line : integer , key : string , values : { type : 'object' , additionalProperties : string } , errors : array ( string ) } ) ,
CsvImportPreview : object ( { kind : string , totalRows : integer , validRows : integer , errorRows : integer , previewToken : string , rows : array ( ref ( 'CsvImportRow' ) ) } ) ,
CsvImportResult : object ( { id : string , kind : string , count : integer , items : array ( object ( { line : integer , key : string , id : string } ) ) , activationSecrets : array ( object ( { sn : string , activationSecret : string } ) ) , replayed : boolean } ) ,
SessionRecord : object ( { id : string , sessionId : string , terminalId : string , terminalName : string , desktopId : string , desktopName : string , username : string , displayName : string , state : { type : 'string' , enum : [ 'connecting' , 'connected' , 'ended' , 'interrupted' ] } , source : { type : 'string' , enum : [ 'ticket' , 'observed' ] } , reservedAt : date , connectedAt : { ... date , nullable : true } , lastReportedAt : date , endedAt : { ... date , nullable : true } , endReason : string } ) ,
2026-11-11 05:46:16 -05:00
AlertRuleInput : object ( { name : string , metric : { type : 'string' , enum : [ 'node_cpu' , 'node_memory' , 'node_storage' , 'node_unavailable' , 'terminal_offline' , 'terminal_cpu' , 'terminal_memory' , 'terminal_temperature' ] } , threshold : number , holdSeconds : integer , enabled : boolean } ) ,
AlertRule : object ( { id : string , name : string , metric : { type : 'string' , enum : [ 'node_cpu' , 'node_memory' , 'node_storage' , 'node_unavailable' , 'terminal_offline' , 'terminal_cpu' , 'terminal_memory' , 'terminal_temperature' ] } , threshold : number , holdSeconds : integer , enabled : boolean } ) ,
AlertRecord : object ( { id : string , ruleId : string , ruleName : string , metric : string , targetId : string , targetName : string , value : number , threshold : number , status : { type : 'string' , enum : [ 'firing' , 'resolved' ] } , startedAt : date , openedAt : date , resolvedAt : { ... date , nullable : true } , resolveReason : string , acknowledgedAt : { ... date , nullable : true } , acknowledgedBy : string , dataFresh : { ... boolean , nullable : true } } ) ,
2026-11-10 04:52:52 -05:00
ResourcePoint : object ( { time : date , cpu : { ... number , nullable : true } , memory : { ... number , nullable : true } , storage : { ... number , nullable : true } , samples : integer } ) ,
ResourceSeries : object ( { items : array ( ref ( 'ResourcePoint' ) ) , bucketSeconds : integer , retentionDays : integer } ) ,
TerminalPolicyInput : object ( { name : string , version : string , heartbeatInterval : { ... integer , minimum : 15 , maximum : 60 } , commandPollInterval : { ... integer , minimum : 3 , maximum : 30 } , desktopRefreshInterval : { ... integer , minimum : 5 , maximum : 120 } } ) ,
TerminalPolicy : object ( { id : string , name : string , version : string , heartbeatInterval : integer , commandPollInterval : integer , desktopRefreshInterval : integer , createdAt : date } ) ,
PolicyDeployment : object ( { id : string , templateId : string , templateName : string , templateVersion : string , actor : string , targetCount : integer , createdAt : date } ) ,
PolicyDeploymentInput : object ( { templateId : string , targetMode : { type : 'string' , enum : [ 'devices' , 'group' ] } , terminalIds : { ... array ( string ) , maxItems : 200 } , group : string } , [ 'templateId' , 'targetMode' ] ) ,
PolicyResult : object ( { id : string , terminalId : string , terminalName : string , group : string , protocol : integer , deploymentId : string , templateName : string , templateVersion : string , configVersion : integer , status : { type : 'string' , enum : [ 'unconfigured' , 'pending' , 'applied' , 'failed' , 'superseded' ] } , errorCode : string , acknowledgedAt : { ... date , nullable : true } , online : boolean , enabled : boolean , registered : boolean } ) ,
PolicyAck : object ( { deploymentId : string , configVersion : integer , status : { type : 'string' , enum : [ 'applied' , 'failed' ] } , errorCode : { type : 'string' , enum : [ 'invalid_policy' , 'persist_failed' , 'runtime_unavailable' ] } } , [ 'deploymentId' , 'configVersion' , 'status' ] ) ,
AdministratorAccount : object ( { id : string , username : string , displayName : string , role : { type : 'string' , enum : [ 'super_admin' , 'operator' , 'viewer' ] } , enabled : boolean } ) ,
2026-11-11 05:46:16 -05:00
AdministratorSession : object ( { key : string , createdAt : { ... date , nullable : true } , expiresAt : date , clientIp : string , userAgent : string , current : boolean } ) ,
2026-11-10 04:52:52 -05:00
CreateAdministrator : object ( { username : { type : 'string' , minLength : 3 , maxLength : 80 } , displayName : string , role : { type : 'string' , enum : [ 'super_admin' , 'operator' , 'viewer' ] } , password : { type : 'string' , format : 'password' , minLength : 12 , maxLength : 72 } } ) ,
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 } ) ,
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 ) } ) ,
CloneInput : object ( { templateId : string , poolId : { ... string , nullable : true } , name : string , vmid : integer , full : boolean , storage : string } ) ,
CloneCheck : object ( { available : boolean , message : string } ) ,
Storage : object ( { storage : string , avail : number , active : integer , enabled : integer , content : string } ) ,
ConnectionInput : object ( { endpoint : string , width : integer , height : integer , fps : integer , initialized : boolean } ) ,
} )
schemas . Desktop . properties . poolId = { ... string , nullable : true }
schemas . Desktop . properties . setupRequired = boolean
2026-11-11 05:46:16 -05:00
schemas . DeviceDesktop = object ( { ... schemas . Desktop . properties , powerOnConnect : boolean } )
schemas . DeviceDesktopList = object ( { items : array ( ref ( 'DeviceDesktop' ) ) , total : integer , page : integer , pageSize : integer } )
schemas . GPUPrepareInput = object ( { desktopId : { ... string , maxLength : 100 } } )
schemas . GPUPrepareResult = object ( { taskId : string , status : { type : 'string' , enum : [ 'queued' , 'running' , 'success' , 'failed' , 'timeout' ] } , message : string } )
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 ) } )
2026-11-10 04:52:52 -05:00
schemas . Administrator . properties . enabled = boolean
schemas . HeartbeatInput . properties . policyProtocol = { ... integer , minimum : 0 , maximum : 1 }
schemas . HeartbeatInput . properties . systemMetrics = { ... ref ( 'TerminalSystemMetrics' ) , nullable : true }
Object . assign ( schemas . DeviceConfig . properties , { deploymentId : string , policyProtocol : integer , commandPollInterval : integer , desktopRefreshInterval : integer } )
schemas . DeviceConfig . required = Object . keys ( schemas . DeviceConfig . properties )
2026-11-09 04:54:52 -05:00
// Older frontends can omit this display field; the API always supplies it.
schemas . Terminal . properties . sessionState = string
2026-11-10 04:52:52 -05:00
schemas . Terminal . properties . enabled = boolean
2026-11-11 05:46:16 -05:00
for ( const name of [ 'Desktop' , 'Terminal' , 'User' , 'Resource' , 'Task' , 'Audit' , 'DiscoveredVM' , 'ImageTemplate' , 'DesktopPool' , 'Storage' , 'AdministratorAccount' , 'AdministratorSession' , 'TerminalPolicy' , 'PolicyDeployment' , 'PolicyResult' , 'SessionRecord' , 'AlertRule' , 'AlertRecord' , 'PowerBatch' ] ) {
2026-11-09 04:54:52 -05:00
schemas [ ` ${ name } List ` ] = object ( { items : array ( ref ( name ) ) , page : integer , pageSize : integer , total : integer , requestId : string } )
}
2026-11-10 04:52:52 -05:00
Object . assign ( schemas . AlertRecordList . properties , { firingCount : integer , unacknowledgedCount : integer } )
schemas . AlertRecordList . required . push ( 'firingCount' , 'unacknowledgedCount' )
2026-11-09 04:54:52 -05:00
const json = schema => ( { 'application/json' : { schema } } )
2026-11-10 04:52:52 -05:00
const operation = ( id , output , { input , status = 200 , list = false , param = false , command = false , node = false , period = false , resource = false , security = 'adminBearer' } = { } ) => ( {
2026-11-09 04:54:52 -05:00
operationId : id ,
security : security ? [ { [ security ] : [ ] } ] : [ ] ,
... ( input ? { requestBody : { required : true , content : json ( ref ( input ) ) } } : { } ) ,
parameters : [
2026-11-10 04:52:52 -05:00
... ( period ? [ 'from' , 'to' ] . map ( name => ( { name , in : 'query' , schema : date } ) ) : [ ] ) ,
... ( resource ? [ { name : 'resourceId' , in : 'query' , required : true , schema : string } ] : [ ] ) ,
... ( node ? [ { name : 'node' , in : 'query' , required : true , schema : string } ] : [ ] ) ,
2026-11-09 04:54:52 -05:00
... ( param ? [ { name : 'id' , in : 'path' , required : true , schema : string } ] : [ ] ) ,
... ( list ? [ 'page' , 'pageSize' , 'keyword' , 'status' ] . map ( name => ( { name , in : 'query' , schema : name . startsWith ( 'page' ) ? integer : string } ) ) : [ ] ) ,
... ( command ? [ { name : 'Idempotency-Key' , in : 'header' , required : true , schema : { type : 'string' , maxLength : 100 } } ] : [ ] ) ,
] ,
responses : {
[ status ] : { description : 'Successful response' , ... ( output ? { content : json ( ref ( output ) ) } : { } ) } ,
2026-11-10 04:52:52 -05:00
... Object . fromEntries ( [ 400 , 401 , 403 , 404 , 409 , 429 , 500 , 502 , 503 ] . map ( code => [ code , { description : 'Request failed' , content : json ( ref ( 'Error' ) ) } ] ) ) ,
2026-11-09 04:54:52 -05:00
} ,
} )
const paths = {
2026-11-10 04:52:52 -05:00
'/settings/pve' : { get : operation ( 'pveConnection' , 'PveConnection' ) , put : operation ( 'savePveConnection' , 'Accepted' , { input : 'PveConnectionInput' } ) } ,
'/settings/pve/check' : { post : operation ( 'checkPveConnection' , 'PveConnectionCheck' , { input : 'PveConnectionInput' } ) } ,
'/desktops/{id}/hardware' : { get : operation ( 'desktopHardware' , 'VMHardware' , { param : true } ) , post : operation ( 'changeDesktopHardware' , 'Task' , { param : true , input : 'HardwareInput' , command : true , status : 202 } ) } ,
'/desktops/{id}/hardware/check' : { post : operation ( 'checkDesktopHardware' , 'HardwareCheck' , { param : true , input : 'HardwareInput' } ) } ,
'/desktop-batches' : { get : operation ( 'desktopPowerBatches' , 'PowerBatchList' , { list : true } ) , post : operation ( 'submitDesktopPowerBatch' , 'PowerBatchDetail' , { input : 'PowerBatchInput' , command : true , status : 202 } ) } ,
'/desktop-batches/check' : { post : operation ( 'checkDesktopPowerBatch' , 'PowerBatchPreview' , { input : 'PowerBatchInput' } ) } ,
'/desktop-batches/{id}' : { get : operation ( 'desktopPowerBatch' , 'PowerBatchDetail' , { param : true } ) } ,
'/tasks/{id}/batch-power-reconcile' : { post : operation ( 'reconcileBatchPower' , 'Accepted' , { param : true } ) } ,
'/desktops/{id}/snapshots' : { get : operation ( 'desktopSnapshots' , 'SnapshotView' , { param : true } ) } ,
'/desktops/{id}/snapshots/check' : { post : operation ( 'checkDesktopSnapshot' , 'HardwareCheck' , { param : true , input : 'SnapshotInput' } ) } ,
'/desktops/{id}/snapshots/actions' : { post : operation ( 'changeDesktopSnapshot' , 'Task' , { param : true , input : 'SnapshotInput' , command : true , status : 202 } ) } ,
'/tasks/{id}/snapshot-reconcile' : { post : operation ( 'reconcileSnapshot' , 'Accepted' , { param : true } ) } ,
'/tasks/{id}/hardware-reconcile' : { post : operation ( 'reconcileHardware' , 'Accepted' , { param : true } ) } ,
'/imports/preview' : { post : operation ( 'previewCsvImport' , 'CsvImportPreview' , { input : 'CsvImportInput' } ) } ,
'/imports/commit' : { post : operation ( 'commitCsvImport' , 'CsvImportResult' , { input : 'CsvImportInput' , command : true } ) } ,
'/sessions/history' : { get : operation ( 'sessionHistory' , 'SessionRecordList' , { list : true , period : true } ) } ,
'/monitor/series' : { get : operation ( 'resourceSeries' , 'ResourceSeries' , { period : true , resource : true } ) } ,
'/monitor/rules' : { get : operation ( 'alertRules' , 'AlertRuleList' , { list : true } ) , post : operation ( 'createAlertRule' , 'AlertRule' , { input : 'AlertRuleInput' } ) } ,
'/monitor/rules/{id}' : { put : operation ( 'updateAlertRule' , 'AlertRule' , { input : 'AlertRuleInput' , param : true } ) } ,
'/monitor/alerts' : { get : operation ( 'alerts' , 'AlertRecordList' , { list : true } ) } ,
'/monitor/alerts/{id}/acknowledge' : { post : operation ( 'acknowledgeAlert' , 'Accepted' , { param : true } ) } ,
'/terminal-policy-templates' : { get : operation ( 'terminalPolicyTemplates' , 'TerminalPolicyList' , { list : true } ) , post : operation ( 'createTerminalPolicy' , 'TerminalPolicy' , { input : 'TerminalPolicyInput' , status : 201 } ) } ,
'/terminal-policy-states' : { get : operation ( 'terminalPolicyStates' , 'PolicyResultList' , { list : true } ) } ,
'/terminal-policy-deployments' : { get : operation ( 'policyDeployments' , 'PolicyDeploymentList' , { list : true } ) , post : operation ( 'deployPolicy' , 'PolicyDeployment' , { input : 'PolicyDeploymentInput' , command : true , status : 202 } ) } ,
'/terminal-policy-deployments/{id}/results' : { get : operation ( 'policyResults' , 'PolicyResultList' , { list : true , param : true } ) } ,
'/terminal/config/ack' : { post : operation ( 'policyAck' , 'Accepted' , { input : 'PolicyAck' , security : 'deviceBearer' } ) } ,
2026-11-09 04:54:52 -05:00
'/auth/login' : { post : operation ( 'login' , 'LoginResult' , { input : 'LoginInput' , security : null } ) } ,
'/auth/me' : { get : operation ( 'me' , 'Administrator' ) } ,
'/auth/logout' : { post : operation ( 'logout' , null , { status : 204 } ) } ,
2026-11-10 04:52:52 -05:00
'/auth/password' : { post : operation ( 'changeOwnPassword' , null , { input : 'ChangePassword' , status : 204 } ) } ,
2026-11-11 05:46:16 -05:00
'/auth/mfa' : { get : operation ( 'mfaStatus' , 'MFAStatus' ) , delete : operation ( 'disableMFA' , null , { input : 'MFACredential' , status : 204 } ) } ,
'/auth/mfa/setup' : { post : operation ( 'beginMFA' , 'MFASetup' , { input : 'MFASetupInput' } ) } ,
'/auth/mfa/confirm' : { post : operation ( 'confirmMFA' , 'MFARecovery' , { input : 'MFAConfirm' } ) } ,
'/auth/mfa/recovery' : { post : operation ( 'rotateMFARecovery' , 'MFARecovery' , { input : 'MFACredential' } ) } ,
2026-11-10 04:52:52 -05:00
'/administrators' : { get : operation ( 'administrators' , 'AdministratorAccountList' , { list : true } ) , post : operation ( 'createAdministrator' , 'AdministratorAccount' , { input : 'CreateAdministrator' , status : 201 } ) } ,
'/administrators/{id}' : { put : operation ( 'updateAdministrator' , 'AdministratorAccount' , { input : 'UpdateAdministrator' , param : true } ) } ,
'/administrators/{id}/password' : { post : operation ( 'resetAdministratorPassword' , null , { input : 'ResetAdministratorPassword' , param : true , status : 204 } ) } ,
2026-11-11 05:46:16 -05:00
'/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 } } ] } } ,
2026-11-09 04:54:52 -05:00
'/overview' : { get : operation ( 'overview' , 'Overview' ) } ,
'/settings' : { get : operation ( 'settings' , 'Settings' ) } ,
'/resources' : { get : operation ( 'resources' , 'ResourceList' , { list : true } ) } ,
2026-11-10 04:52:52 -05:00
'/resources/gpus' : { get : operation ( 'gpuInventory' , 'GPUInventory' , { node : true } ) } ,
2026-11-09 04:54:52 -05:00
'/resources/vms' : { get : operation ( 'discoverVMs' , 'DiscoveredVMList' , { list : true } ) } ,
2026-11-10 04:52:52 -05:00
'/resources/templates' : { get : operation ( 'discoverTemplates' , 'DiscoveredVMList' , { list : true } ) } ,
'/resources/storage' : { get : operation ( 'storages' , 'StorageList' , { list : true , node : true } ) } ,
'/templates' : { get : operation ( 'templates' , 'ImageTemplateList' , { list : true } ) , post : operation ( 'registerTemplate' , 'ImageTemplate' , { input : 'TemplateInput' , status : 201 } ) } ,
'/templates/{id}' : { patch : operation ( 'updateTemplate' , 'ImageTemplate' , { param : true , input : 'TemplateUpdate' } ) } ,
'/templates/{id}/validate' : { post : operation ( 'validateTemplate' , 'ImageTemplate' , { param : true } ) } ,
'/pools' : { get : operation ( 'pools' , 'DesktopPoolList' , { list : true } ) , post : operation ( 'createPool' , 'DesktopPool' , { input : 'PoolInput' } ) } ,
'/pools/{id}' : { put : operation ( 'updatePool' , 'DesktopPool' , { param : true , input : 'PoolInput' } ) } ,
'/pools/{id}/members' : { put : operation ( 'poolMembers' , 'Accepted' , { param : true , input : 'PoolMembers' } ) } ,
'/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' } ) } ,
2026-11-09 04:54:52 -05:00
'/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' } ) } ,
'/desktops/{id}' : { get : operation ( 'desktop' , 'Desktop' , { param : true } ) } ,
'/desktops/{id}/actions' : { post : operation ( 'desktopAction' , 'Task' , { input : 'DesktopAction' , param : true , status : 202 , command : true } ) } ,
'/terminals' : { get : operation ( 'terminals' , 'TerminalList' , { list : true } ) , post : operation ( 'enrollTerminal' , 'Enrollment' , { input : 'EnrollmentInput' , status : 201 } ) } ,
2026-11-10 04:52:52 -05:00
'/terminals/{id}' : { put : operation ( 'updateTerminal' , 'Accepted' , { input : 'TerminalUpdate' , param : true } ) } ,
'/terminals/{id}/metrics' : { get : operation ( 'terminalMetrics' , 'TerminalMetrics' , { param : true , period : true } ) } ,
'/terminals/{id}/disable' : { post : operation ( 'disableTerminal' , 'Accepted' , { param : true } ) } ,
'/terminals/{id}/reactivate' : { post : operation ( 'reactivateTerminal' , 'Enrollment' , { param : true } ) } ,
2026-11-09 04:54:52 -05:00
'/terminals/{id}/commands' : { post : operation ( 'terminalCommand' , 'Task' , { input : 'TerminalAction' , param : true , status : 202 , command : true } ) } ,
'/users' : { get : operation ( 'users' , 'UserList' , { list : true } ) , post : operation ( 'createUser' , 'User' , { input : 'CreateUserInput' , status : 201 } ) } ,
2026-11-10 04:52:52 -05:00
'/users/{id}' : { put : operation ( 'updateUser' , 'User' , { input : 'UserUpdate' , param : true } ) } ,
'/users/batch-bindings' : { post : operation ( 'batchBindings' , 'BatchBindingsResult' , { input : 'BatchBindings' } ) } ,
2026-11-09 04:54:52 -05:00
'/users/{id}/bindings' : { put : operation ( 'updateBindings' , 'User' , { input : 'BindingInput' , param : true } ) } ,
2026-11-11 05:46:16 -05:00
'/tasks' : { get : operation ( 'tasks' , 'TaskList' , { list : true , period : true } ) } ,
'/tasks/export' : { get : operation ( 'exportTasks' , null , { period : true } ) } ,
2026-11-09 04:54:52 -05:00
'/tasks/{id}' : { get : operation ( 'task' , 'Task' , { param : true } ) } ,
2026-11-10 04:52:52 -05:00
'/tasks/{id}/reconcile' : { post : operation ( 'reconcileProvision' , 'Task' , { param : true , status : 202 } ) } ,
2026-11-09 04:54:52 -05:00
'/tasks/{id}/diagnostics' : { get : operation ( 'taskDiagnostics' , 'DeviceDiagnostics' , { param : true } ) } ,
2026-11-11 05:46:16 -05:00
'/diagnostics' : { get : operation ( 'diagnostics' , 'DiagnosticReport' ) } ,
'/diagnostics/export' : { get : operation ( 'exportDiagnostics' , null ) } ,
'/audit-logs' : { get : operation ( 'audits' , 'AuditList' , { list : true , period : true } ) } ,
'/audit-logs/export' : { get : operation ( 'exportAudits' , null , { period : true } ) } ,
2026-11-09 04:54:52 -05:00
'/terminal/register' : { post : operation ( 'registerDevice' , 'RegisterResult' , { input : 'RegisterInput' , security : null } ) } ,
'/terminal/heartbeat' : { post : operation ( 'heartbeat' , 'HeartbeatResult' , { input : 'HeartbeatInput' , security : 'deviceBearer' } ) } ,
'/terminal/config' : { get : operation ( 'deviceConfig' , 'DeviceConfig' , { security : 'deviceBearer' } ) } ,
2026-11-11 05:46:16 -05:00
'/terminal/desktops' : { get : operation ( 'deviceDesktops' , 'DeviceDesktopList' , { security : 'deviceBearer' , list : true } ) } ,
'/terminal/desktop-prepare' : { post : operation ( 'prepareGPUDesktop' , 'GPUPrepareResult' , { input : 'GPUPrepareInput' , status : 202 , security : 'deviceBearer' } ) } ,
'/terminal/desktop-prepare/{id}' : { get : operation ( 'gpuPrepareStatus' , 'GPUPrepareResult' , { param : true , security : 'deviceBearer' } ) } ,
'/tasks/{id}/gpu-reconcile' : { post : operation ( 'reconcileGPUPrepare' , 'GPUReconcileResult' , { param : true } ) } ,
2026-11-09 04:54:52 -05:00
'/terminal/commands' : { get : operation ( 'deviceCommands' , 'TaskList' , { security : 'deviceBearer' , list : true } ) } ,
'/terminal/commands/{id}/ack' : { post : operation ( 'deviceAck' , 'Accepted' , { input : 'AckInput' , param : true , security : 'deviceBearer' } ) } ,
'/terminal/connect-ticket' : { post : operation ( 'createTicket' , 'Ticket' , { input : 'TicketInput' , status : 201 , security : 'deviceBearer' } ) } ,
'/terminal/connect-ticket/consume' : { post : operation ( 'consumeTicket' , 'Connection' , { input : 'ConsumeInput' , security : 'deviceBearer' } ) } ,
'/terminal/session' : { post : operation ( 'deviceSession' , 'Accepted' , { input : 'SessionInput' , security : 'deviceBearer' } ) } ,
}
2026-11-11 05:46:16 -05:00
const taskFilters = [ 'actor' , 'action' , 'kind' , 'targetId' ] . map ( name => ( { name , in : 'query' , schema : { ... string , maxLength : 200 } } ) )
const diagnosticFilters = [
{ name : 'targetType' , in : 'query' , required : true , schema : { type : 'string' , enum : [ 'terminal' , 'desktop' ] } } ,
{ name : 'targetId' , in : 'query' , required : true , schema : { ... string , minLength : 1 , maxLength : 100 } } ,
... [ 'from' , 'to' ] . map ( name => ( { name , in : 'query' , schema : date } ) ) ,
{ name : 'source' , in : 'query' , schema : { type : 'string' , enum : [ '' , 'tasks' , 'audits' , 'sessions' , 'alerts' , 'logs' ] } } ,
]
paths [ '/diagnostics' ] . get . parameters . push ( ... diagnosticFilters , { name : 'page' , in : 'query' , schema : { ... integer , minimum : 1 } } , { name : 'pageSize' , in : 'query' , schema : { ... integer , minimum : 1 , maximum : 100 } } )
paths [ '/diagnostics/export' ] . get . parameters . push ( ... diagnosticFilters )
paths [ '/diagnostics' ] . get . description = 'Super administrators and operators only. Exact ID correlation, merged lifecycle events, maximum seven days. Redacts common credential formats. No collection or VM changes.'
paths [ '/diagnostics/export' ] . get . description = 'Super administrators and operators only. ZIP of all matching events, up to 2000, with diagnostic JSON and scope notes. Uses the same object/time/source filters, ignores pagination, and records the export audit.'
paths [ '/diagnostics/export' ] . get . responses [ 200 ] = { description : 'Scoped diagnostic archive' , content : { 'application/zip' : { schema : { type : 'string' , format : 'binary' } } } }
paths [ '/diagnostics/export' ] . get . responses [ 413 ] = { description : 'More than 2000 matching events; narrow time range or source' , content : json ( ref ( 'Error' ) ) }
taskFilters . push ( { name : 'active' , in : 'query' , schema : boolean , description : 'true includes queued, running and unknown outcomes (timeout)' } )
paths [ '/tasks' ] . get . parameters . push ( ... taskFilters )
paths [ '/tasks/export' ] . get . parameters . push ( ... taskFilters , ... [ 'keyword' , 'status' ] . map ( name => ( { name , in : 'query' , schema : { ... string , maxLength : 200 } } ) ) )
paths [ '/tasks/export' ] . get . responses [ 200 ] = { description : 'Complete filtered CSV; maximum 10000 rows. UTC times, raw action/status codes, no provider payload or idempotency keys.' , headers : { 'X-Export-Count' : { schema : integer } } , content : { 'text/csv' : { schema : { type : 'string' , format : 'binary' } } } }
paths [ '/tasks/export' ] . get . responses [ 413 ] = { description : 'Over 10000 matching tasks' , content : json ( ref ( 'Error' ) ) }
const auditFilters = [ 'actor' , 'action' , 'result' ] . map ( name => ( { name , in : 'query' , schema : { ... string , maxLength : 200 } } ) )
paths [ '/audit-logs' ] . get . parameters . push ( ... auditFilters )
paths [ '/audit-logs/export' ] . get . parameters . push ( ... auditFilters , { name : 'keyword' , in : 'query' , schema : { ... string , maxLength : 200 } } )
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' ) ) }
2026-11-09 04:54:52 -05:00
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 directory = fileURLToPath ( new URL ( '../docs/openapi/' , import . meta . url ) )
mkdirSync ( directory , { recursive : true } )
writeFileSync ( ` ${ directory } /platform.v1.json ` , ` ${ JSON . stringify ( output , null , 2 ) } \n ` )
console . log ( 'Updated docs/openapi/platform.v1.json' )