Spaces:
Paused
Paused
| 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; | |