feat(web): replace 3D graph with fleet map, add desktop background patterns, rename Knowledge Base to Fleet
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

- FleetMap: service-centric host -> container -> service graph replacing
  the WebGL 3D force graph, with health coloring, hover-to-trace blast
  radius, and click-to-open
- Desktop background: configurable CSS pattern picker in Settings ->
  Appearance (8 patterns, color/fill/opacity/fade/size/rotation),
  replacing the hardcoded ambient graph background
- Fix missing data-orientation/data-disabled Tailwind custom variants so
  the shadcn Slider's track actually renders
- Rename "Knowledge Base" app to "Fleet"; scope its table to the same
  fleet entities as the graph (compute-entity descendants + service)
  instead of all entities
- Remove dead code: EntityGraph, GraphBackground, categories.ts,
  MultiSelectFilter (all superseded by the above)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 20:04:53 +02:00
parent c151a66627
commit 4e4e2c169c
16 changed files with 1754 additions and 996 deletions

View File

@@ -0,0 +1,82 @@
// Desktop background patterns — CSS-only (no images), in the spirit of
// magicpattern.design's "CSS backgrounds" gallery. Each pattern is written
// with a single foreground color against `transparent` gaps rather than a
// baked-in second color, so it composes correctly over the app's own
// terracotta/carbon theme background (and follows the theme swap for free)
// instead of needing its own light/dark variant. A separate, optional fill
// color (see background.svelte.ts) sits underneath as `background-color`,
// showing through the transparent gaps when the user wants one.
export type BackgroundPatternId = 'none' | 'dots' | 'bubbles' | 'grid' | 'stripes' | 'checks' | 'zigzag' | 'rings'
export interface PatternDef {
id: BackgroundPatternId
label: string
// Returns a CSS declaration string (background-image/-size/-position/
// -repeat) for the given foreground color and size multiplier (1 = the
// pattern's natural/default tile size). Empty string = no pattern.
css: (color: string, scale: number) => string
}
// Rounds to 1 decimal — enough precision for a smooth size slider without
// producing long floating-point tails in the generated CSS.
function px(base: number, scale: number): string {
return `${Math.round(Math.max(0.5, base * scale) * 10) / 10}px`
}
export const PATTERNS: PatternDef[] = [
{ id: 'none', label: 'None', css: () => '' },
{
id: 'dots',
label: 'Dots',
css: (c, s) =>
`background-image: radial-gradient(${c} ${px(1.6, s)}, transparent ${px(1.6, s)}); background-size: ${px(18, s)} ${px(18, s)};`
},
{
id: 'bubbles',
label: 'Bubbles',
css: (c, s) =>
`background-image: radial-gradient(${c} 17%, transparent 18% 35%, transparent 36.5%), radial-gradient(${c} 17%, transparent 18% 35%, transparent 36.5%), radial-gradient(transparent 34%, ${c} 36% 68%, transparent 70%), repeating-linear-gradient(45deg, ${c} -12.5% 12.5%, transparent 0 37.5%); background-position: -${px(20, s)} -${px(20, s)}, ${px(20, s)} ${px(20, s)}, 0 0, 0 0; background-size: ${px(80, s)} ${px(80, s)}, ${px(80, s)} ${px(80, s)}, ${px(40, s)} ${px(40, s)}, ${px(80, s)} ${px(80, s)};`
},
{
id: 'grid',
label: 'Grid',
css: (c, s) =>
`background-image: linear-gradient(${c} 1px, transparent 1px), linear-gradient(90deg, ${c} 1px, transparent 1px); background-size: ${px(24, s)} ${px(24, s)};`
},
{
id: 'stripes',
label: 'Stripes',
css: (c, s) => `background-image: repeating-linear-gradient(45deg, ${c} 0 ${px(2, s)}, transparent ${px(2, s)} ${px(14, s)});`
},
{
id: 'checks',
label: 'Checks',
css: (c, s) =>
`background-image: conic-gradient(${c} 90deg, transparent 90deg 180deg, ${c} 180deg 270deg, transparent 270deg); background-size: ${px(24, s)} ${px(24, s)};`
},
{
id: 'zigzag',
label: 'Zigzag',
css: (c, s) =>
`background-image: linear-gradient(135deg, ${c} 25%, transparent 25%), linear-gradient(225deg, ${c} 25%, transparent 25%), linear-gradient(45deg, ${c} 25%, transparent 25%), linear-gradient(315deg, ${c} 25%, transparent 25%); background-position: ${px(20, s)} 0, ${px(20, s)} 0, 0 0, 0 0; background-size: ${px(40, s)} ${px(40, s)}; background-repeat: repeat;`
},
{
id: 'rings',
label: 'Rings',
css: (c, s) =>
`background-image: repeating-radial-gradient(circle at 50% 50%, ${c} 0, ${c} ${px(1, s)}, transparent ${px(1, s)}, transparent ${px(14, s)});`
}
]
const byId = new Map(PATTERNS.map((p) => [p.id, p]))
export function patternDef(id: BackgroundPatternId): PatternDef {
return byId.get(id) ?? PATTERNS[0]
}
// Full inline-style CSS text for a pattern + color + size, ready to drop
// into a `style` attribute. Empty string for 'none' (or unknown ids).
export function patternCss(id: BackgroundPatternId, color: string, scale = 1): string {
return patternDef(id).css(color, scale)
}