docs: update documentation to OKF v0.1 format
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
---
|
||||
type: guide
|
||||
title: Admin — System (Backup, Restore, Danger Zone)
|
||||
description: How an admin navigates to /admin/system, creates a backup, stages and commits a restore, resets all scores, and wipes challenges — including the re-authentication confirmation, progress, and side effects.
|
||||
tags: [guide, admin, system, backup, restore, danger-zone, confirmation]
|
||||
timestamp: 2026-07-23T12:04:50Z
|
||||
---
|
||||
|
||||
# Navigation
|
||||
|
||||
1. Sign in as an admin and open the admin side-nav (see the
|
||||
[Admin Shell guide](/guides/admin-shell.md)).
|
||||
2. Click **System** in the side-nav. The route resolves to
|
||||
`/admin/system` and lazily loads the
|
||||
`AdminSystemComponent` standalone component.
|
||||
3. The page renders two panels side by side: **Database** (left) and
|
||||
**Danger Zone** (right). On narrow screens they stack vertically.
|
||||
|
||||
# Database panel
|
||||
|
||||
## Create a backup
|
||||
|
||||
1. Click **Create backup** (data-testid `admin-system-create-backup`).
|
||||
A spinner appears inside the button while the request is in flight.
|
||||
2. The browser receives a JSON file via `Content-Disposition:
|
||||
attachment; filename="hipctf-backup-YYYY-MM-DD.json"` and the
|
||||
browser download UI saves it to disk.
|
||||
3. On success the panel shows a green **Backup ready.** status line
|
||||
(data-testid `admin-system-backup-success`).
|
||||
4. On failure the panel shows a red error message under the buttons
|
||||
(data-testid `admin-system-backup-error`). The friendly message
|
||||
comes from `system.pure.ts` and resolves `SYSTEM_BACKUP_FAILED`,
|
||||
`SYSTEM_FILE_READ_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, etc.
|
||||
|
||||
## Restore a backup
|
||||
|
||||
1. Click **Restore backup** (data-testid `admin-system-restore-backup`).
|
||||
A hidden file picker opens restricted to `application/json`/`.json`.
|
||||
2. Pick a backup JSON file. The progress line shows
|
||||
**Reading selected backup…**, then **Validating backup
|
||||
server-side…** (data-testid `admin-system-restore-progress`).
|
||||
3. The server validates the JSON, decodes uploads, and verifies
|
||||
sizes/checksums. On success a summary card appears
|
||||
(data-testid `admin-system-restore-summary`) showing:
|
||||
* Tables detected
|
||||
* Uploaded files
|
||||
* Total bytes
|
||||
* Stage expiry (ISO timestamp)
|
||||
4. From the summary card, the admin has two options:
|
||||
* **Discard staged backup** (data-testid
|
||||
`admin-system-discard-restore`) — drops the staged state and
|
||||
resumes from a clean panel.
|
||||
* **Continue to destructive restore…** (data-testid
|
||||
`admin-system-confirm-restore`) — opens the confirmation modal.
|
||||
|
||||
### Confirmation modal (restore-backup)
|
||||
|
||||
* The modal title is **Restore database?**.
|
||||
* The detail paragraph explains that the entire database and uploads
|
||||
will be replaced (the modal's `preserved` line says **Nothing is
|
||||
preserved**).
|
||||
* The stage banner explicitly warns: *You will be logged out after
|
||||
restore completes.*
|
||||
* The admin must enter their current password and type
|
||||
`RESTORE BACKUP` exactly into the confirmation phrase field.
|
||||
* Click **Confirm**. The modal swaps the input fields for a
|
||||
*Working…* spinner.
|
||||
* The frontend calls `POST /confirmations` with `{ operation:
|
||||
'restore-backup', password, stagingId }` to receive a single-use
|
||||
token, then immediately calls `POST /restore/commit` with that
|
||||
token and the staging id.
|
||||
* On success the SPA calls `AuthService.forceServerInvalidation()`,
|
||||
resets the `UserStore`, and navigates to `/login`. The
|
||||
`ChallengesStore` and `ScoreboardStore` invalidate themselves via
|
||||
the `SystemDataChangeService` `'restore-completed'` event.
|
||||
* On failure the modal stays open and shows the resolved error
|
||||
(e.g. `SYSTEM_TOKEN_MISMATCH`, `SYSTEM_RESTORE_ROLLED_BACK`). The
|
||||
database panel also reflects the error inline.
|
||||
|
||||
# Danger Zone panel
|
||||
|
||||
The danger zone panel is outlined by a dashed red border and contains
|
||||
two red buttons:
|
||||
|
||||
| Button | Operation |
|
||||
|--------------------------------------------|---------------------|
|
||||
| `admin-system-reset-scores` | `reset-scores` |
|
||||
| `admin-system-wipe-challenges` | `wipe-challenges` |
|
||||
|
||||
Each button opens the same confirmation modal with operation-specific
|
||||
copy:
|
||||
|
||||
* **Reset all scores?** — detail lists what is removed
|
||||
(every solve and award), preserved (users, sessions, roles,
|
||||
challenges, categories, etc.). Confirmation phrase: `RESET SCORES`.
|
||||
* **Wipe all challenges?** — detail explains that every challenge, its
|
||||
attached files, every solve, and every award are removed, and that
|
||||
every uploaded challenge file on disk is deleted. Preserved:
|
||||
users, sessions, roles, categories, settings, blog posts, and
|
||||
root-level uploads (e.g. the site logo). Confirmation phrase:
|
||||
`WIPE CHALLENGES`.
|
||||
|
||||
After the admin enters their password and types the confirmation
|
||||
phrase, the SPA re-authenticates to issue a token and then commits the
|
||||
operation. The panel shows a green summary
|
||||
(`N solve record(s) removed.` or `N challenge(s), M file(s) wiped.`)
|
||||
and a toast notification. The corresponding stores
|
||||
(`ChallengesStore`, `ScoreboardStore`) automatically reset themselves
|
||||
via the `SystemDataChangeService` `scores-reset` and `challenges-wiped`
|
||||
events so every open tab sees fresh data.
|
||||
|
||||
# Cross-tab invalidation
|
||||
|
||||
After any of the destructive operations, the
|
||||
`SystemDataChangeService` notifies all subscribers in the same tab via
|
||||
an Angular signal. The injected effects in `ChallengesStore` and
|
||||
`ScoreboardStore` reset their cached board/matrix/event-log/graph
|
||||
data. For a restore, the SPA additionally forces a cross-tab
|
||||
invalidation by calling `AuthService.forceServerInvalidation()`,
|
||||
which clears the access token and notifies peer tabs via
|
||||
`BroadcastChannel` + the `storage`-event fallback so they redirect to
|
||||
`/login` as well.
|
||||
|
||||
# Error mapping
|
||||
|
||||
The pure helper `friendlySystemErrorMessage(op, err)` in
|
||||
`system.pure.ts` converts the backend error codes into user-facing
|
||||
messages. Notable mappings:
|
||||
|
||||
| Backend code | UI message |
|
||||
|---------------------------------------|----------------------------------------------------------------------------|
|
||||
| `SYSTEM_REAUTH_REQUIRED` | "Your session is no longer valid. Please log in again." |
|
||||
| `SYSTEM_INVALID_CREDENTIALS` | "The current password is incorrect." |
|
||||
| `SYSTEM_TOKEN_INVALID` / `_EXPIRED` | "Confirmation token is invalid / expired. Re-authenticate and try again." |
|
||||
| `SYSTEM_TOKEN_REUSED` | "Confirmation token was already used. Re-authenticate to get a fresh token." |
|
||||
| `SYSTEM_TOKEN_MISMATCH` | "Confirmation token does not match this operation." |
|
||||
| `SYSTEM_RESTORE_VALIDATION_FAILED` | "Backup validation failed. No data was changed." |
|
||||
| `SYSTEM_RESTORE_PAYLOAD_TOO_LARGE` | "The backup is larger than the configured upload limit." |
|
||||
| `SYSTEM_RESTORE_STAGE_EXPIRED` | "The backup staging window expired. Re-upload and validate again." |
|
||||
| `SYSTEM_RESTORE_ROLLED_BACK` | "Restore failed and was rolled back. Your data is unchanged." |
|
||||
| `SYSTEM_DANGER_ROLLED_BACK` | "Operation failed and was rolled back. Nothing was changed." |
|
||||
| `SYSTEM_BACKUP_FAILED` | "Backup could not be generated. No data was changed." |
|
||||
| `SYSTEM_DANGER_FAILED` | "Operation could not complete. No data was changed." |
|
||||
| `SYSTEM_OPERATION_IN_PROGRESS` | "Another destructive operation is currently running." |
|
||||
|
||||
# Examples
|
||||
|
||||
## Manual restore happy path
|
||||
|
||||
1. Download the backup from a known-good environment.
|
||||
2. Sign in as the admin on the target environment.
|
||||
3. Navigate to `/admin/system`, click **Restore backup**, select the
|
||||
downloaded file.
|
||||
4. Verify the summary matches the expected table counts and file
|
||||
counts.
|
||||
5. Click **Continue to destructive restore…**, enter the admin
|
||||
password, type `RESTORE BACKUP`, click **Confirm**.
|
||||
6. The modal closes, the page shows a brief *Restore complete.
|
||||
Logging out…* toast, and the SPA navigates to `/login`.
|
||||
7. Sign in again with the same admin credentials. The data on the
|
||||
target environment now matches the source backup.
|
||||
|
||||
## Manual reset happy path
|
||||
|
||||
1. Navigate to `/admin/system`.
|
||||
2. Click **Reset all scores**, enter the password, type
|
||||
`RESET SCORES`, click **Confirm**.
|
||||
3. The danger zone panel shows the number of solve records removed
|
||||
and a toast notifies the operation completed. Refresh the
|
||||
Scoreboard page — every player is back at zero.
|
||||
|
||||
## Manual wipe happy path
|
||||
|
||||
1. Navigate to `/admin/system`.
|
||||
2. Click **Wipe challenges**, enter the password, type
|
||||
`WIPE CHALLENGES`, click **Confirm**.
|
||||
3. The danger zone panel shows counts of challenges, files, and
|
||||
solves removed. The Challenges page becomes empty and the
|
||||
`/admin/challenges` list is empty. The `challenges/` folder on
|
||||
disk is also gone.
|
||||
Reference in New Issue
Block a user