# UX Reference: Maintenance Banner for Dashboards **Feature Branch**: `031-maintenance-banner` **Created**: 2026-05-21 **Status**: Draft **Updated**: 2026-05-25 — Added API Key management UX flow ## 1. User Persona & Context * **Who is the user?**: - **ETL Operator**: System administrator or DevOps engineer running ETL pipelines (Airflow, cron, dbt). Does not interact with the ss-tools UI directly — API only. - **ss-tools Administrator**: Platform administrator managing maintenance mode settings through the web UI. - **Dashboard Viewer**: Business user viewing dashboards in Superset. Sees the maintenance banner but does not trigger it. * **What is their goal?**: - Operator: Automatically warn dashboard users that data is being refreshed and may be inaccurate. - Administrator: Control the maintenance process, manually remove banners if needed, configure exclusions. - Dashboard viewer: Understand why dashboard data appears incomplete. * **Context**: - Operator calls REST API from a pipeline (Airflow DAG, shell script, CI/CD). - Administrator works in the ss-tools web interface (SvelteKit SPA, desktop). - Dashboard viewer sees the result in Superset (third-party web application). ## 2. The "Happy Path" Narrative **Maintenance start (Operator → ss-tools → Superset)** An Airflow DAG finishes clearing staging tables and triggers: `curl -X POST https://ss-tools/api/maintenance/start -d '{"tables":["raw.sales","raw.inventory"],"start_time":"2026-05-21T22:00:00+03:00","end_time":"2026-05-22T02:00:00+03:00"}'`. The API immediately returns `{"task_id":"task-abc","maintenance_id":"m-abc123","status":"pending"}`. The operator polls `GET /api/tasks/task-abc`. Within 60 seconds, the task completes, and 12 dashboards in Superset display an orange banner at the very top. If the operator doesn't know the end time, they can omit `end_time` — the banner will show «End: TBD» instead of a specific time. Dashboard viewers see the warning immediately upon opening the page. **Maintenance end (Operator → ss-tools → Superset)** The ETL pipeline completes successfully. The Airflow DAG calls `curl -X POST https://ss-tools/api/maintenance/m-abc123/end`. The API returns `{"task_id":"task-end-abc","status":"pending"}`. Upon task completion, banners disappear from all dashboards. In the ss-tools UI, the administrator sees the event marked as "Completed". ## 3. API Key Management UX Flow **Location:** Settings → System Settings → "API Keys" section (NOT in Maintenance panel). API keys are centralized infrastructure, reusable across tools. They live in the System Settings page alongside other cross-cutting configurations (environments, auth, etc.). Maintenance is just the first consumer. ### 3.1 Issuing a New API Key **Step 1: Navigate to Settings → System → API Keys** The System Settings page contains an "API Keys" section showing a table of existing keys (or empty state if none): ``` ┌─ API Keys ─────────────────────────────────────────────────┐ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ Name Prefix Env Permissions Actv │ │ │ │─────────────────────────────────────────────────────│ │ │ │ Airflow ETL ssk_M7xqaP… ss-dev start,end 🟢 ✕ │ │ │ │ GitLab CI ssk_K9yFbZ… ss-dev s/e/a 🟢 ✕ │ │ │ │ Old staging ssk_T1vLmC… ss-dev start 🔴 · │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ No API keys yet. Generate one for external tool access. │ ← empty state │ │ │ [+ Generate New Key] │ └──────────────────────────────────────────────────────────────┘ ``` - **Permission notation:** `s` = maintenance:start, `e` = maintenance:end, `a` = maintenance:end_all - **Status:** 🟢 = active, 🔴 = revoked - **Revoke button:** `✕` on active keys; `·` (dot — no action) on revoked keys - **Empty state:** dashed border, grey text, centered **Step 2: Fill in the generation form** Clicking `[+ Generate New Key]` expands an inline form (not a modal — matches the existing settings pattern): ``` ┌─ Generate API Key ──────────────────────────────────────┐ │ │ │ Name ────────────────────────────────────────────────── │ │ ┌───────────────────────────────────────────────────┐ │ │ │ Airflow Production ETL │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ Environment ────────────────────────────────────────── │ │ ┌──────────────────────────────────────▼─────────────┐ │ │ │ ss-dev │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ Permissions ────────────────────────────────────────── │ │ ☑ maintenance:start Start new maintenance events │ │ ☑ maintenance:end End a specific event │ │ ☐ maintenance:end_all End ALL active events at once │ │ │ │ Expires (optional) ─────────────────────────────────── │ │ ┌──────────────────────┐ 📅 │ │ │ 2027-01-01 │ │ │ └──────────────────────┘ │ │ │ │ [Cancel] [Generate Key] │ └───────────────────────────────────────────────────────────┘ ``` **Step 3: Copy the raw key (ONCE!)** After successful generation, the form is replaced by a **read-once reveal panel**: ``` ┌─ ⚠️ API Key Generated — Copy It Now! ───────────────────┐ │ │ │ This is the ONLY time the full key will be displayed. │ │ Store it securely (password manager, CI/CD variable). │ │ It CANNOT be retrieved later. If lost, you must │ │ revoke this key and generate a new one. │ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ ssk_M7xqaP2zL9vR4nF8kC1bH5jT6dW0yA3 │ │ │ │ [📋 Copy] │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ Name: Airflow Production ETL │ │ Prefix: ssk_M7xqaP… │ │ Environment: ss-dev │ │ Permissions: maintenance:start, maintenance:end │ │ Created: 2026-05-25 19:45 UTC │ │ Expires: 2027-01-01 │ │ │ │ [I have copied the key — dismiss] │ └───────────────────────────────────────────────────────────┘ ``` **UX rules for the reveal panel:** - Warning text in **red/bold** — user must understand this is one-time - Key displayed in monospace `
` with a visual "code block" background
- `[📋 Copy]` button copies to clipboard; on click → shows "Copied!" for 1.5s (green checkmark icon)
- `[I have copied the key — dismiss]` — replaces the panel with the updated key list
- Panel CANNOT be reopened — the raw key is gone from memory
- If user closes without copying: key is irrecoverable. They must revoke and regenerate. This is intentional.
**Step 4: Key appears in the list**
After dismissal, the key list is updated with the new entry (prefix only):
```
Name Prefix Env Perms Status
─────────────────────────────────────────────────────────
Airflow Production ETL ssk_M7xqaP… ss-dev s/e 🟢 ✕ ← NEW
GitLab CI/CD ssk_K9yFbZ… ss-dev s/e/a 🟢 ✕
```
### 3.2 Revoking an API Key
**Step 1: Click `✕` on a key row**
Triggers a confirmation dialog (native `confirm()` or inline confirmation):
```
┌─ Revoke API Key? ───────────────────────────────────────┐
│ │
│ Are you sure you want to revoke: │
│ │
│ "Airflow Production ETL" (ssk_M7xqaP…) │
│ │
│ → All external tools using this key will immediately │
│ receive 401 Unauthorized responses. │
│ │
│ → The key entry will be preserved for audit but │
│ marked as INACTIVE. │
│ │
│ [Cancel] [Revoke Key] │
└──────────────────────────────────────────────────────────┘
```
**Step 2: Key becomes inactive**
After confirmation, the row updates:
```
Airflow Production ETL ssk_M7xqaP… ss-dev s/e 🔴 · ← REVOKED
```
- `🔴` = inactive status
- `✕` replaced by `·` (no action — already revoked)
- Row is slightly greyed out (opacity 0.6)
### 3.3 State Machine
```
┌──────────────┐
│ IDLE │←────────────────────────────┐
│ (key list) │ │
└──────┬───────┘ │
│ │
[+Generate] │ │
▼ │
┌──────────────┐ submit form ┌─────────┐│
│ FORM │ ────────────────→ │ REVEAL ││
│ (name,env, │ │ ⚠️ once ││
│ perm,expiry) │ └────┬─────┘│
└──────┬───────┘ │ │
│ [Cancel] │ │
└─────────────────────────────────┤ │
│dismiss│
▼ │
┌──────────────┐ click [✕] ┌──────────┐ │
│ IDLE │ ───────────────→ │ CONFIRM │ │
│ (list updated)│ │ "Revoke?" │ │
└──────────────┘ └─────┬─────┘ │
↑ │ │
│ [Cancel] │ │
└─────────────────────────────────┤ │
│[Revoke]│
▼ │
┌──────────────┐ │
│ IDLE │─┘
│ (key.inactive) │
└──────────────┘
```
### 3.4 Error & Edge Case Handling
| Scenario | UX Response |
|----------|-------------|
| Network failure during generation | Toast error: "Failed to generate API key. Check your connection and try again." Form stays open — retry possible. |
| User closes REVEAL panel without copying | Key is **gone forever**. Admin must revoke (now-inactive key) and regenerate. The warning text in the panel explicitly states this. |
| Double-click on [Revoke] | Second click → 404 from API. UI handles: "Key already revoked" inline message on the row. No dialog. |
| Revoke already-inactive key | UI prevents: no ✕ button on inactive rows (rendered as `·`). |
| Empty name field submission | Client-side validation: "Name is required" red text below the input. [Generate Key] button disabled until filled. |
| No permissions selected | Client-side validation: "Select at least one permission" red text. Button disabled. |
| Key generation API returns 403 | (Admin-only endpoint). If somehow triggered by non-admin: Toast "Forbidden: admin privileges required." |
### 3.5 Component States
| Component | State | Visual |
|-----------|-------|--------|
| Key list | **idle** | Table with rows |
| Key list | **empty** | Dashed border box: "No API keys yet. Generate one for external tool access." + button |
| Key list | **loading** | 3 skeleton rows (`animate-pulse bg-gray-200 h-8 w-full rounded`) |
| Key list | **error** | Toast with error message; list shows stale data |
| Generation form | **idle** | Collapsed — only `[+ Generate New Key]` visible |
| Generation form | **open** | Form fields expanded |
| Generation form | **submitting** | Button shows spinner: `[⠋ Generating...]` |
| Generation form | **error** | Toast with error; form stays open |
| Reveal panel | **visible** | Warning + code block + copy button |
| Reveal panel | **copied** | Copy button shows "✅ Copied!" for 1.5s |
| Revoke dialog | **confirming** | Button shows `[⠋ Revoking...]` |
| Row | **active** | 🟢 green circle + ✕ button |
| Row | **inactive** | 🔴 red circle + `·` dot (no button) |
| Row | **revoking** | 🔄 spinner replacing the ✕ button |
### 3.6 Accessibility
- All buttons have `aria-label` (e.g., `"Revoke Airflow Production ETL key"`)
- Reveal panel uses `role="alert"` for screen reader announcement
- Copy button announces "Copied" via `aria-live="polite"`
- Form fields have associated `