- Replace all occurrences of 'ss-tools' with 'superset-tools' in 104 files - Rename git bundle file ss-tools.bundle → superset-tools.bundle - Update .gitignore pattern accordingly - Preserve variable names (hasSsTools etc.) and code identifiers
21 KiB
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 superset-tools UI directly — API only.
- superset-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 superset-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 → superset-tools → Superset)
An Airflow DAG finishes clearing staging tables and triggers: curl -X POST https://superset-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 → superset-tools → Superset)
The ETL pipeline completes successfully. The Airflow DAG calls curl -X POST https://superset-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 superset-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
<pre>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
<label>elements - Confirmation dialog is focus-trapped while open
- Key display uses
<code>element for screen reader pronunciation of the key string
4. Interaction Flows
(existing sections 4 through end remain unchanged)
4.1 Maintenance Start (External Tool via API Key)
Using API Key authentication:
# 1. Admin generates key once (via Settings → System → API Keys)
# 2. Admin stores key in CI/CD variable: MAINTENANCE_API_KEY=ssk_M7xqa...
# 3. External tool uses X-API-Key header
curl -X POST https://superset-tools.example.com/api/maintenance/start \
-H "X-API-Key: $MAINTENANCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tables": ["public.messages"],
"start_time": "2026-05-25T22:00:00Z",
"end_time": "2026-05-26T02:00:00Z",
"environment_id": "ss-dev",
"message": "Scheduled ETL refresh"
}'
# Response: {"task_id":"abc123","maintenance_id":"m-xyz","status":"pending"}
Auth decision flow:
- Request arrives with
X-API-Key: ssk_... - System checks
Authorization: Bearer <jwt>— absent - System resolves API key: hash incoming value, lookup in DB by
key_hash - Key found,
active=true,expires_atnot past → APIKeyPrincipal created - Permissions checked: key has
maintenance:start→ allowed - Task dispatched; audit log records
"actor": "api_key: Airflow Production ETL"
If auth fails:
- Invalid key →
401 {"detail":"Invalid API key"} - Expired key →
401 {"detail":"API key has expired"} - Revoked key →
401 {"detail":"API key has been revoked"} - Insufficient permission →
403 {"detail":"API key lacks maintenance:start permission"} - Environment mismatch →
400 {"detail":"API key restricted to ss-dev, got ss-prod"}
4.2 Banner Appearance in Superset
(existing content unchanged — banner rendering details)
4.3 Maintenance End
(existing content unchanged)
4.4 Dashboard Hub Indicator
(existing content unchanged)
4.5 Error Recovery
(existing content unchanged — add API key error path)
API Key authentication errors:
- 401 Invalid key: External tool should check that
$MAINTENANCE_API_KEYmatches the current key. If key was rotated, update the CI/CD variable. - 401 Expired key: Admin must generate a new key (Settings → API Keys → Generate). Update CI/CD variable.
- 403 Permission denied: Admin must update the key's permissions. Or generate a new key with the required rights.
- 400 Environment mismatch: The key is scoped to a specific env. Either change the key's environment or use a different key.
5. Visual Design
5.1 API Key Section (in MaintenanceSettingsPanel)
- Section title:
<details><summary>collapsible, matching other settings sections - Key list table: inline Tailwind table (
min-w-full divide-y), matches EventsTable pattern - Prefix column:
<code>element, monospace font (font-mono text-sm) - Status:
<span class="inline-block w-2 h-2 rounded-full">green (bg-green-500) or red (bg-red-400) - Revoke button:
<button class="text-red-500 hover:text-red-700">✕</button> - Generate button:
<Button variant="secondary" size="sm">from$lib/ui/Button.svelte
5.2 Reveal Panel (Read-Once Key Display)
- Background: yellow-50 border yellow-400 (warning style)
- Warning icon:
⚠️before title - Key code block:
<pre class="bg-gray-900 text-green-400 p-4 rounded font-mono text-sm break-all"> - Copy button: positioned overlapping the code block (top-right corner)
- "Copied!" indicator: absolute positioned, green text,
transition-opacity duration-300
5.3 Revoke Confirmation
- Inline expand panel below the key row (not a modal)
- Red-tinted background (
bg-red-50 border-red-200) - Two buttons:
[Cancel](grey) and[Revoke Key](red outline)
6. Localization
API key UI strings (EN/RU):
| EN | RU |
|---|---|
| API Keys | API-ключи |
| Generate New Key | Создать новый ключ |
| Revoke Key | Отозвать ключ |
| Copy to clipboard | Скопировать |
| Copied! | Скопировано! |
| This is the ONLY time the key will be shown | Ключ показывается ЕДИНСТВЕННЫЙ раз |
| Key has been revoked | Ключ отозван |
| Invalid API key | Неверный API-ключ |
| API key has expired | API-ключ истёк |
| API key lacks {permission} permission | API-ключ не имеет права {permission} |