diff --git a/apps/operator/components/CommandPalette.vue b/apps/operator/components/CommandPalette.vue index c777043..79ffe39 100644 --- a/apps/operator/components/CommandPalette.vue +++ b/apps/operator/components/CommandPalette.vue @@ -2,7 +2,8 @@ import type { IconName } from './UiIcon.vue' import type { Tenant } from '~/types/tenant' import type { Partner } from '~/types/partner' -import { FLAGS, INCIDENT } from '~/data/fixtures' +import type { Flag } from '~/types/flag' +import { INCIDENT } from '~/data/fixtures' // Each row in the palette. `action` decides what happens on Enter / click. // We try `to` first (a navigateTo) since most rows are navigation; `run` is @@ -31,6 +32,7 @@ const inputRef = ref(null) // palette is client-only). const { data: tenants, refresh: rT } = useLazyFetch('/api/tenants', { default: () => [], server: false }) const { data: partners, refresh: rP } = useLazyFetch('/api/partners', { default: () => [], server: false }) +const { data: flags, refresh: rF } = useLazyFetch('/api/flags', { default: () => [], server: false }) const NAV: { id: string; label: string; icon: IconName; to: string }[] = [ { id: 'n-overview', label: 'Overview', icon: 'home', to: '/' }, @@ -72,12 +74,13 @@ function partnerRows(): Row[] { } function flagRows(): Row[] { - return FLAGS.slice(0, 6).map((f) => ({ + return (flags.value ?? []).slice(0, 8).map((f) => ({ id: `f-${f.key}`, groupLabel: 'Feature flags', title: f.key, - subtitle: f.state === 'rollout' ? `rollout · ${f.pct}% · ${f.scope}` : `${f.state} · ${f.scope}`, + subtitle: f.state === 'rollout' ? `rollout · ${f.pct}%` : f.state, icon: 'plug', + badge: f.state, to: '/flags', })) } @@ -154,7 +157,7 @@ watch(isOpen, async (v) => { if (!v) return query.value = '' cursor.value = 0 - await Promise.all([rT(), rP()]) + await Promise.all([rT(), rP(), rF()]) await nextTick() inputRef.value?.focus() }) diff --git a/apps/operator/components/FlagDetail.vue b/apps/operator/components/FlagDetail.vue new file mode 100644 index 0000000..a9878a2 --- /dev/null +++ b/apps/operator/components/FlagDetail.vue @@ -0,0 +1,432 @@ + + + + + diff --git a/apps/operator/components/NewFlagModal.vue b/apps/operator/components/NewFlagModal.vue new file mode 100644 index 0000000..4e44da1 --- /dev/null +++ b/apps/operator/components/NewFlagModal.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/apps/operator/data/fixtures.ts b/apps/operator/data/fixtures.ts index 18baace..6c242bf 100644 --- a/apps/operator/data/fixtures.ts +++ b/apps/operator/data/fixtures.ts @@ -59,27 +59,9 @@ export const INCIDENT: ActiveIncident = { ], } -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' }, -] +// Feature flags moved to a real backend at /api/flags + see types/flag.ts. +// The seed in services/platform-api/src/seed/seed.service.ts creates the +// same 10 flags this fixture used to contain. export type AuditTone = 'info' | 'warn' | 'bad' export interface AuditEntry { diff --git a/apps/operator/pages/flags.vue b/apps/operator/pages/flags.vue index e201954..81109ca 100644 --- a/apps/operator/pages/flags.vue +++ b/apps/operator/pages/flags.vue @@ -1,18 +1,63 @@