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:
Ronni Baslund
2026-05-24 08:17:26 +02:00
parent fbbb43e3e2
commit e0ac643e80
18 changed files with 1332 additions and 120 deletions
+112
View File
@@ -0,0 +1,112 @@
<script setup lang="ts">
import { FLAGS, type FeatureFlag } from '~/data/fixtures'
function stateTone(f: FeatureFlag): 'ok' | 'neutral' | 'warn' | 'info' {
switch (f.state) {
case 'on': return 'ok'
case 'off': return 'neutral'
case 'rollout': return 'warn'
case 'targeted': return 'info'
}
}
function stateLabel(f: FeatureFlag) {
if (f.state === 'rollout') return `${f.pct}% rollout`
return f.state
}
</script>
<template>
<div>
<PageHeader
eyebrow="Engineering"
title="Feature flags"
subtitle="Toggle, target, and roll out platform features. Every change is logged."
>
<template #actions>
<UiButton variant="primary" disabled>
<template #leading><UiIcon name="plus" :size="13" /></template>
New flag
</UiButton>
</template>
</PageHeader>
<div class="stage">
<Card :pad="0">
<table>
<thead>
<tr>
<th>Key</th>
<th>State</th>
<th>Rollout</th>
<th>Scope</th>
<th>Last modified</th>
</tr>
</thead>
<tbody>
<tr v-for="f in FLAGS" :key="f.key">
<td>
<div class="key">
<span class="key-tile" :data-state="f.state">
<UiIcon name="plug" :size="11" />
</span>
<Mono class="key-name">{{ f.key }}</Mono>
</div>
</td>
<td><Badge :tone="stateTone(f)" dot>{{ stateLabel(f) }}</Badge></td>
<td>
<div v-if="f.state === 'rollout'" class="rollout">
<div class="bar"><div class="fill" :style="{ width: `${f.pct}%` }" /></div>
<Mono>{{ f.pct }}%</Mono>
</div>
<Mono v-else dim></Mono>
</td>
<td><Mono dim>{{ f.scope }}</Mono></td>
<td><Mono dim>{{ f.modified }}</Mono></td>
</tr>
</tbody>
</table>
</Card>
<Mono dim class="note">// mock fixtures — wire to a feature-flag service (Unleash / OpenFeature) in a follow-up</Mono>
</div>
</div>
</template>
<style scoped>
.stage { padding: 24px 40px 64px 40px; display: flex; flex-direction: column; gap: 16px; }
table { width: 100%; border-collapse: collapse; }
th {
text-align: left;
font-family: var(--font-mono);
font-size: 9px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-mute);
padding: 12px 20px;
font-weight: 500;
border-bottom: 1px solid var(--border);
}
td { padding: 12px 20px; font-size: 12px; border-top: 1px solid var(--border); vertical-align: middle; }
.key { display: flex; align-items: center; gap: 10px; }
.key-tile {
width: 22px; height: 22px;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text-mute);
}
.key-tile[data-state='on'] { background: rgba(31, 138, 91, 0.12); color: var(--ok); }
.key-tile[data-state='off'] { background: rgba(128, 128, 128, 0.12); color: var(--text-mute); }
.key-tile[data-state='rollout'] { background: rgba(232, 154, 31, 0.12); color: var(--warn); }
.key-tile[data-state='targeted']{ background: rgba(42, 111, 219, 0.1); color: var(--info); }
.key-name { font-weight: 600; }
.rollout { display: inline-flex; align-items: center; gap: 10px; width: 160px; }
.bar { flex: 1; height: 4px; background: var(--border); border-radius: 999px; overflow: hidden; }
.fill { height: 100%; background: var(--warn); }
.note { display: block; padding: 4px 4px 0 4px; }
</style>