Commit edaaa64
authored
feat(admin): SPA degraded banner + conflict hatching for fan-out (Phase 2-C PR-2) (#687)
## Summary
Phase 2-C **PR-2**, per the design doc landed in #685 §6: wire the
fan-out `Fanout` / `conflict` shapes shipped by PR #686 into the admin
SPA so operators can see when a peer failed and which rows are soft due
to a leadership flip.
Backend is unchanged. PR #686's `KeyVizMatrix.Fanout` block is currently
emitted on every fan-out request but the SPA was discarding it.
## What this PR adds
| Piece | Behavior |
|---|---|
| **Header counter** | `cluster view (N of M nodes)` in the per-matrix
metadata strip. Hidden when `fanout` is absent (single-node mode, no
behavior change). |
| **FanoutBanner** | Degraded-mode card above the heatmap when
`responded < expected`. Lists every failed node + error string. Hidden
when the cluster is healthy. |
| **ConflictOverlay** | SVG hatch over rows whose `conflict === true`.
Layered inside the scroll container so the hatch tracks horizontal
scroll (same idiom as `TimeAxis`). 4 px diagonal stripes, alpha 0.45,
inheriting `currentColor` so it works against both light and dark
themes. |
| **RowDetail pill** | Hovering a conflicting row reveals a `conflict`
pill with a hover-tooltip explaining the leadership-flip semantics. |
## Five-lens self-review
1. **Data loss** — n/a; SPA-only change reading existing fields.
2. **Concurrency / distributed** — n/a; single browser tab consuming an
existing API.
3. **Performance** — Banner: zero-cost when healthy (`null` returned).
Hatch: SVG with at most 1024 `<rect>` elements (matches `KeyVizRow`
budget); empirically negligible. Overlay only renders when at least one
row has `conflict === true`.
4. **Data consistency** — UI surfaces existing server signals; semantics
come from PR #685 design §4.2 and #686 implementation. Not relitigated
here.
5. **Test coverage** — `tsc -b --noEmit` and `vite build` clean. UI
behavior (banner visibility, hatch presence, header text) is hard to
assert in `tsc` alone; manual verification documented below.
## Test plan
- [x] `npm run lint` (`tsc -b --noEmit`) — clean
- [x] `npm run build` (Vite) — clean, output writes to
`internal/admin/dist`
- [ ] Manual: `make run` with `--keyvizEnabled
--keyvizFanoutNodes=127.0.0.1:8080`. Open `/keyviz`; header reads
`cluster view (1 of 1 nodes)`, no banner, no hatch.
- [ ] Manual: configure
`--keyvizFanoutNodes=127.0.0.1:8080,127.0.0.1:9999` (port 9999 unused).
Header reads `1 of 2 nodes`, the failed-node banner appears with
`connection refused` for `127.0.0.1:9999`.
- [ ] Manual: smoke a leadership flip mid-window (or fake
`conflict=true` server-side via `/admin/api/v1/keyviz/matrix?...`); the
affected row renders with diagonal hatch overlay; hovering reveals the
`conflict` pill.
## Out of scope
- **Per-cell hatching** — Phase 2-C+ once the proto extension lands and
`conflict` becomes per-cell instead of per-row.
- **Per-node `generated_at` skew indicator** — design §9 Q3, deferred.
- **Banner auto-dismiss / sound notification** — out of scope; an
operator who has the page open will see the banner. CI alert pages are a
separate system.
Closes the SPA half of Phase 2-C; the proto extension follows in PR-3.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* KeyViz now displays cluster fan-out status with node availability and
responsiveness counts.
* Added visual indicators for rows experiencing conflicts during
leadership transitions.
* Row details now include conflict status labels with informative
tooltips.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->2 files changed
Lines changed: 151 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
231 | 231 | | |
232 | 232 | | |
233 | 233 | | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
234 | 261 | | |
235 | 262 | | |
236 | 263 | | |
237 | 264 | | |
238 | 265 | | |
239 | 266 | | |
240 | 267 | | |
| 268 | + | |
241 | 269 | | |
242 | 270 | | |
243 | 271 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
| 60 | + | |
59 | 61 | | |
60 | 62 | | |
61 | 63 | | |
| |||
196 | 198 | | |
197 | 199 | | |
198 | 200 | | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
199 | 209 | | |
200 | 210 | | |
201 | 211 | | |
| |||
209 | 219 | | |
210 | 220 | | |
211 | 221 | | |
212 | | - | |
| 222 | + | |
213 | 223 | | |
214 | 224 | | |
215 | 225 | | |
216 | 226 | | |
217 | 227 | | |
218 | 228 | | |
| 229 | + | |
219 | 230 | | |
220 | 231 | | |
221 | 232 | | |
| |||
226 | 237 | | |
227 | 238 | | |
228 | 239 | | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
229 | 341 | | |
230 | 342 | | |
231 | 343 | | |
| |||
284 | 396 | | |
285 | 397 | | |
286 | 398 | | |
287 | | - | |
| 399 | + | |
288 | 400 | | |
289 | 401 | | |
290 | 402 | | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
291 | 411 | | |
292 | 412 | | |
293 | 413 | | |
| |||
0 commit comments