feat(operator): visual-only screens with real-data overview (O.7)
- Overview (pages/index.vue): KPIs from real /tenants /partners /users, status meter, recent + needs-follow-up tables. Mock activity stream and incident banner overlay come from data/fixtures.ts. - Operator team: real GET /users filtered to platformAdmin === true, with last-seen + tenant counts. - Users (global): real read with All/Admins/Inactive views and search. - Infrastructure / Feature flags / Audit: mock fixtures only — wiring to real backends (Prometheus, OpenFeature, append-only audit) is tracked as follow-ups in OPERATOR-PLAN.md. - Placeholder pages (support/billing/reports/settings) via OpPlaceholder. - Shared: Stat, MetricCell, OpPlaceholder components, /api/users proxy, PlatformUser type. - .gitignore: scope the docker volumes data/ rule so apps/*/data/ is tracked again (operator carries mock fixtures there).
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
// Visual-only fixtures for screens we haven't wired to a real backend yet
|
||||
// (infrastructure, feature flags, audit log, active incident, operator team
|
||||
// extras). Real data sources are GET /tenants, /partners, /users — anything
|
||||
// derivable from those should NOT live here. See OPERATOR-PLAN.md follow-ups
|
||||
// for the path from each fixture to a real implementation.
|
||||
|
||||
export type ServiceStatus = 'ok' | 'warn' | 'bad'
|
||||
export interface PlatformService {
|
||||
id: string
|
||||
name: string
|
||||
role: string
|
||||
status: ServiceStatus
|
||||
uptime: number // percent, 30d
|
||||
p95: number // ms
|
||||
err: number // percent
|
||||
last: string // human duration since last incident
|
||||
}
|
||||
|
||||
export const SERVICES: PlatformService[] = [
|
||||
{ id: 'mail', name: 'Stalwart', role: 'Mail · IMAP/JMAP/SMTP', status: 'ok', uptime: 99.99, p95: 42, err: 0.002, last: '—' },
|
||||
{ id: 'files', name: 'OCIS', role: 'Files · OwnCloud Infinite', status: 'ok', uptime: 99.97, p95: 88, err: 0.004, last: '11 d ago' },
|
||||
{ id: 'video', name: 'Jitsi', role: 'Video meetings', status: 'ok', uptime: 99.91, p95: 124, err: 0.018, last: '4 d ago' },
|
||||
{ id: 'chat', name: 'Zulip', role: 'Team chat', status: 'ok', uptime: 99.99, p95: 35, err: 0.001, last: '—' },
|
||||
{ id: 'auth', name: 'Authentik', role: 'Identity · SSO · MFA', status: 'warn', uptime: 99.94, p95: 412, err: 0.052, last: 'active' },
|
||||
{ id: 'db', name: 'PostgreSQL', role: 'Primary database', status: 'ok', uptime: 99.99, p95: 8, err: 0, last: '—' },
|
||||
{ id: 'obj', name: 'Object storage',role: 'S3-compatible · Hetzner', status: 'ok', uptime: 99.99, p95: 22, err: 0.001, last: '—' },
|
||||
{ id: 'cdn', name: 'Cloudflare', role: 'CDN · WAF', status: 'ok', uptime: 100, p95: 18, err: 0, last: '—' },
|
||||
{ id: 'smtp', name: 'Outbound SMTP', role: 'Email delivery (Postmark)', status: 'ok', uptime: 99.95, p95: 280, err: 0, last: '3 d ago' },
|
||||
]
|
||||
|
||||
export interface ActiveIncident {
|
||||
id: string
|
||||
title: string
|
||||
severity: 'P1' | 'P2' | 'P3'
|
||||
started: string
|
||||
duration: string
|
||||
affected: string
|
||||
state: 'investigating' | 'identified' | 'monitoring'
|
||||
ic: string
|
||||
updates: { t: string; who: string; msg: string }[]
|
||||
}
|
||||
|
||||
export const INCIDENT: ActiveIncident = {
|
||||
id: 'INC-2026-018',
|
||||
title: 'Authentik · elevated SSO login latency',
|
||||
severity: 'P2',
|
||||
started: '14:18',
|
||||
duration: '42 min',
|
||||
affected: 'Login latency p95 above 400ms · 12 tenants impacted',
|
||||
state: 'investigating',
|
||||
ic: 'Mikkel Nørgaard',
|
||||
updates: [
|
||||
{ t: '15:00', who: 'Mikkel N.', msg: 'Pod restart deployed, monitoring' },
|
||||
{ t: '14:36', who: 'auto', msg: 'Page sent to on-call (Mikkel)' },
|
||||
{ t: '14:22', who: 'Anne B.', msg: 'Confirmed: Postgres conn pool exhaustion on auth-db-2' },
|
||||
{ t: '14:18', who: 'auto', msg: 'Alert: authentik p95 > 400ms for 5m · 12 tenants impacted' },
|
||||
],
|
||||
}
|
||||
|
||||
export type FlagState = 'on' | 'off' | 'rollout' | 'targeted'
|
||||
export interface FeatureFlag {
|
||||
key: string
|
||||
state: FlagState
|
||||
pct: number
|
||||
scope: string
|
||||
modified: string
|
||||
}
|
||||
|
||||
export const FLAGS: FeatureFlag[] = [
|
||||
{ key: 'jmap_native_v2', state: 'rollout', pct: 50, scope: 'Business+ · 38 tenants', modified: 'Anne · 2 d ago' },
|
||||
{ key: 'oci_versioning', state: 'on', pct: 100, scope: 'all tenants', modified: 'Anne · 14 d ago' },
|
||||
{ key: 'jitsi_recording_e2ee', state: 'targeted', pct: 0, scope: 'allowlist · 3 tenants', modified: 'Mikkel · 5 d ago' },
|
||||
{ key: 'new_billing_engine', state: 'rollout', pct: 25, scope: '12 tenants', modified: 'Anne · today' },
|
||||
{ key: 'gdpr_export_v2', state: 'off', pct: 0, scope: 'kill-switch', modified: 'Sofie · 21 d ago' },
|
||||
{ key: 'whitelabel_cssprops', state: 'on', pct: 100, scope: 'partners', modified: 'Anne · 1 mo ago' },
|
||||
{ key: 'audit_log_streaming', state: 'on', pct: 100, scope: 'Enterprise', modified: 'Mikkel · 8 d ago' },
|
||||
{ key: 'zulip_topic_threading', state: 'rollout', pct: 75, scope: '63 tenants', modified: 'Sofie · 3 d ago' },
|
||||
{ key: 'tos_2026_acceptance', state: 'on', pct: 100, scope: 'all tenants', modified: 'Anne · 6 d ago' },
|
||||
{ key: 'beta_ai_summaries', state: 'off', pct: 0, scope: 'killed', modified: 'Anne · 1 mo ago' },
|
||||
]
|
||||
|
||||
export type AuditTone = 'info' | 'warn' | 'bad'
|
||||
export interface AuditEntry {
|
||||
id: string
|
||||
when: string
|
||||
actor: string
|
||||
role: string
|
||||
action: string
|
||||
target: string
|
||||
tenant: string
|
||||
ip: string
|
||||
tone: AuditTone
|
||||
}
|
||||
|
||||
export const OP_AUDIT: AuditEntry[] = [
|
||||
{ id: 'op_8821', when: '15:02:11', actor: 'Anne Baslund', role: 'platform admin', action: 'feature_flag.rollout', target: 'jmap_native_v2 · 50%', tenant: '—', ip: '10.0.4.18', tone: 'info' },
|
||||
{ id: 'op_8820', when: '14:58:42', actor: 'Mikkel Nørgaard', role: 'engineer', action: 'service.pod_restart', target: 'authentik-worker-3', tenant: '—', ip: '10.0.4.21', tone: 'warn' },
|
||||
{ id: 'op_8819', when: '14:48:02', actor: 'Sofie Lindberg', role: 'ops', action: 'tenant.impersonate', target: 'oliver@bygherre.dk', tenant: 'Bygherre Cloud', ip: '10.0.4.04', tone: 'info' },
|
||||
{ id: 'op_8818', when: '14:36:00', actor: 'system', role: 'auto', action: 'oncall.paged', target: 'Mikkel Nørgaard', tenant: '—', ip: '—', tone: 'warn' },
|
||||
{ id: 'op_8817', when: '14:18:00', actor: 'system', role: 'auto', action: 'alert.triggered', target: 'authentik p95 > 400ms', tenant: '—', ip: '—', tone: 'bad' },
|
||||
{ id: 'op_8816', when: '13:21:55', actor: 'Anne Baslund', role: 'platform admin', action: 'tenant.refund_issued', target: 'INV-0480 · 980 DKK', tenant: 'Vester Foods', ip: '10.0.4.18', tone: 'info' },
|
||||
{ id: 'op_8815', when: '12:09:30', actor: 'Sofie Lindberg', role: 'ops', action: 'tenant.suspended', target: 'København Kalkulator', tenant: 'København Kalkulator', ip: '10.0.4.04', tone: 'warn' },
|
||||
{ id: 'op_8814', when: '11:44:00', actor: 'Anne Baslund', role: 'platform admin', action: 'partner.created', target: 'Klaussen Digital · invited', tenant: '—', ip: '10.0.4.18', tone: 'info' },
|
||||
{ id: 'op_8813', when: '10:55:41', actor: 'system', role: 'auto', action: 'invoice.past_due', target: 'INV-0522 · 2.940 DKK · 21 d', tenant: 'Bygherre Cloud', ip: '—', tone: 'bad' },
|
||||
{ id: 'op_8812', when: '10:12:08', actor: 'Mikkel Nørgaard', role: 'engineer', action: 'feature_flag.created', target: 'beta_ai_summaries', tenant: '—', ip: '10.0.4.21', tone: 'info' },
|
||||
{ id: 'op_8811', when: '09:30:00', actor: 'Anne Baslund', role: 'platform admin', action: 'tos.published', target: 'v2026.05 · all tenants', tenant: '—', ip: '10.0.4.18', tone: 'info' },
|
||||
]
|
||||
Reference in New Issue
Block a user