Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
import { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import {
Layout,
Grid3X3,
FilePlus,
Home,
Keyboard,
Server,
LayoutGrid,
Compass,
HelpCircle,
ChevronRight,
ArrowDownToLine,
ArrowUpFromLine,
Link2,
Search,
Filter,
Tag,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import ThemeToggle from '@/components/ThemeToggle';
import NotificationCenter from '@/components/NotificationCenter';
import ExportMenu from '@/components/ExportMenu';
import AlertRulesManager from '@/components/AlertRulesManager';
import { InstallButton } from '@/components/InstallButton';
import { useKeyboardShortcuts, shortcutsList } from '@/hooks/useKeyboardShortcuts';
import { useTheme } from '@/contexts/ThemeContext';
import { cn } from '@/lib/utils';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { helpContent, navItems } from '@/config/navHelpData';
const iconMap = {
Home,
Layout,
LayoutGrid,
Compass,
Grid3X3,
FilePlus,
Server,
};
// Help content for each page
// This is a legacy fallback - primary source is config/navHelpData
const _legacyHelpContent: Record<string, any> = {
discover: {
title: 'Widget Discovery',
icon: Compass,
diagram: `β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Widget Discovery β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚widgetRegistryβ”‚ ───▢ β”‚ WidgetMetadata[] (alle widgets)β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Filtrering (useMemo) β”‚ β”‚
β”‚ β”‚ β€’ searchQuery β€’ selectedDataType β€’ filterMode β”‚ β”‚
β”‚ β”‚ β€’ selectedPriority β€’ selectedTags β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ findCompatible(widgetId) β†’ kompatible widgets β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`,
purpose:
'Widget Discovery er et avanceret sΓΈge- og opdagelsesvΓ¦rktΓΈj der hjΓ¦lper brugere med at finde relevante widgets baseret pΓ₯ datatyper, tags og prioritet, forstΓ₯ widget-afhΓ¦ngigheder (hvad en widget krΓ¦ver og leverer), opdage kompatible widgets der kan kobles sammen, og visualisere widget-ΓΈkosystemets dataflow.',
features: [
{ name: 'Fritekst-sΓΈgning', desc: 'SΓΈg i navn, beskrivelse, tags og widget-ID' },
{
name: 'Data Type Filter',
desc: 'Filtrer efter 12 datatyper: security_event, threat_intel, network_traffic, metric, ip_address, geolocation, cve, mitre_technique, domain, file_hash, object, array',
},
{
name: 'Filter Mode',
desc: 'Vælg om datatype-filter skal matche "Leverer" (output), "Kræver" (input) eller "Begge"',
},
{ name: 'Prioritetsfilter', desc: 'Filtrer efter prioritet: critical / high / medium / low' },
{
name: 'Tag-filtrering',
desc: 'Multi-select af alle tilgængelige tags fra widget-økosystemet',
},
{ name: 'Widget-udvælgelse', desc: 'Vælg en widget for at se kompatible widgets i sidebar' },
{
name: 'Expand/Collapse',
desc: 'Vis detaljeret info om requirements (inputs), capabilities (outputs) og relationer',
},
{
name: 'Kompatibilitetsvisning',
desc: 'Se hvilke widgets der kan levere data til / modtage data fra den valgte widget',
},
],
dataModel: [
{ name: 'WidgetMetadata', desc: 'id, name, description, version, priority, tags' },
{
name: 'Requirement (input)',
desc: 'id, name, type (DataType), description, required (bool)',
},
{
name: 'Capability (output)',
desc: 'id, name, outputType (DataType), description, triggers[]',
},
{
name: 'Relation',
desc: 'type (depends_on, provides_to, synchronizes, triggers, aggregates), targetWidgetId, description',
},
],
integration: [
{
name: 'widgetRegistry',
desc: 'Singleton fra /lib/widgetRegistry.ts - central widget-database',
},
{
name: 'securityWidgetSchemas',
desc: 'Sikkerhedswidgets defineret i /types/WidgetSchema.ts',
},
{ name: 'calendarScheduleWidgetSchemas', desc: 'Kalender/schedule widgets' },
{
name: 'findCompatible(widgetId)',
desc: 'Finder widgets der kan kobles sammen baseret pΓ₯ input/output typer',
},
],
useCases: [
{ case: 'Find threat intel widgets', action: 'Filtrer Data Type β†’ "Threat Intelligence"' },
{ case: 'Se hvad ThreatMap kræver', action: 'Søg "ThreatMap" → Expand → Se KRÆVER sektion' },
{
case: 'Find kompatible widgets',
action: 'Klik ΓΈje-ikon pΓ₯ widget β†’ Se "Kompatible widgets" i sidebar',
},
{ case: 'Build et dashboard', action: 'Filtrer efter tags → Find widgets med samme domæne' },
{
case: 'Debug dataflow',
action: 'Expand widget β†’ Se relationer (depends_on, triggers, etc.)',
},
],
uiComponents: [
{
name: 'WidgetDiscovery',
file: 'pages/WidgetDiscovery.tsx',
role: 'Side-container med layout, state og filtrering',
},
{
name: 'WidgetCard',
file: 'Inline komponent',
role: 'Viser enkelt widget med expand/select',
},
{ name: 'MatrixRain', file: 'components/MatrixRain.tsx', role: 'Γ†stetisk matrix baggrund' },
],
relatedPages: [
{ path: '/widgets', relation: '"Tilbage" link - Widget catalog med alle widgets' },
{ path: '/gallery', relation: 'Visual gallery - Samme data, anden præsentation' },
{ path: '/dashboard', relation: 'Bruger widgets fundet her' },
],
summary:
'/discover er widget-sΓΈgemaskinen - stedet hvor du finder og forstΓ₯r widgets fΓΈr du bruger dem pΓ₯ dit dashboard.',
},
dashboard: {
title: 'Dashboard',
icon: Layout,
diagram: `β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Dashboard β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ WebSocket β”‚ ───▢ β”‚ Real-time events & data stream β”‚ β”‚
β”‚ β”‚ws://...:3001 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β”‚ β–Ό β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ β”‚ MCPContext (React Context) β”‚ β”‚
β”‚ β”‚ β”‚ β€’ tools[] β€’ invoke() β€’ status β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β–Ό β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ DashboardLayout (react-grid-layout) β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ Widget β”‚ β”‚ Widget β”‚ β”‚ Widget β”‚ β”‚ Widget β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ localStorage β†’ dashboard-layout (persistens) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`,
purpose:
'Dashboard er din personlige kommandocentral hvor du kan overvΓ₯ge sikkerhedsstatus, se aktive trusler og fΓ₯ overblik over systemets helbred.',
features: [
{ name: 'Real-time data', desc: 'Live opdateringer fra backend via WebSocket' },
{ name: 'Widget-layout', desc: 'Tilpas dit dashboard med drag-and-drop' },
{ name: 'Alerts', desc: 'Se kritiske advarsler ΓΈverst' },
{ name: 'Persistens', desc: 'Layout gemmes automatisk i localStorage' },
{ name: 'Responsive grid', desc: 'Tilpasser sig skærmstørrelse' },
],
dataModel: [
{ name: 'DashboardLayout', desc: 'widgets[], columns, rows, breakpoints' },
{ name: 'WidgetInstance', desc: 'id, widgetType, position {x, y, w, h}, config' },
{ name: 'WidgetState', desc: 'data, loading, error, lastUpdated' },
],
integration: [
{ name: 'WebSocket', desc: 'ws://localhost:3001/mcp/ws - real-time data stream' },
{ name: 'MCPContext', desc: 'React context der hΓ₯ndterer MCP-forbindelse' },
{ name: 'NotificationContext', desc: 'Push notifications fra backend events' },
{ name: 'localStorage', desc: 'dashboard-layout key til persistens' },
],
useCases: [
{ case: 'OvervΓ₯g trusler', action: 'Se Threat Feed widget' },
{ case: 'Check system health', action: 'Se System Status widget' },
],
uiComponents: [
{ name: 'Dashboard', file: 'pages/Dashboard.tsx', role: 'Hovedside med widget grid' },
{
name: 'ConfigurableWidget',
file: 'components/ConfigurableWidget.tsx',
role: 'HOC der wrapper widgets med config',
},
{ name: 'WidgetGrid', file: 'react-grid-layout', role: 'Drag-and-drop grid system' },
],
relatedPages: [
{ path: '/widgets', relation: 'TilfΓΈj nye widgets til dashboard' },
{ path: '/discover', relation: 'Find kompatible widgets' },
{ path: '/pages', relation: 'Gem dashboard som custom side' },
],
summary:
'/dashboard er kommandocentralen - dit personlige overblik over systemets tilstand med live data.',
},
widgets: {
title: 'Widgets Katalog',
icon: LayoutGrid,
diagram: `β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Widgets Katalog β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚widgetRegistryβ”‚ ───▢ β”‚ WidgetDefinition[] (alle typer)β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Kategorisering β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ Security β”‚ β”‚ Data β”‚ β”‚ System β”‚ β”‚ OSINT β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ ConfigurableWidget (HOC) β†’ Dashboard integration β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`,
purpose:
'Widget-kataloget viser alle tilgængelige widgets organiseret efter kategori. Herfra kan du tilføje widgets til dit dashboard.',
features: [
{ name: 'Kategorier', desc: 'Security, Data, System, Monitoring, OSINT, AI' },
{ name: 'Preview', desc: 'Se widget fΓΈr du tilfΓΈjer den' },
{ name: 'Quick add', desc: 'TilfΓΈj direkte til dashboard' },
{ name: 'SΓΈgning', desc: 'Filtrer widgets efter navn eller beskrivelse' },
],
dataModel: [
{ name: 'WidgetDefinition', desc: 'id, name, description, category, icon, component' },
{ name: 'WidgetCategory', desc: 'Security | Data | System | Monitoring | OSINT | AI' },
{ name: 'WidgetConfig', desc: 'refreshInterval, dataSource, displayOptions' },
],
integration: [
{ name: 'widgetRegistry', desc: 'Central registry med alle widget-definitioner' },
{ name: '/api/mcp/tools', desc: 'Backend endpoint for MCP tool-liste' },
{ name: 'ConfigurableWidget', desc: 'HOC der wrapper alle widgets med config UI' },
],
useCases: [
{ case: 'Find ny widget', action: 'Gennemse kategorier eller sΓΈg' },
{ case: 'Se widget detaljer', action: 'Klik pΓ₯ widget for preview' },
],
uiComponents: [
{ name: 'Widgets', file: 'pages/Widgets.tsx', role: 'Hovedside med kategori-tabs' },
{
name: 'WidgetCard',
file: 'components/WidgetCard.tsx',
role: 'Preview card for hver widget',
},
{ name: 'CategoryTabs', file: 'Inline', role: 'Tab-navigation mellem kategorier' },
],
relatedPages: [
{ path: '/discover', relation: 'Avanceret sΓΈgning og filtrering' },
{ path: '/dashboard', relation: 'TilfΓΈj valgte widgets' },
{ path: '/gallery', relation: 'Se alle widgets visuelt' },
],
summary: '/widgets er kataloget - browse og vælg widgets efter kategori før du tilføjer dem.',
},
gallery: {
title: 'Gallery',
icon: Grid3X3,
diagram: `β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Gallery β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚widgetRegistryβ”‚ ───▢ β”‚ Alle widgets med live data β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ MCPContext β†’ Hent preview data for hver widget β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Masonry Grid Layout β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚Previewβ”‚ β”‚Previewβ”‚ β”‚Previewβ”‚ β”‚Previewβ”‚ β”‚Previewβ”‚ β”‚ β”‚
β”‚ β”‚ β”‚ + β”‚ β”‚ + β”‚ β”‚ + β”‚ β”‚ + β”‚ β”‚ + β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ Data β”‚ β”‚ Data β”‚ β”‚ Data β”‚ β”‚ Data β”‚ β”‚ Data β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`,
purpose:
'Gallery viser en visuel oversigt over alle widgets med deres aktuelle tilstand og data.',
features: [
{ name: 'Grid view', desc: 'Se alle widgets i et grid' },
{ name: 'Live preview', desc: 'Widgets viser live data' },
{ name: 'Kategorivisning', desc: 'Grupperet efter widget-kategori' },
{ name: 'Responsive layout', desc: 'Tilpasser sig vinduesstΓΈrrelse' },
],
dataModel: [
{ name: 'GalleryItem', desc: 'widget, previewData, thumbnail, status' },
{ name: 'PreviewState', desc: 'loading, error, data, refreshTimer' },
],
integration: [
{ name: 'widgetRegistry', desc: 'Henter alle tilgængelige widgets' },
{ name: 'MCPContext', desc: 'Henter live data til previews' },
{ name: 'Masonry layout', desc: 'CSS grid til dynamisk placering' },
],
useCases: [{ case: 'Overblik', action: 'Se alle widgets pΓ₯ Γ©n gang' }],
uiComponents: [
{
name: 'WidgetGallery',
file: 'pages/WidgetGallery.tsx',
role: 'Hovedside med masonry grid',
},
{ name: 'GalleryCard', file: 'Inline', role: 'Widget preview med live data' },
{ name: 'FilterBar', file: 'Inline', role: 'Filtrer efter kategori' },
],
relatedPages: [
{ path: '/widgets', relation: 'Liste-visning af samme widgets' },
{ path: '/discover', relation: 'SΓΈg og filtrer widgets' },
{ path: '/dashboard', relation: 'TilfΓΈj widget til dashboard' },
],
summary: '/gallery er showroom - se alle widgets visuelt med live data før du vælger.',
},
pages: {
title: 'Mine Sider',
icon: FilePlus,
diagram: `β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Mine Sider β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ localStorage β”‚ ◀──▢ β”‚ CustomPage[] (alle sider) β”‚ β”‚
β”‚ β”‚ custom-pages β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ PageBuilder (drag-and-drop) β”‚ β”‚
β”‚ β”‚ β€’ TilfΓΈj widgets β€’ Arranger layout β€’ Gem side β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Templates β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ SOC β”‚ β”‚ OSINT β”‚ β”‚Executiveβ”‚ β”‚ Custom β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ /page/:slug β†’ react-router dynamic routing β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`,
purpose: 'Opret og administrer dine egne custom sider med widgets arrangeret efter dit behov.',
features: [
{ name: 'Page builder', desc: 'Drag-and-drop side-builder' },
{ name: 'Templates', desc: 'Brug foruddefinerede templates (SOC, OSINT, Executive)' },
{ name: 'Share', desc: 'Del sider med kolleger via URL' },
{ name: 'Export/Import', desc: 'Eksporter sider som JSON' },
],
dataModel: [
{ name: 'CustomPage', desc: 'id, slug, title, description, layout, widgets[], createdAt' },
{ name: 'PageLayout', desc: 'type (grid | freeform), columns, spacing' },
{ name: 'PageTemplate', desc: 'id, name, description, defaultWidgets[], thumbnail' },
],
integration: [
{ name: 'localStorage', desc: 'custom-pages key til persistens' },
{ name: '/page/:slug', desc: 'Dynamic routing til custom pages' },
{ name: 'ExportMenu', desc: 'Eksporter side som JSON eller PDF' },
{ name: 'react-router', desc: 'Navigation mellem sider' },
],
useCases: [
{ case: 'Opret rapport-side', action: 'Ny side β†’ TilfΓΈj relevante widgets' },
{ case: 'SOC dashboard', action: 'Brug SOC template' },
],
uiComponents: [
{ name: 'PageBuilder', file: 'pages/PageBuilder.tsx', role: 'Drag-and-drop side-editor' },
{ name: 'CustomPage', file: 'pages/CustomPage.tsx', role: 'Renderer for gemte sider' },
{ name: 'TemplateSelector', file: 'Inline', role: 'Vælg mellem foruddefinerede templates' },
],
relatedPages: [
{ path: '/page/:slug', relation: 'Vis oprettet side' },
{ path: '/widgets', relation: 'Vælg widgets til siden' },
{ path: '/dashboard', relation: 'Standard dashboard som inspiration' },
],
summary:
'/pages er dit værksted - byg og gem custom dashboards med præcis de widgets du har brug for.',
},
backend: {
title: 'Backend',
icon: Server,
diagram: `β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Backend β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Express Server (port 3001) β”‚ β”‚
β”‚ β”‚ β€’ /api/mcp/tools β€’ /api/health β€’ /api/sys β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Neo4j β”‚ β”‚ PostgreSQL β”‚ β”‚
β”‚ β”‚bolt://neo4j β”‚ β”‚ (Prisma) β”‚ β”‚
β”‚ β”‚ :7687 β”‚ β”‚ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ MCP Tools (59 registrerede) β”‚ β”‚
β”‚ β”‚ β€’ vidensarkiv.* β€’ autonomous.* β€’ widgets.* β€’ pal.* β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ WebSocket ws://localhost:3001/mcp/ws β”‚ β”‚
β”‚ β”‚ β†’ Real-time events til frontend β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`,
purpose:
'Backend-siden giver indsigt i systemets infrastruktur, MCP-tools, database-status og API-endpoints.',
features: [
{ name: 'MCP Tools', desc: 'Se alle 59 registrerede MCP tools med beskrivelser' },
{ name: 'Database status', desc: 'Neo4j og PostgreSQL connection health' },
{ name: 'API explorer', desc: 'Test API endpoints direkte i browseren' },
{ name: 'System metrics', desc: 'CPU, memory, uptime for backend' },
{ name: 'Log viewer', desc: 'Se seneste system logs' },
],
dataModel: [
{ name: 'MCPTool', desc: 'name, description, inputSchema, handler' },
{ name: 'SystemHealth', desc: 'neo4j, postgres, redis, uptime, memory' },
{ name: 'APIEndpoint', desc: 'path, method, description, parameters' },
],
integration: [
{ name: '/api/mcp/tools', desc: 'Liste over alle MCP tools' },
{ name: '/api/health', desc: 'System health check endpoint' },
{ name: '/api/sys', desc: 'System info og metrics' },
{ name: 'Neo4j bolt://neo4j:7687', desc: 'Graph database connection' },
{ name: 'PostgreSQL', desc: 'Prisma ORM til structured data' },
],
useCases: [
{ case: 'Debug integration', action: 'Check MCP tool status' },
{ case: 'Se system health', action: 'Check database connections' },
],
uiComponents: [
{ name: 'Backend', file: 'pages/Backend.tsx', role: 'Hovedside med system-info tabs' },
{ name: 'MCPToolList', file: 'Inline', role: 'Liste over alle MCP tools' },
{ name: 'HealthIndicator', file: 'Inline', role: 'Viser database connection status' },
{ name: 'APIExplorer', file: 'Inline', role: 'Test endpoints direkte' },
],
relatedPages: [
{ path: '/dashboard', relation: 'Se data fra backend i widgets' },
{ path: '/widgets', relation: 'Widgets bruger MCP tools' },
{ path: '/', relation: 'System status pΓ₯ forsiden' },
],
summary:
'/backend er kontrolrummet - se og test hele systemets infrastruktur, APIs og database-forbindelser.',
},
};
const GlobalHeader = () => {
const location = useLocation();
const { currentSkin } = useTheme();
const [helpTab, setHelpTab] = useState('discover');
useKeyboardShortcuts();
return (
<header className="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-md border-b border-border/50">
<div className="w-full px-4 h-14 flex items-center justify-between">
{/* Logo & Nav */}
<div className="flex items-center gap-4 flex-shrink-0">
<Link
to="/"
className="font-display text-xl text-primary hover:text-primary/80 transition-colors"
>
{currentSkin.logo || 'CT'}
</Link>
<nav className="hidden md:flex items-center gap-0.5">
{navItems.map(item => {
const Icon = iconMap[item.icon];
const isActive = location.pathname === item.path;
return (
<Link key={item.path} to={item.path}>
<Button
variant="ghost"
size="sm"
className={cn('gap-1.5 px-2', isActive && 'bg-primary/10 text-primary')}
>
<Icon className="w-4 h-4" />
<span className="hidden lg:inline">{item.label}</span>
</Button>
</Link>
);
})}
</nav>
</div>
{/* Actions - kompakt layout */}
<div className="flex items-center gap-1 flex-shrink-0">
<InstallButton />
<AlertRulesManager />
<ExportMenu />
<Dialog>
<DialogTrigger asChild>
<Button variant="ghost" size="sm">
<Keyboard className="w-5 h-5" />
</Button>
</DialogTrigger>
<DialogContent className="bg-card border-border">
<DialogHeader>
<DialogTitle className="text-primary font-display">Keyboard Shortcuts</DialogTitle>
</DialogHeader>
<div className="space-y-2 mt-4">
{shortcutsList.map(shortcut => (
<div
key={shortcut.keys}
className="flex items-center justify-between py-2 border-b border-border/30"
>
<span className="text-sm text-muted-foreground">{shortcut.description}</span>
<kbd className="px-2 py-1 bg-secondary/50 border border-border rounded text-xs font-mono">
{shortcut.keys}
</kbd>
</div>
))}
</div>
</DialogContent>
</Dialog>
{/* Help Dialog */}
<Dialog>
<Tooltip>
<TooltipTrigger asChild>
<DialogTrigger asChild>
<Button variant="ghost" size="sm" className="text-primary hover:bg-primary/10">
<HelpCircle className="w-5 h-5" />
</Button>
</DialogTrigger>
</TooltipTrigger>
<TooltipContent>Hjælp & Dokumentation</TooltipContent>
</Tooltip>
<DialogContent className="bg-card border-border max-w-3xl max-h-[80vh]">
<DialogHeader>
<DialogTitle className="text-primary font-display flex items-center gap-2">
<HelpCircle className="w-5 h-5" />
Hjælp & Dokumentation
</DialogTitle>
</DialogHeader>
<Tabs value={helpTab} onValueChange={setHelpTab} className="mt-4">
<TabsList className="grid grid-cols-6 gap-1 bg-secondary/30">
{Object.entries(helpContent).map(([key, content]) => {
const Icon = iconMap[content.icon];
return (
<TabsTrigger key={key} value={key} className="text-xs gap-1">
<Icon className="w-3 h-3" />
<span className="hidden lg:inline">{content.title.split(' ')[0]}</span>
</TabsTrigger>
);
})}
</TabsList>
<ScrollArea className="h-[50vh] mt-4">
{Object.entries(helpContent).map(([key, content]) => {
const Icon = content.icon;
return (
<TabsContent key={key} value={key} className="space-y-4 pr-4">
{/* Header */}
<div className="flex items-center gap-3 pb-3 border-b border-border/30">
<div className="p-2 bg-primary/10 rounded-lg">
<Icon className="w-6 h-6 text-primary" />
</div>
<div>
<h3 className="font-mono text-lg text-foreground">{content.title}</h3>
<p className="text-sm text-muted-foreground">
/{key === 'pages' ? 'pages' : key}
</p>
</div>
</div>
{/* Architecture Diagram */}
{'diagram' in content && content.diagram && (
<div className="bg-black/80 p-3 rounded-lg border border-primary/30 overflow-x-auto">
<h4 className="text-xs font-mono text-primary mb-2 flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
ARKITEKTUR
</h4>
<pre className="text-[10px] font-mono text-green-400 whitespace-pre leading-tight">
{content.diagram}
</pre>
</div>
)}
{/* Purpose */}
<div className="bg-secondary/20 p-3 rounded-lg">
<h4 className="text-xs font-mono text-primary mb-2 flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
FORMΓ…L
</h4>
<p className="text-sm text-muted-foreground">{content.purpose}</p>
</div>
{/* Features */}
<div>
<h4 className="text-xs font-mono text-primary mb-2 flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
FUNKTIONER
</h4>
<div className="grid gap-2">
{content.features.map((f, idx) => (
<div
key={idx}
className="flex items-start gap-2 p-2 bg-background/50 rounded border border-border/30"
>
<div className="w-1.5 h-1.5 rounded-full bg-primary mt-1.5 shrink-0" />
<div>
<span className="text-sm font-medium text-foreground">
{f.name}
</span>
<p className="text-xs text-muted-foreground">{f.desc}</p>
</div>
</div>
))}
</div>
</div>
{/* Data Model - only for discover */}
{'dataModel' in content && content.dataModel && (
<div>
<h4 className="text-xs font-mono text-primary mb-2 flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
DATA MODEL
</h4>
<div className="grid gap-2">
{content.dataModel.map(
(dm: { name: string; desc: string }, idx: number) => (
<div
key={idx}
className="flex items-start gap-2 p-2 bg-blue-500/5 rounded border border-blue-500/20"
>
<code className="text-xs font-mono text-blue-400 bg-blue-500/10 px-1.5 py-0.5 rounded shrink-0">
{dm.name}
</code>
<p className="text-xs text-muted-foreground">{dm.desc}</p>
</div>
)
)}
</div>
</div>
)}
{/* Integration - only for discover */}
{'integration' in content && content.integration && (
<div>
<h4 className="text-xs font-mono text-primary mb-2 flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
INTEGRATION
</h4>
<div className="grid gap-2">
{content.integration.map(
(int: { name: string; desc: string }, idx: number) => (
<div
key={idx}
className="flex items-start gap-2 p-2 bg-purple-500/5 rounded border border-purple-500/20"
>
<code className="text-xs font-mono text-purple-400 bg-purple-500/10 px-1.5 py-0.5 rounded shrink-0">
{int.name}
</code>
<p className="text-xs text-muted-foreground">{int.desc}</p>
</div>
)
)}
</div>
</div>
)}
{/* Use Cases */}
<div>
<h4 className="text-xs font-mono text-primary mb-2 flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
ANVENDELSE
</h4>
<div className="space-y-2">
{content.useCases.map((uc, idx) => (
<div key={idx} className="flex items-center gap-2 text-sm">
<span className="text-muted-foreground">{uc.case}:</span>
<span className="text-primary font-mono text-xs bg-primary/10 px-2 py-0.5 rounded">
{uc.action}
</span>
</div>
))}
</div>
</div>
{/* UI Components */}
{'uiComponents' in content && content.uiComponents && (
<div>
<h4 className="text-xs font-mono text-primary mb-2 flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
🎨 UI KOMPONENTER
</h4>
<div className="rounded border border-border/50 overflow-hidden">
<table className="w-full text-xs">
<thead className="bg-muted/30">
<tr>
<th className="px-2 py-1.5 text-left font-mono text-muted-foreground">
Komponent
</th>
<th className="px-2 py-1.5 text-left font-mono text-muted-foreground">
Fil
</th>
<th className="px-2 py-1.5 text-left font-mono text-muted-foreground">
Ansvar
</th>
</tr>
</thead>
<tbody>
{content.uiComponents.map(
(
ui: {
component: string;
file: string;
responsibility: string;
},
idx: number
) => (
<tr
key={idx}
className="border-t border-border/30 hover:bg-muted/20"
>
<td className="px-2 py-1.5 font-mono text-cyan-400">
{ui.component}
</td>
<td className="px-2 py-1.5 font-mono text-muted-foreground">
{ui.file}
</td>
<td className="px-2 py-1.5 text-foreground">
{ui.responsibility}
</td>
</tr>
)
)}
</tbody>
</table>
</div>
</div>
)}
{/* Related Pages */}
{'relatedPages' in content && content.relatedPages && (
<div>
<h4 className="text-xs font-mono text-primary mb-2 flex items-center gap-1">
<ChevronRight className="w-3 h-3" />
πŸ“ RELATEREDE SIDER
</h4>
<div className="rounded border border-border/50 overflow-hidden">
<table className="w-full text-xs">
<thead className="bg-muted/30">
<tr>
<th className="px-2 py-1.5 text-left font-mono text-muted-foreground">
Side
</th>
<th className="px-2 py-1.5 text-left font-mono text-muted-foreground">
Relation
</th>
</tr>
</thead>
<tbody>
{content.relatedPages.map(
(rp: { page: string; relation: string }, idx: number) => (
<tr
key={idx}
className="border-t border-border/30 hover:bg-muted/20"
>
<td className="px-2 py-1.5 font-mono text-green-400">
{rp.page}
</td>
<td className="px-2 py-1.5 text-foreground">
{rp.relation}
</td>
</tr>
)
)}
</tbody>
</table>
</div>
</div>
)}
{/* Summary */}
{'summary' in content && content.summary && (
<div className="mt-2 p-3 bg-gradient-to-r from-primary/10 to-primary/5 rounded-lg border border-primary/20">
<p className="text-sm font-medium text-foreground">
<span className="text-primary">πŸ’‘ Kort sagt:</span> {content.summary}
</p>
</div>
)}
</TabsContent>
);
})}
</ScrollArea>
</Tabs>
</DialogContent>
</Dialog>
<NotificationCenter />
<ThemeToggle />
</div>
</div>
</header>
);
};
export default GlobalHeader;