You've already forked AstralRinth
forked from didirus/AstralRinth
feat: ws client & new backups frontend (#4813)
* feat: ws client * feat: v1 backups endpoints * feat: migrate backups page to api-client and new DI ctx * feat: switch to ws client via api-client * fix: disgust * fix: stats * fix: console * feat: v0 backups api * feat: migrate backups.vue to page system w/ components to ui pkgs * feat: polish backups frontend * feat: pending refactor for ws handling of backups * fix: vue shit * fix: cancel logic fix * fix: qa issues * fix: alignment issues for backups page * fix: bar positioning * feat: finish QA * fix: icons * fix: lint & i18n * fix: clear comment * lint --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
95
packages/api-client/src/modules/archon/backups/v0.ts
Normal file
95
packages/api-client/src/modules/archon/backups/v0.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
export class ArchonBackupsV0Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'archon_backups_v0'
|
||||
}
|
||||
|
||||
/** GET /modrinth/v0/servers/:server_id/backups */
|
||||
public async list(serverId: string): Promise<Archon.Backups.v1.Backup[]> {
|
||||
return this.client.request<Archon.Backups.v1.Backup[]>(`/servers/${serverId}/backups`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** GET /modrinth/v0/servers/:server_id/backups/:backup_id */
|
||||
public async get(serverId: string, backupId: string): Promise<Archon.Backups.v1.Backup> {
|
||||
return this.client.request<Archon.Backups.v1.Backup>(
|
||||
`/servers/${serverId}/backups/${backupId}`,
|
||||
{ api: 'archon', version: 'modrinth/v0', method: 'GET' },
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/backups */
|
||||
public async create(
|
||||
serverId: string,
|
||||
request: Archon.Backups.v1.BackupRequest,
|
||||
): Promise<Archon.Backups.v1.PostBackupResponse> {
|
||||
return this.client.request<Archon.Backups.v1.PostBackupResponse>(
|
||||
`/servers/${serverId}/backups`,
|
||||
{ api: 'archon', version: 'modrinth/v0', method: 'POST', body: request },
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/backups/:backup_id/restore */
|
||||
public async restore(serverId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}/restore`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** DELETE /modrinth/v0/servers/:server_id/backups/:backup_id */
|
||||
public async delete(serverId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/backups/:backup_id/lock */
|
||||
public async lock(serverId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}/lock`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/backups/:backup_id/unlock */
|
||||
public async unlock(serverId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}/unlock`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /modrinth/v0/servers/:server_id/backups/:backup_id/retry */
|
||||
public async retry(serverId: string, backupId: string): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}/retry`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** PATCH /modrinth/v0/servers/:server_id/backups/:backup_id */
|
||||
public async rename(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
request: Archon.Backups.v1.PatchBackup,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/servers/${serverId}/backups/${backupId}`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'PATCH',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
}
|
||||
132
packages/api-client/src/modules/archon/backups/v1.ts
Normal file
132
packages/api-client/src/modules/archon/backups/v1.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { AbstractModule } from '../../../core/abstract-module'
|
||||
import type { Archon } from '../types'
|
||||
|
||||
/**
|
||||
* Default world ID - Uuid::nil() which the backend treats as "first/active world"
|
||||
* See: apps/archon/src/routes/v1/servers/worlds/mod.rs - world_id_nullish()
|
||||
* TODO:
|
||||
* - Make sure world ID is being passed before we ship worlds.
|
||||
* - The schema will change when Backups v4 (routes stay as v1) so remember to do that.
|
||||
*/
|
||||
const DEFAULT_WORLD_ID: string = '00000000-0000-0000-0000-000000000000' as const
|
||||
|
||||
export class ArchonBackupsV1Module extends AbstractModule {
|
||||
public getModuleID(): string {
|
||||
return 'archon_backups_v1'
|
||||
}
|
||||
|
||||
/** GET /v1/:server_id/worlds/:world_id/backups */
|
||||
public async list(
|
||||
serverId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<Archon.Backups.v1.Backup[]> {
|
||||
return this.client.request<Archon.Backups.v1.Backup[]>(
|
||||
`/${serverId}/worlds/${worldId}/backups`,
|
||||
{ api: 'archon', version: 1, method: 'GET' },
|
||||
)
|
||||
}
|
||||
|
||||
/** GET /v1/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
public async get(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<Archon.Backups.v1.Backup> {
|
||||
return this.client.request<Archon.Backups.v1.Backup>(
|
||||
`/${serverId}/worlds/${worldId}/backups/${backupId}`,
|
||||
{ api: 'archon', version: 1, method: 'GET' },
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/backups */
|
||||
public async create(
|
||||
serverId: string,
|
||||
request: Archon.Backups.v1.BackupRequest,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<Archon.Backups.v1.PostBackupResponse> {
|
||||
return this.client.request<Archon.Backups.v1.PostBackupResponse>(
|
||||
`/${serverId}/worlds/${worldId}/backups`,
|
||||
{ api: 'archon', version: 1, method: 'POST', body: request },
|
||||
)
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/backups/:backup_id/restore */
|
||||
public async restore(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}/restore`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** DELETE /v1/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
public async delete(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/backups/:backup_id/lock */
|
||||
public async lock(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}/lock`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/backups/:backup_id/unlock */
|
||||
public async unlock(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}/unlock`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** POST /v1/:server_id/worlds/:world_id/backups/:backup_id/retry */
|
||||
public async retry(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}/retry`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** PATCH /v1/:server_id/worlds/:world_id/backups/:backup_id */
|
||||
public async rename(
|
||||
serverId: string,
|
||||
backupId: string,
|
||||
request: Archon.Backups.v1.PatchBackup,
|
||||
worldId: string = DEFAULT_WORLD_ID,
|
||||
): Promise<void> {
|
||||
await this.client.request<void>(`/${serverId}/worlds/${worldId}/backups/${backupId}`, {
|
||||
api: 'archon',
|
||||
version: 1,
|
||||
method: 'PATCH',
|
||||
body: request,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './servers/types'
|
||||
export * from './backups/v0'
|
||||
export * from './backups/v1'
|
||||
export * from './servers/v0'
|
||||
export * from './servers/v1'
|
||||
export * from './types'
|
||||
|
||||
@@ -6,6 +6,18 @@ export class ArchonServersV0Module extends AbstractModule {
|
||||
return 'archon_servers_v0'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific server by ID
|
||||
* GET /modrinth/v0/servers/:id
|
||||
*/
|
||||
public async get(serverId: string): Promise<Archon.Servers.v0.Server> {
|
||||
return this.client.request<Archon.Servers.v0.Server>(`/servers/${serverId}`, {
|
||||
api: 'archon',
|
||||
method: 'GET',
|
||||
version: 'modrinth/v0',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of servers for the authenticated user
|
||||
* GET /modrinth/v0/servers
|
||||
@@ -54,4 +66,16 @@ export class ArchonServersV0Module extends AbstractModule {
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WebSocket authentication credentials for a server
|
||||
* GET /modrinth/v0/servers/:id/ws
|
||||
*/
|
||||
public async getWebSocketAuth(serverId: string): Promise<Archon.Websocket.v0.WSAuth> {
|
||||
return this.client.request<Archon.Websocket.v0.WSAuth>(`/servers/${serverId}/ws`, {
|
||||
api: 'archon',
|
||||
version: 'modrinth/v0',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,4 +125,187 @@ export namespace Archon {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Backups {
|
||||
export namespace v1 {
|
||||
export type BackupState = 'ongoing' | 'done' | 'failed' | 'cancelled' | 'unchanged'
|
||||
export type BackupTask = 'file' | 'create' | 'restore'
|
||||
|
||||
export type BackupTaskProgress = {
|
||||
progress: number // 0.0 to 1.0
|
||||
state: BackupState
|
||||
}
|
||||
|
||||
export type Backup = {
|
||||
id: string
|
||||
name: string
|
||||
created_at: string
|
||||
locked: boolean
|
||||
automated: boolean
|
||||
interrupted: boolean
|
||||
ongoing: boolean
|
||||
task?: {
|
||||
file?: BackupTaskProgress
|
||||
create?: BackupTaskProgress
|
||||
restore?: BackupTaskProgress
|
||||
}
|
||||
// TODO: Uncomment when API supports these fields
|
||||
// size?: number // bytes
|
||||
// creator_id?: string // user ID, or 'auto' for automated backups
|
||||
}
|
||||
|
||||
export type BackupRequest = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type PatchBackup = {
|
||||
name?: string
|
||||
}
|
||||
|
||||
export type PostBackupResponse = {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Websocket {
|
||||
export namespace v0 {
|
||||
export type WSAuth = {
|
||||
url: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export type BackupState = 'ongoing' | 'done' | 'failed' | 'cancelled' | 'unchanged'
|
||||
export type BackupTask = 'file' | 'create' | 'restore'
|
||||
|
||||
export type WSBackupProgressEvent = {
|
||||
event: 'backup-progress'
|
||||
id: string
|
||||
task: BackupTask
|
||||
state: BackupState
|
||||
progress: number
|
||||
}
|
||||
|
||||
export type WSLogEvent = {
|
||||
event: 'log'
|
||||
stream: 'stdout' | 'stderr'
|
||||
message: string
|
||||
}
|
||||
|
||||
export type WSStatsEvent = {
|
||||
event: 'stats'
|
||||
cpu_percent: number
|
||||
ram_usage_bytes: number
|
||||
ram_total_bytes: number
|
||||
storage_usage_bytes: number
|
||||
storage_total_bytes: number
|
||||
net_tx_bytes: number
|
||||
net_rx_bytes: number
|
||||
}
|
||||
|
||||
export type PowerState = 'running' | 'stopped' | 'starting' | 'stopping' | 'crashed'
|
||||
|
||||
export type WSPowerStateEvent = {
|
||||
event: 'power-state'
|
||||
state: PowerState
|
||||
oom_killed?: boolean
|
||||
exit_code?: number
|
||||
}
|
||||
|
||||
export type WSAuthExpiringEvent = {
|
||||
event: 'auth-expiring'
|
||||
}
|
||||
|
||||
export type WSAuthIncorrectEvent = {
|
||||
event: 'auth-incorrect'
|
||||
}
|
||||
|
||||
export type WSAuthOkEvent = {
|
||||
event: 'auth-ok'
|
||||
}
|
||||
|
||||
export type WSInstallationResultEvent =
|
||||
| WSInstallationResultOkEvent
|
||||
| WSInstallationResultErrEvent
|
||||
|
||||
export type WSInstallationResultOkEvent = {
|
||||
event: 'installation-result'
|
||||
result: 'ok'
|
||||
}
|
||||
|
||||
export type WSInstallationResultErrEvent = {
|
||||
event: 'installation-result'
|
||||
result: 'err'
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export type WSUptimeEvent = {
|
||||
event: 'uptime'
|
||||
uptime: number
|
||||
}
|
||||
|
||||
export type WSNewModEvent = {
|
||||
event: 'new-mod'
|
||||
project_id: string
|
||||
version_id: string
|
||||
}
|
||||
|
||||
export type FilesystemOpKind = 'unarchive'
|
||||
|
||||
export type FilesystemOpState =
|
||||
| 'queued'
|
||||
| 'ongoing'
|
||||
| 'done'
|
||||
| 'cancelled'
|
||||
| 'failure-corrupted'
|
||||
| 'failure-invalid-path'
|
||||
|
||||
export type FilesystemOperation = {
|
||||
op: FilesystemOpKind
|
||||
id: string
|
||||
progress: number
|
||||
bytes_processed: number
|
||||
files_processed: number
|
||||
state: FilesystemOpState
|
||||
mime: string
|
||||
current_file?: string
|
||||
invalid_path?: string
|
||||
src: string
|
||||
started: string
|
||||
}
|
||||
|
||||
export type WSFilesystemOpsEvent = {
|
||||
event: 'filesystem-ops'
|
||||
all: FilesystemOperation[]
|
||||
}
|
||||
|
||||
// Outgoing messages (client -> server)
|
||||
export type WSOutgoingMessage = WSAuthMessage | WSCommandMessage
|
||||
|
||||
export type WSAuthMessage = {
|
||||
event: 'auth'
|
||||
jwt: string
|
||||
}
|
||||
|
||||
export type WSCommandMessage = {
|
||||
event: 'command'
|
||||
cmd: string
|
||||
}
|
||||
|
||||
export type WSEvent =
|
||||
| WSBackupProgressEvent
|
||||
| WSLogEvent
|
||||
| WSStatsEvent
|
||||
| WSPowerStateEvent
|
||||
| WSAuthExpiringEvent
|
||||
| WSAuthIncorrectEvent
|
||||
| WSAuthOkEvent
|
||||
| WSInstallationResultEvent
|
||||
| WSUptimeEvent
|
||||
| WSNewModEvent
|
||||
| WSFilesystemOpsEvent
|
||||
|
||||
export type WSEventType = WSEvent['event']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { AbstractModrinthClient } from '../core/abstract-client'
|
||||
import type { AbstractModule } from '../core/abstract-module'
|
||||
import { ArchonBackupsV0Module } from './archon/backups/v0'
|
||||
import { ArchonBackupsV1Module } from './archon/backups/v1'
|
||||
import { ArchonServersV0Module } from './archon/servers/v0'
|
||||
import { ArchonServersV1Module } from './archon/servers/v1'
|
||||
import { ISO3166Module } from './iso3166'
|
||||
@@ -21,6 +23,8 @@ type ModuleConstructor = new (client: AbstractModrinthClient) => AbstractModule
|
||||
* TODO: Better way? Probably not
|
||||
*/
|
||||
export const MODULE_REGISTRY = {
|
||||
archon_backups_v0: ArchonBackupsV0Module,
|
||||
archon_backups_v1: ArchonBackupsV1Module,
|
||||
archon_servers_v0: ArchonServersV0Module,
|
||||
archon_servers_v1: ArchonServersV1Module,
|
||||
iso3166_data: ISO3166Module,
|
||||
|
||||
Reference in New Issue
Block a user