feat: backups page cleanup before worlds (#5844)

* feat: card alignment + fix modals

* feat: change admon title in restore alert modal

* fix: lint

* feat: backups queue api into api-client

* feat: impl backup queue api endpoints into frontend

* feat: ack fix

* feat: bulk actions

* feat: bulk delete impl

* fix: lint

* fix: align error states

* fix: transition group

* feat: ready for qa

* fix: lint

* feat: qa

* feat: stacked admonitions component

* fix: issues with stacking

* feat: hook up admonition stacking + fix app csp for staging kyros nodes

* fix: logs.vue

* qa: close stack on admonitions click

* fix: all problems with stacked admonitions

* qa: admonition cleanup and copy overhaul draft

* fix: qa issues padding

* fix: padding bug

* feat: qa

* fix: intercom in app csp bug

* fix: positioning intercom

* feat: loading overlay on top of console + admon consistency changes

* feat: scroll indicator fade in backup delete modal + admon timestamp fix

* feat: move action bar behind modal

* fix: lint + i18n

* fix: server ping spam on filter (cache but clear on unmount)

* fix: 1 admon fade in flicker issue

* chore: temp staging undo

* qa: changes

* fix: lint

* chore: revert staging to use staging

* fix: scoping
This commit is contained in:
Calum H.
2026-04-27 20:03:48 +01:00
committed by GitHub
parent 85ae1f2074
commit 620894aecb
79 changed files with 4640 additions and 1656 deletions

View File

@@ -9,6 +9,11 @@ import { AbstractUploadClient } from './abstract-upload-client'
import type { AbstractWebSocketClient } from './abstract-websocket'
import { ModrinthApiError, ModrinthServerError } from './errors'
type ArchonClientModules = Omit<InferredClientModules['archon'], 'backups_v1'> & {
/** @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<string, Record<string, AbstractModule>> = 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']

View File

@@ -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<Archon.BackupsQueue.v1.BackupsQueueResponse> {
return this.client.request<Archon.BackupsQueue.v1.BackupsQueueResponse>(
`/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<Archon.BackupsQueue.v1.PostBackupQueueResponse> {
return this.client.request<Archon.BackupsQueue.v1.PostBackupQueueResponse>(
`/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<void> {
await this.client.request<void>(
`/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<void> {
await this.client.request<void>(
`/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<void> {
await this.client.request<void>(
`/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<void> {
await this.client.request<void>(
`/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<void> {
await this.client.request<void>(
`/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<void> {
await this.client.request<void>(
`/servers/${serverId}/worlds/${worldId}/backups-queue/${backupId}/retry`,
{ api: 'archon', version: 1, method: 'POST' },
)
}
}

View File

@@ -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<Archon.Backups.v1.Backup[]> {
return this.client.request<Archon.Backups.v1.Backup[]>(
@@ -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<void> {
await this.client.request<void>(
@@ -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<void> {
await this.client.request<void>(`/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<void> {
await this.client.request<void>(
@@ -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,

View File

@@ -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'

View File

@@ -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 = {

View File

@@ -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'
@@ -55,6 +56,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,