Files
HIPCTF2/docs/guides/admin-general-settings.md
T
2026-07-22 12:31:57 +00:00

133 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: guide
title: Admin — General Settings
description: How an admin edits global platform settings (page title, logo, theme, event window, default challenge IP, registrations, welcome Markdown) from the /admin/general page.
tags: [guide, admin, settings, general, tester]
timestamp: 2026-07-22T12:00:00Z
---
# When this view is available
The General Settings page is rendered at `/admin/general` for users with
`role === 'admin'` once the instance is initialized. It is reached from
the [Admin Shell](/guides/admin-shell.md) side-nav (`General` entry) or
by navigating directly to the URL.
| Layer | File | Check |
|------------------|----------------------------------------------------------------------------|------------------------------------------------|
| Client route | `frontend/src/app/app.routes.ts` | `/admin/general` child of `adminGuard`. |
| Client component | `frontend/src/app/features/admin/general.component.ts` | `AdminGeneralComponent.ngOnInit` fetches settings + themes. |
| Client predicate | `frontend/src/app/features/admin/general.pure.ts` (`deriveEventState`) | Computes the read-only event-state label. |
| Server route | `backend/src/modules/admin/admin-general.controller.ts` | `GET/PUT /api/v1/admin/general/settings` + `GET /api/v1/admin/general/themes`. |
# How to access (tester steps)
1. Sign in as an admin user (see [First-Run Bootstrap](/guides/bootstrap.md)
if no admin exists).
2. Open the username menu in the shell header and click **Admin area**,
or use the side-nav's **General** entry, or visit `/admin/general`
directly.
3. The page renders a `Loading settings...` placeholder while the
initial `GET /api/v1/admin/general/settings` + `GET .../themes`
requests are in flight.
# Fields
The page is a single reactive form with these controls (every
`data-testid` listed is asserted in the existing test suite):
| Label | `data-testid` | Backend field | Notes |
|------------------------|---------------------------|-------------------------|-------|
| Page title | `general-pageTitle` | `pageTitle` | Required, non-blank, max 120 chars; trimmed server-side. |
| Logo (file picker) | `general-logo-file` | `logo` (public URL) | Uploads via `POST /api/v1/uploads/logo`; the returned `publicUrl` is bound to a hidden input `general-logo`. |
| Logo upload status | `general-logo-uploading` / `general-logo-error` / `general-logo-current` | — | Inline status text under the file picker. |
| Global theme | `general-themeKey` | `themeKey` | `<select>` populated from `/themes`. Value is one of `THEME_IDS`. |
| Event start (UTC) | `general-eventStart` | `eventStartUtc` | `datetime-local` input converted to ISO UTC on save. |
| Event end (UTC) | `general-eventEnd` | `eventEndUtc` | Must be strictly after Event start. Invalid pair renders `general-endBeforeStart`. |
| Default challenge IP | `general-defaultIp` | `defaultChallengeIp` | Required, max 255 chars. |
| Enable registrations | `general-registrations` | `registrationsEnabled` | Boolean checkbox. When `false`, the public register endpoint returns `REGISTRATIONS_DISABLED`. |
| Welcome description | `general-welcome` | `welcomeMarkdown` | Multi-line textarea; a live preview is rendered into `general-welcome-preview`. |
| Event controls | `general-event-toggle` | (derived) | Disabled button whose text is the derived event state (`UNCONFIGURED` / `COUNTDOWN` / `RUNNING` / `STOPPED`). |
| Save button | `general-save` | — | Disabled while `submitting()` or `form.invalid`. |
| Save error / success | `general-save-error` / `general-save-ok` | — | Inline status. |
# Expected behavior
* **Initial load:** `loading() === true` renders `general-loading`. After
both requests resolve, the form is patched with the values from the
backend (UTC timestamps are converted to `datetime-local` strings via
`toDatetimeLocal` so the native picker shows them).
* **Logo upload:** selecting a file fires `POST /api/v1/uploads/logo`,
then writes the returned `publicUrl` into the hidden `logo` control.
If the upload fails, `general-logo-error` shows the message; the
previous logo is preserved.
* **Welcome Markdown preview:** every keystroke in `general-welcome`
triggers `MarkdownService.render` and updates `general-welcome-preview`
synchronously.
* **Event-state derivation:** the disabled `general-event-toggle` label
is computed from `deriveEventState(start, end)`:
* both empty → `UNCONFIGURED`
* `now < start``COUNTDOWN`
* `start <= now < end``RUNNING`
* `now >= end``STOPPED`
* **End-before-start validation:** if the user picks an end that is not
strictly after the start, the form becomes invalid and
`general-endBeforeStart` appears under the end input. Save remains
disabled.
* **Save:** clicking Save sends `PUT /api/v1/admin/general/settings`
with all fields. The Page title input is validated client-side for
non-blank content; an empty or whitespace-only Page title disables the
Save button and renders `general-pageTitle-error` so the request is
never issued. The server trims surrounding whitespace and re-validates
against the same 1120 character rule, so invalid payloads return
`400 VALIDATION_FAILED` and the stored Page title is unchanged.
On success the form is patched with the response,
`general-save-ok` renders briefly, and the backend emits an SSE
`general` event via `SseHubService` so other tabs refresh their theme.
* **Error states:** load failures render `general-error`; save failures
render `general-save-error` with the `error.message` (or
`error.error.message`) from the standard envelope.
# Visual elements
| Element | Selector |
|-------------------------------|-----------------------------------------------------|
| Page section | `[data-testid="admin-general"]` |
| Loading placeholder | `[data-testid="general-loading"]` |
| Load error | `[data-testid="general-error"]` |
| Form | `[data-testid="general-form"]` |
| Welcome preview | `[data-testid="general-welcome-preview"]` |
| End-before-start message | `[data-testid="general-endBeforeStart"]` |
# Architecture map
| Step | Where | What happens |
|------|------------------------------------------------------|---------------------------------------------------------------------------|
| 1 | `frontend/src/app/app.routes.ts` | `/admin/general` lazy-loads `AdminGeneralComponent`. |
| 2 | `frontend/src/app/features/admin/general.component.ts` | `ngOnInit` calls `AdminService.getGeneralSettings()` + `listAdminThemes()` in parallel. |
| 3 | `frontend/src/app/core/services/admin.service.ts` | `getGeneralSettings()``GET /api/v1/admin/general/settings`; `listAdminThemes()``GET /api/v1/admin/general/themes`; `updateGeneralSettings()``PUT .../settings`; `uploadLogo()``POST /api/v1/uploads/logo`. |
| 4 | `backend/src/modules/admin/admin-general.controller.ts` | `AdminGuard` + `@Roles('admin')` on every handler. |
| 5 | `backend/src/modules/admin/general.service.ts` | `getSettings` reads 8 keys via `SettingsService`; `updateSettings` writes all 8, emits `{ topic: 'general', themeKey }` via `SseHubService`. |
| 6 | `backend/src/modules/admin/dto/general.dto.ts` | `GeneralSettingsSchema` enforces string lengths, `themeKey` enum, and `eventEndUtc > eventStartUtc` via `superRefine`. |
# Notes
* The `general` SSE event (`{ topic: 'general', themeKey }`) is a
lightweight signal so authenticated tabs can pick up the new theme
without polling. Other tabs do not auto-refresh settings values.
* The "Event controls" toggle is intentionally a derived display, not
an editable control — adjust the UTC timestamps to change state.
* All timestamps are stored as ISO-8601 UTC strings in the `setting`
table; the UI converts to/from `datetime-local` for display.
* Saving the form requires the page title and default challenge IP to
be non-empty; the form is `invalid` and Save stays disabled until
they are.
# See also
- [Admin Shell](/guides/admin-shell.md) — side-nav layout and guard chain.
- [Admin — Categories](/guides/admin-categories.md) — categories management page that renders below General settings.
- [Admin Endpoints](/api/admin.md) — `GET/PUT /api/v1/admin/general/*` reference.
- [Uploads Endpoints](/api/uploads.md) — `POST /api/v1/uploads/logo` reference.
- [Backend Module Map](/architecture/backend-modules.md)