- 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
359 lines
21 KiB
Markdown
359 lines
21 KiB
Markdown
# 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:**
|
|
|
|
```bash
|
|
# 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} |
|