diff --git a/packages/api-client/CLAUDE.md b/packages/api-client/CLAUDE.md index 015556ee67..6b7d150824 100644 --- a/packages/api-client/CLAUDE.md +++ b/packages/api-client/CLAUDE.md @@ -37,7 +37,7 @@ client.labrinth.collections client.labrinth.billing_internal client.archon.servers_v0 client.archon.servers_v1 -client.archon.backups_v0 +client.archon.backups_queue_v1 client.archon.backups_v1 client.archon.content_v0 client.kyros.files_v0 diff --git a/packages/api-client/src/core/abstract-client.ts b/packages/api-client/src/core/abstract-client.ts index a8dd1a3e91..d9161d9e65 100644 --- a/packages/api-client/src/core/abstract-client.ts +++ b/packages/api-client/src/core/abstract-client.ts @@ -9,6 +9,11 @@ import { AbstractUploadClient } from './abstract-upload-client' import type { AbstractWebSocketClient } from './abstract-websocket' import { ModrinthApiError, ModrinthServerError } from './errors' +type ArchonClientModules = Omit & { + /** @deprecated Use `backups_queue_v1` for the Backups Queue API. */ + backups_v1: InferredClientModules['archon']['backups_v1'] +} + /** * Abstract base client for Modrinth APIs */ @@ -27,7 +32,7 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient { private _moduleNamespaces: Map> = new Map() public readonly labrinth!: InferredClientModules['labrinth'] - public readonly archon!: InferredClientModules['archon'] & { sockets: AbstractWebSocketClient } + public readonly archon!: ArchonClientModules & { sockets: AbstractWebSocketClient } public readonly kyros!: InferredClientModules['kyros'] public readonly iso3166!: InferredClientModules['iso3166'] public readonly mclogs!: InferredClientModules['mclogs'] diff --git a/packages/api-client/src/modules/archon/backups-queue/v1.ts b/packages/api-client/src/modules/archon/backups-queue/v1.ts new file mode 100644 index 0000000000..99d5d8531e --- /dev/null +++ b/packages/api-client/src/modules/archon/backups-queue/v1.ts @@ -0,0 +1,93 @@ +import { AbstractModule } from '../../../core/abstract-module' +import type { Archon } from '../types' + +export class ArchonBackupsQueueV1Module extends AbstractModule { + public getModuleID(): string { + return 'archon_backups_queue_v1' + } + + /** GET /v1/servers/:server_id/worlds/:world_id/backups-queue */ + public async list( + serverId: string, + worldId: string, + ): Promise { + return this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue`, + { api: 'archon', version: 1, method: 'GET' }, + ) + } + + /** POST /v1/servers/:server_id/worlds/:world_id/backups-queue */ + public async create( + serverId: string, + worldId: string, + request: Archon.BackupsQueue.v1.BackupRequest, + ): Promise { + return this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue`, + { api: 'archon', version: 1, method: 'POST', body: request }, + ) + } + + /** POST /v1/servers/:server_id/worlds/:world_id/backups-queue/history/create/:operation_id/ack */ + public async ackCreate(serverId: string, worldId: string, operationId: number): Promise { + await this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue/history/create/${operationId}/ack`, + { api: 'archon', version: 1, method: 'POST' }, + ) + } + + /** POST /v1/servers/:server_id/worlds/:world_id/backups-queue/history/restore/:operation_id/ack */ + public async ackRestore(serverId: string, worldId: string, operationId: number): Promise { + await this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue/history/restore/${operationId}/ack`, + { api: 'archon', version: 1, method: 'POST' }, + ) + } + + /** DELETE /v1/servers/:server_id/worlds/:world_id/backups-queue/:backup_id */ + public async delete(serverId: string, worldId: string, backupId: string): Promise { + await this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue/${backupId}`, + { + api: 'archon', + version: 1, + method: 'DELETE', + }, + ) + } + + /** POST /v1/servers/:server_id/worlds/:world_id/backups-queue/delete-many */ + public async deleteMany(serverId: string, worldId: string, backupIds: string[]): Promise { + await this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue/delete-many`, + { + api: 'archon', + version: 1, + method: 'POST', + body: { backup_ids: backupIds } satisfies Archon.BackupsQueue.v1.DeleteManyBackupRequest, + }, + ) + } + + /** POST /v1/servers/:server_id/worlds/:world_id/backups-queue/:backup_id/restore */ + public async restore( + serverId: string, + worldId: string, + backupId: string, + request: Archon.BackupsQueue.v1.BackupRequest, + ): Promise { + await this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue/${backupId}/restore`, + { api: 'archon', version: 1, method: 'POST', body: request }, + ) + } + + /** POST /v1/servers/:server_id/worlds/:world_id/backups-queue/:backup_id/retry */ + public async retry(serverId: string, worldId: string, backupId: string): Promise { + await this.client.request( + `/servers/${serverId}/worlds/${worldId}/backups-queue/${backupId}/retry`, + { api: 'archon', version: 1, method: 'POST' }, + ) + } +} diff --git a/packages/api-client/src/modules/archon/backups/v1.ts b/packages/api-client/src/modules/archon/backups/v1.ts index d2d8da14e9..86fadfdf73 100644 --- a/packages/api-client/src/modules/archon/backups/v1.ts +++ b/packages/api-client/src/modules/archon/backups/v1.ts @@ -1,11 +1,17 @@ import { AbstractModule } from '../../../core/abstract-module' import type { Archon } from '../types' +/** + * @deprecated Use `client.archon.backups_queue_v1` (Backups Queue API) instead. + */ export class ArchonBackupsV1Module extends AbstractModule { public getModuleID(): string { return 'archon_backups_v1' } + /** + * @deprecated Use `client.archon.backups_queue_v1.list` instead. + */ /** GET /v1/servers/:server_id/worlds/:world_id/backups */ public async list(serverId: string, worldId: string): Promise { return this.client.request( @@ -14,6 +20,9 @@ export class ArchonBackupsV1Module extends AbstractModule { ) } + /** + * @deprecated Use `client.archon.backups_queue_v1.list` instead. + */ /** GET /v1/servers/:server_id/worlds/:world_id/backups/:backup_id */ public async get( serverId: string, @@ -26,6 +35,9 @@ export class ArchonBackupsV1Module extends AbstractModule { ) } + /** + * @deprecated Use `client.archon.backups_queue_v1.create` instead. + */ /** POST /v1/servers/:server_id/worlds/:world_id/backups */ public async create( serverId: string, @@ -38,6 +50,9 @@ export class ArchonBackupsV1Module extends AbstractModule { ) } + /** + * @deprecated Use `client.archon.backups_queue_v1.restore` instead. + */ /** POST /v1/servers/:server_id/worlds/:world_id/backups/:backup_id/restore */ public async restore(serverId: string, worldId: string, backupId: string): Promise { await this.client.request( @@ -50,6 +65,9 @@ export class ArchonBackupsV1Module extends AbstractModule { ) } + /** + * @deprecated Use `client.archon.backups_queue_v1.delete` instead. + */ /** DELETE /v1/servers/:server_id/worlds/:world_id/backups/:backup_id */ public async delete(serverId: string, worldId: string, backupId: string): Promise { await this.client.request(`/servers/${serverId}/worlds/${worldId}/backups/${backupId}`, { @@ -59,6 +77,9 @@ export class ArchonBackupsV1Module extends AbstractModule { }) } + /** + * @deprecated Use `client.archon.backups_queue_v1.retry` instead. + */ /** POST /v1/servers/:server_id/worlds/:world_id/backups/:backup_id/retry */ public async retry(serverId: string, worldId: string, backupId: string): Promise { await this.client.request( @@ -71,6 +92,9 @@ export class ArchonBackupsV1Module extends AbstractModule { ) } + /** + * @deprecated Legacy backups only; no queue equivalent. Prefer renaming via other supported flows if available. + */ /** PATCH /v1/servers/:server_id/worlds/:world_id/backups/:backup_id */ public async rename( serverId: string, diff --git a/packages/api-client/src/modules/archon/index.ts b/packages/api-client/src/modules/archon/index.ts index 917630f840..ed9719ad3c 100644 --- a/packages/api-client/src/modules/archon/index.ts +++ b/packages/api-client/src/modules/archon/index.ts @@ -1,4 +1,5 @@ export * from './backups/v1' +export * from './backups-queue/v1' export * from './content/v1' export * from './properties/v1' export * from './servers/v0' diff --git a/packages/api-client/src/modules/archon/types.ts b/packages/api-client/src/modules/archon/types.ts index 9af5472ce2..b6435c0be2 100644 --- a/packages/api-client/src/modules/archon/types.ts +++ b/packages/api-client/src/modules/archon/types.ts @@ -404,6 +404,9 @@ export namespace Archon { name: string created_at: string is_active: boolean + /** + * @deprecated Prefer `client.archon.backups_queue_v1.list()` for queue-aware backup state. + */ backups: Archon.Backups.v1.Backup[] content: WorldContentInfo | null readiness: WorldReadiness @@ -434,16 +437,24 @@ export namespace Archon { } export namespace Backups { + /** + * @deprecated Use {@link Archon.BackupsQueue.v1} and `client.archon.backups_queue_v1` instead. + */ export namespace v1 { + /** @deprecated Use {@link Archon.BackupsQueue.v1} instead. */ export type BackupState = 'ongoing' | 'done' | 'failed' | 'cancelled' | 'unchanged' + /** @deprecated Use {@link Archon.BackupsQueue.v1} instead. */ export type BackupTask = 'file' | 'create' | 'restore' + /** @deprecated Use {@link Archon.BackupsQueue.v1} instead. */ export type BackupStatus = 'pending' | 'in_progress' | 'timed_out' | 'error' | 'done' + /** @deprecated Use {@link Archon.BackupsQueue.v1} instead. */ export type BackupTaskProgress = { progress: number // 0.0 to 1.0 state: BackupState } + /** @deprecated Use {@link Archon.BackupsQueue.v1.BackupQueueBackup} instead. */ export type Backup = { id: string physical_id: string @@ -461,20 +472,87 @@ export namespace Archon { } } + /** @deprecated Use {@link Archon.BackupsQueue.v1.BackupRequest} instead. */ export type BackupRequest = { name: string } + /** @deprecated Use {@link Archon.BackupsQueue.v1} instead. */ export type PatchBackup = { name?: string } + /** @deprecated Use {@link Archon.BackupsQueue.v1.PostBackupQueueResponse} instead. */ export type PostBackupResponse = { id: string } } } + export namespace BackupsQueue { + export namespace v1 { + export type BackupQueueOperationType = 'create' | 'restore' + + export type BackupQueueState = + | 'pending' + | 'ongoing' + | 'completed' + | 'cancelled' + | 'failed' + | 'timed_out' + + export type BackupStatus = 'pending' | 'in_progress' | 'timed_out' | 'error' | 'done' + + export type BackupRequest = { + name: string + } + + export type PostBackupQueueResponse = { + id: string + } + + export type DeleteManyBackupRequest = { + backup_ids: string[] + } + + export type ActiveOperation = { + backup_id: string + operation_type: BackupQueueOperationType + operation_id?: number | null + has_parent: boolean + scheduled_for: string + synthetic_legacy: boolean + } + + export type BackupQueueOperation = { + operation_type: BackupQueueOperationType + operation_id?: number | null + state: BackupQueueState + scheduled_for: string + completed_at?: string | null + has_parent: boolean + error?: string | null + should_prompt: boolean + synthetic_legacy: boolean + } + + export type BackupQueueBackup = { + id: string + name: string + created_at: string + status: BackupStatus + locked: boolean + automated: boolean + history: BackupQueueOperation[] + } + + export type BackupsQueueResponse = { + active_operations: ActiveOperation[] + backups: BackupQueueBackup[] + } + } + } + export namespace Websocket { export namespace v0 { export type WSAuth = { @@ -482,7 +560,14 @@ export namespace Archon { token: string } - export type BackupState = 'ongoing' | 'done' | 'failed' | 'cancelled' | 'unchanged' + export type BackupState = + | 'pending' + | 'ongoing' + | 'done' + | 'failed' + | 'cancelled' + | 'unchanged' + | 'damaged' export type BackupTask = 'file' | 'create' | 'restore' export type WSBackupProgressEvent = { @@ -491,6 +576,8 @@ export namespace Archon { task: BackupTask state: BackupState progress: number + start_time?: number | null + finish_time?: number | null } export type WSLogEvent = { diff --git a/packages/api-client/src/modules/index.ts b/packages/api-client/src/modules/index.ts index 9eed22bc72..c7143b6384 100644 --- a/packages/api-client/src/modules/index.ts +++ b/packages/api-client/src/modules/index.ts @@ -1,6 +1,7 @@ import type { AbstractModrinthClient } from '../core/abstract-client' import type { AbstractModule } from '../core/abstract-module' import { ArchonBackupsV1Module } from './archon/backups/v1' +import { ArchonBackupsQueueV1Module } from './archon/backups-queue/v1' import { ArchonContentV1Module } from './archon/content/v1' import { ArchonOptionsV1Module } from './archon/options/v1' import { ArchonPropertiesV1Module } from './archon/properties/v1' @@ -54,6 +55,7 @@ type ModuleConstructor = new (client: AbstractModrinthClient) => AbstractModule * TODO: Better way? Probably not */ export const MODULE_REGISTRY = { + archon_backups_queue_v1: ArchonBackupsQueueV1Module, archon_backups_v1: ArchonBackupsV1Module, archon_content_v1: ArchonContentV1Module, archon_options_v1: ArchonOptionsV1Module, diff --git a/packages/ui/src/components/base/Checkbox.vue b/packages/ui/src/components/base/Checkbox.vue index 02a935e3ab..16d7b1f949 100644 --- a/packages/ui/src/components/base/Checkbox.vue +++ b/packages/ui/src/components/base/Checkbox.vue @@ -7,8 +7,8 @@ ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:brightness-[--hover-brightness] focus-visible:brightness-[--hover-brightness]' " - :aria-label="description || label" - :aria-checked="modelValue" + :aria-label="description || label || undefined" + :aria-checked="indeterminate ? 'mixed' : modelValue" role="checkbox" @click="toggle" > diff --git a/packages/ui/src/components/servers/backups/BackupCreateModal.vue b/packages/ui/src/components/servers/backups/BackupCreateModal.vue index b907c8487c..5cf16b547a 100644 --- a/packages/ui/src/components/servers/backups/BackupCreateModal.vue +++ b/packages/ui/src/components/servers/backups/BackupCreateModal.vue @@ -45,9 +45,9 @@