Files
ss-tools/specs/031-maintenance-banner/ux_reference.md
busya ec6421de35 rename ss-tools to superset-tools across the entire project
- 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
2026-06-16 11:15:19 +03:00

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:

  1. Request arrives with X-API-Key: ssk_...
  2. System checks Authorization: Bearer <jwt> — absent
  3. System resolves API key: hash incoming value, lookup in DB by key_hash
  4. Key found, active=true, expires_at not past → APIKeyPrincipal created
  5. Permissions checked: key has maintenance:start → allowed
  6. 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_KEY matches 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}