From 44793598ee94ad2026d18163131c917536f96620 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 4 May 2026 11:38:05 -0500 Subject: [PATCH 1/4] Add Dstl8 Lite web dashboard and What's New modal --- .gitignore | 4 + Makefile | 48 +- README.md | 61 +- USAGE_GUIDE.md | 44 +- cmd/gonzo/app.go | 50 + cmd/gonzo/main.go | 11 +- cmd/gonzo/processing.go | 5 + go.mod | 17 +- go.sum | 42 +- internal/engine/engine.go | 1380 ++++++ internal/engine/streams.go | 31 + internal/engine/types.go | 150 + internal/releases/releases.go | 127 + internal/state/state.go | 69 + internal/tui/components.go | 8 +- internal/tui/modal_help.go | 2 + internal/tui/modal_whats_new.go | 194 + internal/tui/model.go | 56 +- internal/tui/navigation.go | 54 +- internal/tui/update.go | 5 + internal/tui/view.go | 5 + internal/web/api.go | 233 + internal/web/server.go | 195 + internal/web/websocket.go | 133 + web/Dstl8_logo_dark.svg | 91 + web/Dstl8_logo_light.svg | 91 + web/embed.go | 6 + web/index.html | 13 + web/package-lock.json | 4283 +++++++++++++++++ web/package.json | 27 + web/postcss.config.js | 6 + web/public/Dstl8_Gradient_Dark.svg | 91 + web/public/Dstl8_logo_dark.svg | 91 + web/public/Dstl8_logo_light.svg | 91 + web/public/Gonzo-Profile.png | Bin 0 -> 264034 bytes web/public/MCP_icon.png | Bin 0 -> 61177 bytes web/public/Mobius_Circle.svg | 33 + web/public/Mobius_Square_dark.svg | 43 + web/src/App.tsx | 159 + web/src/api/client.ts | 101 + web/src/api/types.ts | 117 + web/src/api/websocket.ts | 57 + web/src/components/layout/AppHeader.tsx | 93 + web/src/components/layout/AppSidebar.tsx | 133 + web/src/components/layout/DashboardLayout.tsx | 21 + web/src/components/layout/Footer.tsx | 37 + web/src/components/layout/Header.tsx | 51 + web/src/components/layout/MobiusChat.tsx | 68 + web/src/components/panels/AISummaryPanel.tsx | 79 + web/src/components/panels/HeatmapPanel.tsx | 100 + web/src/components/panels/LogViewer.tsx | 203 + web/src/components/panels/PatternAnalysis.tsx | 85 + .../panels/SeverityDistribution.tsx | 118 + web/src/components/panels/SourcesSection.tsx | 478 ++ web/src/components/panels/StreamSelector.tsx | 79 + web/src/components/ui/Button.tsx | 36 + web/src/components/ui/Card.tsx | 30 + web/src/components/ui/ErrorBoundary.tsx | 48 + web/src/components/ui/SeverityBadge.tsx | 21 + web/src/components/ui/ThemeProvider.tsx | 32 + web/src/components/ui/WhatsNewModal.tsx | 258 + .../components/upsell/IncidentPlaceholder.tsx | 29 + web/src/components/upsell/MobiusUpgrade.tsx | 30 + web/src/components/upsell/RetentionBanner.tsx | 27 + web/src/hooks/useTheme.ts | 8 + web/src/hooks/useWebSocket.ts | 22 + web/src/index.css | 851 ++++ web/src/lib/colors.ts | 24 + web/src/lib/constants.ts | 10 + web/src/lib/icons.tsx | 68 + web/src/main.tsx | 10 + web/src/pages/HeatmapPage.tsx | 418 ++ web/src/pages/LogViewerPage.tsx | 621 +++ web/src/pages/SourcesPage.tsx | 378 ++ web/src/pages/StreamDetailsPage.tsx | 146 + web/src/pages/UpgradePage.tsx | 71 + web/src/pages/WorkspaceDetailsPage.tsx | 423 ++ web/src/pages/WorkspacesPage.tsx | 210 + web/src/vite-env.d.ts | 1 + web/tailwind.config.ts | 36 + web/tsconfig.json | 21 + web/vite.config.ts | 27 + 82 files changed, 13598 insertions(+), 27 deletions(-) create mode 100644 internal/engine/engine.go create mode 100644 internal/engine/streams.go create mode 100644 internal/engine/types.go create mode 100644 internal/releases/releases.go create mode 100644 internal/state/state.go create mode 100644 internal/tui/modal_whats_new.go create mode 100644 internal/web/api.go create mode 100644 internal/web/server.go create mode 100644 internal/web/websocket.go create mode 100644 web/Dstl8_logo_dark.svg create mode 100644 web/Dstl8_logo_light.svg create mode 100644 web/embed.go create mode 100644 web/index.html create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/postcss.config.js create mode 100644 web/public/Dstl8_Gradient_Dark.svg create mode 100644 web/public/Dstl8_logo_dark.svg create mode 100644 web/public/Dstl8_logo_light.svg create mode 100644 web/public/Gonzo-Profile.png create mode 100644 web/public/MCP_icon.png create mode 100644 web/public/Mobius_Circle.svg create mode 100644 web/public/Mobius_Square_dark.svg create mode 100644 web/src/App.tsx create mode 100644 web/src/api/client.ts create mode 100644 web/src/api/types.ts create mode 100644 web/src/api/websocket.ts create mode 100644 web/src/components/layout/AppHeader.tsx create mode 100644 web/src/components/layout/AppSidebar.tsx create mode 100644 web/src/components/layout/DashboardLayout.tsx create mode 100644 web/src/components/layout/Footer.tsx create mode 100644 web/src/components/layout/Header.tsx create mode 100644 web/src/components/layout/MobiusChat.tsx create mode 100644 web/src/components/panels/AISummaryPanel.tsx create mode 100644 web/src/components/panels/HeatmapPanel.tsx create mode 100644 web/src/components/panels/LogViewer.tsx create mode 100644 web/src/components/panels/PatternAnalysis.tsx create mode 100644 web/src/components/panels/SeverityDistribution.tsx create mode 100644 web/src/components/panels/SourcesSection.tsx create mode 100644 web/src/components/panels/StreamSelector.tsx create mode 100644 web/src/components/ui/Button.tsx create mode 100644 web/src/components/ui/Card.tsx create mode 100644 web/src/components/ui/ErrorBoundary.tsx create mode 100644 web/src/components/ui/SeverityBadge.tsx create mode 100644 web/src/components/ui/ThemeProvider.tsx create mode 100644 web/src/components/ui/WhatsNewModal.tsx create mode 100644 web/src/components/upsell/IncidentPlaceholder.tsx create mode 100644 web/src/components/upsell/MobiusUpgrade.tsx create mode 100644 web/src/components/upsell/RetentionBanner.tsx create mode 100644 web/src/hooks/useTheme.ts create mode 100644 web/src/hooks/useWebSocket.ts create mode 100644 web/src/index.css create mode 100644 web/src/lib/colors.ts create mode 100644 web/src/lib/constants.ts create mode 100644 web/src/lib/icons.tsx create mode 100644 web/src/main.tsx create mode 100644 web/src/pages/HeatmapPage.tsx create mode 100644 web/src/pages/LogViewerPage.tsx create mode 100644 web/src/pages/SourcesPage.tsx create mode 100644 web/src/pages/StreamDetailsPage.tsx create mode 100644 web/src/pages/UpgradePage.tsx create mode 100644 web/src/pages/WorkspaceDetailsPage.tsx create mode 100644 web/src/pages/WorkspacesPage.tsx create mode 100644 web/src/vite-env.d.ts create mode 100644 web/tailwind.config.ts create mode 100644 web/tsconfig.json create mode 100644 web/vite.config.ts diff --git a/.gitignore b/.gitignore index 415ccd9..a5e7ade 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,10 @@ tmp/ # Testing artifacts testdata/output/ +# Web dashboard +web/node_modules/ +web/dist/ + # Claude Code settings .claude/settings.local.json result diff --git a/Makefile b/Makefile index d908edc..2d4d6be 100644 --- a/Makefile +++ b/Makefile @@ -32,9 +32,13 @@ PURPLE := \033[0;35m CYAN := \033[0;36m NC := \033[0m # No Color -.PHONY: all build clean clean-all test test-race test-integration \ +# Code signing (macOS) — set via environment or here +CODESIGN_IDENTITY ?= - + +.PHONY: all build build-tui clean clean-all test test-race test-integration \ fmt vet lint install uninstall run demo \ - deps deps-update deps-tidy cross-build release help dev ci info + deps deps-update deps-tidy cross-build release help dev ci info \ + web-deps web-build web-dev web-clean build-signed # Default target all: clean build @@ -53,14 +57,49 @@ help: ## Show this help message @echo " make cross-build # Build for all platforms" @echo " make install # Install to GOPATH/bin" +# Web dashboard targets +web-deps: ## Install web dashboard dependencies + @echo "$(BLUE)Installing web dependencies...$(NC)" + @cd web && rm -rf node_modules && npm ci + @echo "$(GREEN)✓ Web dependencies installed$(NC)" + +web-build: web-deps ## Build web dashboard + @echo "$(BLUE)Building web dashboard...$(NC)" + @cd web && npm run build + @echo "$(GREEN)✓ Web dashboard built$(NC)" + +web-dev: web-deps ## Start web dev server with API proxy + @echo "$(BLUE)Starting web dev server...$(NC)" + @cd web && npm run dev + +web-clean: ## Clean web build artifacts + @echo "$(BLUE)Cleaning web artifacts...$(NC)" + @rm -rf web/dist web/node_modules + @echo "$(GREEN)✓ Web artifacts cleaned$(NC)" + # Build targets -build: deps ## Build the TUI binary - @echo "$(BLUE)Building TUI version...$(NC)" +build: deps web-build ## Build binary with embedded web dashboard + @echo "$(BLUE)Building Gonzo with web dashboard...$(NC)" @mkdir -p $(BUILD_DIR) CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) \ $(GO) build $(BUILD_FLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) $(CMD_DIR) @echo "$(GREEN)✓ Built $(BUILD_DIR)/$(BINARY_NAME)$(NC)" +build-signed: build ## Build and codesign for macOS (set CODESIGN_IDENTITY for distribution) + @echo "$(BLUE)Signing $(BUILD_DIR)/$(BINARY_NAME)...$(NC)" + @codesign --force --options runtime --sign "$(CODESIGN_IDENTITY)" --timestamp $(BUILD_DIR)/$(BINARY_NAME) + @echo "$(GREEN)✓ Signed $(BUILD_DIR)/$(BINARY_NAME)$(NC)" + @codesign -dvv $(BUILD_DIR)/$(BINARY_NAME) 2>&1 | head -5 + +build-tui: deps ## Build TUI-only binary (placeholder web dashboard) + @echo "$(BLUE)Building TUI-only version...$(NC)" + @mkdir -p $(BUILD_DIR) + @mkdir -p web/dist + @test -f web/dist/index.html || echo '

Run make build to embed the full web dashboard

' > web/dist/index.html + CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) \ + $(GO) build $(BUILD_FLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) $(CMD_DIR) + @echo "$(GREEN)✓ Built $(BUILD_DIR)/$(BINARY_NAME) (TUI only)$(NC)" + # Cross-platform builds cross-build: clean deps ## Build for multiple platforms @echo "$(BLUE)Building for multiple platforms...$(NC)" @@ -211,6 +250,7 @@ clean: ## Clean build artifacts @echo "$(BLUE)Cleaning build artifacts...$(NC)" @rm -rf $(BUILD_DIR) @rm -rf $(DIST_DIR) + @rm -rf web/dist @rm -f $(BINARY_NAME) @echo "$(GREEN)✓ Cleaned$(NC)" diff --git a/README.md b/README.md index afd118f..a1de077 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,17 @@ Here are some references to get you started: - **Log Counts analysis** - Detailed modal with heatmap visualization, pattern analysis by severity, and service distribution - **AI analysis** - Get intelligent insights about log patterns and anomalies with configurable models +### 🌐 Web Dashboard (Dstl8 Lite) + +- **Embedded React UI** - Full web dashboard served directly from the Gonzo binary (no external dependencies) +- **Real-time updates** - WebSocket-powered live streaming with 1-second refresh +- **Severity distribution** - Interactive time-series severity charts with stream-level filtering +- **Sentiment heatmap** - Color-coded heatmap visualization grouped by pod, namespace, service, host, or deployment +- **Pattern analysis** - Drain3-powered log pattern detection and classification +- **Log viewer** - Searchable, auto-scrolling log viewer with click-to-expand details +- **Source browser** - Explore log sources with dimension breakdowns +- **Light/dark mode** - Automatic theme support + ### 🔍 Advanced Filtering - **Regex support** - Filter logs with regular expressions @@ -306,6 +317,30 @@ export OPENAI_API_KEY=sk-your-key-here cat logs.json | gonzo --ai-model="gpt-4" ``` +### Web Dashboard (Dstl8 Lite) + +Gonzo includes an embedded web dashboard that runs alongside the TUI. It starts automatically on port 5718. + +```bash +# Gonzo starts the web dashboard automatically +gonzo -f application.log --follow +# Open http://localhost:5718 in your browser + +# Use a custom port +gonzo -f application.log --web-port=3000 + +# Disable the web dashboard +gonzo -f application.log --web-disabled +``` + +The dashboard includes: +- **Workspaces** - Overview of all active log streams with sparkline previews +- **Stream Details** - Severity distribution, top attributes, pattern analysis, and live log viewer per stream +- **Sentiment Heatmap** - Real-time heatmap grouped by pod, namespace, service, host, or deployment with auto-detection of available dimensions +- **Sources** - Browse log sources with dimension breakdowns + +All data updates in real-time via WebSocket, matching what you see in the TUI. + ### Keyboard Shortcuts #### Navigation @@ -452,6 +487,10 @@ Flags: -s, --skin string Color scheme/skin to use (default, or name of a skin file) --stop-words strings Additional stop words to filter out from analysis (adds to built-in list) +Web Dashboard Flags: + --web-port int Port for the Dstl8 Lite web dashboard (default: 5718) + --web-disabled Disable the web dashboard + Kubernetes Flags: --k8s-enabled=true Enable Kubernetes log streaming mode --k8s-namespaces stringArray Kubernetes namespace(s) to watch (can specify multiple, default: all) @@ -501,6 +540,10 @@ test-mode: false # AI configuration ai-provider: "openai" # Options: "openai" (default), "claude-code" ai-model: "gpt-4" + +# Web dashboard (Dstl8 Lite) +web-port: 5718 # Port for the web dashboard +web-disabled: false # Set to true to disable ``` See [examples/config.yml](examples/config.yml) for a complete configuration example with detailed comments. @@ -689,6 +732,8 @@ When you don't specify the `--ai-model` flag, Gonzo automatically selects the be | `GONZO_LOG_BUFFER` | Override log buffer size | | `GONZO_MEMORY_SIZE` | Override memory size | | `GONZO_AI_MODEL` | Override default AI model | +| `GONZO_WEB_PORT` | Override web dashboard port (default: 5718) | +| `GONZO_WEB_DISABLED` | Disable web dashboard (true/false) | | `GONZO_TEST_MODE` | Enable test mode | | `NO_COLOR` | Disable colored output | @@ -760,16 +805,19 @@ Gonzo is built with: - **OpenTelemetry** - Native OTLP support - **Large amounts of** ☕️ -The architecture follows a clean separation: +The architecture follows a clean separation with a shared analysis engine: ``` cmd/gonzo/ # Main application entry internal/ -├── tui/ # Terminal UI implementation -├── analyzer/ # Log analysis engine +├── engine/ # Shared analysis engine (feeds TUI and web) +├── tui/ # Terminal UI implementation +├── web/ # Dstl8 Lite web dashboard (embedded React) +├── analyzer/ # Log analysis engine ├── memory/ # Frequency tracking ├── otlplog/ # OTLP format handling └── ai/ # AI integration +web/ # React frontend source (Vite + TypeScript) ``` ## 🧪 Development @@ -782,9 +830,13 @@ internal/ ### Building ```bash -# Quick build +# Quick build (includes web dashboard) make build +# Build web dashboard only +make web-deps # Install npm dependencies +make web-build # Build React app + # Run tests make test @@ -879,6 +931,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file - [AWS CloudWatch Logs Usage Guide](guides/CLOUDWATCH_USAGE_GUIDE.md) - Usage instructions for AWS CLI log tail and live tail with Gonzo - [Stern Usage Guide](guides/STERN_USAGE_GUIDE.md) - Usage and examples for using Stern with Gonzo - [Victoria Logs Integration](guides/VICTORIA_LOGS_USAGE.md) - Using Gonzo with Victoria Logs API +- [Web Dashboard](guides/WEB_DASHBOARD_USAGE.md) - Dstl8 Lite web UI setup - [Contributing Guide](CONTRIBUTING.md) - How to contribute - [Changelog](CHANGELOG.md) - Version history diff --git a/USAGE_GUIDE.md b/USAGE_GUIDE.md index d4c45ac..33fb55d 100644 --- a/USAGE_GUIDE.md +++ b/USAGE_GUIDE.md @@ -143,8 +143,12 @@ cat test.log | ./build/gonzo --reverse-scroll-wheel # Reverse scroll wheel direction (natural scrolling) --config string # Config file (default: ~/.gonzo.yaml) +# Web dashboard (Dstl8 Lite) + --web-port=5718 # Port for the web dashboard + --web-disabled # Disable the web dashboard + # Version and help --v, --version # Show version information +-v, --version # Show version information -h, --help # Show help message # Commands @@ -273,6 +277,44 @@ This affects all scrollable areas: - **Consistency**: Keep same scroll direction across all applications - **Personal preference**: Use whichever feels more intuitive to you +## Web Dashboard (Dstl8 Lite) + +Gonzo includes an embedded web dashboard that starts automatically alongside the TUI. + +### Accessing the Dashboard + +```bash +# Start Gonzo — web dashboard is available at http://localhost:5718 +./build/gonzo -f test.log --follow + +# Custom port +./build/gonzo -f test.log --web-port=3000 + +# Disable the web dashboard +./build/gonzo -f test.log --web-disabled +``` + +Open `http://localhost:5718` in your browser to see: +- **Workspaces** - Overview of active log streams with live sparklines +- **Stream Details** - Severity distribution, top attributes, pattern analysis, and live log viewer +- **Sentiment Heatmap** - Real-time heatmap grouped by pod, namespace, service, host, or deployment +- **Sources** - Browse log sources and their dimensions + +The dashboard updates in real-time via WebSocket — the same data that powers the TUI. + +### Building the Web Dashboard + +The web dashboard is built with Vite + React + TypeScript and embedded into the Go binary. + +```bash +# Install dependencies and build (included in make build) +make web-deps +make web-build + +# Full build including web dashboard +make build +``` + ### Supported Integrations - [Victoria Logs Integration](guides/VICTORIA_LOGS_USAGE.md) - Using Gonzo with Victoria Logs API diff --git a/cmd/gonzo/app.go b/cmd/gonzo/app.go index 7939c69..eeaa0e1 100644 --- a/cmd/gonzo/app.go +++ b/cmd/gonzo/app.go @@ -12,7 +12,12 @@ import ( "strings" "time" + "io/fs" + "github.com/control-theory/gonzo/internal/analyzer" + "github.com/control-theory/gonzo/internal/engine" + "github.com/control-theory/gonzo/internal/releases" + "github.com/control-theory/gonzo/internal/state" "github.com/control-theory/gonzo/internal/filereader" "github.com/control-theory/gonzo/internal/formats" "github.com/control-theory/gonzo/internal/k8s" @@ -22,6 +27,8 @@ import ( "github.com/control-theory/gonzo/internal/tui" versioncheck "github.com/control-theory/gonzo/internal/version" "github.com/control-theory/gonzo/internal/vmlogs" + gonzoweb "github.com/control-theory/gonzo/internal/web" + webembed "github.com/control-theory/gonzo/web" tea "github.com/charmbracelet/bubbletea" "github.com/spf13/cobra" @@ -108,6 +115,23 @@ func runApp(cmd *cobra.Command, args []string) error { if versionChecker != nil { dashboard.SetVersionChecker(versionChecker) } + if !cfg.WebDisabled { + dashboard.SetWebPort(cfg.WebPort) + } + + // What's New: fetch GitHub releases in background and wire up auto-show + currentVersion, _ := GetVersionInfo() + relFetcher := releases.NewFetcher() + if currentVersion != "dev" && currentVersion != "" && !cfg.DisableVersionCheck { + relFetcher.FetchInBackground() + } + appState := state.Load() + dashboard.SetReleasesFetcher(relFetcher) + dashboard.SetCurrentVersion(currentVersion) + dashboard.SetLastSeenVersion(appState.LastSeenVersion) + + // Create shared analysis engine (used by web dashboard) + eng := engine.NewEngine(cfg.LogBuffer, textAnalyzer.GetStopWords(), nil, cfg.UseLogTime) tuiModel := &simpleTuiModel{ formatDetector: formatDetector, @@ -117,6 +141,7 @@ func runApp(cmd *cobra.Command, args []string) error { otlpAnalyzer: otlpAnalyzer, freqMemory: freqMemory, dashboard: dashboard, + engine: eng, updateInterval: cfg.UpdateInterval, testMode: cfg.TestMode, versionChecker: versionChecker, @@ -138,6 +163,19 @@ func runApp(cmd *cobra.Command, args []string) error { tuiModel.ctx = ctx tuiModel.cancelFunc = cancel + // Start web dashboard in background (if not disabled) + if !cfg.WebDisabled { + // Get embedded web dashboard assets + var staticFS fs.FS + if distFS, err := fs.Sub(webembed.DistFS, "dist"); err == nil { + staticFS = distFS + } + webSrv := gonzoweb.NewServer(eng, staticFS, currentVersion, relFetcher) + if err := webSrv.Start(ctx, cfg.WebPort); err != nil { + log.Printf("Warning: web dashboard failed to start: %v", err) + } + } + if _, err := p.Run(); err != nil { if strings.Contains(err.Error(), "TTY") || strings.Contains(err.Error(), "/dev/tty") { return fmt.Errorf("TUI requires a real terminal. Try --test-mode for non-interactive testing") @@ -168,6 +206,7 @@ type simpleTuiModel struct { otlpAnalyzer *analyzer.OTLPAnalyzer freqMemory *memory.FrequencyMemory dashboard *tui.DashboardModel + engine *engine.Engine // Shared analysis state for web dashboard updateInterval time.Duration testMode bool ctx context.Context @@ -633,6 +672,11 @@ func (m *simpleTuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Send periodic snapshot with current count (even if 0) snapshot := m.freqMemory.GetSnapshot() + // Feed snapshot to the shared engine + if m.engine != nil { + m.engine.UpdateFrequencySnapshot(snapshot) + } + // No automatic reset - only manual reset via 'r' key now shouldReset := false @@ -644,6 +688,11 @@ func (m *simpleTuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ResetDrain3: shouldReset, // Reset drain3 when frequency memory resets } + // Feed interval counts to the shared engine + if m.engine != nil && m.severityCounts != nil { + m.engine.IngestSeverityCounts(*m.severityCounts) + } + // Reset severity counts for next interval m.severityCounts = &tui.SeverityCounts{} m.logCount = 0 @@ -783,6 +832,7 @@ func systemLogPath() string { case "darwin": return "/var/log/system.log" case "linux": + // Try syslog first, then messages for _, p := range []string{"/var/log/syslog", "/var/log/messages"} { if _, err := os.Stat(p); err == nil { return p diff --git a/cmd/gonzo/main.go b/cmd/gonzo/main.go index ae05aee..c924626 100644 --- a/cmd/gonzo/main.go +++ b/cmd/gonzo/main.go @@ -55,6 +55,10 @@ type Config struct { DisableVersionCheck bool `mapstructure:"disable-version-check"` ReverseScrollWheel bool `mapstructure:"reverse-scroll-wheel"` UseLogTime bool `mapstructure:"use-log-time"` + + // Web dashboard (Dstl8 Lite) + WebPort int `mapstructure:"web-port"` + WebDisabled bool `mapstructure:"web-disabled"` } var ( @@ -186,6 +190,10 @@ func init() { rootCmd.Flags().Bool("reverse-scroll-wheel", false, "Reverse scroll wheel direction (natural scrolling)") rootCmd.Flags().Bool("use-log-time", false, "Use original log timestamps instead of receive time for heatmap and display (falls back to receive time if log has no timestamp)") + // Dstl8 Lite web dashboard flags + rootCmd.Flags().Int("web-port", 5718, "Port for the Dstl8 Lite web dashboard") + rootCmd.Flags().Bool("web-disabled", false, "Disable the web dashboard") + // Bind flags to viper viper.BindPFlag("memory-size", rootCmd.Flags().Lookup("memory-size")) viper.BindPFlag("update-interval", rootCmd.Flags().Lookup("update-interval")) @@ -215,7 +223,8 @@ func init() { viper.BindPFlag("disable-version-check", rootCmd.Flags().Lookup("disable-version-check")) viper.BindPFlag("reverse-scroll-wheel", rootCmd.Flags().Lookup("reverse-scroll-wheel")) viper.BindPFlag("use-log-time", rootCmd.Flags().Lookup("use-log-time")) - + viper.BindPFlag("web-port", rootCmd.Flags().Lookup("web-port")) + viper.BindPFlag("web-disabled", rootCmd.Flags().Lookup("web-disabled")) // Add version command rootCmd.AddCommand(versionCmd) } diff --git a/cmd/gonzo/processing.go b/cmd/gonzo/processing.go index ee8ecea..0d11de2 100644 --- a/cmd/gonzo/processing.go +++ b/cmd/gonzo/processing.go @@ -151,6 +151,11 @@ func (m *simpleTuiModel) processSingleLogEntry(result *analyzer.AnalysisResult, // Count severity for this interval m.severityCounts.AddCount(logEntry.Severity) + // Feed the shared analysis engine (for web dashboard) + if m.engine != nil { + m.engine.Ingest(*logEntry) + } + updateMsg := tui.UpdateMsg{NewLogEntry: logEntry} m.dashboard.Update(updateMsg) } diff --git a/go.mod b/go.mod index 36039bf..e591ca8 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/NimbleMarkets/ntcharts v0.3.1 github.com/charmbracelet/bubbles v0.21.0 github.com/charmbracelet/bubbletea v1.3.6 + github.com/charmbracelet/glamour v1.0.0 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 github.com/fsnotify/fsnotify v1.9.0 github.com/jaeyo/go-drain3 v0.1.2 @@ -19,16 +20,21 @@ require ( k8s.io/apimachinery v0.34.2 k8s.io/client-go v0.34.2 k8s.io/klog/v2 v2.130.1 + nhooyr.io/websocket v1.8.17 ) require ( + github.com/alecthomas/chroma/v2 v2.20.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/x/ansi v0.9.3 // indirect + github.com/charmbracelet/x/ansi v0.10.2 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect + github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect @@ -41,21 +47,24 @@ require ( github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.17 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect @@ -71,6 +80,8 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + github.com/yuin/goldmark v1.7.13 // indirect + github.com/yuin/goldmark-emoji v1.0.6 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect diff --git a/go.sum b/go.sum index 669debe..122fc03 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,20 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/NimbleMarkets/ntcharts v0.3.1 h1:EH4O80RMy5rqDmZM7aWjTbCSuRDDJ5fXOv/qAzdwOjk= github.com/NimbleMarkets/ntcharts v0.3.1/go.mod h1:zVeRqYkh2n59YPe1bflaSL4O2aD2ZemNmrbdEqZ70hk= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw= +github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA= +github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg= +github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= @@ -16,14 +24,18 @@ github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGs github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/glamour v1.0.0 h1:AWMLOVFHTsysl4WV8T8QgkQ0s/ZNZo7CiE4WKhk8l08= +github.com/charmbracelet/glamour v1.0.0/go.mod h1:DSdohgOBkMr2ZQNhw4LZxSGpx3SvpeujNoXrQyH2hxo= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= -github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0= -github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw= +github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8= github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI= +github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -31,6 +43,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= @@ -70,10 +84,14 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jaeyo/go-drain3 v0.1.2 h1:fY21wgbwhzzaoRNSQ+6HVbpYw4KkAYjCFCoERYozIJ8= @@ -93,16 +111,19 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e h1:OLwZ8xVaeVrru0xyeuOX+fne0gQTFEGlzfNjipCbxlU= github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e/go.mod h1:NQ34EGeu8FAYGBMDzwhfNJL8YQYoWZP5xYJPRDAwN3E= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ= +github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -113,6 +134,8 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -127,6 +150,7 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -166,6 +190,10 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= +github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= +github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= @@ -265,6 +293,8 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y= +nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/internal/engine/engine.go b/internal/engine/engine.go new file mode 100644 index 0000000..505f53e --- /dev/null +++ b/internal/engine/engine.go @@ -0,0 +1,1380 @@ +package engine + +import ( + "context" + "fmt" + "sort" + "strings" + "sync" + "time" + + "github.com/control-theory/gonzo/internal/ai" + "github.com/control-theory/gonzo/internal/memory" + "github.com/control-theory/gonzo/internal/tui" +) + +// Engine is the shared, concurrency-safe analysis state that the TUI +// and web dashboard read from. The processing pipeline +// writes via Ingest(); all Query*/Get* methods acquire a read lock. +type Engine struct { + mu sync.RWMutex + + // Log buffer (ring buffer) + allLogEntries []tui.LogEntry + maxLogBuffer int + + // Severity tracking + lifetimeSeverityCounts map[string]int64 + countsHistory []tui.SeverityCounts + severityTimeSeries []SeverityTimePoint // timestamped 1-second severity history + + // Heatmap data (minute-by-minute severity counts) + heatmapData []tui.HeatmapMinute + + // Pattern clustering per severity + drain3BySeverity map[string]*tui.Drain3Manager + // Combined drain3 for all logs + drain3All *tui.Drain3Manager + + // Service tracking per severity + servicesBySeverity map[string][]tui.ServiceCount + + // Dimension tracking for QueryInsightsParams + lifetimeHostCounts map[string]int64 + lifetimeServiceCounts map[string]int64 + lifetimeAttrKeyCounts map[string]map[string]int64 + + // Frequency snapshot (latest) + snapshot *memory.FrequencySnapshot + + // Word/attribute tracking for classes + lifetimeWordCounts map[string]int64 + lifetimeAttrCounts map[string]int64 + stopWords map[string]bool + + // Stream tracking + streams map[string]*StreamInfo + + // Statistics + statsStartTime time.Time + totalLogsEver int + totalBytes int64 + + // AI client for summary queries + aiClient ai.Client + + // Config + useLogTime bool +} + +// NewEngine creates a new Engine with the given configuration. +func NewEngine(maxLogBuffer int, stopWords map[string]bool, aiClient ai.Client, useLogTime bool) *Engine { + return &Engine{ + allLogEntries: make([]tui.LogEntry, 0, maxLogBuffer), + maxLogBuffer: maxLogBuffer, + lifetimeSeverityCounts: make(map[string]int64), + countsHistory: make([]tui.SeverityCounts, 0), + severityTimeSeries: make([]SeverityTimePoint, 0, 120), + heatmapData: make([]tui.HeatmapMinute, 0), + drain3BySeverity: tui.InitializeDrain3BySeverity(), + drain3All: tui.NewDrain3Manager(), + servicesBySeverity: make(map[string][]tui.ServiceCount), + lifetimeHostCounts: make(map[string]int64), + lifetimeServiceCounts: make(map[string]int64), + lifetimeAttrKeyCounts: make(map[string]map[string]int64), + lifetimeWordCounts: make(map[string]int64), + lifetimeAttrCounts: make(map[string]int64), + stopWords: stopWords, + streams: make(map[string]*StreamInfo), + statsStartTime: time.Now(), + aiClient: aiClient, + useLogTime: useLogTime, + } +} + +// Ingest adds a new log entry to the engine. Called from the processing pipeline. +// This is the only write path — it acquires a write lock. +func (e *Engine) Ingest(entry tui.LogEntry) { + e.mu.Lock() + defer e.mu.Unlock() + + // Add to buffer + e.allLogEntries = append(e.allLogEntries, entry) + if len(e.allLogEntries) > e.maxLogBuffer { + e.allLogEntries = e.allLogEntries[1:] + } + + // Update statistics + e.totalLogsEver++ + e.totalBytes += int64(len(entry.RawLine)) + + // Update lifetime severity counts + e.lifetimeSeverityCounts[entry.Severity]++ + + // Update host counts + if host := entry.Attributes["host"]; host != "" { + e.lifetimeHostCounts[host]++ + } + + // Update service counts + serviceName := getServiceName(entry) + if serviceName != "" && serviceName != "unknown" { + e.lifetimeServiceCounts[serviceName]++ + } + + // Update per-attribute-key value counts + for key, value := range entry.Attributes { + attrKey := fmt.Sprintf("%s=%s", key, value) + if len(attrKey) < 200 { + e.lifetimeAttrCounts[attrKey]++ + } + if e.lifetimeAttrKeyCounts[key] == nil { + e.lifetimeAttrKeyCounts[key] = make(map[string]int64) + } + e.lifetimeAttrKeyCounts[key][value]++ + } + + // Update word counts + words := strings.Fields(strings.ToLower(entry.Message)) + for _, word := range words { + if len(word) >= 2 && len(word) <= 50 { + word = strings.Trim(word, ".,!?;:()[]{}\"'") + if len(word) >= 3 && !e.stopWords[word] { + e.lifetimeWordCounts[word]++ + } + } + } + + // Update heatmap + e.updateHeatmapData(entry) + + // Update services by severity + e.updateServicesBySeverity(entry) + + // Update drain3 patterns + if drain3Instance, exists := e.drain3BySeverity[entry.Severity]; exists && drain3Instance != nil { + drain3Instance.AddLogMessage(entry.Message) + } + if e.drain3All != nil { + e.drain3All.AddLogMessage(entry.Message) + } + + // Update stream tracking + e.updateStreamTracking(entry) +} + +// IngestSeverityCounts adds interval-level severity counts to history. +func (e *Engine) IngestSeverityCounts(counts tui.SeverityCounts) { + e.mu.Lock() + defer e.mu.Unlock() + + e.countsHistory = append(e.countsHistory, counts) + if len(e.countsHistory) > 50 { + e.countsHistory = e.countsHistory[1:] + } + + // Also store in timestamped time series for the web severity chart + point := SeverityTimePoint{ + Timestamp: time.Now().Unix(), + Counts: map[string]int{ + "FATAL": counts.Fatal, + "ERROR": counts.Error, + "WARN": counts.Warn, + "INFO": counts.Info, + "DEBUG": counts.Debug, + "TRACE": counts.Trace, + }, + Total: counts.Total, + } + e.severityTimeSeries = append(e.severityTimeSeries, point) + // Keep up to 120 seconds (2 minutes) + if len(e.severityTimeSeries) > 120 { + e.severityTimeSeries = e.severityTimeSeries[len(e.severityTimeSeries)-120:] + } +} + +// UpdateFrequencySnapshot updates the latest frequency snapshot. +func (e *Engine) UpdateFrequencySnapshot(snapshot *memory.FrequencySnapshot) { + e.mu.Lock() + defer e.mu.Unlock() + e.snapshot = snapshot +} + +// Reset clears all engine state. +func (e *Engine) Reset() { + e.mu.Lock() + defer e.mu.Unlock() + + e.allLogEntries = e.allLogEntries[:0] + e.lifetimeSeverityCounts = make(map[string]int64) + e.countsHistory = e.countsHistory[:0] + e.severityTimeSeries = e.severityTimeSeries[:0] + e.heatmapData = e.heatmapData[:0] + e.drain3BySeverity = tui.InitializeDrain3BySeverity() + e.drain3All = tui.NewDrain3Manager() + e.servicesBySeverity = make(map[string][]tui.ServiceCount) + e.lifetimeHostCounts = make(map[string]int64) + e.lifetimeServiceCounts = make(map[string]int64) + e.lifetimeAttrKeyCounts = make(map[string]map[string]int64) + e.lifetimeWordCounts = make(map[string]int64) + e.lifetimeAttrCounts = make(map[string]int64) + e.streams = make(map[string]*StreamInfo) + e.snapshot = nil + e.totalLogsEver = 0 + e.totalBytes = 0 + e.statsStartTime = time.Now() +} + +// --- Read-path: State Accessors (for TUI backward compat) --- + +// GetAllLogEntries returns a copy of all log entries. +func (e *Engine) GetAllLogEntries() []tui.LogEntry { + e.mu.RLock() + defer e.mu.RUnlock() + result := make([]tui.LogEntry, len(e.allLogEntries)) + copy(result, e.allLogEntries) + return result +} + +// GetLogEntryCount returns the number of log entries in the buffer. +func (e *Engine) GetLogEntryCount() int { + e.mu.RLock() + defer e.mu.RUnlock() + return len(e.allLogEntries) +} + +// GetHeatmapData returns a copy of heatmap data. +func (e *Engine) GetHeatmapData() []tui.HeatmapMinute { + e.mu.RLock() + defer e.mu.RUnlock() + result := make([]tui.HeatmapMinute, len(e.heatmapData)) + copy(result, e.heatmapData) + return result +} + +// GetSeverityTimeSeries returns timestamped per-second severity counts. +// When no filters are provided, returns the pre-aggregated global series. +// When filters are present (e.g. search), builds the series from filtered log entries. +func (e *Engine) GetSeverityTimeSeries() []SeverityTimePoint { + e.mu.RLock() + defer e.mu.RUnlock() + result := make([]SeverityTimePoint, len(e.severityTimeSeries)) + copy(result, e.severityTimeSeries) + return result +} + +// QuerySeverityTimeSeries returns per-second severity counts filtered by the given filters. +// This recomputes the time series from raw log entries to support search/stream filtering. +func (e *Engine) QuerySeverityTimeSeries(_ context.Context, filters InsightsFilters) []SeverityTimePoint { + e.mu.RLock() + defer e.mu.RUnlock() + + // If no meaningful filters, return the pre-aggregated series + if filters.Search == nil && filters.Severity == nil && filters.Start == nil && filters.End == nil { + result := make([]SeverityTimePoint, len(e.severityTimeSeries)) + copy(result, e.severityTimeSeries) + return result + } + + filtered := e.filterEntries(filters) + if len(filtered) == 0 { + return nil + } + + // Bucket entries by second + buckets := make(map[int64]map[string]int) + for _, entry := range filtered { + ts := entry.Timestamp.Unix() + if e.useLogTime && !entry.OrigTimestamp.IsZero() { + ts = entry.OrigTimestamp.Unix() + } + if buckets[ts] == nil { + buckets[ts] = make(map[string]int) + } + sev := strings.ToUpper(entry.Severity) + if sev == "" { + sev = "INFO" + } + buckets[ts][sev]++ + } + + // Sort timestamps + timestamps := make([]int64, 0, len(buckets)) + for ts := range buckets { + timestamps = append(timestamps, ts) + } + sort.Slice(timestamps, func(i, j int) bool { return timestamps[i] < timestamps[j] }) + + // Build result + result := make([]SeverityTimePoint, 0, len(timestamps)) + for _, ts := range timestamps { + counts := buckets[ts] + total := 0 + for _, c := range counts { + total += c + } + result = append(result, SeverityTimePoint{ + Timestamp: ts, + Counts: counts, + Total: total, + }) + } + return result +} + +// GetDrain3BySeverity returns the drain3 map (not a copy — callers should not modify). +func (e *Engine) GetDrain3BySeverity() map[string]*tui.Drain3Manager { + e.mu.RLock() + defer e.mu.RUnlock() + return e.drain3BySeverity +} + +// GetServicesBySeverity returns the services map. +func (e *Engine) GetServicesBySeverity() map[string][]tui.ServiceCount { + e.mu.RLock() + defer e.mu.RUnlock() + // Return a shallow copy + result := make(map[string][]tui.ServiceCount, len(e.servicesBySeverity)) + for k, v := range e.servicesBySeverity { + copied := make([]tui.ServiceCount, len(v)) + copy(copied, v) + result[k] = copied + } + return result +} + +// GetCountsHistory returns a copy of the counts history. +func (e *Engine) GetCountsHistory() []tui.SeverityCounts { + e.mu.RLock() + defer e.mu.RUnlock() + result := make([]tui.SeverityCounts, len(e.countsHistory)) + copy(result, e.countsHistory) + return result +} + +// GetFrequencySnapshot returns the latest frequency snapshot. +func (e *Engine) GetFrequencySnapshot() *memory.FrequencySnapshot { + e.mu.RLock() + defer e.mu.RUnlock() + return e.snapshot +} + +// GetLifetimeSeverityCounts returns a copy of lifetime severity counts. +func (e *Engine) GetLifetimeSeverityCounts() map[string]int64 { + e.mu.RLock() + defer e.mu.RUnlock() + result := make(map[string]int64, len(e.lifetimeSeverityCounts)) + for k, v := range e.lifetimeSeverityCounts { + result[k] = v + } + return result +} + +// GetLifetimeHostCounts returns a copy of lifetime host counts. +func (e *Engine) GetLifetimeHostCounts() map[string]int64 { + e.mu.RLock() + defer e.mu.RUnlock() + result := make(map[string]int64, len(e.lifetimeHostCounts)) + for k, v := range e.lifetimeHostCounts { + result[k] = v + } + return result +} + +// GetLifetimeServiceCounts returns a copy of lifetime service counts. +func (e *Engine) GetLifetimeServiceCounts() map[string]int64 { + e.mu.RLock() + defer e.mu.RUnlock() + result := make(map[string]int64, len(e.lifetimeServiceCounts)) + for k, v := range e.lifetimeServiceCounts { + result[k] = v + } + return result +} + +// GetLifetimeAttrCounts returns a copy of lifetime attribute counts. +func (e *Engine) GetLifetimeAttrCounts() map[string]int64 { + e.mu.RLock() + defer e.mu.RUnlock() + result := make(map[string]int64, len(e.lifetimeAttrCounts)) + for k, v := range e.lifetimeAttrCounts { + result[k] = v + } + return result +} + +// GetLifetimeWordCounts returns a copy of lifetime word counts. +func (e *Engine) GetLifetimeWordCounts() map[string]int64 { + e.mu.RLock() + defer e.mu.RUnlock() + result := make(map[string]int64, len(e.lifetimeWordCounts)) + for k, v := range e.lifetimeWordCounts { + result[k] = v + } + return result +} + +// GetLifetimeAttrKeyCounts returns a copy of per-key attribute value counts. +func (e *Engine) GetLifetimeAttrKeyCounts() map[string]map[string]int64 { + e.mu.RLock() + defer e.mu.RUnlock() + result := make(map[string]map[string]int64, len(e.lifetimeAttrKeyCounts)) + for k, v := range e.lifetimeAttrKeyCounts { + inner := make(map[string]int64, len(v)) + for ik, iv := range v { + inner[ik] = iv + } + result[k] = inner + } + return result +} + +// GetStats returns engine statistics. +func (e *Engine) GetStats() EngineStats { + e.mu.RLock() + defer e.mu.RUnlock() + return EngineStats{ + StartTime: e.statsStartTime, + TotalLogsEver: e.totalLogsEver, + TotalBytes: e.totalBytes, + BufferSize: e.maxLogBuffer, + BufferUsed: len(e.allLogEntries), + } +} + +// GetStreams returns all tracked streams. +func (e *Engine) GetStreams() []StreamInfo { + e.mu.RLock() + defer e.mu.RUnlock() + result := make([]StreamInfo, 0, len(e.streams)) + for _, s := range e.streams { + result = append(result, *s) + } + return result +} + +// --- Read-path: Query Methods (for Web API) --- + +// QueryLogSamples returns log entries matching the given filters. +func (e *Engine) QueryLogSamples(_ context.Context, filters InsightsFilters) ([]LogSample, error) { + e.mu.RLock() + defer e.mu.RUnlock() + + filtered := e.filterEntries(filters) + + // Apply limit + limit := len(filtered) + if filters.Limit != nil && *filters.Limit > 0 && *filters.Limit < limit { + limit = *filters.Limit + } + + // Take the most recent entries (from the end) + start := len(filtered) - limit + if start < 0 { + start = 0 + } + + samples := make([]LogSample, 0, limit) + for _, entry := range filtered[start:] { + ts := entry.Timestamp.Unix() + if e.useLogTime && !entry.OrigTimestamp.IsZero() { + ts = entry.OrigTimestamp.Unix() + } + samples = append(samples, LogSample{ + Timestamp: ts, + Severity: entry.Severity, + Message: entry.Message, + Attributes: entry.Attributes, + RawLine: entry.RawLine, + }) + } + + return samples, nil +} + +// QuerySeverityData returns severity distribution grouped by the specified dimension. +func (e *Engine) QuerySeverityData(_ context.Context, filters InsightsFilters) ([]SeverityGroup, error) { + e.mu.RLock() + defer e.mu.RUnlock() + + filtered := e.filterEntries(filters) + groupBy := filters.GroupBy + if groupBy == "" { + groupBy = "service" + } + + groups := make(map[string]map[string]int) + for _, entry := range filtered { + groupVal := e.getGroupValue(entry, groupBy) + if groups[groupVal] == nil { + groups[groupVal] = make(map[string]int) + } + groups[groupVal][entry.Severity]++ + } + + result := make([]SeverityGroup, 0, len(groups)) + for groupVal, counts := range groups { + total := 0 + for _, c := range counts { + total += c + } + result = append(result, SeverityGroup{ + GroupValue: groupVal, + Counts: counts, + Total: total, + }) + } + + // Sort by total descending + sort.Slice(result, func(i, j int) bool { + return result[i].Total > result[j].Total + }) + + return result, nil +} + +// QuerySentimentData returns sentiment distribution derived from severity. +func (e *Engine) QuerySentimentData(_ context.Context, filters InsightsFilters) (*SentimentData, error) { + e.mu.RLock() + defer e.mu.RUnlock() + + filtered := e.filterEntries(filters) + groupBy := filters.GroupBy + if groupBy == "" { + groupBy = "service" + } + + // Group entries by time bucket (1-second) and group_by dimension + type bucketKey struct { + second int64 + groupVal string + } + buckets := make(map[bucketKey][]float64) + groupValueSet := make(map[string]bool) + + for _, entry := range filtered { + groupVal := e.getGroupValue(entry, groupBy) + groupValueSet[groupVal] = true + + ts := entry.Timestamp + if e.useLogTime && !entry.OrigTimestamp.IsZero() { + ts = entry.OrigTimestamp + } + sec := ts.Unix() + + key := bucketKey{second: sec, groupVal: groupVal} + sentiment := severityToSentiment(entry.Severity) + buckets[key] = append(buckets[key], sentiment) + } + + // Build response + groupValues := make([]string, 0, len(groupValueSet)) + for gv := range groupValueSet { + groupValues = append(groupValues, gv) + } + sort.Strings(groupValues) + + sentimentBuckets := make([]SentimentBucket, 0, len(buckets)) + for key, sentiments := range buckets { + avg := 0.0 + for _, s := range sentiments { + avg += s + } + avg /= float64(len(sentiments)) + + sentimentBuckets = append(sentimentBuckets, SentimentBucket{ + Timestamp: key.second, + GroupValue: key.groupVal, + Sentiment: avg, + LogCount: len(sentiments), + }) + } + + return &SentimentData{ + GroupValues: groupValues, + Buckets: sentimentBuckets, + }, nil +} + +// QueryPatterns returns drain3 patterns grouped by the specified dimension. +func (e *Engine) QueryPatterns(_ context.Context, filters InsightsFilters) ([]PatternGroup, error) { + e.mu.RLock() + defer e.mu.RUnlock() + + groupBy := filters.GroupBy + if groupBy == "" { + groupBy = "service" + } + + limit := 20 + if filters.Limit != nil && *filters.Limit > 0 { + limit = *filters.Limit + } + + // For severity grouping, use the pre-built drain3 instances + if groupBy == "severity" { + result := make([]PatternGroup, 0) + for severity, dm := range e.drain3BySeverity { + if dm == nil { + continue + } + patterns := dm.GetTopPatterns(limit) + if len(patterns) == 0 { + continue + } + items := make([]PatternItem, len(patterns)) + for i, p := range patterns { + items[i] = PatternItem{ + Pattern: p.Template, + Count: p.Count, + Percentage: p.Percentage, + } + } + result = append(result, PatternGroup{ + GroupValue: severity, + Patterns: items, + }) + } + return result, nil + } + + // For other groupings, use the combined drain3 and return as single group + patterns := e.drain3All.GetTopPatterns(limit) + items := make([]PatternItem, len(patterns)) + for i, p := range patterns { + items[i] = PatternItem{ + Pattern: p.Template, + Count: p.Count, + Percentage: p.Percentage, + } + } + + return []PatternGroup{{ + GroupValue: "all", + Patterns: items, + }}, nil +} + +// QueryClasses returns log classification distribution. +func (e *Engine) QueryClasses(_ context.Context, filters InsightsFilters) ([]ClassItem, error) { + e.mu.RLock() + defer e.mu.RUnlock() + + filtered := e.filterEntries(filters) + + // Classify logs by severity as a simple categorization + classCounts := make(map[string]int) + for _, entry := range filtered { + classCounts[entry.Severity]++ + } + + total := len(filtered) + result := make([]ClassItem, 0, len(classCounts)) + for name, count := range classCounts { + pct := 0.0 + if total > 0 { + pct = float64(count) * 100.0 / float64(total) + } + result = append(result, ClassItem{ + Name: name, + Count: count, + Percentage: pct, + }) + } + + sort.Slice(result, func(i, j int) bool { + return result[i].Count > result[j].Count + }) + + if filters.Limit != nil && *filters.Limit > 0 && len(result) > *filters.Limit { + result = result[:*filters.Limit] + } + + return result, nil +} + +// QuerySummary returns an AI-generated summary of the logs. +func (e *Engine) QuerySummary(_ context.Context, filters InsightsFilters) (string, error) { + e.mu.RLock() + entries := e.filterEntries(filters) + severityCounts := make(map[string]int) + for _, entry := range entries { + severityCounts[entry.Severity]++ + } + totalLogs := len(entries) + aiClient := e.aiClient + e.mu.RUnlock() + + if aiClient == nil { + // Return a statistical summary when AI is not configured + return e.generateStatisticalSummary(totalLogs, severityCounts, entries), nil + } + + // Build a prompt for the AI + var sb strings.Builder + sb.WriteString("Analyze these log entries and provide a brief summary of key issues, patterns, and recommendations:\n\n") + sb.WriteString(fmt.Sprintf("Total logs: %d\n", totalLogs)) + sb.WriteString("Severity distribution:\n") + for sev, count := range severityCounts { + sb.WriteString(fmt.Sprintf(" %s: %d\n", sev, count)) + } + sb.WriteString("\nRecent log samples:\n") + + // Include up to 50 recent entries + sampleCount := min(50, len(entries)) + start := len(entries) - sampleCount + for _, entry := range entries[start:] { + sb.WriteString(fmt.Sprintf("[%s] %s\n", entry.Severity, entry.Message)) + } + + result, err := aiClient.AnalyzeLog(sb.String(), "SUMMARY", time.Now().Format(time.RFC3339), nil) + if err != nil { + return "", fmt.Errorf("AI analysis failed: %w", err) + } + + return result, nil +} + +// GetSentimentHeatmap returns an ASCII heatmap of sentiment over time. +func (e *Engine) GetSentimentHeatmap(_ context.Context, filters InsightsFilters) (string, error) { + e.mu.RLock() + defer e.mu.RUnlock() + + filtered := e.filterEntries(filters) + groupBy := filters.GroupBy + if groupBy == "" { + groupBy = "service" + } + + // Build minute-by-group sentiment map + type cellKey struct { + minute time.Time + groupVal string + } + cells := make(map[cellKey][]float64) + groupValueSet := make(map[string]bool) + var minTime, maxTime time.Time + + for _, entry := range filtered { + groupVal := e.getGroupValue(entry, groupBy) + groupValueSet[groupVal] = true + + ts := entry.Timestamp + if e.useLogTime && !entry.OrigTimestamp.IsZero() { + ts = entry.OrigTimestamp + } + minute := ts.Truncate(time.Minute) + + if minTime.IsZero() || minute.Before(minTime) { + minTime = minute + } + if maxTime.IsZero() || minute.After(maxTime) { + maxTime = minute + } + + key := cellKey{minute: minute, groupVal: groupVal} + cells[key] = append(cells[key], severityToSentiment(entry.Severity)) + } + + if len(cells) == 0 { + return "No data available for heatmap.", nil + } + + // Build ASCII heatmap + groupValues := make([]string, 0, len(groupValueSet)) + for gv := range groupValueSet { + groupValues = append(groupValues, gv) + } + sort.Strings(groupValues) + + // Generate time columns (every 5 minutes) + var sb strings.Builder + sb.WriteString("Sentiment Heatmap (+ positive, . neutral, - negative)\n\n") + + // Determine label width + maxLabelLen := 0 + for _, gv := range groupValues { + if len(gv) > maxLabelLen { + maxLabelLen = len(gv) + } + } + if maxLabelLen > 20 { + maxLabelLen = 20 + } + + for _, gv := range groupValues { + label := gv + if len(label) > maxLabelLen { + label = label[:maxLabelLen] + } + sb.WriteString(fmt.Sprintf("%-*s │", maxLabelLen, label)) + + // Walk through time buckets + t := minTime + for !t.After(maxTime) { + key := cellKey{minute: t, groupVal: gv} + if sentiments, ok := cells[key]; ok { + avg := 0.0 + for _, s := range sentiments { + avg += s + } + avg /= float64(len(sentiments)) + sb.WriteByte(sentimentToChar(avg)) + } else { + sb.WriteByte(' ') + } + t = t.Add(time.Minute) + } + sb.WriteByte('\n') + } + + return sb.String(), nil +} + +// GetAnomalies detects anomalies in the log data. +func (e *Engine) GetAnomalies(_ context.Context, filters InsightsFilters) (string, error) { + e.mu.RLock() + defer e.mu.RUnlock() + + filtered := e.filterEntries(filters) + if len(filtered) == 0 { + return "No log data available for anomaly detection.", nil + } + + var sb strings.Builder + sb.WriteString("# Anomaly Detection Report\n\n") + + // Check for fatal/critical logs + fatalCount := 0 + errorCount := 0 + for _, entry := range filtered { + switch entry.Severity { + case "FATAL", "CRITICAL": + fatalCount++ + case "ERROR": + errorCount++ + } + } + + if fatalCount > 0 { + sb.WriteString(fmt.Sprintf("## Fatal/Critical Logs Detected\n- **%d** fatal/critical log entries found\n\n", fatalCount)) + } + + // Check error rate + totalCount := len(filtered) + if totalCount > 0 { + errorRate := float64(errorCount) * 100.0 / float64(totalCount) + if errorRate > 10 { + sb.WriteString(fmt.Sprintf("## High Error Rate\n- Error rate: **%.1f%%** (%d/%d logs)\n\n", errorRate, errorCount, totalCount)) + } + } + + // Check for error spikes (compare last 5 min vs overall rate) + now := time.Now() + recentCutoff := now.Add(-5 * time.Minute) + recentErrors := 0 + recentTotal := 0 + for _, entry := range filtered { + ts := entry.Timestamp + if e.useLogTime && !entry.OrigTimestamp.IsZero() { + ts = entry.OrigTimestamp + } + if ts.After(recentCutoff) { + recentTotal++ + if entry.Severity == "ERROR" || entry.Severity == "FATAL" || entry.Severity == "CRITICAL" { + recentErrors++ + } + } + } + + if recentTotal > 0 { + recentErrorRate := float64(recentErrors) * 100.0 / float64(recentTotal) + overallErrorRate := float64(errorCount+fatalCount) * 100.0 / float64(totalCount) + if recentErrorRate > overallErrorRate*2 && recentErrors > 5 { + sb.WriteString(fmt.Sprintf("## Error Spike Detected\n- Last 5 minutes: **%.1f%%** error rate (%d errors in %d logs)\n- Overall: **%.1f%%** error rate\n\n", + recentErrorRate, recentErrors, recentTotal, overallErrorRate)) + } + } + + // Check sentiment variance by service + type serviceStats struct { + sentiments []float64 + } + serviceMap := make(map[string]*serviceStats) + for _, entry := range filtered { + svc := getServiceName(entry) + if svc == "" || svc == "unknown" { + continue + } + if serviceMap[svc] == nil { + serviceMap[svc] = &serviceStats{} + } + serviceMap[svc].sentiments = append(serviceMap[svc].sentiments, severityToSentiment(entry.Severity)) + } + + for svc, stats := range serviceMap { + if len(stats.sentiments) < 10 { + continue + } + mean := 0.0 + for _, s := range stats.sentiments { + mean += s + } + mean /= float64(len(stats.sentiments)) + + variance := 0.0 + for _, s := range stats.sentiments { + diff := s - mean + variance += diff * diff + } + variance /= float64(len(stats.sentiments)) + + if mean < -0.3 { + sb.WriteString(fmt.Sprintf("## Negative Sentiment: %s\n- Mean sentiment: **%.2f** (%.0f logs)\n- Variance: %.2f\n\n", + svc, mean, float64(len(stats.sentiments)), variance)) + } + } + + if sb.Len() == len("# Anomaly Detection Report\n\n") { + sb.WriteString("No anomalies detected in the current data.\n") + } + + return sb.String(), nil +} + +// QueryInsightsParams returns available filter dimension values. +func (e *Engine) QueryInsightsParams(_ context.Context, _ InsightsFilters) (*InsightsParams, error) { + e.mu.RLock() + defer e.mu.RUnlock() + + params := &InsightsParams{ + TotalLogs: int64(e.totalLogsEver), + } + + // Collect unique values from lifetime counts + for host := range e.lifetimeHostCounts { + params.Hosts = append(params.Hosts, host) + } + for svc := range e.lifetimeServiceCounts { + params.Services = append(params.Services, svc) + } + for sev := range e.lifetimeSeverityCounts { + params.Severities = append(params.Severities, sev) + } + + // Collect namespace, pod, deployment, environment, cluster from attributes + dimKeys := map[string]*[]string{ + "k8s.namespace": ¶ms.Namespaces, + "k8s.pod": ¶ms.Pods, + "k8s.deployment": ¶ms.Deployments, + "environment": ¶ms.Environments, + "env": ¶ms.Environments, + "cluster": ¶ms.Clusters, + } + for key, target := range dimKeys { + if vals, ok := e.lifetimeAttrKeyCounts[key]; ok { + for val := range vals { + *target = append(*target, val) + } + } + } + + // Time range + if len(e.allLogEntries) > 0 { + first := e.allLogEntries[0].Timestamp.Unix() + last := e.allLogEntries[len(e.allLogEntries)-1].Timestamp.Unix() + params.OldestLog = &first + params.NewestLog = &last + } + + // Sort all slices + sort.Strings(params.Hosts) + sort.Strings(params.Services) + sort.Strings(params.Severities) + sort.Strings(params.Namespaces) + sort.Strings(params.Pods) + sort.Strings(params.Deployments) + sort.Strings(params.Environments) + sort.Strings(params.Clusters) + + return params, nil +} + +// --- Internal helpers --- + +// filterEntries applies InsightsFilters to the in-memory log buffer. +// Must be called with at least a read lock held. +func (e *Engine) filterEntries(filters InsightsFilters) []tui.LogEntry { + result := make([]tui.LogEntry, 0, len(e.allLogEntries)) + + for _, entry := range e.allLogEntries { + ts := entry.Timestamp + if e.useLogTime && !entry.OrigTimestamp.IsZero() { + ts = entry.OrigTimestamp + } + + // Time range filter + if filters.Start != nil && ts.Unix() < *filters.Start { + continue + } + if filters.End != nil && ts.Unix() > *filters.End { + continue + } + + // Severity filter + if filters.Severity != nil && *filters.Severity != "" { + if !strings.EqualFold(entry.Severity, *filters.Severity) { + continue + } + } + + // Search filter + if filters.Search != nil && *filters.Search != "" { + search := strings.ToLower(*filters.Search) + if !strings.Contains(strings.ToLower(entry.Message), search) && + !strings.Contains(strings.ToLower(entry.RawLine), search) { + found := false + for _, v := range entry.Attributes { + if strings.Contains(strings.ToLower(v), search) { + found = true + break + } + } + if !found { + continue + } + } + } + + // Dimension filters + if !e.matchesDimensionFilter(entry, filters) { + continue + } + + result = append(result, entry) + } + + return result +} + +// matchesDimensionFilter checks if an entry matches the dimension filters. +func (e *Engine) matchesDimensionFilter(entry tui.LogEntry, filters InsightsFilters) bool { + if filters.Namespaces != nil && len(*filters.Namespaces) > 0 { + ns := entry.Attributes["k8s.namespace"] + if !containsString(*filters.Namespaces, ns) { + return false + } + } + if filters.Pods != nil && len(*filters.Pods) > 0 { + pod := entry.Attributes["k8s.pod"] + if !containsString(*filters.Pods, pod) { + return false + } + } + if filters.Hosts != nil && len(*filters.Hosts) > 0 { + host := entry.Attributes["host"] + if !containsString(*filters.Hosts, host) { + return false + } + } + if filters.Services != nil && len(*filters.Services) > 0 { + svc := getServiceName(entry) + if !containsString(*filters.Services, svc) { + return false + } + } + if filters.Environments != nil && len(*filters.Environments) > 0 { + env := entry.Attributes["environment"] + if env == "" { + env = entry.Attributes["env"] + } + if !containsString(*filters.Environments, env) { + return false + } + } + if filters.Clusters != nil && len(*filters.Clusters) > 0 { + cluster := entry.Attributes["cluster"] + if !containsString(*filters.Clusters, cluster) { + return false + } + } + if filters.Deployments != nil && len(*filters.Deployments) > 0 { + deploy := entry.Attributes["k8s.deployment"] + if !containsString(*filters.Deployments, deploy) { + return false + } + } + return true +} + +// getGroupValue extracts the group_by dimension value from a log entry. +func (e *Engine) getGroupValue(entry tui.LogEntry, groupBy string) string { + switch groupBy { + case "service": + return getServiceName(entry) + case "host": + if h := entry.Attributes["host"]; h != "" { + return h + } + return "unknown" + case "severity": + return entry.Severity + case "namespace": + if ns := entry.Attributes["k8s.namespace"]; ns != "" { + return ns + } + return "default" + case "pod": + if pod := entry.Attributes["k8s.pod"]; pod != "" { + return pod + } + return "unknown" + case "deployment": + if dep := entry.Attributes["k8s.deployment"]; dep != "" { + return dep + } + return "unknown" + case "env", "environment": + if env := entry.Attributes["environment"]; env != "" { + return env + } + if env := entry.Attributes["env"]; env != "" { + return env + } + return "default" + case "cluster": + if cl := entry.Attributes["cluster"]; cl != "" { + return cl + } + return "default" + case "category": + return entry.Severity // Use severity as category for local + default: + return getServiceName(entry) + } +} + +// updateHeatmapData updates minute-by-minute heatmap data. +func (e *Engine) updateHeatmapData(entry tui.LogEntry) { + ts := entry.Timestamp + if e.useLogTime && !entry.OrigTimestamp.IsZero() { + ts = entry.OrigTimestamp + } + entryTime := ts.Truncate(time.Minute) + + // Find or create the minute entry + var targetMinute *tui.HeatmapMinute + for i := range e.heatmapData { + if e.heatmapData[i].Timestamp.Equal(entryTime) { + targetMinute = &e.heatmapData[i] + break + } + } + + if targetMinute == nil { + e.heatmapData = append(e.heatmapData, tui.HeatmapMinute{ + Timestamp: entryTime, + Counts: tui.SeverityCounts{}, + }) + targetMinute = &e.heatmapData[len(e.heatmapData)-1] + } + + // Update severity count + switch entry.Severity { + case "TRACE": + targetMinute.Counts.Trace++ + case "DEBUG": + targetMinute.Counts.Debug++ + case "INFO": + targetMinute.Counts.Info++ + case "WARN", "WARNING": + targetMinute.Counts.Warn++ + case "ERROR": + targetMinute.Counts.Error++ + case "FATAL": + targetMinute.Counts.Fatal++ + case "CRITICAL": + targetMinute.Counts.Critical++ + default: + targetMinute.Counts.Unknown++ + } + targetMinute.Counts.Total++ + + // Prune old data (keep 6 hours) + cutoffTime := time.Now().Add(-6 * time.Hour) + filtered := e.heatmapData[:0] + for _, minute := range e.heatmapData { + if minute.Timestamp.After(cutoffTime) { + filtered = append(filtered, minute) + } + } + e.heatmapData = filtered +} + +// updateServicesBySeverity updates the services-by-severity tracking. +func (e *Engine) updateServicesBySeverity(entry tui.LogEntry) { + severity := entry.Severity + if severity == "" { + severity = "UNKNOWN" + } + + serviceName := getServiceName(entry) + if serviceName == "" || serviceName == "unknown" { + return + } + + if e.servicesBySeverity[severity] == nil { + e.servicesBySeverity[severity] = make([]tui.ServiceCount, 0) + } + + found := false + for i := range e.servicesBySeverity[severity] { + if e.servicesBySeverity[severity][i].Service == serviceName { + e.servicesBySeverity[severity][i].Count++ + found = true + break + } + } + + if !found { + e.servicesBySeverity[severity] = append(e.servicesBySeverity[severity], tui.ServiceCount{ + Service: serviceName, + Count: 1, + }) + } + + // Keep top 10 sorted + services := e.servicesBySeverity[severity] + sort.Slice(services, func(i, j int) bool { + return services[i].Count > services[j].Count + }) + if len(services) > 10 { + e.servicesBySeverity[severity] = services[:10] + } +} + +// updateStreamTracking updates stream info for a log entry. +func (e *Engine) updateStreamTracking(entry tui.LogEntry) { + // Determine source and stream from attributes + source := "stdin" + stream := "stdin" + + if ns := entry.Attributes["k8s.namespace"]; ns != "" { + source = "k8s" + pod := entry.Attributes["k8s.pod"] + if pod != "" { + stream = ns + "/" + pod + } else { + stream = ns + } + } else if svc := entry.Attributes["service.name"]; svc != "" { + source = "otlp" + stream = svc + } else if filename := entry.Attributes["source_file"]; filename != "" { + source = "file" + stream = filename + } + + key := source + ":" + stream + if s, ok := e.streams[key]; ok { + s.LogCount++ + s.LastSeen = time.Now() + } else { + e.streams[key] = &StreamInfo{ + Source: source, + Stream: stream, + LogCount: 1, + LastSeen: time.Now(), + Active: true, + } + } +} + +// generateStatisticalSummary creates a text summary when AI is not configured. +func (e *Engine) generateStatisticalSummary(totalLogs int, severityCounts map[string]int, entries []tui.LogEntry) string { + var sb strings.Builder + sb.WriteString("## Log Analysis Summary (Statistical)\n\n") + sb.WriteString(fmt.Sprintf("**Total logs analyzed:** %d\n\n", totalLogs)) + + sb.WriteString("### Severity Distribution\n") + for sev, count := range severityCounts { + pct := float64(count) * 100.0 / float64(max(totalLogs, 1)) + sb.WriteString(fmt.Sprintf("- %s: %d (%.1f%%)\n", sev, count, pct)) + } + + sb.WriteString("\n### Top Patterns\n") + patterns := e.drain3All.GetTopPatterns(5) + for i, p := range patterns { + sb.WriteString(fmt.Sprintf("%d. `%s` (%d occurrences, %.1f%%)\n", i+1, p.Template, p.Count, p.Percentage)) + } + + if len(entries) > 0 { + uptime := time.Since(e.statsStartTime).Round(time.Second) + sb.WriteString(fmt.Sprintf("\n**Uptime:** %s | **Buffer:** %d/%d\n", uptime, len(e.allLogEntries), e.maxLogBuffer)) + } + + sb.WriteString("\n*Note: AI analysis is not configured. Set up an AI provider for deeper insights.*\n") + return sb.String() +} + +// --- Utility functions --- + +func getServiceName(entry tui.LogEntry) string { + if svc := entry.Attributes["service"]; svc != "" { + return svc + } + if svc := entry.Attributes["service.name"]; svc != "" { + return svc + } + if svc := entry.Attributes["serviceName"]; svc != "" { + return svc + } + if svc := entry.Attributes["app"]; svc != "" { + return svc + } + if svc := entry.Attributes["application"]; svc != "" { + return svc + } + if host := entry.Attributes["host"]; host != "" { + return "host:" + host + } + return "unknown" +} + +func severityToSentiment(severity string) float64 { + switch strings.ToUpper(severity) { + case "FATAL", "CRITICAL": + return -1.0 + case "ERROR": + return -0.6 + case "WARN", "WARNING": + return 0.0 + case "INFO": + return 0.5 + case "DEBUG": + return 0.7 + case "TRACE": + return 0.9 + default: + return 0.0 + } +} + +func sentimentToChar(sentiment float64) byte { + if sentiment > 0.3 { + return '+' + } else if sentiment < -0.3 { + return '-' + } + return '.' +} + +func containsString(slice []string, s string) bool { + for _, item := range slice { + if strings.EqualFold(item, s) { + return true + } + } + return false +} + diff --git a/internal/engine/streams.go b/internal/engine/streams.go new file mode 100644 index 0000000..74d5384 --- /dev/null +++ b/internal/engine/streams.go @@ -0,0 +1,31 @@ +package engine + +import "time" + +// RegisterStream explicitly registers a stream (called at startup for known inputs). +func (e *Engine) RegisterStream(source, stream string) { + e.mu.Lock() + defer e.mu.Unlock() + + key := source + ":" + stream + if _, ok := e.streams[key]; !ok { + e.streams[key] = &StreamInfo{ + Source: source, + Stream: stream, + LogCount: 0, + LastSeen: time.Now(), + Active: true, + } + } +} + +// MarkStreamInactive marks a stream as inactive (e.g., file reader finished). +func (e *Engine) MarkStreamInactive(source, stream string) { + e.mu.Lock() + defer e.mu.Unlock() + + key := source + ":" + stream + if s, ok := e.streams[key]; ok { + s.Active = false + } +} diff --git a/internal/engine/types.go b/internal/engine/types.go new file mode 100644 index 0000000..8720c51 --- /dev/null +++ b/internal/engine/types.go @@ -0,0 +1,150 @@ +package engine + +import "time" + +// InsightsFilters defines filters for querying log analysis data. +// For local Gonzo, time range filters apply to the in-memory log buffer. +// Dimension filters (environments, namespaces, etc.) map to log entry attributes. +type InsightsFilters struct { + Start *int64 `json:"start,omitempty"` + End *int64 `json:"end,omitempty"` + GroupBy string `json:"group_by,omitempty"` + Environments *[]string `json:"environments,omitempty"` + Clusters *[]string `json:"clusters,omitempty"` + Namespaces *[]string `json:"namespaces,omitempty"` + Deployments *[]string `json:"deployments,omitempty"` + Pods *[]string `json:"pods,omitempty"` + Hosts *[]string `json:"hosts,omitempty"` + Services *[]string `json:"services,omitempty"` + Search *string `json:"search,omitempty"` + Severity *string `json:"severity,omitempty"` + Limit *int `json:"limit,omitempty"` +} + +// LogSample is a single log entry for API responses. +type LogSample struct { + Timestamp int64 `json:"timestamp"` + Severity string `json:"severity"` + Message string `json:"message"` + Attributes map[string]string `json:"attributes,omitempty"` + RawLine string `json:"raw_line,omitempty"` +} + +// SeverityGroup represents severity distribution for one group_by value. +type SeverityGroup struct { + GroupValue string `json:"group_value"` + Counts map[string]int `json:"counts"` + Total int `json:"total"` +} + +// SentimentBucket represents one time bucket of sentiment data for a group. +type SentimentBucket struct { + Timestamp int64 `json:"timestamp"` + GroupValue string `json:"group_value"` + Sentiment float64 `json:"sentiment"` + IsAnomaly bool `json:"is_anomaly,omitempty"` + LogCount int `json:"log_count"` +} + +// SentimentData is the response format for sentiment queries. +type SentimentData struct { + GroupValues []string `json:"group_values"` + Buckets []SentimentBucket `json:"buckets"` +} + +// PatternItem represents a single log pattern. +type PatternItem struct { + Pattern string `json:"pattern"` + Count int `json:"count"` + Percentage float64 `json:"percentage"` + Sample string `json:"sample,omitempty"` +} + +// PatternGroup represents patterns for one group_by value. +type PatternGroup struct { + GroupValue string `json:"group_value"` + Patterns []PatternItem `json:"patterns"` +} + +// ClassItem represents a log class/category. +type ClassItem struct { + Name string `json:"name"` + Count int `json:"count"` + Percentage float64 `json:"percentage"` +} + +// HeatmapMinuteData represents one minute of heatmap data. +type HeatmapMinuteData struct { + Timestamp int64 `json:"timestamp"` + Counts map[string]int `json:"counts"` +} + +// SeverityTimePoint represents severity counts at a point in time (1-second interval). +type SeverityTimePoint struct { + Timestamp int64 `json:"timestamp"` + Counts map[string]int `json:"counts"` + Total int `json:"total"` +} + +// AnomalyInfo represents a detected anomaly. +type AnomalyInfo struct { + Type string `json:"type"` + Severity string `json:"severity"` + Description string `json:"description"` + Timestamp int64 `json:"timestamp"` + Score float64 `json:"score"` +} + +// InsightsParams describes available filter dimension values. +type InsightsParams struct { + Environments []string `json:"environments,omitempty"` + Clusters []string `json:"clusters,omitempty"` + Namespaces []string `json:"namespaces,omitempty"` + Deployments []string `json:"deployments,omitempty"` + Pods []string `json:"pods,omitempty"` + Hosts []string `json:"hosts,omitempty"` + Services []string `json:"services,omitempty"` + Severities []string `json:"severities,omitempty"` + Categories []string `json:"categories,omitempty"` + TotalLogs int64 `json:"total_logs"` + OldestLog *int64 `json:"oldest_log,omitempty"` + NewestLog *int64 `json:"newest_log,omitempty"` +} + +// StreamInfo represents an active log input stream. +type StreamInfo struct { + Source string `json:"source"` + Stream string `json:"stream"` + LogCount int64 `json:"log_count"` + LastSeen time.Time `json:"last_seen"` + Active bool `json:"active"` +} + +// StatusInfo represents server status. +type StatusInfo struct { + Uptime string `json:"uptime"` + TotalLogs int64 `json:"total_logs"` + TotalBytes int64 `json:"total_bytes"` + LogRate float64 `json:"log_rate"` + Streams []StreamInfo `json:"streams"` + BufferSize int `json:"buffer_size"` + BufferUsed int `json:"buffer_used"` + AIConfigured bool `json:"ai_configured"` +} + +// AttributeEntry represents a single top attribute key+value with count. +type AttributeEntry struct { + Key string `json:"key"` + Value string `json:"value"` + Count int64 `json:"count"` + Percentage float64 `json:"percentage"` +} + +// EngineStats holds engine-level statistics. +type EngineStats struct { + StartTime time.Time `json:"start_time"` + TotalLogsEver int `json:"total_logs_ever"` + TotalBytes int64 `json:"total_bytes"` + BufferSize int `json:"buffer_size"` + BufferUsed int `json:"buffer_used"` +} diff --git a/internal/releases/releases.go b/internal/releases/releases.go new file mode 100644 index 0000000..2316059 --- /dev/null +++ b/internal/releases/releases.go @@ -0,0 +1,127 @@ +package releases + +import ( + "context" + "encoding/json" + "io" + "net/http" + "sync" + "time" +) + +// Release represents a GitHub release. +type Release struct { + TagName string `json:"tag_name"` + Name string `json:"name"` + Body string `json:"body"` + PublishedAt string `json:"published_at"` + URL string `json:"url"` +} + +// githubRelease is the raw GitHub API response shape (we only extract what we need). +type githubRelease struct { + TagName string `json:"tag_name"` + Name string `json:"name"` + Body string `json:"body"` + PublishedAt string `json:"published_at"` + HTMLURL string `json:"html_url"` +} + +// Fetcher fetches and caches GitHub releases. +type Fetcher struct { + releases []Release + done chan struct{} + once sync.Once +} + +// NewFetcher creates a new releases fetcher. +func NewFetcher() *Fetcher { + return &Fetcher{ + done: make(chan struct{}), + } +} + +// FetchInBackground starts a goroutine to fetch releases from the GitHub API. +// Non-blocking. Silently ignores errors. +func (f *Fetcher) FetchInBackground() { + go func() { + defer func() { + f.once.Do(func() { close(f.done) }) + }() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", + "https://api.github.com/repos/control-theory/gonzo/releases", nil) + if err != nil { + return + } + req.Header.Set("Accept", "application/vnd.github+json") + + resp, err := (&http.Client{Timeout: 5 * time.Second}).Do(req) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return + } + + var ghReleases []githubRelease + if err := json.Unmarshal(body, &ghReleases); err != nil { + return + } + + releases := make([]Release, 0, len(ghReleases)) + for _, gh := range ghReleases { + releases = append(releases, Release{ + TagName: gh.TagName, + Name: gh.Name, + Body: gh.Body, + PublishedAt: gh.PublishedAt, + URL: gh.HTMLURL, + }) + } + + f.releases = releases + }() +} + +// GetReleases returns fetched releases (newest first), or nil if not ready/failed. +// On the first call, waits up to 2 seconds for the fetch to complete. +func (f *Fetcher) GetReleases() []Release { + select { + case <-f.done: + return f.releases + case <-time.After(2 * time.Second): + return f.releases + } +} + +// GetReleasesNonBlocking returns whatever releases are available without waiting. +func (f *Fetcher) GetReleasesNonBlocking() []Release { + select { + case <-f.done: + return f.releases + default: + return nil + } +} + +// GetRelease returns the release matching the given tag (e.g. "v0.3.1"), or nil. +func (f *Fetcher) GetRelease(tag string) *Release { + rels := f.GetReleasesNonBlocking() + for i := range rels { + if rels[i].TagName == tag { + return &rels[i] + } + } + return nil +} diff --git a/internal/state/state.go b/internal/state/state.go new file mode 100644 index 0000000..237e121 --- /dev/null +++ b/internal/state/state.go @@ -0,0 +1,69 @@ +package state + +import ( + "encoding/json" + "os" + "path/filepath" +) + +// AppState holds persistent application state (separate from user config). +type AppState struct { + LastSeenVersion string `json:"last_seen_version"` +} + +// stateDir returns ~/.config/gonzo (same base as config). +func stateDir() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, ".config", "gonzo"), nil +} + +// statePath returns the full path to the state file. +func statePath() (string, error) { + dir, err := stateDir() + if err != nil { + return "", err + } + return filepath.Join(dir, "state.json"), nil +} + +// Load reads the app state from disk. Returns zero value if file is missing or unreadable. +func Load() AppState { + p, err := statePath() + if err != nil { + return AppState{} + } + + data, err := os.ReadFile(p) + if err != nil { + return AppState{} + } + + var s AppState + if err := json.Unmarshal(data, &s); err != nil { + return AppState{} + } + return s +} + +// Save writes the app state to disk, creating the directory if needed. +func Save(s AppState) error { + p, err := statePath() + if err != nil { + return err + } + + dir := filepath.Dir(p) + if err := os.MkdirAll(dir, 0o755); err != nil { + return err + } + + data, err := json.MarshalIndent(s, "", " ") + if err != nil { + return err + } + + return os.WriteFile(p, data, 0o644) +} diff --git a/internal/tui/components.go b/internal/tui/components.go index f082bdd..069cbe2 100644 --- a/internal/tui/components.go +++ b/internal/tui/components.go @@ -99,13 +99,13 @@ func (m *DashboardModel) renderStatusLine() string { } else { // Default status showing main actions if veryNarrow { - statusText = "Tab • Space • i • ? • q" + statusText = "Tab • Space • i • d • ? • q" } else if narrow { - statusText = "?: Help • Tab: Nav • Space: Pause • i: Stats • q: Quit" + statusText = "?: Help • d: Web UI • Tab: Nav • Space: Pause • i: Stats • q: Quit" } else if medium { - statusText = "Tab: Navigate • Space: Pause • i: Stats • Enter: Select • q: Quit" + statusText = "?: Help • d: Web UI • Tab: Navigate • Space: Pause • i: Stats • Enter: Select • q: Quit" } else { - statusText = "?: Help • Click sections • Wheel: scroll • Space: Pause • Tab: Navigate • i: Stats • Enter: Select • q: Quit" + statusText = "?: Help • d: Web UI • Click sections • Wheel: scroll • Space: Pause • Tab: Navigate • i: Stats • Enter: Select • q: Quit" } } diff --git a/internal/tui/modal_help.go b/internal/tui/modal_help.go index 19396ff..f7c29ae 100644 --- a/internal/tui/modal_help.go +++ b/internal/tui/modal_help.go @@ -85,6 +85,8 @@ ACTIONS: i - Show comprehensive statistics modal i - AI analysis (when viewing log details) m - Switch AI model (shows available models) + d - Open Dstl8 Lite web dashboard in browser + w - Show what's new (release notes) ? or h - Toggle this help q/Ctrl+C - Quit diff --git a/internal/tui/modal_whats_new.go b/internal/tui/modal_whats_new.go new file mode 100644 index 0000000..e2a7f64 --- /dev/null +++ b/internal/tui/modal_whats_new.go @@ -0,0 +1,194 @@ +package tui + +import ( + "fmt" + "regexp" + "strings" + "time" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + + "github.com/control-theory/gonzo/internal/state" +) + +// gitHashRe matches full 40-character git commit hashes. +var gitHashRe = regexp.MustCompile(`\b([0-9a-f]{40})\b`) + +// renderWhatsNewModal renders the "What's New" modal with GitHub release notes. +func (m *DashboardModel) renderWhatsNewModal() string { + modalWidth := m.width - 8 + modalHeight := m.height - 4 + + contentWidth := modalWidth - 4 + contentHeight := modalHeight - 4 + + m.infoViewport.Width = contentWidth + m.infoViewport.Height = contentHeight + + // Only re-render content if width changed or cache is empty + if m.whatsNewRenderedCache == "" || m.whatsNewCacheWidth != contentWidth { + m.whatsNewRenderedCache = m.buildWhatsNewContent(contentWidth) + m.whatsNewCacheWidth = contentWidth + m.infoViewport.SetContent(m.whatsNewRenderedCache) + } + + contentPane := lipgloss.NewStyle(). + Width(contentWidth). + Height(contentHeight). + Border(lipgloss.NormalBorder()). + BorderForeground(ColorGray). + Render(m.infoViewport.View()) + + header := lipgloss.NewStyle(). + Width(contentWidth). + Foreground(ColorGreen). + Bold(true). + Render("🚀 What's New in Gonzo") + + statusBar := lipgloss.NewStyle(). + Foreground(ColorGray). + Render("↑↓/Wheel: Scroll • ESC: Close") + + modal := lipgloss.JoinVertical(lipgloss.Left, header, contentPane, statusBar) + + finalModal := lipgloss.NewStyle(). + Width(modalWidth). + Height(modalHeight). + Border(lipgloss.RoundedBorder()). + BorderForeground(ColorGreen). + Render(modal) + + return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, finalModal) +} + +// buildWhatsNewContent renders all release notes via glamour. Called once and cached. +func (m *DashboardModel) buildWhatsNewContent(width int) string { + if m.releasesFetcher == nil { + return "Loading release notes..." + } + + releases := m.releasesFetcher.GetReleasesNonBlocking() + if len(releases) == 0 { + return "Could not load release notes. Check https://github.com/control-theory/gonzo/releases" + } + + renderWidth := max(40, width-4) + renderer, err := glamour.NewTermRenderer( + glamour.WithAutoStyle(), + glamour.WithWordWrap(renderWidth), + ) + + var sb strings.Builder + + for i, rel := range releases { + version := rel.TagName + name := rel.Name + if name == "" { + name = version + } + + dateStr := "" + if t, parseErr := time.Parse(time.RFC3339, rel.PublishedAt); parseErr == nil { + dateStr = t.Format("January 2, 2006") + } + + if i == 0 && m.currentVersion != "" && ("v"+m.currentVersion == version || m.currentVersion == version || baseVersion("v"+m.currentVersion) == version) { + badge := lipgloss.NewStyle().Bold(true).Foreground(ColorGreen).Render(fmt.Sprintf("★ %s (current)", name)) + if dateStr != "" { + badge += lipgloss.NewStyle().Foreground(ColorGray).Render(" " + dateStr) + } + sb.WriteString(badge) + } else { + badge := lipgloss.NewStyle().Bold(true).Foreground(ColorBlue).Render(name) + if dateStr != "" { + badge += lipgloss.NewStyle().Foreground(ColorGray).Render(" " + dateStr) + } + sb.WriteString(badge) + } + sb.WriteString("\n") + + body := gitHashRe.ReplaceAllStringFunc(strings.TrimSpace(rel.Body), func(hash string) string { + return hash[:7] + }) + if body != "" && err == nil { + rendered, renderErr := renderer.Render(body) + if renderErr == nil { + sb.WriteString(rendered) + } else { + sb.WriteString(body) + sb.WriteString("\n") + } + } else if body != "" { + sb.WriteString(body) + sb.WriteString("\n") + } else { + sb.WriteString(lipgloss.NewStyle().Foreground(ColorGray).Render(" No release notes.")) + sb.WriteString("\n") + } + + if i < len(releases)-1 { + sep := lipgloss.NewStyle().Foreground(ColorGray).Render(strings.Repeat("─", min(width-4, 60))) + sb.WriteString("\n" + sep + "\n\n") + } + } + + return sb.String() +} + +// saveWhatsNewState saves the current version as "last seen" so the modal won't auto-show again. +func (m *DashboardModel) saveWhatsNewState() tea.Cmd { + base := baseVersion(m.currentVersion) + return func() tea.Msg { + state.Save(state.AppState{LastSeenVersion: base}) + return nil + } +} + +// baseVersion strips git describe suffixes like "-13-g58cce74-dirty" from a version string. +func baseVersion(v string) string { + s := v + prefix := "" + if strings.HasPrefix(s, "v") { + prefix = "v" + s = s[1:] + } + parts := strings.SplitN(s, "-", 2) + return prefix + parts[0] +} + +// checkWhatsNewAutoShow checks if we should auto-show the what's-new modal. +func (m *DashboardModel) checkWhatsNewAutoShow() { + if m.whatsNewCheckDone || m.currentVersion == "" || m.currentVersion == "dev" { + m.whatsNewCheckDone = true + return + } + + if m.releasesFetcher == nil { + m.whatsNewCheckDone = true + return + } + + base := baseVersion(m.currentVersion) + if baseVersion(m.lastSeenVersion) == base { + m.whatsNewCheckDone = true + return + } + + rels := m.releasesFetcher.GetReleasesNonBlocking() + if rels == nil { + m.whatsNewRetries++ + if m.whatsNewRetries > 5 { + m.whatsNewCheckDone = true + } + return + } + m.whatsNewCheckDone = true + + if len(rels) > 0 { + m.showWhatsNewModal = true + m.whatsNewRenderedCache = "" // force re-render + m.infoViewport.GotoTop() + } +} diff --git a/internal/tui/model.go b/internal/tui/model.go index 67d2ba5..6983247 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -1,12 +1,15 @@ package tui import ( + "os/exec" "regexp" + "runtime" "sort" "time" "github.com/control-theory/gonzo/internal/ai" "github.com/control-theory/gonzo/internal/memory" + "github.com/control-theory/gonzo/internal/releases" versioncheck "github.com/control-theory/gonzo/internal/version" "github.com/charmbracelet/bubbles/textarea" @@ -225,6 +228,19 @@ type DashboardModel struct { // Version checking versionChecker *versioncheck.Checker // Version checker for update notifications + + // Web dashboard + webPort int // Port for the Dstl8 Lite web dashboard (for browser open shortcut) + + // What's New modal + showWhatsNewModal bool + releasesFetcher *releases.Fetcher + currentVersion string + lastSeenVersion string + whatsNewCheckDone bool // true after we've checked whether to auto-show + whatsNewRetries int // retry counter for waiting on background fetch + whatsNewRenderedCache string // pre-rendered content (glamour is expensive) + whatsNewCacheWidth int // width the cache was rendered at } // UpdateMsg contains data updates for the dashboard @@ -254,8 +270,8 @@ type AIAnalysisMsg struct { // ManualResetMsg represents a manual reset request triggered by user type ManualResetMsg struct{} -// initializeDrain3BySeverity creates separate drain3 instances for each severity level -func initializeDrain3BySeverity() map[string]*Drain3Manager { +// InitializeDrain3BySeverity creates separate drain3 instances for each severity level +func InitializeDrain3BySeverity() map[string]*Drain3Manager { severities := []string{"FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "UNKNOWN"} drain3Map := make(map[string]*Drain3Manager) @@ -317,7 +333,7 @@ func NewDashboardModel(maxLogBuffer int, updateInterval time.Duration, aiProvide allLogEntries: make([]LogEntry, 0, maxLogBuffer), countsHistory: make([]SeverityCounts, 0), heatmapData: make([]HeatmapMinute, 0), - drain3BySeverity: initializeDrain3BySeverity(), + drain3BySeverity: InitializeDrain3BySeverity(), servicesBySeverity: make(map[string][]ServiceCount), availableIntervals: availableIntervals, currentIntervalIdx: currentIdx, @@ -486,6 +502,40 @@ func (m *DashboardModel) SetVersionChecker(checker *versioncheck.Checker) { m.versionChecker = checker } +// SetWebPort sets the web dashboard port for the browser open shortcut +func (m *DashboardModel) SetWebPort(port int) { + m.webPort = port +} + +// SetReleasesFetcher sets the releases fetcher for the what's-new modal +func (m *DashboardModel) SetReleasesFetcher(f *releases.Fetcher) { + m.releasesFetcher = f +} + +// SetCurrentVersion sets the current app version for the what's-new modal +func (m *DashboardModel) SetCurrentVersion(v string) { + m.currentVersion = v +} + +// SetLastSeenVersion sets the last version the user has seen +func (m *DashboardModel) SetLastSeenVersion(v string) { + m.lastSeenVersion = v +} + +// openBrowser opens the given URL in the default browser +func (m *DashboardModel) openBrowser(url string) { + var cmd *exec.Cmd + switch runtime.GOOS { + case "darwin": + cmd = exec.Command("open", url) + case "windows": + cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) + default: + cmd = exec.Command("xdg-open", url) + } + cmd.Start() +} + // SetK8sSource sets the Kubernetes log source for the dashboard func (m *DashboardModel) SetK8sSource(source K8sSourceInterface) { m.k8sSource = source diff --git a/internal/tui/navigation.go b/internal/tui/navigation.go index b63232e..a95d7bc 100644 --- a/internal/tui/navigation.go +++ b/internal/tui/navigation.go @@ -201,6 +201,10 @@ func (m *DashboardModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, tea.Quit case "escape", "esc": + if m.showWhatsNewModal { + m.showWhatsNewModal = false + return m, m.saveWhatsNewState() + } if m.showModelSelectionModal { m.showModelSelectionModal = false return m, nil @@ -455,6 +459,26 @@ func (m *DashboardModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, nil } + case "d": + // Open Dstl8 Lite web dashboard in browser + if !m.showModal && !m.filterActive && !m.searchActive && !m.showSeverityFilterModal && !m.showK8sFilterModal && !m.showColumnConfigModal { + if m.webPort > 0 { + m.openBrowser(fmt.Sprintf("http://localhost:%d", m.webPort)) + } + return m, nil + } + + case "w": + // Open What's New modal + if !m.showModal && !m.showWhatsNewModal && !m.filterActive && !m.searchActive && !m.showSeverityFilterModal && !m.showK8sFilterModal && !m.showColumnConfigModal && !m.showHelp && !m.showPatternsModal && !m.showStatsModal && !m.showCountsModal { + if m.releasesFetcher != nil { + m.showWhatsNewModal = true + m.whatsNewRenderedCache = "" // force fresh render + m.infoViewport.GotoTop() + } + return m, nil + } + case " ": // Spacebar: Global pause/unpause toggle for entire UI if !m.showModal && !m.filterActive && !m.searchActive && !m.showSeverityFilterModal && !m.showK8sFilterModal && !m.showColumnConfigModal { @@ -577,7 +601,6 @@ func (m *DashboardModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, nil } - // Patterns modal shortcuts if m.showPatternsModal { switch msg.String() { case "up", "k": @@ -603,6 +626,35 @@ func (m *DashboardModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, cmd } + // What's New modal shortcuts (same pattern as stats/patterns modals) + if m.showWhatsNewModal { + switch msg.String() { + case "up", "k": + m.infoViewport.ScrollUp(1) + return m, nil + case "down", "j": + m.infoViewport.ScrollDown(1) + return m, nil + case "pgup": + m.infoViewport.HalfPageUp() + return m, nil + case "pgdown": + m.infoViewport.HalfPageDown() + return m, nil + case "w": + // Allow 'w' to toggle what's-new modal off + m.showWhatsNewModal = false + return m, m.saveWhatsNewState() + case "escape", "esc": + m.showWhatsNewModal = false + return m, m.saveWhatsNewState() + } + + var cmd tea.Cmd + m.infoViewport, cmd = m.infoViewport.Update(msg) + return m, cmd + } + // Statistics modal shortcuts if m.showStatsModal { switch msg.String() { diff --git a/internal/tui/update.go b/internal/tui/update.go index 3ca2be4..c49c560 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -34,6 +34,11 @@ func (m *DashboardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, func() tea.Msg { return msg } case TickMsg: + // Check if we should auto-show the what's-new modal (once) + if !m.whatsNewCheckDone { + m.checkWhatsNewAutoShow() + } + // Update processing rate statistics on every tick m.updateProcessingRateStats() diff --git a/internal/tui/view.go b/internal/tui/view.go index 5fbb527..1616d0d 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -22,6 +22,11 @@ func (m *DashboardModel) View() string { return m.renderHelpModal() } + // Show what's new modal + if m.showWhatsNewModal { + return m.renderWhatsNewModal() + } + // Show patterns modal if m.showPatternsModal { return m.renderPatternsModal() diff --git a/internal/web/api.go b/internal/web/api.go new file mode 100644 index 0000000..362653c --- /dev/null +++ b/internal/web/api.go @@ -0,0 +1,233 @@ +package web + +import ( + "net/http" + "sort" + "strconv" + "time" + + "github.com/control-theory/gonzo/internal/engine" +) + +func (s *Server) handleStatus(w http.ResponseWriter, _ *http.Request) { + stats := s.engine.GetStats() + streams := s.engine.GetStreams() + + uptime := time.Since(stats.StartTime).Round(time.Second).String() + logRate := 0.0 + elapsed := time.Since(stats.StartTime).Seconds() + if elapsed > 0 { + logRate = float64(stats.TotalLogsEver) / elapsed + } + + streamInfos := make([]engine.StreamInfo, len(streams)) + copy(streamInfos, streams) + + writeJSON(w, engine.StatusInfo{ + Uptime: uptime, + TotalLogs: int64(stats.TotalLogsEver), + TotalBytes: stats.TotalBytes, + LogRate: logRate, + Streams: streamInfos, + BufferSize: stats.BufferSize, + BufferUsed: stats.BufferUsed, + AIConfigured: false, + }) +} + +func (s *Server) handleSeverity(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + data, err := s.engine.QuerySeverityData(r.Context(), filters) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, data) +} + +func (s *Server) handleSentiment(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + data, err := s.engine.QuerySentimentData(r.Context(), filters) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, data) +} + +func (s *Server) handlePatterns(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + data, err := s.engine.QueryPatterns(r.Context(), filters) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, data) +} + +func (s *Server) handleClasses(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + data, err := s.engine.QueryClasses(r.Context(), filters) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, data) +} + +func (s *Server) handleLogs(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + data, err := s.engine.QueryLogSamples(r.Context(), filters) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, data) +} + +func (s *Server) handleHeatmap(w http.ResponseWriter, _ *http.Request) { + raw := s.engine.GetHeatmapData() + // Convert tui.HeatmapMinute (struct fields) to engine.HeatmapMinuteData (map) + data := make([]engine.HeatmapMinuteData, 0, len(raw)) + for _, m := range raw { + counts := map[string]int{ + "FATAL": m.Counts.Fatal, + "ERROR": m.Counts.Error, + "WARN": m.Counts.Warn, + "INFO": m.Counts.Info, + "DEBUG": m.Counts.Debug, + "TRACE": m.Counts.Trace, + } + data = append(data, engine.HeatmapMinuteData{ + Timestamp: m.Timestamp.Unix(), + Counts: counts, + }) + } + writeJSON(w, data) +} + +func (s *Server) handleAnomalies(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + report, err := s.engine.GetAnomalies(r.Context(), filters) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, map[string]string{"report": report}) +} + +func (s *Server) handleStreams(w http.ResponseWriter, _ *http.Request) { + streams := s.engine.GetStreams() + writeJSON(w, streams) +} + +func (s *Server) handleInsightsParams(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + params, err := s.engine.QueryInsightsParams(r.Context(), filters) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, params) +} + +func (s *Server) handleSeverityTimeSeries(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + writeJSON(w, s.engine.QuerySeverityTimeSeries(r.Context(), filters)) +} + +func (s *Server) handleTopAttributes(w http.ResponseWriter, r *http.Request) { + limitStr := r.URL.Query().Get("limit") + limit := 20 + if limitStr != "" { + if l, err := strconv.Atoi(limitStr); err == nil && l > 0 { + limit = l + } + } + + attrKeyCounts := s.engine.GetLifetimeAttrKeyCounts() + stats := s.engine.GetStats() + totalLogs := int64(stats.TotalLogsEver) + + // Flatten into entries and sort by count + var entries []engine.AttributeEntry + for key, vals := range attrKeyCounts { + for val, count := range vals { + pct := 0.0 + if totalLogs > 0 { + pct = float64(count) * 100.0 / float64(totalLogs) + } + entries = append(entries, engine.AttributeEntry{ + Key: key, + Value: val, + Count: count, + Percentage: pct, + }) + } + } + + // Sort descending by count + sort.Slice(entries, func(i, j int) bool { + return entries[i].Count > entries[j].Count + }) + + if len(entries) > limit { + entries = entries[:limit] + } + + writeJSON(w, entries) +} + +func (s *Server) handleSummary(w http.ResponseWriter, r *http.Request) { + filters := parseFilters(r) + summary, err := s.engine.QuerySummary(r.Context(), filters) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + writeJSON(w, map[string]string{"summary": summary}) +} + +// parseFilters extracts InsightsFilters from query parameters. +func parseFilters(r *http.Request) engine.InsightsFilters { + q := r.URL.Query() + var filters engine.InsightsFilters + + if v := q.Get("start"); v != "" { + if ts, err := strconv.ParseInt(v, 10, 64); err == nil { + filters.Start = &ts + } + } + if v := q.Get("end"); v != "" { + if ts, err := strconv.ParseInt(v, 10, 64); err == nil { + filters.End = &ts + } + } + if v := q.Get("group_by"); v != "" { + filters.GroupBy = v + } + if v := q.Get("search"); v != "" { + filters.Search = &v + } + if v := q.Get("severity"); v != "" { + filters.Severity = &v + } + if v := q.Get("limit"); v != "" { + if limit, err := strconv.Atoi(v); err == nil { + filters.Limit = &limit + } + } + + return filters +} + +func (s *Server) handleReleases(w http.ResponseWriter, _ *http.Request) { + var rels interface{} + if s.relFetcher != nil { + rels = s.relFetcher.GetReleases() + } + writeJSON(w, map[string]interface{}{ + "version": s.version, + "releases": rels, + }) +} diff --git a/internal/web/server.go b/internal/web/server.go new file mode 100644 index 0000000..7b1046d --- /dev/null +++ b/internal/web/server.go @@ -0,0 +1,195 @@ +package web + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "log" + "net/http" + "time" + + "github.com/control-theory/gonzo/internal/engine" + "github.com/control-theory/gonzo/internal/releases" +) + +// Server is the HTTP server for the Dstl8 Lite web dashboard. +type Server struct { + engine *engine.Engine + httpServer *http.Server + hub *Hub + staticFS fs.FS // embedded React build assets (nil if not embedded) + version string + relFetcher *releases.Fetcher +} + +// NewServer creates a new web dashboard server. +func NewServer(eng *engine.Engine, staticFS fs.FS, version string, relFetcher *releases.Fetcher) *Server { + return &Server{ + engine: eng, + hub: NewHub(), + staticFS: staticFS, + version: version, + relFetcher: relFetcher, + } +} + +// Start starts the web server on the given port. +func (s *Server) Start(ctx context.Context, port int) error { + mux := http.NewServeMux() + + // API endpoints + mux.HandleFunc("GET /api/status", s.handleStatus) + mux.HandleFunc("GET /api/severity", s.handleSeverity) + mux.HandleFunc("GET /api/sentiment", s.handleSentiment) + mux.HandleFunc("GET /api/patterns", s.handlePatterns) + mux.HandleFunc("GET /api/classes", s.handleClasses) + mux.HandleFunc("GET /api/logs", s.handleLogs) + mux.HandleFunc("GET /api/heatmap", s.handleHeatmap) + mux.HandleFunc("GET /api/anomalies", s.handleAnomalies) + mux.HandleFunc("GET /api/streams", s.handleStreams) + mux.HandleFunc("POST /api/summary", s.handleSummary) + mux.HandleFunc("GET /api/insights-params", s.handleInsightsParams) + mux.HandleFunc("GET /api/severity-history", s.handleSeverityTimeSeries) + mux.HandleFunc("GET /api/top-attributes", s.handleTopAttributes) + mux.HandleFunc("GET /api/releases", s.handleReleases) + + // WebSocket endpoint + mux.HandleFunc("/ws", s.handleWebSocket) + + // Static assets (React app) + if s.staticFS != nil { + mux.Handle("/", spaHandler(s.staticFS)) + } else { + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + fmt.Fprintf(w, ` +

Dstl8 Lite

+

Web dashboard assets not embedded. Run make build to include them.

+

API available at /api/*

+`) + }) + } + + // Start WebSocket hub + go s.hub.Run(ctx) + + // Start broadcasting engine updates to WebSocket clients + go s.broadcastUpdates(ctx) + + addr := fmt.Sprintf("127.0.0.1:%d", port) + s.httpServer = &http.Server{ + Addr: addr, + Handler: corsMiddleware(mux), + ReadTimeout: 30 * time.Second, + WriteTimeout: 60 * time.Second, + IdleTimeout: 120 * time.Second, + } + + errChan := make(chan error, 1) + go func() { + if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + errChan <- err + } + }() + + select { + case err := <-errChan: + return fmt.Errorf("web server failed to start: %w", err) + case <-time.After(100 * time.Millisecond): + // Started successfully + } + + go func() { + <-ctx.Done() + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + s.httpServer.Shutdown(shutdownCtx) + }() + + return nil +} + +// broadcastUpdates periodically sends engine state updates to WebSocket clients. +func (s *Server) broadcastUpdates(ctx context.Context) { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + lastLogCount := 0 + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + stats := s.engine.GetStats() + if stats.TotalLogsEver == lastLogCount { + continue // No new data + } + lastLogCount = stats.TotalLogsEver + + // Build delta update + update := map[string]interface{}{ + "type": "update", + "total_logs": stats.TotalLogsEver, + "buffer_used": stats.BufferUsed, + } + + data, err := json.Marshal(update) + if err != nil { + continue + } + s.hub.Broadcast(data) + } + } +} + +// spaHandler serves static files and falls back to index.html for SPA routing. +func spaHandler(staticFS fs.FS) http.Handler { + fileServer := http.FileServerFS(staticFS) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Try to serve the file directly + path := r.URL.Path + if path == "/" { + path = "index.html" + } else if path[0] == '/' { + path = path[1:] + } + + // Check if file exists + if _, err := fs.Stat(staticFS, path); err == nil { + fileServer.ServeHTTP(w, r) + return + } + + // SPA fallback — serve index.html + r.URL.Path = "/" + fileServer.ServeHTTP(w, r) + }) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + next.ServeHTTP(w, r) + }) +} + +func writeJSON(w http.ResponseWriter, v interface{}) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(v); err != nil { + log.Printf("Error writing JSON response: %v", err) + } +} + +func writeError(w http.ResponseWriter, code int, msg string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + json.NewEncoder(w).Encode(map[string]string{"error": msg}) +} diff --git a/internal/web/websocket.go b/internal/web/websocket.go new file mode 100644 index 0000000..cdb3791 --- /dev/null +++ b/internal/web/websocket.go @@ -0,0 +1,133 @@ +package web + +import ( + "context" + "log" + "net/http" + "sync" + "time" + + "nhooyr.io/websocket" +) + +// Hub manages WebSocket client connections and broadcasts messages. +type Hub struct { + mu sync.RWMutex + clients map[*Client]bool +} + +// Client represents a single WebSocket connection. +type Client struct { + conn *websocket.Conn + send chan []byte +} + +// NewHub creates a new WebSocket hub. +func NewHub() *Hub { + return &Hub{ + clients: make(map[*Client]bool), + } +} + +// Run starts the hub, cleaning up closed connections. +func (h *Hub) Run(ctx context.Context) { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + h.mu.Lock() + for c := range h.clients { + close(c.send) + delete(h.clients, c) + } + h.mu.Unlock() + return + case <-ticker.C: + // Periodic cleanup handled by write pump detecting closed conns + } + } +} + +// Register adds a client to the hub. +func (h *Hub) Register(c *Client) { + h.mu.Lock() + h.clients[c] = true + h.mu.Unlock() +} + +// Unregister removes a client from the hub. +func (h *Hub) Unregister(c *Client) { + h.mu.Lock() + if _, ok := h.clients[c]; ok { + close(c.send) + delete(h.clients, c) + } + h.mu.Unlock() +} + +// Broadcast sends a message to all connected clients. +func (h *Hub) Broadcast(msg []byte) { + h.mu.RLock() + defer h.mu.RUnlock() + + for c := range h.clients { + select { + case c.send <- msg: + default: + // Client buffer full, skip + } + } +} + +// handleWebSocket handles WebSocket upgrade and manages the connection. +func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) { + conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{ + InsecureSkipVerify: true, // Allow any origin (localhost only) + }) + if err != nil { + log.Printf("WebSocket accept error: %v", err) + return + } + + client := &Client{ + conn: conn, + send: make(chan []byte, 64), + } + s.hub.Register(client) + + ctx := r.Context() + + // Write pump + go func() { + defer func() { + s.hub.Unregister(client) + conn.Close(websocket.StatusNormalClosure, "") + }() + for { + select { + case <-ctx.Done(): + return + case msg, ok := <-client.send: + if !ok { + return + } + writeCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + err := conn.Write(writeCtx, websocket.MessageText, msg) + cancel() + if err != nil { + return + } + } + } + }() + + // Read pump (just consume and discard — we don't expect client messages) + for { + _, _, err := conn.Read(ctx) + if err != nil { + break + } + } +} diff --git a/web/Dstl8_logo_dark.svg b/web/Dstl8_logo_dark.svg new file mode 100644 index 0000000..8a0ddd8 --- /dev/null +++ b/web/Dstl8_logo_dark.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/Dstl8_logo_light.svg b/web/Dstl8_logo_light.svg new file mode 100644 index 0000000..094c505 --- /dev/null +++ b/web/Dstl8_logo_light.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/embed.go b/web/embed.go new file mode 100644 index 0000000..5f89293 --- /dev/null +++ b/web/embed.go @@ -0,0 +1,6 @@ +package web + +import "embed" + +//go:embed all:dist +var DistFS embed.FS diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..0d7066f --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + Dstl8 Lite — powered by Gonzo + + + +
+ + + diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..6d16826 --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,4283 @@ +{ + "name": "dstl8-lite", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dstl8-lite", + "version": "0.0.1", + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-markdown": "^10.1.0", + "recharts": "^2.15.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.0", + "vite": "^6.0.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz", + "integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001780", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", + "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.321", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", + "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..ba2267a --- /dev/null +++ b/web/package.json @@ -0,0 +1,27 @@ +{ + "name": "dstl8-lite", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-markdown": "^10.1.0", + "recharts": "^2.15.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.0", + "vite": "^6.0.0" + } +} diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/web/public/Dstl8_Gradient_Dark.svg b/web/public/Dstl8_Gradient_Dark.svg new file mode 100644 index 0000000..8a0ddd8 --- /dev/null +++ b/web/public/Dstl8_Gradient_Dark.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/public/Dstl8_logo_dark.svg b/web/public/Dstl8_logo_dark.svg new file mode 100644 index 0000000..8a0ddd8 --- /dev/null +++ b/web/public/Dstl8_logo_dark.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/public/Dstl8_logo_light.svg b/web/public/Dstl8_logo_light.svg new file mode 100644 index 0000000..094c505 --- /dev/null +++ b/web/public/Dstl8_logo_light.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/public/Gonzo-Profile.png b/web/public/Gonzo-Profile.png new file mode 100644 index 0000000000000000000000000000000000000000..279a98b123e7913ef6ed79a99cd45ff273d2d745 GIT binary patch literal 264034 zcmdRWgUKf|}Jm;A+XU@z$_sn_L56X&939gY}gFqkz&}UCnArM@8%n$BWaK((B zzY6?=XZK9o5dz`7kNLUamL~23fzU#rPb8naC9EKn-oIY(W;!h3dNWnxF;#7*e4|$2 zadl}gC%GqS(#v_AwGgNZNf4mjT?(jE<`!Xd` zVkvyIE*9TbZp!UtZMzKq&iiGFM;yK1J?O1V`q>qus;Y*DcIwvMy9L*idvK_$vemu2 z*98}9UTtiN!kI1rHUGc(_}8>q`ZY6=-jBX%cKx*ZFEX(r`A>2vyv|W%O7z4N zyAogeHk)uX{Q!M3|J`DG_42e!*c&;g+2_*$Ho3BaSt=Xi&~Eed%=)R-8_S>k=?oV%D*ceD~y7& z2i%SHZC{(*u@@n1#tANEW_O+W{~M-$UDcJFnd*taM-K9TkFpVjcdCj7a|iuDm(3V2r_9d&TbRzXyZ>Bp1Pk%oRN^D8$SBU*UcGLIa_3~`8Lun%c@`bBC(#DWbkb}7&WV=or&x~SIio*0E2 z1v(l!2CJTE-~Km#aa1upB~lL}CQ4}hAohSjZTCP}79EWBgWhkF?XEHcmMU)LDS|)h zvLgvv|5I<+8WtimluNAyS3Tjo&9>u-uejjjmVOVecIBKYOUa|IJ^! zWYvJw)4m65TxP>?8!0>-J%#sa1gbW|H*}n_<3HI~;V}ePe<74(y`xCxs{3z#HmgYR zKs8lYVDoh~n;Z;^V?J)bm{)O#L%%1Uvc|P&5S(9xl$b>d@$|lCHi|as?eh{qGM8Ck z?|i)^Y%U(jy=28F(ni~66o>3-!u^81#~sKig_n}g*z~1^#gQuz5sejBFJvhkY3Jlc zc=+Jim!q7Ce}Rn;E^`NIoO*vrmCyW%#`=>Xi*+DzjS;ez_0<|ezttIgxzI?)oa@tu zdpM%|wUlbQaAWRPHr5uS&r(WAp9#pKhs9PWpkF-sRw`_;D)EgnuLcmOBNSfdC=49y zD+m=}5yUryU(xfMf9+cOkEnMn16{_w_6Y1VRv)0__zWvf0%6u_--R11P~ZQ}!--?g z^obkgK&)dDJHIzy_`f@1euFb__@wY=fjnMWr>A$F^>~0dtG5N)dKj^}-z1mI=W#eF zk7UAKbp+P<$kPewdF?s>+F1Lk^F}LrD zC0jDCLo<|o4YXYa2dP`xs(bHe;~DroB?!*s5)3ao^+?AtILy`$leRV2$lD z&|-|r?7++YAsOeIRXDAyA0@=>5PNdzibAVVAKO2#@RbbLZs&YUO+^#KM3Ht-nlF^^ z9n)GE@E3ZpNz9Tiw69Y>J46YowdISTiAFYKXE2NAlM{Noc-&@#mi> z^6)%67~YxXWGZCL=7Hy{xAGQL8^r}m4BM}ep5ckkEdL=VMlZfmG<`wpiq230ll%R) z#J8=wu1MScb!0F~u)t^;CAv}`T!|3mJvm57u5~|N*uWFt(inJ)U6Jg#(pY&36VRsr zDm1iz#e=Y#(}O@=8y$)}Fi7>pC1T}v?7Gcz&)UlV{!@Z5yz|GDCuwhx@XCc4#oW>| zO5rj1Id7%D43S3~`L%{lUoW6kq=g6FkjA znjkeIc&cG3-E8{SO_9hKsci!9^Pt#4^L+AS(i3RK90MvpT*t_V&$jHIwteb?IX%V> z&}xUZbN1)x&%!>+HDc0W!!0*ma(Q@P{o5=;c5_Fn^#y71U~=UvbdJcrm30cMv!=fh{M8>= zI1S^$);=2GWTE|KVj#YozteBJ0s3pYZ$mO>ErI+r0Y@@GM|7yk!6kz7w#d{-v1m`d z;<7|z(UknAaDavOQ%`7JGOU^im+oBsLiOyypr5mVwgoQ1Lk_;rZdC04{i~a8tQp8h znL1f(TO2z
ku2d{W;iKE=}uZ`1)#Jjx|-KH8j(g5N9J; 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

{AK|UpO1I<* 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 F@;~y+9TNZm literal 0 HcmV?d00001 diff --git a/web/public/MCP_icon.png b/web/public/MCP_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..783e179066b3b41f5f2269bc59ffe2355c55f50c GIT binary patch literal 61177 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 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/public/Mobius_Square_dark.svg b/web/public/Mobius_Square_dark.svg new file mode 100644 index 0000000..c7ac06e --- /dev/null +++ b/web/public/Mobius_Square_dark.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 0000000..8093da9 --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,159 @@ +import { useState, useEffect, useCallback } from 'react' +import { ThemeProvider } from './components/ui/ThemeProvider' +import { AppHeader } from './components/layout/AppHeader' +import { AppSidebar } from './components/layout/AppSidebar' +import { WhatsNewModal } from './components/ui/WhatsNewModal' +import { WorkspacesPage } from './pages/WorkspacesPage' +import { WorkspaceDetailsPage } from './pages/WorkspaceDetailsPage' +import { StreamDetailsPage } from './pages/StreamDetailsPage' +import { SourcesPage } from './pages/SourcesPage' +import { HeatmapPage } from './pages/HeatmapPage' +import { LogViewerPage } from './pages/LogViewerPage' +import { UpgradePage } from './pages/UpgradePage' +import { fetchReleases } from './api/client' +import type { ReleaseInfo } from './api/types' + +type Page = + | { type: 'workspaces' } + | { type: 'workspace-details' } + | { type: 'logs' } + | { type: 'sources' } + | { type: 'heatmap' } + | { type: 'stream-details'; dimensionType: string; dimensionValue: string } + | { type: 'upgrade'; featureId: string } + +const LAST_SEEN_KEY = 'gonzo-last-seen-version' + +// Strip git describe suffixes: "v0.3.1-13-g58cce74-dirty" -> "v0.3.1" +function baseVersion(v: string): string { + const s = v.startsWith('v') ? v.slice(1) : v + const base = s.split('-')[0] + return v.startsWith('v') ? `v${base}` : base +} + +export default function App() { + const [page, setPage] = useState({ type: 'workspaces' }) + const [releasesCache, setReleasesCache] = useState<{ version: string; releases: ReleaseInfo[] } | null>(null) + const [showWhatsNew, setShowWhatsNew] = useState(false) + + // Check for new releases on mount + useEffect(() => { + fetchReleases() + .then((data) => { + if (!data.releases?.length || !data.version || data.version === 'dev') return + setReleasesCache({ version: data.version, releases: data.releases }) + const lastSeen = localStorage.getItem(LAST_SEEN_KEY) + if (lastSeen !== baseVersion(data.version)) { + setShowWhatsNew(true) + } + }) + .catch(() => {}) + }, []) + + const dismissWhatsNew = useCallback(() => { + if (releasesCache) { + localStorage.setItem(LAST_SEEN_KEY, baseVersion(releasesCache.version)) + } + setShowWhatsNew(false) + }, [releasesCache]) + + const openWhatsNew = useCallback(() => { + setShowWhatsNew(true) + }, []) + + const handleNavigate = useCallback((target: string) => { + if (target.startsWith('upgrade:')) { + setPage({ type: 'upgrade', featureId: target.replace('upgrade:', '') }) + } else if (target === 'workspaces') { + setPage({ type: 'workspaces' }) + } else if (target === 'workspace-details') { + setPage({ type: 'workspace-details' }) + } else if (target === 'logs') { + setPage({ type: 'logs' }) + } else if (target === 'sources') { + setPage({ type: 'sources' }) + } else if (target === 'heatmap') { + setPage({ type: 'heatmap' }) + } + }, []) + + const handleOpenStream = useCallback((dimensionType: string, dimensionValue: string) => { + setPage({ type: 'stream-details', dimensionType, dimensionValue }) + }, []) + + const activeSidebarPage = + page.type === 'upgrade' + ? page.featureId + : page.type === 'workspace-details' || page.type === 'stream-details' + ? 'workspaces' + : page.type + + const breadcrumbs = + page.type === 'workspace-details' + ? [ + { label: 'Workspaces', onClick: () => setPage({ type: 'workspaces' }) }, + { label: 'Gonzo!' }, + ] + : page.type === 'stream-details' + ? [ + { label: 'Workspaces', onClick: () => setPage({ type: 'workspaces' }) }, + { label: 'Gonzo!', onClick: () => setPage({ type: 'workspace-details' }) }, + { label: page.dimensionValue }, + ] + : page.type === 'logs' + ? [{ label: 'Logs' }] + : page.type === 'sources' + ? [{ label: 'Sources' }] + : page.type === 'heatmap' + ? [{ label: 'Heatmap' }] + : page.type === 'upgrade' + ? [ + { label: 'Workspaces', onClick: () => setPage({ type: 'workspaces' }) }, + { label: page.featureId.charAt(0).toUpperCase() + page.featureId.slice(1) }, + ] + : [] + + return ( + +

+ {showWhatsNew && releasesCache && ( + + )} + + ) +} diff --git a/web/src/api/client.ts b/web/src/api/client.ts new file mode 100644 index 0000000..7d5ed6f --- /dev/null +++ b/web/src/api/client.ts @@ -0,0 +1,101 @@ +import { API_BASE } from '../lib/constants' +import type { + StatusInfo, + StreamInfo, + SeverityGroup, + SentimentData, + PatternGroup, + ClassItem, + LogSample, + HeatmapMinuteData, + SeverityTimePoint, + InsightsParams, +} from './types' + +async function fetchJSON(path: string, init?: RequestInit): Promise { + const res = await fetch(`${API_BASE}${path}`, init) + if (!res.ok) { + const body = await res.text() + throw new Error(`API ${path}: ${res.status} ${body}`) + } + return res.json() +} + +export function fetchStatus(): Promise { + return fetchJSON('/status') +} + +export async function fetchStreams(): Promise { + return (await fetchJSON('/streams')) ?? [] +} + +export async function fetchSeverity(opts?: { groupBy?: string; search?: string }): Promise { + const params = new URLSearchParams() + if (opts?.groupBy) params.set('group_by', opts.groupBy) + if (opts?.search) params.set('search', opts.search) + const q = params.toString() + return (await fetchJSON(`/severity${q ? '?' + q : ''}`)) ?? [] +} + +export async function fetchSentiment(groupBy?: string): Promise { + const q = groupBy ? `?group_by=${groupBy}` : '' + const data = await fetchJSON(`/sentiment${q}`) + return data ?? { group_values: [], buckets: [] } +} + +export async function fetchPatterns(opts?: { severity?: string; search?: string }): Promise { + const params = new URLSearchParams() + if (opts?.severity) params.set('severity', opts.severity) + if (opts?.search) params.set('search', opts.search) + const q = params.toString() + return (await fetchJSON(`/patterns${q ? '?' + q : ''}`)) ?? [] +} + +export async function fetchClasses(): Promise { + return (await fetchJSON('/classes')) ?? [] +} + +export async function fetchLogs(opts?: { + severity?: string + search?: string + limit?: number +}): Promise { + const params = new URLSearchParams() + if (opts?.severity) params.set('severity', opts.severity) + if (opts?.search) params.set('search', opts.search) + if (opts?.limit) params.set('limit', String(opts.limit)) + const q = params.toString() + return (await fetchJSON(`/logs${q ? '?' + q : ''}`)) ?? [] +} + +export async function fetchHeatmap(): Promise { + return (await fetchJSON('/heatmap')) ?? [] +} + +export function fetchAnomalies(): Promise<{ report: string }> { + return fetchJSON('/anomalies') +} + +export function fetchSummary(): Promise<{ summary: string }> { + return fetchJSON('/summary', { method: 'POST' }) +} + +export async function fetchSeverityHistory(opts?: { search?: string }): Promise { + const params = new URLSearchParams() + if (opts?.search) params.set('search', opts.search) + const q = params.toString() + return (await fetchJSON(`/severity-history${q ? '?' + q : ''}`)) ?? [] +} + +export async function fetchInsightsParams(): Promise { + const data = await fetchJSON('/insights-params') + return data ?? { total_logs: 0 } +} + +export async function fetchTopAttributes(limit = 20): Promise { + return (await fetchJSON(`/top-attributes?limit=${limit}`)) ?? [] +} + +export function fetchReleases(): Promise { + return fetchJSON('/releases') +} diff --git a/web/src/api/types.ts b/web/src/api/types.ts new file mode 100644 index 0000000..e19b4cf --- /dev/null +++ b/web/src/api/types.ts @@ -0,0 +1,117 @@ +// TypeScript types matching Go engine/types.go response shapes + +export interface StatusInfo { + uptime: string + total_logs: number + total_bytes: number + log_rate: number + streams: StreamInfo[] + buffer_size: number + buffer_used: number + ai_configured: boolean +} + +export interface StreamInfo { + source: string + stream: string + log_count: number + last_seen: string + active: boolean +} + +export interface LogSample { + timestamp: number + severity: string + message: string + attributes?: Record + raw_line?: string +} + +export interface SeverityGroup { + group_value: string + counts: Record + total: number +} + +export interface SentimentBucket { + timestamp: number + group_value: string + sentiment: number + is_anomaly?: boolean + log_count: number +} + +export interface SentimentData { + group_values: string[] + buckets: SentimentBucket[] +} + +export interface PatternItem { + pattern: string + count: number + percentage: number + sample?: string +} + +export interface PatternGroup { + group_value: string + patterns: PatternItem[] +} + +export interface ClassItem { + name: string + count: number + percentage: number +} + +export interface HeatmapMinuteData { + timestamp: number + counts: Record +} + +export interface InsightsParams { + environments?: string[] + clusters?: string[] + namespaces?: string[] + deployments?: string[] + pods?: string[] + hosts?: string[] + services?: string[] + severities?: string[] + categories?: string[] + total_logs: number + oldest_log?: number + newest_log?: number +} + +export interface SeverityTimePoint { + timestamp: number + counts: Record + total: number +} + +export interface AttributeEntry { + key: string + value: string + count: number + percentage: number +} + +export interface WebSocketUpdate { + type: string + total_logs: number + buffer_used: number +} + +export interface ReleaseInfo { + tag_name: string + name: string + body: string + published_at: string + url: string +} + +export interface ReleasesResponse { + version: string + releases: ReleaseInfo[] | null +} diff --git a/web/src/api/websocket.ts b/web/src/api/websocket.ts new file mode 100644 index 0000000..6845ada --- /dev/null +++ b/web/src/api/websocket.ts @@ -0,0 +1,57 @@ +import type { WebSocketUpdate } from './types' + +type UpdateHandler = (update: WebSocketUpdate) => void + +export class DashboardWebSocket { + private ws: WebSocket | null = null + private handlers: Set = new Set() + private reconnectTimer: ReturnType | null = null + private url: string + + constructor() { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' + this.url = `${protocol}//${window.location.host}/ws` + } + + connect() { + if (this.ws?.readyState === WebSocket.OPEN) return + + this.ws = new WebSocket(this.url) + this.ws.onmessage = (event) => { + try { + const update: WebSocketUpdate = JSON.parse(event.data) + this.handlers.forEach((h) => h(update)) + } catch { + // ignore malformed messages + } + } + this.ws.onclose = () => { + this.scheduleReconnect() + } + this.ws.onerror = () => { + this.ws?.close() + } + } + + disconnect() { + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer) + this.reconnectTimer = null + } + this.ws?.close() + this.ws = null + } + + subscribe(handler: UpdateHandler): () => void { + this.handlers.add(handler) + return () => this.handlers.delete(handler) + } + + private scheduleReconnect() { + if (this.reconnectTimer) return + this.reconnectTimer = setTimeout(() => { + this.reconnectTimer = null + this.connect() + }, 2000) + } +} diff --git a/web/src/components/layout/AppHeader.tsx b/web/src/components/layout/AppHeader.tsx new file mode 100644 index 0000000..4f4d2c2 --- /dev/null +++ b/web/src/components/layout/AppHeader.tsx @@ -0,0 +1,93 @@ +import { useTheme } from '../../hooks/useTheme' +import { Icons } from '../../lib/icons' + +interface Breadcrumb { + label: string + onClick?: () => void +} + +interface AppHeaderProps { + breadcrumbs?: Breadcrumb[] + onLogoClick?: () => void +} + +export function AppHeader({ breadcrumbs = [], onLogoClick }: AppHeaderProps) { + const { theme, toggle } = useTheme() + + return ( +
+
+ + Dstl8 + + + + Lite + + + + Powered by Gonzo + + + + {breadcrumbs.length > 0 && ( +
+ {breadcrumbs.map((crumb, i) => { + const isLast = i === breadcrumbs.length - 1 + return ( + + {i > 0 && /} + {isLast ? ( + {crumb.label} + ) : ( + + {crumb.label} + + )} + + ) + })} +
+ )} +
+
+ +
+ Gonzo +
+ Gonzo + Local +
+
+
+
+ ) +} diff --git a/web/src/components/layout/AppSidebar.tsx b/web/src/components/layout/AppSidebar.tsx new file mode 100644 index 0000000..7f020d9 --- /dev/null +++ b/web/src/components/layout/AppSidebar.tsx @@ -0,0 +1,133 @@ +import { useState, useCallback } from 'react' +import { Icons } from '../../lib/icons' +import { upsellURL } from '../../lib/constants' + +interface AppSidebarProps { + activePage: string + onNavigate: (page: string) => void + onWhatsNew?: () => void +} + +const mainNav = [ + { id: 'workspaces', label: 'Workspaces', icon: Icons.workspaces, locked: false }, + { id: 'alerts', label: 'Alerts', icon: Icons.incidents, locked: true }, + { id: 'logs', label: 'Logs', icon: Icons.logs, locked: false }, + { id: 'sources', label: 'Sources', icon: Icons.sources, locked: false }, + { id: 'heatmap', label: 'Heatmap', icon: Icons.heatmap, locked: false }, + { id: 'users', label: 'Users', icon: Icons.users, locked: true }, + { id: 'mcp', label: 'MCP', icon: null, locked: true, useImage: true }, +] + +const bottomNav = [ + { id: 'retention', label: 'Retention', icon: Icons.settings, locked: true }, +] + +export function AppSidebar({ activePage, onNavigate, onWhatsNew }: AppSidebarProps) { + const [showMcpModal, setShowMcpModal] = useState(false) + const dismissMcpModal = useCallback(() => setShowMcpModal(false), []) + + return ( +
+ ) +} diff --git a/web/src/components/layout/DashboardLayout.tsx b/web/src/components/layout/DashboardLayout.tsx new file mode 100644 index 0000000..5da025c --- /dev/null +++ b/web/src/components/layout/DashboardLayout.tsx @@ -0,0 +1,21 @@ +import type { ReactNode } from 'react' + +interface DashboardLayoutProps { + header: ReactNode + footer: ReactNode + children: ReactNode +} + +export function DashboardLayout({ header, footer, children }: DashboardLayoutProps) { + return ( +
+ {header} +
+
+ {children} +
+
+ {footer} +
+ ) +} diff --git a/web/src/components/layout/Footer.tsx b/web/src/components/layout/Footer.tsx new file mode 100644 index 0000000..4db3be5 --- /dev/null +++ b/web/src/components/layout/Footer.tsx @@ -0,0 +1,37 @@ +import { useTheme } from '../../hooks/useTheme' +import { upsellURL } from '../../lib/constants' + +export function Footer() { + const { theme } = useTheme() + + return ( + + ) +} diff --git a/web/src/components/layout/Header.tsx b/web/src/components/layout/Header.tsx new file mode 100644 index 0000000..ebbe1bc --- /dev/null +++ b/web/src/components/layout/Header.tsx @@ -0,0 +1,51 @@ +import { useTheme } from '../../hooks/useTheme' +import { upsellURL } from '../../lib/constants' +import { Button } from '../ui/Button' +import type { StatusInfo } from '../../api/types' + +interface HeaderProps { + status: StatusInfo | null +} + +export function Header({ status }: HeaderProps) { + const { theme, toggle } = useTheme() + + return ( +
+
+
+ Dstl8 +
+ + Lite + +
+ Powered by Gonzo +
+
+
+ {status && ( +
+ {status.total_logs.toLocaleString()} logs + {status.log_rate.toFixed(1)}/s + {status.uptime} uptime +
+ )} +
+
+ + + + +
+
+ ) +} diff --git a/web/src/components/layout/MobiusChat.tsx b/web/src/components/layout/MobiusChat.tsx new file mode 100644 index 0000000..968beb2 --- /dev/null +++ b/web/src/components/layout/MobiusChat.tsx @@ -0,0 +1,68 @@ +import { upsellURL } from '../../lib/constants' + +export function MobiusChat() { + return ( +
+
+ Möbius +
+
+ Möbius +
+
+
+ Online +
+
+
+ +
+
+ Möbius +
+
+ Welcome to Dstl8 Lite! I'm Möbius, your AI log analyst. + I can help you understand patterns, anomalies, and issues in your log data. +
+
Just now
+
+
+ +
+ Möbius +
+
+ For continuous AI analysis and real-time insights,{' '} + + upgrade to Dstl8 Pro + {' '} + to unlock the full Möbius experience. +
+
Just now
+
+
+
+ +
+
+ + +
+
+
+ ) +} diff --git a/web/src/components/panels/AISummaryPanel.tsx b/web/src/components/panels/AISummaryPanel.tsx new file mode 100644 index 0000000..7495427 --- /dev/null +++ b/web/src/components/panels/AISummaryPanel.tsx @@ -0,0 +1,79 @@ +import { useState } from 'react' +import { fetchSummary } from '../../api/client' +import { upsellURL } from '../../lib/constants' +import { Card, CardHeader, CardTitle } from '../ui/Card' +import { Button } from '../ui/Button' + +export function AISummaryPanel() { + const [summary, setSummary] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const handleAnalyze = () => { + setLoading(true) + setError(null) + fetchSummary() + .then((res) => setSummary(res.summary)) + .catch((err) => setError(err.message)) + .finally(() => setLoading(false)) + } + + return ( + + + AI Analysis + + {summary ? ( +
+

+ {summary} +

+ +
+ ) : error ? ( +
+

{error}

+
+

+ AI analysis requires a configured AI provider. + Configure --ai-provider or try{' '} + + Mobius on Dstl8 Pro + + {' '}for built-in AI analysis. +

+
+
+ ) : ( +
+

+ Generate an AI-powered summary of your current log data. +

+ +
+

+ Want continuous AI analysis?{' '} + + Try Mobius on Dstl8 Pro + +

+
+
+ )} +
+ ) +} diff --git a/web/src/components/panels/HeatmapPanel.tsx b/web/src/components/panels/HeatmapPanel.tsx new file mode 100644 index 0000000..dab6f14 --- /dev/null +++ b/web/src/components/panels/HeatmapPanel.tsx @@ -0,0 +1,100 @@ +import { useEffect, useState } from 'react' +import { fetchHeatmap } from '../../api/client' +import type { HeatmapMinuteData } from '../../api/types' +import { SEVERITY_COLORS, SEVERITY_ORDER } from '../../lib/colors' +import { Card, CardHeader, CardTitle } from '../ui/Card' + +interface Props { + refreshKey: number +} + +export function HeatmapPanel({ refreshKey }: Props) { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetchHeatmap() + .then(setData) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [refreshKey]) + + if (loading) { + return ( + + Severity Heatmap +
+ Loading... +
+
+ ) + } + + // Show last 60 minutes (or less if less data available) + const minutes = data.slice(-60) + const safeCounts = (m: HeatmapMinuteData) => + m.counts ? Object.values(m.counts).reduce((a, b) => a + b, 0) : 0 + const maxCount = minutes.length > 0 + ? Math.max(1, ...minutes.map(safeCounts)) + : 1 + + return ( + + + Severity Heatmap + + {minutes.length} min + + + {minutes.length === 0 ? ( +
+ No data yet +
+ ) : ( +
+
+ {minutes.map((minute, i) => { + const counts = minute.counts || {} + const total = Object.values(counts).reduce((a, b) => a + b, 0) + const opacity = Math.max(0.1, total / maxCount) + // Dominant severity determines color + const dominant = SEVERITY_ORDER.find((s) => (counts[s] || 0) > 0) || 'INFO' + const color = SEVERITY_COLORS[dominant] || SEVERITY_COLORS.UNKNOWN + const time = new Date(minute.timestamp * 1000) + const label = `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}` + return ( +
+
+ {i % 10 === 0 && ( + + {label} + + )} +
+ ) + })} +
+
+ {SEVERITY_ORDER.slice(0, 5).map((s) => ( + + + {s} + + ))} +
+
+ )} + + ) +} diff --git a/web/src/components/panels/LogViewer.tsx b/web/src/components/panels/LogViewer.tsx new file mode 100644 index 0000000..16e7601 --- /dev/null +++ b/web/src/components/panels/LogViewer.tsx @@ -0,0 +1,203 @@ +import { useEffect, useState, useRef, useCallback } from 'react' +import { fetchLogs } from '../../api/client' +import type { LogSample } from '../../api/types' +import { severityColor } from '../../lib/colors' +import { Card, CardHeader, CardTitle } from '../ui/Card' +import { Button } from '../ui/Button' + +interface Props { + refreshKey: number + search?: string + compact?: boolean + fullHeight?: boolean +} + +export function LogViewer({ refreshKey, search, compact, fullHeight }: Props) { + const [logs, setLogs] = useState([]) + const [loading, setLoading] = useState(true) + const [autoScroll, setAutoScroll] = useState(true) + const [filter, setFilter] = useState('') + const [expandedRow, setExpandedRow] = useState(null) + const listRef = useRef(null) + const autoScrollRef = useRef(true) + + // Combine external search with local filter + const combinedSearch = [search, filter].filter(Boolean).join(' ') || undefined + + const toggleAutoScroll = useCallback(() => { + setAutoScroll((prev) => { + const next = !prev + autoScrollRef.current = next + if (next) { + // Resuming: fetch fresh data and scroll to bottom + fetchLogs({ limit: 200, search: combinedSearch }) + .then((data) => { + setLogs(data) + requestAnimationFrame(() => { + if (listRef.current) { + listRef.current.scrollTop = listRef.current.scrollHeight + } + }) + }) + .catch(() => {}) + } + return next + }) + }, [combinedSearch]) + + useEffect(() => { + // When paused (autoScroll off), don't fetch new data — only fetch on search change + if (!autoScrollRef.current && !loading) return + fetchLogs({ limit: 200, search: combinedSearch }) + .then(setLogs) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [refreshKey, combinedSearch]) + + useEffect(() => { + if (autoScrollRef.current && listRef.current) { + const el = listRef.current + requestAnimationFrame(() => { + el.scrollTop = el.scrollHeight + }) + } + }, [logs]) + + const formatTime = (ts: number) => { + const d = new Date(ts * 1000) + return d.toLocaleTimeString() + } + + const handleRowClick = (index: number) => { + if (compact) return // no expand in compact mode + setExpandedRow(expandedRow === index ? null : index) + } + + const renderLogRow = (log: LogSample, i: number, isCompact: boolean) => { + const isExpanded = !isCompact && expandedRow === i + return ( + handleRowClick(i)} + > + + {formatTime(log.timestamp)} + + + + {log.severity} + + + + {isExpanded ? ( +
+ {log.message} + {log.attributes && Object.keys(log.attributes).length > 0 && ( +
+ {Object.entries(log.attributes).map(([k, v]) => ( +
+ {k}: {v} +
+ ))} +
+ )} +
+ ) : ( + {log.message} + )} + + + ) + } + + const renderEmpty = () => ( + loading ? ( +
+ Loading... +
+ ) : logs.length === 0 ? ( +
+ No logs yet +
+ ) : null + ) + + // Compact mode: no card wrapper, no header, constrained height + if (compact) { + return ( +
+ {renderEmpty() || ( + <> + + + {logs.map((log, i) => renderLogRow(log, i, true))} + +
+
+ + )} +
+ ) + } + + const heightClass = fullHeight ? 'h-full' : 'h-64' + + return ( + + + Log Viewer +
+ setFilter(e.target.value)} + className="rounded border border-[var(--color-border)] bg-[var(--color-bg)] px-2 py-1 text-xs text-[var(--color-text)] outline-none focus:border-[var(--color-accent)]" + /> + + + {logs.length} entries + +
+
+
+ {renderEmpty() || ( + <> + + + {logs.map((log, i) => renderLogRow(log, i, false))} + +
+
+ + )} +
+ + ) +} diff --git a/web/src/components/panels/PatternAnalysis.tsx b/web/src/components/panels/PatternAnalysis.tsx new file mode 100644 index 0000000..51d9eaf --- /dev/null +++ b/web/src/components/panels/PatternAnalysis.tsx @@ -0,0 +1,85 @@ +import { useEffect, useState } from 'react' +import { fetchPatterns } from '../../api/client' +import type { PatternGroup } from '../../api/types' +import { SeverityBadge } from '../ui/SeverityBadge' +import { Card, CardHeader, CardTitle } from '../ui/Card' + +interface Props { + refreshKey: number + search?: string +} + +export function PatternAnalysis({ refreshKey, search }: Props) { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetchPatterns({ search }) + .then(setData) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [refreshKey, search]) + + const allPatterns = data.flatMap((g) => + g.patterns.map((p) => ({ ...p, group: g.group_value })) + ) + + // Sort by count descending, take top 20 + const topPatterns = allPatterns + .sort((a, b) => b.count - a.count) + .slice(0, 20) + + return ( + + + Patterns + + {allPatterns.length} patterns detected + + + {loading ? ( +
+ Loading... +
+ ) : topPatterns.length === 0 ? ( +
+ No patterns detected yet +
+ ) : ( +
+ + + + + + + + + + + {topPatterns.map((p, i) => ( + + + + + + + ))} + +
GroupPatternCount%
+ + + {p.pattern} + + {p.count.toLocaleString()} + + {p.percentage.toFixed(1)}% +
+
+ )} +
+ ) +} diff --git a/web/src/components/panels/SeverityDistribution.tsx b/web/src/components/panels/SeverityDistribution.tsx new file mode 100644 index 0000000..eb49265 --- /dev/null +++ b/web/src/components/panels/SeverityDistribution.tsx @@ -0,0 +1,118 @@ +import { useEffect, useState } from 'react' +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + Legend, +} from 'recharts' +import { fetchSeverityHistory } from '../../api/client' +import type { SeverityTimePoint } from '../../api/types' +import { SEVERITY_COLORS, SEVERITY_ORDER } from '../../lib/colors' +import { Card, CardHeader, CardTitle } from '../ui/Card' + +interface Props { + refreshKey: number + search?: string +} + +export function SeverityDistribution({ refreshKey, search }: Props) { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetchSeverityHistory(search ? { search } : undefined) + .then(setData) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [refreshKey, search]) + + if (loading) { + return ( + + Log Severity +
+ Loading... +
+
+ ) + } + + // Build chart data from per-second time points + const chartData: Record[] = data.map((point) => { + const d = new Date(point.timestamp * 1000) + const h = d.getHours().toString().padStart(2, '0') + const m = d.getMinutes().toString().padStart(2, '0') + const s = d.getSeconds().toString().padStart(2, '0') + return { + name: `${h}:${m}:${s}`, + ...point.counts, + total: point.total, + } + }) + + const totalLogs = chartData.reduce((sum, d) => sum + ((d.total as number) || 0), 0) + + // Determine which severities are present + const activeSeverities = SEVERITY_ORDER.filter((sev) => + chartData.some((d) => ((d[sev] as number) || 0) > 0) + ) + + return ( + + + Log Severity + + {totalLogs.toLocaleString()} logs · {data.length}s + + + {chartData.length === 0 ? ( +
+ No data yet +
+ ) : ( + + + + + + + {activeSeverities.map((sev, i) => ( + + ))} + + + )} +
+ ) +} diff --git a/web/src/components/panels/SourcesSection.tsx b/web/src/components/panels/SourcesSection.tsx new file mode 100644 index 0000000..d6c2d3c --- /dev/null +++ b/web/src/components/panels/SourcesSection.tsx @@ -0,0 +1,478 @@ +import { useEffect, useState } from 'react' +import { fetchStreams, fetchStatus, fetchInsightsParams } from '../../api/client' +import type { StreamInfo, StatusInfo, InsightsParams } from '../../api/types' +import { upsellURL } from '../../lib/constants' + +interface Props { + refreshKey: number + onOpenStream?: (dimensionType: string, dimensionValue: string) => void +} + +/** Group streams by their `source` field (e.g. "file", "k8s", "otlp", "stdin"). */ +interface SourceGroup { + source: string + streams: StreamInfo[] + totalLogs: number + activeCount: number +} + +/** A dimension group like "Host", "Service", "Namespace", etc. */ +interface DimensionGroup { + label: string + values: { name: string; logCount: number; active: boolean }[] +} + +function groupStreams(streams: StreamInfo[]): SourceGroup[] { + const map = new Map() + for (const s of streams) { + const key = s.source || 'unknown' + if (!map.has(key)) map.set(key, []) + map.get(key)!.push(s) + } + const groups: SourceGroup[] = [] + for (const [source, items] of map) { + groups.push({ + source, + streams: items, + totalLogs: items.reduce((sum, s) => sum + s.log_count, 0), + activeCount: items.filter((s) => s.active).length, + }) + } + groups.sort((a, b) => b.totalLogs - a.totalLogs) + return groups +} + +/** Build dimension groups from insights params. */ +function buildDimensionGroups(params: InsightsParams, streams: StreamInfo[]): DimensionGroup[] { + const groups: DimensionGroup[] = [] + + // Determine which streams are active by name + const activeStreams = new Set(streams.filter((s) => s.active).map((s) => s.stream)) + + const addGroup = (label: string, values: string[] | undefined) => { + if (!values || values.length === 0) return + groups.push({ + label, + values: values.map((v) => ({ + name: v, + logCount: 0, // We don't have per-dimension counts from insights params + active: activeStreams.size > 0, // Approximate: if any stream is active + })), + }) + } + + addGroup('Host', params.hosts) + addGroup('Service', params.services) + addGroup('Deployment', params.deployments) + addGroup('Namespace', params.namespaces) + addGroup('Pod', params.pods) + addGroup('Environment', params.environments) + addGroup('Cluster', params.clusters) + + return groups +} + +function SourceCard({ group, expanded, onToggle, dimensionGroups, onOpenStream }: { + group: SourceGroup + expanded: boolean + onToggle: () => void + dimensionGroups: DimensionGroup[] + onOpenStream?: (dimensionType: string, dimensionValue: string) => void +}) { + const isHealthy = group.activeCount > 0 + const statusClass = isHealthy ? 'ok' : '' + const [collapsedDims, setCollapsedDims] = useState>(new Set()) + + const toggleDim = (label: string) => { + setCollapsedDims((prev) => { + const next = new Set(prev) + if (next.has(label)) next.delete(label) + else next.add(label) + return next + }) + } + + const totalStreamCount = dimensionGroups.reduce((sum, g) => sum + g.values.length, 0) + + return ( +
+ {/* Main card */} +
+
+
+
+
{group.source}
+ + ▼ + +
+ +
+
+
Streams
+
{group.streams.length}
+
+
+
Logs
+
{group.totalLogs.toLocaleString()}
+
+
+
Active
+
0 ? 'var(--health-ok)' : 'var(--text-muted)', + }}> + {group.activeCount} +
+
+
+
+ +
+
+ Stream Info +
+
+ {group.activeCount} of {group.streams.length} streams active +
+
+ {group.totalLogs.toLocaleString()} total logs ingested +
+
+
+ + {/* Expanded: dimension groups */} + {expanded && ( +
+
+ + {totalStreamCount || group.streams.length} Streams + +
+ + +
+
+ + {dimensionGroups.length === 0 ? ( + /* Fallback: show raw streams if no dimensions available */ +
+ {group.streams.map((stream, i) => ( + onOpenStream('stream', stream.stream) : undefined} + /> + ))} +
+ ) : ( +
+ {dimensionGroups.map((dim) => ( +
+ {/* Dimension header */} +
{ e.stopPropagation(); toggleDim(dim.label) }} + style={{ + display: 'flex', + alignItems: 'center', + gap: 8, + padding: '8px 4px', + cursor: 'pointer', + userSelect: 'none', + }} + > + + ▼ + + + {dim.label} + + + {dim.values.length} {dim.values.length === 1 ? 'stream' : 'streams'} + +
+ + {/* Dimension values (streams) */} + {!collapsedDims.has(dim.label) && ( +
+ {dim.values.map((v) => ( + onOpenStream(dim.label.toLowerCase(), v.name) : undefined} + /> + ))} +
+ )} +
+ ))} +
+ )} +
+ )} +
+ ) +} + +function StreamRow({ name, active, logCount, onClick }: { + name: string + active: boolean + logCount?: number + onClick?: () => void +}) { + return ( +
{ e.currentTarget.style.background = 'var(--bg-tertiary)' }} + onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent' }} + > + {/* Health dot */} + + + {/* Stream name */} + + {name} + + + {/* Log count (if available) */} + {logCount !== undefined && ( + + {logCount.toLocaleString()} logs + + )} + + {/* Arrow indicator for clickable rows */} + {onClick && ( + + )} +
+ ) +} + +export function SourcesSection({ refreshKey, onOpenStream }: Props) { + const [streams, setStreams] = useState([]) + const [status, setStatus] = useState(null) + const [params, setParams] = useState(null) + const [loading, setLoading] = useState(true) + const [activeTab, setActiveTab] = useState('all') + const [expandedSource, setExpandedSource] = useState(null) + + useEffect(() => { + Promise.all([fetchStreams(), fetchStatus(), fetchInsightsParams()]) + .then(([s, st, p]) => { setStreams(s); setStatus(st); setParams(p) }) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [refreshKey]) + + const groups = groupStreams(streams) + const filteredGroups = activeTab === 'all' + ? groups + : groups.filter((g) => g.source === activeTab) + + const dimensionGroups = params ? buildDimensionGroups(params, streams) : [] + + if (loading) { + return ( +
+

Sources

+
+ Loading sources... +
+
+ ) + } + + return ( +
+

Sources

+ + {/* Tab bar */} +
+ + {groups.map((g) => ( + + ))} +
+ + {/* Source cards */} + {filteredGroups.length === 0 ? ( +
+ No active sources.{' '} + {status && status.total_logs === 0 && ( + Start sending logs to Gonzo to see sources here. + )} +
+ ) : ( +
+ {filteredGroups.map((g) => ( + + setExpandedSource(expandedSource === g.source ? null : g.source) + } + dimensionGroups={dimensionGroups} + onOpenStream={onOpenStream} + /> + ))} + + {/* Upgrade teaser for more sources */} + +
+ )} +
+ ) +} diff --git a/web/src/components/panels/StreamSelector.tsx b/web/src/components/panels/StreamSelector.tsx new file mode 100644 index 0000000..9354b2e --- /dev/null +++ b/web/src/components/panels/StreamSelector.tsx @@ -0,0 +1,79 @@ +import { useEffect, useState } from 'react' +import { fetchStreams } from '../../api/client' +import type { StreamInfo } from '../../api/types' +import { Card, CardHeader, CardTitle } from '../ui/Card' + +interface Props { + refreshKey: number +} + +export function StreamSelector({ refreshKey }: Props) { + const [streams, setStreams] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetchStreams() + .then(setStreams) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [refreshKey]) + + const formatLastSeen = (ts: string) => { + const d = new Date(ts) + const diff = Date.now() - d.getTime() + if (diff < 60_000) return 'just now' + if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago` + return `${Math.floor(diff / 3_600_000)}h ago` + } + + return ( + + + Streams + + {streams.filter((s) => s.active).length} active + + + {loading ? ( +
+ Loading... +
+ ) : streams.length === 0 ? ( +
+ No active streams +
+ ) : ( +
+ {streams.map((s, i) => ( +
+
+ +
+
+ {s.source} +
+
+ {s.stream} +
+
+
+
+
+ {s.log_count.toLocaleString()} +
+
+ {formatLastSeen(s.last_seen)} +
+
+
+ ))} +
+ )} +
+ ) +} diff --git a/web/src/components/ui/Button.tsx b/web/src/components/ui/Button.tsx new file mode 100644 index 0000000..4e8b17c --- /dev/null +++ b/web/src/components/ui/Button.tsx @@ -0,0 +1,36 @@ +import type { ButtonHTMLAttributes, ReactNode } from 'react' + +interface ButtonProps extends ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'ghost' + size?: 'sm' | 'md' + children: ReactNode +} + +const variants = { + primary: 'bg-[var(--color-accent)] text-white hover:opacity-90', + secondary: + 'border border-[var(--color-border)] bg-[var(--color-card)] text-[var(--color-text)] hover:bg-[var(--color-card-hover)]', + ghost: 'text-[var(--color-text-secondary)] hover:text-[var(--color-text)] hover:bg-[var(--color-card-hover)]', +} + +const sizes = { + sm: 'px-2 py-1 text-xs', + md: 'px-3 py-1.5 text-sm', +} + +export function Button({ + variant = 'secondary', + size = 'md', + className = '', + children, + ...props +}: ButtonProps) { + return ( + + ) +} diff --git a/web/src/components/ui/Card.tsx b/web/src/components/ui/Card.tsx new file mode 100644 index 0000000..efbd42c --- /dev/null +++ b/web/src/components/ui/Card.tsx @@ -0,0 +1,30 @@ +import type { ReactNode } from 'react' + +interface CardProps { + children: ReactNode + className?: string +} + +export function Card({ children, className = '' }: CardProps) { + return ( +
+ {children} +
+ ) +} + +export function CardHeader({ children, className = '' }: CardProps) { + return ( +
+ {children} +
+ ) +} + +export function CardTitle({ children }: { children: ReactNode }) { + return ( +

{children}

+ ) +} diff --git a/web/src/components/ui/ErrorBoundary.tsx b/web/src/components/ui/ErrorBoundary.tsx new file mode 100644 index 0000000..057329b --- /dev/null +++ b/web/src/components/ui/ErrorBoundary.tsx @@ -0,0 +1,48 @@ +import { Component, type ReactNode } from 'react' +import { Card, CardHeader, CardTitle } from './Card' + +interface Props { + name: string + children: ReactNode +} + +interface State { + hasError: boolean + error: Error | null +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props) + this.state = { hasError: false, error: null } + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error } + } + + render() { + if (this.state.hasError) { + return ( + + + {this.props.name} + +
+

Something went wrong

+

+ {this.state.error?.message} +

+ +
+
+ ) + } + return this.props.children + } +} diff --git a/web/src/components/ui/SeverityBadge.tsx b/web/src/components/ui/SeverityBadge.tsx new file mode 100644 index 0000000..54d279c --- /dev/null +++ b/web/src/components/ui/SeverityBadge.tsx @@ -0,0 +1,21 @@ +import { severityColor } from '../../lib/colors' + +interface SeverityBadgeProps { + severity: string + count?: number +} + +export function SeverityBadge({ severity, count }: SeverityBadgeProps) { + const color = severityColor(severity) + return ( + + {severity} + {count !== undefined && ( + {count.toLocaleString()} + )} + + ) +} diff --git a/web/src/components/ui/ThemeProvider.tsx b/web/src/components/ui/ThemeProvider.tsx new file mode 100644 index 0000000..0acea80 --- /dev/null +++ b/web/src/components/ui/ThemeProvider.tsx @@ -0,0 +1,32 @@ +import { createContext, useState, useEffect, type ReactNode } from 'react' + +type Theme = 'light' | 'dark' + +interface ThemeContextType { + theme: Theme + toggle: () => void +} + +export const ThemeContext = createContext(null) + +export function ThemeProvider({ children }: { children: ReactNode }) { + const [theme, setTheme] = useState(() => { + const stored = localStorage.getItem('dstl8-theme') + if (stored === 'light' || stored === 'dark') return stored + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + }) + + useEffect(() => { + const root = document.documentElement + root.classList.toggle('dark', theme === 'dark') + localStorage.setItem('dstl8-theme', theme) + }, [theme]) + + const toggle = () => setTheme((t) => (t === 'dark' ? 'light' : 'dark')) + + return ( + + {children} + + ) +} diff --git a/web/src/components/ui/WhatsNewModal.tsx b/web/src/components/ui/WhatsNewModal.tsx new file mode 100644 index 0000000..e93bb82 --- /dev/null +++ b/web/src/components/ui/WhatsNewModal.tsx @@ -0,0 +1,258 @@ +import { useEffect, useState, useCallback } from 'react' +import Markdown from 'react-markdown' +import { useTheme } from '../../hooks/useTheme' +import type { ReleaseInfo } from '../../api/types' + +// Collapse full 40-char git commit hashes to short 7-char form +function collapseHashes(text: string): string { + return text.replace(/\b([0-9a-f]{40})\b/g, (_, hash: string) => hash.slice(0, 7)) +} + +interface WhatsNewModalProps { + version: string + releases: ReleaseInfo[] + onDismiss: () => void +} + +export function WhatsNewModal({ version, releases, onDismiss }: WhatsNewModalProps) { + const { theme } = useTheme() + const [expandedIdx, setExpandedIdx] = useState(null) + + // Find the current release + const currentIdx = releases.findIndex( + (r) => r.tag_name === version || r.tag_name === `v${version}` + ) + const currentRelease = currentIdx >= 0 ? releases[currentIdx] : releases[0] + const otherReleases = releases.filter((_, i) => i !== (currentIdx >= 0 ? currentIdx : 0)) + + // ESC to dismiss + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key === 'Escape') onDismiss() + } + window.addEventListener('keydown', handler) + return () => window.removeEventListener('keydown', handler) + }, [onDismiss]) + + const toggleExpanded = useCallback((idx: number) => { + setExpandedIdx((prev) => (prev === idx ? null : idx)) + }, []) + + const formatDate = (iso: string) => { + try { + return new Date(iso).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }) + } catch { + return iso + } + } + + return ( +
{ + if (e.target === e.currentTarget) onDismiss() + }} + > +
+ {/* Header */} +
+ Dstl8 +
+
+ What's New +
+
+ {currentRelease && ( + + {currentRelease.tag_name} + + )} +
+ + {/* Scrollable content */} +
+ {/* Current release */} + {currentRelease && ( +
+
+ {currentRelease.name || currentRelease.tag_name} +
+
+ {formatDate(currentRelease.published_at)} +
+
+ {collapseHashes(currentRelease.body || 'No release notes.')} +
+
+ )} + + {/* Previous releases */} + {otherReleases.length > 0 && ( +
+
+ Previous Releases +
+ {otherReleases.map((rel, idx) => ( +
+ + {expandedIdx === idx && ( +
+ {collapseHashes(rel.body || 'No release notes.')} +
+ )} +
+ ))} +
+ )} +
+ + {/* Footer */} +
+ {currentRelease?.url && ( + + View on GitHub + + )} + +
+
+
+ ) +} diff --git a/web/src/components/upsell/IncidentPlaceholder.tsx b/web/src/components/upsell/IncidentPlaceholder.tsx new file mode 100644 index 0000000..9c44d0c --- /dev/null +++ b/web/src/components/upsell/IncidentPlaceholder.tsx @@ -0,0 +1,29 @@ +import { upsellURL } from '../../lib/constants' +import { Card, CardHeader, CardTitle } from '../ui/Card' + +export function IncidentPlaceholder() { + return ( + + + Incidents + +
+
🔒
+

+ Incident Management +

+

+ Create, track, and resolve incidents with AI-powered root cause analysis. +

+ + Available on Dstl8 Pro + +
+
+ ) +} diff --git a/web/src/components/upsell/MobiusUpgrade.tsx b/web/src/components/upsell/MobiusUpgrade.tsx new file mode 100644 index 0000000..7234d6a --- /dev/null +++ b/web/src/components/upsell/MobiusUpgrade.tsx @@ -0,0 +1,30 @@ +import { upsellURL } from '../../lib/constants' +import { Card, CardHeader, CardTitle } from '../ui/Card' + +export function MobiusUpgrade() { + return ( + + + Mobius AI + +
+
🤖
+

+ Continuous AI Analysis +

+

+ Mobius provides real-time AI insights, anomaly detection, and intelligent alerting + across all your log streams. +

+ + Try Mobius Free + +
+
+ ) +} diff --git a/web/src/components/upsell/RetentionBanner.tsx b/web/src/components/upsell/RetentionBanner.tsx new file mode 100644 index 0000000..68e5c04 --- /dev/null +++ b/web/src/components/upsell/RetentionBanner.tsx @@ -0,0 +1,27 @@ +import { upsellURL } from '../../lib/constants' + +export function RetentionBanner() { + return ( +
+
+
+

+ Analyzing last 60 minutes of log data +

+

+ Dstl8 Lite keeps a rolling 60-minute window. For unlimited retention and historical analysis, + upgrade to the full dashboard. +

+
+ + Get Full Retention + +
+
+ ) +} diff --git a/web/src/hooks/useTheme.ts b/web/src/hooks/useTheme.ts new file mode 100644 index 0000000..0c42181 --- /dev/null +++ b/web/src/hooks/useTheme.ts @@ -0,0 +1,8 @@ +import { useContext } from 'react' +import { ThemeContext } from '../components/ui/ThemeProvider' + +export function useTheme() { + const ctx = useContext(ThemeContext) + if (!ctx) throw new Error('useTheme must be used within ThemeProvider') + return ctx +} diff --git a/web/src/hooks/useWebSocket.ts b/web/src/hooks/useWebSocket.ts new file mode 100644 index 0000000..bd164ce --- /dev/null +++ b/web/src/hooks/useWebSocket.ts @@ -0,0 +1,22 @@ +import { useEffect, useRef, useCallback, useState } from 'react' +import { DashboardWebSocket } from '../api/websocket' +import type { WebSocketUpdate } from '../api/types' + +export function useWebSocket() { + const wsRef = useRef(null) + const [lastUpdate, setLastUpdate] = useState(null) + + const onUpdate = useCallback((update: WebSocketUpdate) => { + setLastUpdate(update) + }, []) + + useEffect(() => { + const ws = new DashboardWebSocket() + wsRef.current = ws + ws.subscribe(onUpdate) + ws.connect() + return () => ws.disconnect() + }, [onUpdate]) + + return lastUpdate +} diff --git a/web/src/index.css b/web/src/index.css new file mode 100644 index 0000000..2841678 --- /dev/null +++ b/web/src/index.css @@ -0,0 +1,851 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* ── Dstl8 Design System ── */ +:root { + --radius: 0.5rem; + + /* Light mode (default) */ + --background: hsl(0 0% 100%); + --bg-primary: hsl(210 40% 98%); + --bg-secondary: hsl(0 0% 100%); + --bg-tertiary: hsl(210 40% 96.1%); + --bg-card: hsl(0 0% 100%); + + --text-primary: hsl(222.2 84% 4.9%); + --text-secondary: hsl(215.4 16.3% 46.9%); + --text-muted: hsl(215 16.3% 56.9%); + + --border: hsl(214.3 31.8% 91.4%); + --input: hsl(214.3 31.8% 91.4%); + + --critical: hsl(0 84.2% 60.2%); + --critical-glow: hsla(0 84.2% 60.2% / 0.15); + --serious: hsl(27 87% 50%); + --serious-glow: hsla(27 87% 50% / 0.12); + --moderate: hsl(38 92% 40%); + --moderate-glow: hsla(38 92% 40% / 0.1); + --mild: hsl(142 71% 35%); + --mild-glow: hsla(142 71% 35% / 0.1); + --health-ok: hsl(160 60% 38%); + --health-ok-glow: hsla(160 60% 38% / 0.1); + + --accent-blue: hsl(217 91% 50%); + --accent-blue-hover: hsl(217 91% 45%); + --accent-glow: hsla(217 91% 50% / 0.12); + + /* Legacy compat for existing components */ + --color-bg: var(--bg-primary); + --color-bg-secondary: var(--bg-tertiary); + --color-text: var(--text-primary); + --color-text-secondary: var(--text-secondary); + --color-border: var(--border); + --color-card: var(--bg-card); + --color-card-hover: var(--bg-tertiary); + --color-accent: var(--accent-blue); +} + +.dark { + --background: hsl(222.2 84% 4.9%); + --bg-primary: hsl(222.2 84% 4.9%); + --bg-secondary: hsl(222.2 84% 6.5%); + --bg-tertiary: hsl(217.2 32.6% 17.5%); + --bg-card: hsl(222.2 84% 6.5%); + + --text-primary: hsl(210 40% 98%); + --text-secondary: hsl(215 20.2% 65.1%); + --text-muted: hsl(215 16.3% 46.9%); + + --border: hsl(217.2 32.6% 17.5%); + --input: hsl(217.2 32.6% 17.5%); + + --critical: hsl(0 84.2% 60.2%); + --critical-glow: hsla(0 84.2% 60.2% / 0.25); + --serious: hsl(30 87% 67%); + --serious-glow: hsla(30 87% 67% / 0.2); + --moderate: hsl(43 74% 66%); + --moderate-glow: hsla(43 74% 66% / 0.15); + --mild: hsl(142 71% 45%); + --mild-glow: hsla(142 71% 45% / 0.15); + --health-ok: hsl(160 60% 45%); + --health-ok-glow: hsla(160 60% 45% / 0.15); + + --accent-blue: hsl(217 91% 60%); + --accent-blue-hover: hsl(217 91% 55%); + --accent-glow: hsla(217 91% 60% / 0.2); +} + +* { box-sizing: border-box; } + +body { + margin: 0; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + font-size: 14px; + line-height: 1.5; + color: var(--text-primary); + background: var(--bg-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ── App Shell ── */ +.app-shell { + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; +} + +.app-header { + height: 56px; + background: var(--bg-card); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; + flex-shrink: 0; + z-index: 100; +} + +.app-header-left { + display: flex; + align-items: center; +} + +.app-header-right { + display: flex; + align-items: center; + gap: 8px; +} + +.app-logo { + display: flex; + align-items: center; + gap: 10px; + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + text-decoration: none; + cursor: pointer; +} + +.header-breadcrumb { + display: flex; + align-items: center; + gap: 8px; + margin-left: 16px; + padding-left: 16px; + border-left: 1px solid var(--border); +} + +.breadcrumb-link { + font-size: 13px; + color: var(--text-muted); + text-decoration: none; + cursor: pointer; + transition: color 0.15s ease; +} + +.breadcrumb-link:hover { color: var(--text-primary); } + +.breadcrumb-separator { color: var(--text-muted); font-size: 12px; } + +.breadcrumb-current { font-size: 13px; font-weight: 500; color: var(--text-primary); } + +.header-icon-btn { + width: 36px; + height: 36px; + border-radius: calc(var(--radius) - 2px); + border: 1px solid transparent; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s ease; +} + +.header-icon-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.header-icon-btn svg { width: 18px; height: 18px; } + +.header-user { + display: flex; + align-items: center; + gap: 10px; + padding: 6px 12px 6px 6px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: calc(var(--radius) - 2px); + cursor: pointer; + transition: all 0.15s ease; +} + +.header-user:hover { border-color: var(--text-muted); } + +.header-user-avatar { + width: 26px; + height: 26px; + border-radius: 50%; + background: var(--accent-blue); + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 600; + color: white; +} + +.header-user-info { display: flex; flex-direction: column; gap: 1px; } +.header-user-name { font-size: 12px; font-weight: 500; color: var(--text-primary); } +.header-user-role { font-size: 10px; color: var(--text-muted); } + +/* ── Sidebar ── */ +.app-container { + display: flex; + flex: 1; + overflow: hidden; +} + +.app-sidebar { + width: 220px; + background: var(--bg-card); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + flex-shrink: 0; +} + +.sidebar-nav { + flex: 1; + padding: 16px 12px; + display: flex; + flex-direction: column; + gap: 4px; + overflow-y: auto; +} + +.sidebar-nav-item { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + border-radius: calc(var(--radius) - 2px); + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); + text-decoration: none; + cursor: pointer; + transition: all 0.15s ease; + border: none; + background: transparent; + width: 100%; + text-align: left; +} + +.sidebar-nav-item:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.sidebar-nav-item.active { + background: var(--accent-glow); + color: var(--accent-blue); +} + +.sidebar-nav-item.locked { + opacity: 0.4; +} +.sidebar-nav-item.locked:hover { + opacity: 0.6; +} + +.sidebar-nav-item svg { width: 18px; height: 18px; flex-shrink: 0; } + +.sidebar-bottom { + padding: 12px; + border-top: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 4px; +} + +/* ── Main Content ── */ +.app-main { + flex: 1; + overflow-y: auto; + background: var(--bg-primary); +} + +.page-content { + padding: 24px 32px; + max-width: 1400px; + margin: 0 auto; +} + +.page-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; +} + +.page-title { + font-size: 20px; + font-weight: 600; + color: var(--text-primary); +} + +/* ── Buttons ── */ +.btn-primary { + display: inline-flex; + align-items: center; + gap: 8px; + font-family: 'Inter', sans-serif; + font-size: 13px; + font-weight: 500; + padding: 10px 16px; + border-radius: calc(var(--radius) - 2px); + border: none; + background: var(--accent-blue); + color: white; + cursor: pointer; + transition: all 0.15s ease; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + text-decoration: none; +} + +.btn-primary:hover { + background: var(--accent-blue-hover); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); +} + +.btn-primary svg { width: 16px; height: 16px; } + +/* ── Cards ── */ +.dstl8-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); +} + +.dstl8-card-header { + padding: 16px 20px; + border-bottom: 1px solid var(--border); + display: flex; + justify-content: space-between; + align-items: center; +} + +.dstl8-card-title { + font-size: 13px; + font-weight: 600; + letter-spacing: 0.01em; + color: var(--text-primary); +} + +.dstl8-card-body { padding: 20px; } + +/* ── Workspace Card ── */ +.workspace-card { + background: var(--bg-card); + border: 2px solid var(--border); + border-radius: var(--radius); + padding: 24px; + cursor: pointer; + transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); +} + +.workspace-card:hover { + transform: translateY(-4px); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); +} + +.workspace-card.ok { + border-color: var(--health-ok); + box-shadow: 0 0 8px var(--health-ok-glow); +} + +.workspace-card.ok:hover { + box-shadow: 0 0 14px var(--health-ok-glow), 0 10px 15px -3px rgba(0, 0, 0, 0.1); +} + +.workspace-card.critical { + border-color: var(--critical); + box-shadow: 0 0 16px var(--critical-glow); +} + +.workspace-card.serious { + border-color: var(--serious); + box-shadow: 0 0 12px var(--serious-glow); +} + +.workspace-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +.workspace-name { + font-size: 18px; + font-weight: 600; + letter-spacing: -0.01em; +} + +.workspace-health-dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.workspace-card.ok .workspace-health-dot { + background: var(--health-ok); + box-shadow: 0 0 8px var(--health-ok-glow); + animation: pulse-dot 2s ease-in-out infinite; +} + +.workspace-stats { + display: flex; + gap: 20px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +.workspace-stat { display: flex; flex-direction: column; gap: 2px; } + +.workspace-stat-label { + font-size: 11px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.workspace-stat-value { + font-size: 20px; + font-weight: 700; + font-variant-numeric: tabular-nums; +} + +.workspace-meta { + display: flex; + gap: 16px; + padding-top: 12px; + border-top: 1px solid var(--border); +} + +.workspace-meta-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--text-secondary); +} + +/* ── Badges ── */ +.badge { + font-family: 'Inter', sans-serif; + font-size: 11px; + font-weight: 600; + padding: 3px 10px; + border-radius: 9999px; + display: inline-block; +} + +.badge-critical { background: hsla(0 84.2% 60.2% / 0.15); color: var(--critical); } +.badge-serious { background: hsla(30 87% 60% / 0.15); color: var(--serious); } +.badge-ok { background: hsla(160 60% 45% / 0.15); color: var(--health-ok); } + +/* ── Source Type Card (Details page) ── */ +.source-type-card { + background: var(--bg-card); + border-radius: var(--radius); + padding: 20px 24px; + cursor: pointer; + transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1); + border: 2px solid var(--border); + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + display: flex; + gap: 24px; +} + +.source-type-card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px -4px rgba(0, 0, 0, 0.12); +} + +.source-type-card.ok { + border-color: var(--health-ok); + box-shadow: 0 0 8px var(--health-ok-glow); +} + +.source-type-left { flex: 1; min-width: 0; } + +.source-type-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 14px; +} + +.source-type-health-dot { + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; +} + +.source-type-card.ok .source-type-health-dot { + background: var(--health-ok); + box-shadow: 0 0 8px var(--health-ok-glow); +} + +.source-type-name { + font-size: 17px; + font-weight: 600; + letter-spacing: -0.01em; +} + +.source-type-stats { + display: flex; + gap: 24px; + margin-bottom: 12px; +} + +.source-type-stat { display: flex; flex-direction: column; gap: 2px; } + +.source-type-stat-label { + font-size: 11px; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.02em; +} + +.source-type-stat-value { + font-size: 20px; + font-weight: 700; + font-variant-numeric: tabular-nums; +} + +.source-type-right { + width: 320px; + flex-shrink: 0; + border-left: 1px solid var(--border); + padding-left: 24px; + display: flex; + flex-direction: column; +} + +/* ── Chat Widget (Mobius) ── */ +.chat-widget { + display: flex; + flex-direction: column; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} + +.chat-header { + padding: 16px 20px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + gap: 10px; +} + +.chat-avatar { + width: 28px; + height: 28px; + border-radius: 50%; + background: linear-gradient(135deg, hsl(30 80% 55%) 0%, hsl(25 90% 50%) 100%); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: white; + font-size: 14px; +} + +.chat-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--text-muted); +} + +.chat-status-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--health-ok); + animation: pulse-status 2s ease-in-out infinite; +} + +.chat-messages { + flex: 1; + overflow-y: auto; + padding: 16px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.chat-message { display: flex; gap: 12px; } +.chat-message.user { flex-direction: row-reverse; } + +.chat-message-avatar { + width: 28px; + height: 28px; + border-radius: 50%; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 600; +} + +.chat-message.assistant .chat-message-avatar { + background: linear-gradient(135deg, hsl(30 80% 55%), hsl(25 90% 50%)); + color: white; +} + +.chat-message.user .chat-message-avatar { + background: var(--accent-blue); + color: white; +} + +.chat-message-content { flex: 1; min-width: 0; } + +.chat-message-bubble { + padding: 10px 14px; + border-radius: var(--radius); + font-size: 13px; + line-height: 1.5; +} + +.chat-message.assistant .chat-message-bubble { + background: var(--bg-tertiary); + color: var(--text-primary); + border-top-left-radius: 4px; +} + +.chat-message.user .chat-message-bubble { + background: var(--accent-blue); + color: white; + border-top-right-radius: 4px; +} + +.chat-message-time { + font-size: 10px; + color: var(--text-muted); + margin-top: 4px; + padding: 0 4px; +} + +.chat-message.user .chat-message-time { text-align: right; } + +.chat-input-container { + padding: 12px 16px; + border-top: 1px solid var(--border); + background: var(--bg-tertiary); +} + +.chat-input-wrapper { display: flex; gap: 10px; align-items: flex-end; } + +.chat-input { + flex: 1; + padding: 10px 14px; + border-radius: calc(var(--radius) - 2px); + border: 1px solid var(--border); + background: var(--bg-secondary); + color: var(--text-primary); + font-family: 'Inter', sans-serif; + font-size: 13px; + resize: none; + min-height: 40px; + outline: none; + transition: border-color 0.15s ease; +} + +.chat-input::placeholder { color: var(--text-muted); } +.chat-input:focus { border-color: var(--accent-blue); } + +.chat-send-btn { + width: 40px; + height: 40px; + border-radius: calc(var(--radius) - 2px); + border: none; + background: var(--accent-blue); + color: white; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s ease; + flex-shrink: 0; +} + +.chat-send-btn:hover { background: var(--accent-blue-hover); } +.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; } +.chat-send-btn svg { width: 18px; height: 18px; } + +/* ── Layout Grids ── */ +.main-layout { + display: grid; + grid-template-columns: 1fr 320px; + gap: 24px; + flex: 1; +} + +.main-layout-full { grid-template-columns: 1fr; } + +.left-column { min-width: 0; } + +.right-column { + display: flex; + flex-direction: column; + position: sticky; + top: 0; + height: calc(100vh - 56px - 48px); + align-self: start; +} + +.workspace-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); + gap: 24px; +} + +/* ── Summary Stats Bar ── */ +.stats-bar { + display: flex; + gap: 32px; + padding: 16px 20px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + margin-bottom: 24px; +} + +.stat-item { display: flex; align-items: center; gap: 10px; } + +.stat-icon { + width: 36px; + height: 36px; + border-radius: calc(var(--radius) - 2px); + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; +} + +.stat-icon.blue { background: var(--accent-glow); color: var(--accent-blue); } +.stat-icon.green { background: var(--health-ok-glow); color: var(--health-ok); } +.stat-icon.red { background: var(--critical-glow); color: var(--critical); } +.stat-icon.orange { background: var(--serious-glow); color: var(--serious); } + +.stat-value { + font-size: 20px; + font-weight: 700; + font-variant-numeric: tabular-nums; + color: var(--text-primary); +} + +.stat-label { font-size: 12px; color: var(--text-muted); } + +/* ── Upgrade Page ── */ +.upgrade-page { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 80px 32px; + text-align: center; +} + +.upgrade-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.4; } +.upgrade-title { font-size: 24px; font-weight: 600; margin-bottom: 8px; color: var(--text-primary); } +.upgrade-desc { font-size: 14px; color: var(--text-secondary); max-width: 400px; margin-bottom: 24px; } + +/* ── Scrollbar ── */ +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } + +/* ── Animations ── */ +@keyframes pulse-dot { + 0%, 100% { opacity: 1; box-shadow: 0 0 6px var(--health-ok-glow); } + 50% { opacity: 0.6; box-shadow: 0 0 12px var(--health-ok-glow); } +} + +@keyframes pulse-status { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } +} + +/* ── What's New Modal markdown ── */ +.whats-new-markdown { + font-size: 13px; + line-height: 1.6; + color: var(--text-secondary); +} +.whats-new-markdown h1, +.whats-new-markdown h2, +.whats-new-markdown h3 { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); + margin: 12px 0 4px; +} +.whats-new-markdown h1 { font-size: 15px; } +.whats-new-markdown p { + margin: 4px 0; +} +.whats-new-markdown ul, +.whats-new-markdown ol { + padding-left: 20px; + margin: 4px 0; +} +.whats-new-markdown li { + margin: 2px 0; +} +.whats-new-markdown code { + font-size: 12px; + background: var(--bg-tertiary); + padding: 1px 5px; + border-radius: 3px; +} +.whats-new-markdown pre { + background: var(--bg-tertiary); + padding: 8px 12px; + border-radius: 6px; + overflow-x: auto; + margin: 8px 0; +} +.whats-new-markdown pre code { + background: none; + padding: 0; +} +.whats-new-markdown a { + color: var(--accent-blue); + text-decoration: none; +} +.whats-new-markdown a:hover { + text-decoration: underline; +} diff --git a/web/src/lib/colors.ts b/web/src/lib/colors.ts new file mode 100644 index 0000000..283551f --- /dev/null +++ b/web/src/lib/colors.ts @@ -0,0 +1,24 @@ +// Severity colors matching Dstl8 cloud dashboard +export const SEVERITY_COLORS: Record = { + FATAL: '#b91c1c', + CRITICAL: '#b91c1c', + ERROR: '#dc2626', + WARN: '#f97316', + WARNING: '#f97316', + INFO: '#3b82f6', + DEBUG: '#6b7280', + TRACE: '#10b981', + UNKNOWN: '#9ca3af', +} + +export const SEVERITY_ORDER = ['FATAL', 'CRITICAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE'] + +// Heatmap gradient (negative → neutral → positive) +export const HEATMAP_COLORS = [ + '#7f1d1d', '#991b1b', '#b91c1c', '#dc2626', '#ef4444', '#f87171', + '#fbbf24', '#a3e635', '#4ade80', '#22c55e', '#16a34a', '#166534', +] + +export function severityColor(sev: string): string { + return SEVERITY_COLORS[sev.toUpperCase()] || SEVERITY_COLORS.UNKNOWN +} diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts new file mode 100644 index 0000000..0e30d20 --- /dev/null +++ b/web/src/lib/constants.ts @@ -0,0 +1,10 @@ +export const API_BASE = '/api' + +export const UTM_PARAMS = { + source: 'gonzo', + medium: 'dstl8lite', +} as const + +export function upsellURL(campaign: string): string { + return `https://app.dstl8.ai/lp/gonzo-to-dstl8-pro/?utm_source=${UTM_PARAMS.source}&utm_medium=${UTM_PARAMS.medium}&utm_campaign=${campaign}` +} diff --git a/web/src/lib/icons.tsx b/web/src/lib/icons.tsx new file mode 100644 index 0000000..358f470 --- /dev/null +++ b/web/src/lib/icons.tsx @@ -0,0 +1,68 @@ +const A = { + viewBox: '0 0 24 24', + fill: 'none', + stroke: 'currentColor', + strokeWidth: 2, + strokeLinecap: 'round' as const, + strokeLinejoin: 'round' as const, +} + +export const Icons = { + workspaces: ( + + ), + overview: ( + + ), + incidents: ( + + ), + sources: ( + + ), + users: ( + + ), + audit: ( + + ), + account: ( + + ), + settings: ( + + ), + support: ( + + ), + sparkle: ( + + ), + search: ( + + ), + moon: ( + + ), + sun: ( + + ), + send: ( + + ), + plus: ( + + ), + chevronRight: ( + + ), + lock: ( + + ), + logs: ( + + ), + heatmap: ( + + ), +} diff --git a/web/src/main.tsx b/web/src/main.tsx new file mode 100644 index 0000000..fab1219 --- /dev/null +++ b/web/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import App from './App' +import './index.css' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/web/src/pages/HeatmapPage.tsx b/web/src/pages/HeatmapPage.tsx new file mode 100644 index 0000000..3324f6a --- /dev/null +++ b/web/src/pages/HeatmapPage.tsx @@ -0,0 +1,418 @@ +import { useEffect, useState, useRef, useMemo, useCallback } from 'react' +import { fetchSentiment, fetchInsightsParams } from '../api/client' +import type { SentimentData, InsightsParams } from '../api/types' +import { useWebSocket } from '../hooks/useWebSocket' + +// Sentiment color scale matching Dstl8 portal-ui +const SENTIMENT_COLORS: { min: number; max: number; color: string; label: string }[] = [ + { min: -1.0, max: -0.75, color: '#b71c1c', label: 'Critical' }, + { min: -0.75, max: -0.5, color: '#d32f2f', label: 'Poor' }, + { min: -0.5, max: -0.25, color: '#ff6f00', label: 'Weak' }, + { min: -0.25, max: 0.0, color: '#eee472', label: 'Fair' }, + { min: 0.0, max: 0.38, color: '#b3c49f', label: 'Neutral' }, + { min: 0.38, max: 1.01, color: '#388e3c', label: 'Positive' }, +] + +const EMPTY_COLOR = 'var(--bg-tertiary)' + +function sentimentColor(value: number): string { + for (const band of SENTIMENT_COLORS) { + if (value >= band.min && value < band.max) return band.color + } + return SENTIMENT_COLORS[SENTIMENT_COLORS.length - 1].color +} + +function formatTime(ts: number): string { + const d = new Date(ts * 1000) + const m = d.getMinutes().toString().padStart(2, '0') + const s = d.getSeconds().toString().padStart(2, '0') + return `${m}:${s}` +} + +function sentimentLabel(value: number): string { + for (const band of SENTIMENT_COLORS) { + if (value >= band.min && value < band.max) return band.label + } + return 'Positive' +} + +interface CellData { + sentiment: number + logCount: number + timestamp: number +} + +export function HeatmapPage() { + const [data, setData] = useState(null) + const [groupBy, setGroupBy] = useState('pod') + const [loading, setLoading] = useState(true) + const [params, setParams] = useState(null) + const [tooltip, setTooltip] = useState<{ x: number; y: number; flipBelow?: boolean; cell: CellData; group: string } | null>(null) + const containerRef = useRef(null) + const [containerWidth, setContainerWidth] = useState(0) + const wsUpdate = useWebSocket() + const initialLoadDone = useRef(false) + + // Fetch available dimensions once + useEffect(() => { + fetchInsightsParams().then(setParams).catch(() => {}) + }, []) + + // Auto-select best groupBy based on available data + useEffect(() => { + if (!params) return + // Pick the first dimension that has more than 1 distinct value (i.e. not just "unknown") + const candidates: { key: string; values?: string[] }[] = [ + { key: 'pod', values: params.pods }, + { key: 'namespace', values: params.namespaces }, + { key: 'service', values: params.services }, + { key: 'host', values: params.hosts }, + { key: 'deployment', values: params.deployments }, + ] + for (const c of candidates) { + const real = c.values?.filter((v) => v !== 'unknown' && v !== 'default') ?? [] + if (real.length > 1) { + setGroupBy(c.key) + return + } + } + }, [params]) + + const loadData = useCallback(() => { + fetchSentiment(groupBy) + .then(setData) + .catch(() => {}) + .finally(() => { + if (!initialLoadDone.current) { + initialLoadDone.current = true + setLoading(false) + } + }) + }, [groupBy]) + + // Initial load + refresh on groupBy change + useEffect(() => { + initialLoadDone.current = false + setLoading(true) + loadData() + }, [groupBy]) // eslint-disable-line react-hooks/exhaustive-deps + + // Real-time updates (no loading flash) + useEffect(() => { + if (wsUpdate && initialLoadDone.current) { + loadData() + } + }, [wsUpdate, loadData]) + + // Build grid data: rows = group_values, columns = sorted unique timestamps + const { rows, columns, grid } = useMemo(() => { + if (!data || data.group_values.length === 0) { + return { rows: [] as string[], columns: [] as number[], grid: new Map>() } + } + + // Collect all unique timestamps + const tsSet = new Set() + for (const b of data.buckets) tsSet.add(b.timestamp) + const cols = Array.from(tsSet).sort((a, b) => a - b) + + // Build grid map + const g = new Map>() + for (const b of data.buckets) { + if (!g.has(b.group_value)) g.set(b.group_value, new Map()) + g.get(b.group_value)!.set(b.timestamp, { + sentiment: b.sentiment, + logCount: b.log_count, + timestamp: b.timestamp, + }) + } + + // Sort rows alphabetically + const sortedRows = [...data.group_values].sort((a, b) => a.localeCompare(b)) + + return { rows: sortedRows, columns: cols, grid: g } + }, [data]) + + // Track container width for column limiting + // Re-run when loading changes so we attach after the card div mounts + useEffect(() => { + const el = containerRef.current + if (!el) return + const ro = new ResizeObserver((entries) => { + for (const entry of entries) { + setContainerWidth(entry.contentRect.width) + } + }) + ro.observe(el) + return () => ro.disconnect() + }, [loading]) + + const CELL_SIZE = 20 + const CELL_GAP = 2 + const LABEL_WIDTH = 280 + + // Show as many columns as fit at fixed cell size, dropping oldest from left + const visibleColumns = useMemo(() => { + if (columns.length === 0) return columns + if (containerWidth === 0) return [] as number[] + // containerWidth = card width (from ResizeObserver on the card div) + // Subtract label column and inner padding (16px each side = 32px) + const available = containerWidth - LABEL_WIDTH - 32 + const maxCols = Math.max(1, Math.floor(available / (CELL_SIZE + CELL_GAP))) + // available space is correct — data just hasn't accumulated enough columns yet + if (columns.length <= maxCols) return columns + return columns.slice(columns.length - maxCols) + }, [columns, containerWidth]) + + // Build group-by options, showing count of distinct values + const groupByOptions = useMemo(() => { + const dims: { value: string; label: string; values?: string[] }[] = [ + { value: 'pod', label: 'Pod', values: params?.pods }, + { value: 'namespace', label: 'Namespace', values: params?.namespaces }, + { value: 'service', label: 'Service', values: params?.services }, + { value: 'host', label: 'Host', values: params?.hosts }, + { value: 'deployment', label: 'Deployment', values: params?.deployments }, + ] + return dims.map((d) => { + const real = d.values?.filter((v) => v !== 'unknown' && v !== 'default') ?? [] + return { + value: d.value, + label: real.length > 0 ? `${d.label} (${real.length})` : d.label, + hasData: real.length > 0, + } + }) + }, [params]) + + return ( +
+
+

Severity Heatmap

+
+ Group by + +
+
+ + {loading ? ( +
+ Loading heatmap data... +
+ ) : rows.length === 0 ? ( +
+ No data yet. Start sending logs to see the severity heatmap. +
+ ) : ( +
+ {/* Heatmap container — vertically scrollable, columns fit horizontally */} +
+
+ {/* Time axis header — right-aligned so data grows from the right */} +
+ {visibleColumns.map((ts) => { + // Show label every 5 seconds + const showLabel = ts % 5 === 0 + return ( +
+ {formatTime(ts)} +
+ ) + })} +
+ + {/* Heatmap rows */} + {rows.map((rowName) => ( +
+ {/* Row label */} +
+ {rowName} +
+ + {/* Cells — right-aligned so they grow from the right edge */} +
+ {visibleColumns.map((ts) => { + const cell = grid.get(rowName)?.get(ts) + return ( +
{ + if (!cell) return + const rect = e.currentTarget.getBoundingClientRect() + const containerRect = containerRef.current?.getBoundingClientRect() + if (containerRect) { + const relY = rect.top - containerRect.top + const distFromBottom = containerRect.height - relY - rect.height + // Show below cell if near top, but not if also near bottom + const flipBelow = relY < 60 && distFromBottom > 100 + setTooltip({ + x: rect.left - containerRect.left + rect.width / 2, + y: flipBelow ? relY + rect.height + 8 : relY - 8, + flipBelow, + cell, + group: rowName, + }) + } + }} + onMouseLeave={() => setTooltip(null)} + /> + ) + })} +
+
+ ))} +
+ + {/* Tooltip */} + {tooltip && ( +
+
{tooltip.group}
+
+ {formatTime(tooltip.cell.timestamp)} · {tooltip.cell.logCount} logs +
+
+ + {sentimentLabel(tooltip.cell.sentiment)} ({tooltip.cell.sentiment.toFixed(2)}) +
+
+ )} +
+ + {/* Legend */} +
+ {SENTIMENT_COLORS.map((band) => ( +
+ +
+
+ {band.label} +
+
+ [{band.min.toFixed(2)}, {band.max === 1.01 ? '1.0' : band.max.toFixed(2)}) +
+
+
+ ))} +
+
+ )} +
+ ) +} diff --git a/web/src/pages/LogViewerPage.tsx b/web/src/pages/LogViewerPage.tsx new file mode 100644 index 0000000..8e78ebe --- /dev/null +++ b/web/src/pages/LogViewerPage.tsx @@ -0,0 +1,621 @@ +import { useEffect, useState, useRef, useCallback } from 'react' +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + Legend, + CartesianGrid, +} from 'recharts' +import { fetchSeverityHistory, fetchLogs } from '../api/client' +import type { LogSample } from '../api/types' +import { SEVERITY_COLORS, SEVERITY_ORDER, severityColor } from '../lib/colors' +import { useWebSocket } from '../hooks/useWebSocket' + +const SEVERITY_LABELS: Record = { + FATAL: 'Fatal', + ERROR: 'Error', + WARN: 'Warn', + INFO: 'Info', + DEBUG: 'Debug', + TRACE: 'Trace', +} + +function formatTimestamp(ts: number): string { + const d = new Date(ts * 1000) + const mo = (d.getMonth() + 1).toString().padStart(2, '0') + const day = d.getDate().toString().padStart(2, '0') + const h = d.getHours().toString().padStart(2, '0') + const m = d.getMinutes().toString().padStart(2, '0') + const s = d.getSeconds().toString().padStart(2, '0') + return `${mo}/${day} ${h}:${m}:${s}` +} + +/** Stable identity for a log entry (timestamp + first 80 chars of message) */ +function logKey(log: LogSample): string { + return `${log.timestamp}:${log.message.slice(0, 80)}` +} + +export function LogViewerPage() { + const [chartData, setChartData] = useState[]>([]) + const [logs, setLogs] = useState([]) + const [search, setSearch] = useState('') + const [searchInput, setSearchInput] = useState('') + const [loading, setLoading] = useState(true) + const [expandedKey, setExpandedKey] = useState(null) + const [paused, setPaused] = useState(false) + const pausedRef = useRef(false) + const listRef = useRef(null) + const wsUpdate = useWebSocket() + const debounceRef = useRef>(null) + const [severityFilter, setSeverityFilter] = useState>(new Set()) + + // Debounced search + const handleSearchInput = useCallback((val: string) => { + setSearchInput(val) + if (debounceRef.current) clearTimeout(debounceRef.current) + debounceRef.current = setTimeout(() => setSearch(val), 300) + }, []) + + // Build fetch opts + const severityParam = severityFilter.size > 0 ? Array.from(severityFilter).join(',') : undefined + + // Fetch chart data — only when not paused + useEffect(() => { + if (pausedRef.current) return + fetchSeverityHistory(search ? { search } : undefined) + .then((data) => { + const mapped = data.map((point) => { + const d = new Date(point.timestamp * 1000) + const h = d.getHours().toString().padStart(2, '0') + const m = d.getMinutes().toString().padStart(2, '0') + const s = d.getSeconds().toString().padStart(2, '0') + return { + name: `${h}:${m}:${s}`, + ...point.counts, + total: point.total, + } + }) + setChartData(mapped) + }) + .catch(() => {}) + }, [wsUpdate, search]) // eslint-disable-line react-hooks/exhaustive-deps + + // Fetch logs — only when not paused + useEffect(() => { + if (pausedRef.current) return + fetchLogs({ limit: 500, search: search || undefined, severity: severityParam }) + .then(setLogs) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [wsUpdate, search, severityParam]) // eslint-disable-line react-hooks/exhaustive-deps + + // Auto-scroll when not paused + useEffect(() => { + if (!pausedRef.current && listRef.current) { + const el = listRef.current + requestAnimationFrame(() => { + el.scrollTop = el.scrollHeight + }) + } + }, [logs]) + + const togglePaused = useCallback(() => { + setPaused((prev) => { + const next = !prev + pausedRef.current = next + // When resuming, clear expanded row and scroll to bottom + if (!next) { + setExpandedKey(null) + if (listRef.current) { + requestAnimationFrame(() => { + listRef.current!.scrollTop = listRef.current!.scrollHeight + }) + } + } + return next + }) + }, []) + + const handleRowClick = useCallback((key: string) => { + // If user is selecting text (drag-to-copy), don't toggle the row + const sel = window.getSelection() + if (sel && sel.toString().length > 0) return + + setExpandedKey((prev) => { + if (prev === key) { + // Collapsing — resume and scroll to bottom + setPaused(false) + pausedRef.current = false + if (listRef.current) { + requestAnimationFrame(() => { + listRef.current!.scrollTop = listRef.current!.scrollHeight + }) + } + return null + } else { + // Expanding — pause + setPaused(true) + pausedRef.current = true + return key + } + }) + }, []) + + const toggleSeverity = useCallback((sev: string) => { + setSeverityFilter((prev) => { + const next = new Set(prev) + if (next.has(sev)) { + next.delete(sev) + } else { + next.add(sev) + } + return next + }) + }, []) + + const activeSeverities = SEVERITY_ORDER.filter((sev) => + chartData.some((d) => ((d[sev] as number) || 0) > 0) + ) + + const totalLogs = chartData.reduce((sum, d) => sum + ((d.total as number) || 0), 0) + + return ( +
+ {/* Header */} +
+

Log Viewer

+
+ {/* Search */} +
+ + + + + handleSearchInput(e.target.value)} + style={{ + width: 220, + height: 32, + paddingLeft: 28, + paddingRight: 8, + fontSize: 12, + border: '1px solid var(--border)', + borderRadius: 6, + background: 'var(--bg-card)', + color: 'var(--text-primary)', + outline: 'none', + }} + /> +
+ {/* Live / Paused toggle */} + + + {totalLogs.toLocaleString()} logs + +
+
+ + {/* Severity filter pills */} +
+ Severity: + {SEVERITY_ORDER.map((sev) => { + const active = severityFilter.has(sev) + const anyActive = severityFilter.size > 0 + return ( + + ) + })} + {severityFilter.size > 0 && ( + + )} +
+ + {/* Severity Chart */} +
+
+ + Log Severity Over Time + + + {paused ? 'paused' : `${chartData.length}s window`} + +
+ {chartData.length === 0 ? ( +
+ No data yet +
+ ) : ( + + + + + + + SEVERITY_LABELS[value] || value} + /> + {activeSeverities.map((sev, i) => ( + + ))} + + + )} +
+ + {/* Log Table */} +
+ {/* Table header */} +
+
+ Timestamp +
+
+ Severity +
+
+ Message +
+
+ + {/* Scrollable rows */} +
+ {loading ? ( +
+ Loading logs... +
+ ) : logs.length === 0 ? ( +
+ No logs yet. Start sending logs to see them here. +
+ ) : ( + <> + {logs.map((log, i) => { + const key = logKey(log) + const isExpanded = expandedKey === key + const attrs = log.attributes || {} + const badges = ['service', 'pod', 'namespace', 'host', 'deployment', 'environment', 'cluster', 'category'] + .filter((k) => attrs[k] && attrs[k] !== 'unknown' && attrs[k] !== 'default') + .map((k) => ({ key: k, value: attrs[k] })) + + return ( +
handleRowClick(key)} + style={{ + display: 'flex', + alignItems: isExpanded ? 'flex-start' : 'center', + borderBottom: '1px solid var(--border)', + padding: '6px 0', + minHeight: 36, + cursor: 'pointer', + transition: 'background 0.1s', + background: isExpanded ? 'var(--bg-tertiary)' : undefined, + }} + onMouseEnter={(e) => { if (!isExpanded) e.currentTarget.style.background = 'var(--bg-tertiary)' }} + onMouseLeave={(e) => { if (!isExpanded) e.currentTarget.style.background = 'transparent' }} + > + {/* Timestamp */} +
+ {formatTimestamp(log.timestamp)} +
+ + {/* Severity */} +
+ + {log.severity} + +
+ + {/* Message */} +
+ {isExpanded ? ( +
+
+ + + +
+ {log.message} +
+
+ + {/* Metadata badges */} + {badges.length > 0 && ( +
+ {badges.map((b) => ( + + {b.key}: {b.value} + + ))} +
+ )} + + {/* Full attributes */} + {Object.keys(attrs).length > 0 && ( +
+ {Object.entries(attrs).map(([k, v]) => ( +
+ {k} + = + {v} +
+ ))} +
+ )} +
+ ) : ( +
+ + + + + {log.message} + + {badges.length > 0 && ( +
+ {badges.slice(0, 3).map((b) => ( + + {b.value} + + ))} + {badges.length > 3 && ( + + +{badges.length - 3} + + )} +
+ )} +
+ )} +
+
+ ) + })} +
+ + )} +
+
+
+ ) +} diff --git a/web/src/pages/SourcesPage.tsx b/web/src/pages/SourcesPage.tsx new file mode 100644 index 0000000..60bda9c --- /dev/null +++ b/web/src/pages/SourcesPage.tsx @@ -0,0 +1,378 @@ +import { useEffect, useState, useCallback } from 'react' +import { fetchStreams, fetchStatus, fetchInsightsParams } from '../api/client' +import type { StreamInfo, StatusInfo, InsightsParams } from '../api/types' +import { useWebSocket } from '../hooks/useWebSocket' +import { upsellURL } from '../lib/constants' + +interface Props { + onOpenStream?: (dimensionType: string, dimensionValue: string) => void +} + +interface SourceGroup { + source: string + streams: StreamInfo[] + totalLogs: number + activeCount: number +} + +interface DimensionGroup { + label: string + values: { name: string; active: boolean }[] +} + +function groupStreams(streams: StreamInfo[]): SourceGroup[] { + const map = new Map() + for (const s of streams) { + const key = s.source || 'unknown' + if (!map.has(key)) map.set(key, []) + map.get(key)!.push(s) + } + const groups: SourceGroup[] = [] + for (const [source, items] of map) { + groups.push({ + source, + streams: items, + totalLogs: items.reduce((sum, s) => sum + s.log_count, 0), + activeCount: items.filter((s) => s.active).length, + }) + } + groups.sort((a, b) => b.totalLogs - a.totalLogs) + return groups +} + +function buildDimensionGroups(params: InsightsParams, streams: StreamInfo[]): DimensionGroup[] { + const groups: DimensionGroup[] = [] + const activeStreams = new Set(streams.filter((s) => s.active).map((s) => s.stream)) + const addGroup = (label: string, values: string[] | undefined) => { + if (!values || values.length === 0) return + groups.push({ + label, + values: values.map((v) => ({ + name: v, + active: activeStreams.size > 0, + })), + }) + } + addGroup('Host', params.hosts) + addGroup('Service', params.services) + addGroup('Deployment', params.deployments) + addGroup('Namespace', params.namespaces) + addGroup('Pod', params.pods) + addGroup('Environment', params.environments) + addGroup('Cluster', params.clusters) + return groups +} + +const sourceIcons: Record = { + file: '\u{1F4C4}', + stdin: '\u{2328}', + k8s: '\u{2699}', + otlp: '\u{1F4E1}', + unknown: '\u{2753}', +} + +function formatLastSeen(lastSeen: string): string { + const d = new Date(lastSeen) + const now = Date.now() + const diff = Math.floor((now - d.getTime()) / 1000) + if (diff < 60) return `${diff}s ago` + if (diff < 3600) return `${Math.floor(diff / 60)}m ago` + if (diff < 86400) return `${Math.floor(diff / 3600)}h ago` + return `${Math.floor(diff / 86400)}d ago` +} + +export function SourcesPage({ onOpenStream }: Props) { + const [streams, setStreams] = useState([]) + const [, setStatus] = useState(null) + const [params, setParams] = useState(null) + const [loading, setLoading] = useState(true) + const [expandedSource, setExpandedSource] = useState(null) + const wsUpdate = useWebSocket() + + useEffect(() => { + Promise.all([fetchStreams(), fetchStatus(), fetchInsightsParams()]) + .then(([s, st, p]) => { setStreams(s); setStatus(st); setParams(p) }) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [wsUpdate]) + + const groups = groupStreams(streams) + const dimensionGroups = params ? buildDimensionGroups(params, streams) : [] + const totalSources = groups.length + const activeSources = groups.filter((g) => g.activeCount > 0).length + const totalStreams = streams.length + const activeStreamCount = streams.filter((s) => s.active).length + + const [showAddModal, setShowAddModal] = useState(false) + const dismissAddModal = useCallback(() => setShowAddModal(false), []) + + return ( +
+
+

Sources

+ +
+ + {showAddModal && ( +
{ if (e.target === e.currentTarget) dismissAddModal() }} + > +
+

+ Add Source +

+

+ Add always-on sources for AWS, Vercel, Supabase, OTel and more for continuous runtime feedback for your AI tooling. +

+
+ + + Learn More + +
+
+
+ )} + + {/* Summary stats row — matches Dstl8 sources page */} +
+ + + +
+ + {/* Source list */} + {loading ? ( +
+ Loading sources... +
+ ) : groups.length === 0 ? ( +
+ No active sources. Start sending logs to Gonzo to see sources here. +
+ ) : ( +
+ {groups.map((g) => { + const isExpanded = expandedSource === g.source + const isHealthy = g.activeCount > 0 + return ( +
+ {/* Source row */} +
setExpandedSource(isExpanded ? null : g.source)} + style={{ + display: 'flex', + alignItems: 'center', + gap: 16, + padding: '16px 24px', + background: 'var(--bg-card)', + border: '1px solid var(--border)', + borderBottom: isExpanded ? 'none' : undefined, + borderTopLeftRadius: 'var(--radius)', + borderTopRightRadius: 'var(--radius)', + borderBottomLeftRadius: isExpanded ? 0 : 'var(--radius)', + borderBottomRightRadius: isExpanded ? 0 : 'var(--radius)', + marginBottom: isExpanded ? 0 : 8, + cursor: 'pointer', + transition: 'all 0.15s ease', + }} + onMouseEnter={(e) => { if (!isExpanded) e.currentTarget.style.background = 'var(--bg-tertiary)' }} + onMouseLeave={(e) => { if (!isExpanded) e.currentTarget.style.background = 'var(--bg-card)' }} + > + {/* Source icon */} + + {sourceIcons[g.source] || sourceIcons.unknown} + + + {/* Source name + meta */} +
+
+ {g.source.charAt(0).toUpperCase() + g.source.slice(1)} +
+
+ {g.streams.length} stream{g.streams.length !== 1 ? 's' : ''} · Last seen { + g.streams.length > 0 ? formatLastSeen(g.streams[0].last_seen) : 'never' + } +
+
+ + {/* Health badge */} +
+ + + {isHealthy ? 'Healthy' : 'Inactive'} + +
+ + {/* Chevron */} + + ▼ + +
+ + {/* Expanded streams panel */} + {isExpanded && ( +
+ {/* Dimension groups — shows streams grouped by Host, Service, etc. */} + {dimensionGroups.length > 0 && ( +
+
+ Dimensions +
+ {dimensionGroups.map((dim) => ( +
+
+ {dim.label} ({dim.values.length}) +
+ {dim.values.map((v) => ( +
{ + e.stopPropagation() + onOpenStream?.(dim.label.toLowerCase(), v.name) + }} + style={{ + display: 'flex', + alignItems: 'center', + gap: 12, + padding: '6px 12px 6px 20px', + cursor: onOpenStream ? 'pointer' : 'default', + borderRadius: 'calc(var(--radius) - 2px)', + transition: 'background 0.15s ease', + }} + onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--bg-tertiary)' }} + onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent' }} + > + + + {v.name} + + {onOpenStream && ( + + )} +
+ ))} +
+ ))} +
+ )} +
+ )} +
+ ) + })} + +
+ )} +
+ ) +} + +function SummaryStat({ icon, value, label, color }: { icon: string; value: number | string; label: string; color: string }) { + return ( +
+ {icon} +
+
{value}
+
{label}
+
+
+ ) +} diff --git a/web/src/pages/StreamDetailsPage.tsx b/web/src/pages/StreamDetailsPage.tsx new file mode 100644 index 0000000..fa66e9d --- /dev/null +++ b/web/src/pages/StreamDetailsPage.tsx @@ -0,0 +1,146 @@ +import { useEffect, useState, useCallback } from 'react' +import { ErrorBoundary } from '../components/ui/ErrorBoundary' +import { MobiusChat } from '../components/layout/MobiusChat' +import { SeverityDistribution } from '../components/panels/SeverityDistribution' +import { PatternAnalysis } from '../components/panels/PatternAnalysis' +import { LogViewer } from '../components/panels/LogViewer' +import { useWebSocket } from '../hooks/useWebSocket' +import { fetchStatus } from '../api/client' +import type { StatusInfo } from '../api/types' + +interface Props { + dimensionType: string + dimensionValue: string +} + +export function StreamDetailsPage({ dimensionType, dimensionValue }: Props) { + const [status, setStatus] = useState(null) + const [refreshKey, setRefreshKey] = useState(0) + const [activeTab, setActiveTab] = useState<'patterns' | 'logs'>('patterns') + const wsUpdate = useWebSocket() + + const loadStatus = useCallback(() => { + fetchStatus().then(setStatus).catch(() => {}) + }, []) + + useEffect(() => { + loadStatus() + const interval = setInterval(loadStatus, 5000) + return () => clearInterval(interval) + }, [loadStatus]) + + useEffect(() => { + if (wsUpdate) setRefreshKey((k) => k + 1) + }, [wsUpdate]) + + const search = dimensionValue + + return ( +
+ {/* Stream header */} +
+
+

{dimensionValue}

+ + {dimensionType} + + {status && ( +
+ {status.total_logs.toLocaleString()} total logs + {status.log_rate.toFixed(1)}/s +
+ )} +
+
+ + {/* Main content + Mobius sidebar */} +
+
+ + {/* Severity distribution (real-time stacked bar chart) */} +
+ + + +
+ + {/* Tabbed section: Patterns | Logs */} +
+ {/* Tab bar */} +
+ + +
+ + {/* Tab content */} +
+ {activeTab === 'patterns' && ( + + + + )} + {activeTab === 'logs' && ( + + + + )} +
+
+
+ +
+ +
+
+
+ ) +} diff --git a/web/src/pages/UpgradePage.tsx b/web/src/pages/UpgradePage.tsx new file mode 100644 index 0000000..18bacbd --- /dev/null +++ b/web/src/pages/UpgradePage.tsx @@ -0,0 +1,71 @@ +import { upsellURL } from '../lib/constants' +import { Icons } from '../lib/icons' + +const pageLabels: Record = { + alerts: { + title: 'Alerts', + description: + 'Create, track, and resolve alerts with AI-powered root cause analysis and team collaboration.', + }, + sources: { + title: 'Source Management', + description: + 'Connect to AWS, GCP, Azure, Kubernetes, and 20+ other log sources with one-click setup.', + }, + users: { + title: 'Team Management', + description: + '...And share runtime context across your team and AI tooling.', + }, + retention: { + title: 'Retention', + description: + 'Configure log retention policies, storage tiers, and archival rules for long-term analysis.', + }, + support: { + title: 'Priority Support', + description: + 'Get direct access to our engineering team with priority response times and dedicated support.', + }, +} + +interface Props { + featureId: string +} + +export function UpgradePage({ featureId }: Props) { + const info = pageLabels[featureId] ?? { + title: 'Premium Feature', + description: 'This feature is available on the full Dstl8 dashboard.', + } + + return ( +
+
+
{Icons.lock}
+

{info.title}

+

{info.description}

+ + Upgrade to Dstl8 Pro + +

+ Dstl8 Lite is powered by{' '} + + Gonzo + + {' '}— the full Dstl8 dashboard unlocks all features. +

+
+
+ ) +} diff --git a/web/src/pages/WorkspaceDetailsPage.tsx b/web/src/pages/WorkspaceDetailsPage.tsx new file mode 100644 index 0000000..d316f7f --- /dev/null +++ b/web/src/pages/WorkspaceDetailsPage.tsx @@ -0,0 +1,423 @@ +import { useEffect, useState, useCallback, useRef } from 'react' +import { ErrorBoundary } from '../components/ui/ErrorBoundary' +import { MobiusChat } from '../components/layout/MobiusChat' +import { SourcesSection } from '../components/panels/SourcesSection' +import { SeverityDistribution } from '../components/panels/SeverityDistribution' +import { LogViewer } from '../components/panels/LogViewer' +import { useWebSocket } from '../hooks/useWebSocket' +import { fetchStatus, fetchTopAttributes } from '../api/client' +import { upsellURL } from '../lib/constants' +import type { StatusInfo, AttributeEntry } from '../api/types' + +interface Props { + onOpenStream?: (dimensionType: string, dimensionValue: string) => void +} + +function formatBytes(bytes: number): string { + if (bytes < 1024) return `${bytes} B` + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` + if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB` + return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB` +} + +function StatRow({ label, value }: { label: string; value: string }) { + return ( +
+ {label} + {value} +
+ ) +} + +export function WorkspaceDetailsPage({ onOpenStream }: Props) { + const [status, setStatus] = useState(null) + const [attributes, setAttributes] = useState([]) + const [refreshKey, setRefreshKey] = useState(0) + const [logModalOpen, setLogModalOpen] = useState(false) + const [paused, setPaused] = useState(false) + const pausedRef = useRef(false) + const wsUpdate = useWebSocket() + + const togglePaused = useCallback(() => { + setPaused((prev) => { + const next = !prev + pausedRef.current = next + return next + }) + }, []) + + const loadStatus = useCallback(() => { + fetchStatus().then(setStatus).catch(() => {}) + }, []) + + const loadAttributes = useCallback(() => { + fetchTopAttributes(5).then(setAttributes).catch(() => {}) + }, []) + + useEffect(() => { + loadStatus() + loadAttributes() + const interval = setInterval(() => { + if (!pausedRef.current) { + loadStatus() + loadAttributes() + } + }, 5000) + return () => clearInterval(interval) + }, [loadStatus, loadAttributes]) + + useEffect(() => { + if (wsUpdate && !pausedRef.current) setRefreshKey((k) => k + 1) + }, [wsUpdate]) + + return ( +
+ {/* Workspace header with inline stats */} +
+
+

Gonzo!

+ + {status && ( +
+ {status.total_logs.toLocaleString()} logs + {status.log_rate.toFixed(1)}/s + {status.uptime} +
+ )} +
+
+ + {/* Main content + Mobius sidebar */} +
+
+ + {/* ── General Statistics + Severity Distribution ── */} +
+ {/* General Statistics card */} + {status && ( +
+

+ General Statistics +

+ + + + + + s.active).length.toString()} /> +
+ )} + + {/* Severity Distribution chart */} + + + +
+ + {/* ── Top Attributes table ── */} + {attributes.length > 0 && ( +
+
+

+ Top Attributes +

+
+ + + + + + + + + + + {attributes.map((attr, i) => ( + + + + + + + ))} + +
KeyValueCount%
{attr.key}{attr.value} + {attr.count.toLocaleString()} + +
+
+
+
+ + {attr.percentage.toFixed(1)}% + +
+
+
+ )} + + {/* ── Live Log Stream preview ── */} +
+
+

+ Live Logs +

+ +
+
+ + + +
+
+ + {/* ── Full-screen Log Modal ── */} + {logModalOpen && ( +
{ + if (e.target === e.currentTarget) setLogModalOpen(false) + }} + > +
+
+

+ Live Logs +

+ +
+
+ + + +
+
+
+ )} + + {/* ── Sources section ── */} + + + +
+ +
+ {/* Mobius Analysis upsell */} +
+
+ Möbius + + Möbius Analysis + +
+

+ Get AI-powered summaries, correlated anomaly detection, and + root-cause analysis across all your sources. +

+ + Upgrade for Möbius AI Analysis + Root cause analysis, summaries, alerts and chat + +
+ + {/* Alerts upsell */} +
+
+ + Alerts + + + Cloud + +
+

+ Create, track, and resolve alerts with automatic correlation, + timeline views, and team collaboration. +

+ + Upgrade for Alerts + +
+ + +
+
+
+ ) +} diff --git a/web/src/pages/WorkspacesPage.tsx b/web/src/pages/WorkspacesPage.tsx new file mode 100644 index 0000000..36a5980 --- /dev/null +++ b/web/src/pages/WorkspacesPage.tsx @@ -0,0 +1,210 @@ +import { useEffect, useState } from 'react' +import { fetchStatus } from '../api/client' +import type { StatusInfo } from '../api/types' +import { upsellURL } from '../lib/constants' +import { Icons } from '../lib/icons' + +interface Props { + onOpenWorkspace: () => void +} + +export function WorkspacesPage({ onOpenWorkspace }: Props) { + const [status, setStatus] = useState(null) + const [showNewModal, setShowNewModal] = useState(false) + + useEffect(() => { + fetchStatus().then(setStatus).catch(() => {}) + const interval = setInterval(() => { + fetchStatus().then(setStatus).catch(() => {}) + }, 5000) + return () => clearInterval(interval) + }, []) + + const activeStreams = status?.streams.filter((s) => s.active) ?? [] + const streams = activeStreams.length + const sources = new Set(activeStreams.map((s) => s.source)).size + const totalLogs = status?.total_logs ?? 0 + + return ( +
+
+

Workspaces

+ +
+ + {/* Stats bar */} +
+
+
+ + + + +
+
+
1
+
Workspace
+
+
+
+
+ + + +
+
+
{streams}
+
Active Streams
+
+
+
+
+ + + +
+
+
{totalLogs.toLocaleString()}
+
Total Logs
+
+
+
+ + {/* Workspace grid */} +
+
+
+
Gonzo!
+
+
+ +
+
+
Streams
+
{streams}
+
+
+
Logs
+
{totalLogs.toLocaleString()}
+
+
+
Rate
+
+ {status ? `${status.log_rate.toFixed(0)}/s` : '—'} +
+
+
+ + {/* Simple sparkline placeholder */} +
+ {Array.from({ length: 24 }, (_, i) => { + const h = Math.max(4, Math.random() * 32 + (i > 18 ? 10 : 0)) + return ( +
+ ) + })} +
+ +
+
+ + + + {sources} sources +
+
+ + + + {status?.uptime ?? '—'} uptime +
+
+
+
+ + {/* New Workspace upsell modal */} + {showNewModal && ( +
{ if (e.target === e.currentTarget) setShowNewModal(false) }} + > +
+
+

+ Add Workspace +

+ +
+

+ Dstl8 Lite supports a single local workspace powered by Gonzo. +

+

+ Upgrade to Dstl8 Pro for multiple workspaces, team collaboration, + cloud log sources (AWS, GCP, Azure, Supabase, Vercel), and more. +

+
+ + Upgrade to Dstl8 Pro + + +
+
+
+ )} +
+ ) +} diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/web/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts new file mode 100644 index 0000000..f2ab0fd --- /dev/null +++ b/web/tailwind.config.ts @@ -0,0 +1,36 @@ +import type { Config } from 'tailwindcss' + +const config: Config = { + content: ['./index.html', './src/**/*.{ts,tsx}'], + darkMode: 'class', + theme: { + extend: { + colors: { + // Dstl8 severity colors + severity: { + fatal: '#b91c1c', + error: '#dc2626', + warn: '#f97316', + info: '#3b82f6', + debug: '#6b7280', + trace: '#10b981', + unknown: '#9ca3af', + }, + // Dstl8 brand + brand: { + primary: '#6366f1', + secondary: '#8b5cf6', + }, + // Dashboard surface colors + surface: { + DEFAULT: '#ffffff', + secondary: '#f8fafc', + dark: '#0f172a', + 'dark-secondary': '#1e293b', + }, + }, + }, + }, + plugins: [], +} +export default config diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..39a405b --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 0000000..a0018c4 --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,27 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + build: { + outDir: 'dist', + sourcemap: false, + // Keep bundle small for embedding + rollupOptions: { + output: { + manualChunks: { + recharts: ['recharts'], + }, + }, + }, + }, + server: { + proxy: { + '/api': 'http://localhost:5718', + '/ws': { + target: 'ws://localhost:5718', + ws: true, + }, + }, + }, +}) From f422c190beb70a7b031eb29aa9a409eca53da709 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 4 May 2026 16:30:10 -0500 Subject: [PATCH 2/4] Fix Vet --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 2d4d6be..237bb40 100644 --- a/Makefile +++ b/Makefile @@ -183,6 +183,8 @@ fmt: ## Format code vet: ## Run go vet @echo "$(BLUE)Running go vet...$(NC)" + @mkdir -p web/dist + @test -f web/dist/index.html || echo '' > web/dist/index.html @$(GO) vet ./... lint: ## Run linter (requires golangci-lint) From 1130541503e3b141cc5c3dd36fb66aca6e6ae960 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 4 May 2026 16:34:07 -0500 Subject: [PATCH 3/4] Flake was flaky, fix it --- flake.nix | 8 +- gomod2nix.toml | 641 +++++++++++++++++++++++++++++-------------------- 2 files changed, 390 insertions(+), 259 deletions(-) diff --git a/flake.nix b/flake.nix index 73cc30e..7422e9a 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,13 @@ src = ./.; modules = ./gomod2nix.toml; - # If you split binaries later, enable: subPackages = [ "cmd/gonzo" ]; + # Create stub web/dist so //go:embed all:dist compiles + # Full web dashboard is built separately via `make build` + preBuild = '' + mkdir -p web/dist + echo '' > web/dist/index.html + ''; + ldflags = ["-s" "-w"]; meta = with pkgs.lib; { diff --git a/gomod2nix.toml b/gomod2nix.toml index 88dcf54..0006913 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -1,261 +1,386 @@ schema = 3 [mod] - [mod."github.com/NimbleMarkets/ntcharts"] - version = "v0.3.1" - hash = "sha256-ZybI8VrsxgK890OYFJazgFnNR6bGi4suowVHd1+XJlM=" - [mod."github.com/atotto/clipboard"] - version = "v0.1.4" - hash = "sha256-ZZ7U5X0gWOu8zcjZcWbcpzGOGdycwq0TjTFh/eZHjXk=" - [mod."github.com/aymanbagabas/go-osc52/v2"] - version = "v2.0.1" - hash = "sha256-6Bp0jBZ6npvsYcKZGHHIUSVSTAMEyieweAX2YAKDjjg=" - [mod."github.com/charmbracelet/bubbles"] - version = "v0.21.0" - hash = "sha256-cfjUHgy9eq5SretTHuYuRaeeT6QmJYQBB9dsI8QSnW0=" - [mod."github.com/charmbracelet/bubbletea"] - version = "v1.3.6" - hash = "sha256-79MpQk+92RDXnNc2hSct8EDf0AhV6MZpvFMmmrVC/bs=" - [mod."github.com/charmbracelet/colorprofile"] - version = "v0.2.3-0.20250311203215-f60798e515dc" - hash = "sha256-D9E/bMOyLXAUVOHA1/6o3i+vVmLfwIMOWib6sU7A6+Q=" - [mod."github.com/charmbracelet/lipgloss"] - version = "v1.1.1-0.20250404203927-76690c660834" - hash = "sha256-CwS1AyoRsPOFA4SrC0CTtwLnbg57FmmSW/mvVQUFYcc=" - [mod."github.com/charmbracelet/x/ansi"] - version = "v0.9.3" - hash = "sha256-Bzum17p7UQZeNxL155Pho/+GXj1DElB9Bp3O194CYf8=" - [mod."github.com/charmbracelet/x/cellbuf"] - version = "v0.0.13" - hash = "sha256-ubgBd82jcS5L4i6rCFLOB1BXGrDEu6wWlciJA0gsH2g=" - [mod."github.com/charmbracelet/x/term"] - version = "v0.2.1" - hash = "sha256-VBkCZLI90PhMasftGw3403IqoV7d3E5WEGAIVrN5xQM=" - [mod."github.com/davecgh/go-spew"] - version = "v1.1.1" - hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI=" - [mod."github.com/emicklei/go-restful/v3"] - version = "v3.12.2" - hash = "sha256-eQ0qtVH7c5jgqB7F9B17GhZujYelBA2g9KwpPuSS0sE=" - [mod."github.com/erikgeiser/coninput"] - version = "v0.0.0-20211004153227-1c3628e74d0f" - hash = "sha256-OWSqN1+IoL73rWXWdbbcahZu8n2al90Y3eT5Z0vgHvU=" - [mod."github.com/fsnotify/fsnotify"] - version = "v1.9.0" - hash = "sha256-WtpE1N6dpHwEvIub7Xp/CrWm0fd6PX7MKA4PV44rp2g=" - [mod."github.com/fxamacker/cbor/v2"] - version = "v2.9.0" - hash = "sha256-/IZK76MRCrz9XCiilieH5tKaLnIWyPJhwxDoVKB8dFc=" - [mod."github.com/go-logr/logr"] - version = "v1.4.3" - hash = "sha256-Nnp/dEVNMxLp3RSPDHZzGbI8BkSNuZMX0I0cjWKXXLA=" - [mod."github.com/go-openapi/jsonpointer"] - version = "v0.21.0" - hash = "sha256-bB8XTzo4hzXemi8Ey3tIXia3mfn38bvwIzKYLJYC650=" - [mod."github.com/go-openapi/jsonreference"] - version = "v0.20.2" - hash = "sha256-klWZKK7LZqSg3HMIrSkjh/NwaZTr+8kTW2ok2+JlioE=" - [mod."github.com/go-openapi/swag"] - version = "v0.23.0" - hash = "sha256-D5CzsSQ3SYJLwXT6BDahnG66LI8du59Dy1mY4KutA7A=" - [mod."github.com/go-viper/mapstructure/v2"] - version = "v2.4.0" - hash = "sha256-lLfcV9z4n94hDhgyXJlde4bFB0hfzlbh+polqcJCwGE=" - [mod."github.com/gogo/protobuf"] - version = "v1.3.2" - hash = "sha256-pogILFrrk+cAtb0ulqn9+gRZJ7sGnnLLdtqITvxvG6c=" - [mod."github.com/google/gnostic-models"] - version = "v0.7.0" - hash = "sha256-sxShRxqOUVlz9IkAz0C/NP/7CmLBW3ffwoZTdh+rIOc=" - [mod."github.com/google/go-cmp"] - version = "v0.7.0" - hash = "sha256-JbxZFBFGCh/Rj5XZ1vG94V2x7c18L8XKB0N9ZD5F2rM=" - [mod."github.com/google/uuid"] - version = "v1.6.0" - hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw=" - [mod."github.com/grpc-ecosystem/grpc-gateway/v2"] - version = "v2.26.3" - hash = "sha256-j/nyE8OgiwZ1YP0h3gmmxY1Fx4GDMCohi9g9kgNz4U8=" - [mod."github.com/hashicorp/golang-lru/v2"] - version = "v2.0.7" - hash = "sha256-t1bcXLgrQNOYUVyYEZ0knxcXpsTk4IuJZDjKvyJX75g=" - [mod."github.com/inconshreveable/mousetrap"] - version = "v1.1.0" - hash = "sha256-XWlYH0c8IcxAwQTnIi6WYqq44nOKUylSWxWO/vi+8pE=" - [mod."github.com/jaeyo/go-drain3"] - version = "v0.1.2" - hash = "sha256-mBQensNJV8/yKb8cqJTk7QG9B8PwN3NoZcWmvSv3vPo=" - [mod."github.com/josharian/intern"] - version = "v1.0.0" - hash = "sha256-LJR0QE2vOQ2/2shBbO5Yl8cVPq+NFrE3ers0vu9FRP0=" - [mod."github.com/json-iterator/go"] - version = "v1.1.12" - hash = "sha256-To8A0h+lbfZ/6zM+2PpRpY3+L6725OPC66lffq6fUoM=" - [mod."github.com/lrstanley/bubblezone"] - version = "v0.0.0-20240914071701-b48c55a5e78e" - hash = "sha256-FoYUk7NlM5r1yYuhVPVSj72dWVHX1gb6v8Doqw0jx3I=" - [mod."github.com/lucasb-eyer/go-colorful"] - version = "v1.2.0" - hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE=" - [mod."github.com/mailru/easyjson"] - version = "v0.7.7" - hash = "sha256-NVCz8MURpxgOjHXqxOZExqV4bnpHggpeAOyZDArjcy4=" - [mod."github.com/mattn/go-isatty"] - version = "v0.0.20" - hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ=" - [mod."github.com/mattn/go-localereader"] - version = "v0.0.1" - hash = "sha256-JlWckeGaWG+bXK8l8WEdZqmSiTwCA8b1qbmBKa/Fj3E=" - [mod."github.com/mattn/go-runewidth"] - version = "v0.0.16" - hash = "sha256-NC+ntvwIpqDNmXb7aixcg09il80ygq6JAnW0Gb5b/DQ=" - [mod."github.com/modern-go/concurrent"] - version = "v0.0.0-20180306012644-bacd9c7ef1dd" - hash = "sha256-OTySieAgPWR4oJnlohaFTeK1tRaVp/b0d1rYY8xKMzo=" - [mod."github.com/modern-go/reflect2"] - version = "v1.0.3-0.20250322232337-35a7c28c31ee" - hash = "sha256-0pkWWZRB3lGFyzmlxxrm0KWVQo9HNXNafaUu3k+rE1g=" - [mod."github.com/muesli/ansi"] - version = "v0.0.0-20230316100256-276c6243b2f6" - hash = "sha256-qRKn0Bh2yvP0QxeEMeZe11Vz0BPFIkVcleKsPeybKMs=" - [mod."github.com/muesli/cancelreader"] - version = "v0.2.2" - hash = "sha256-uEPpzwRJBJsQWBw6M71FDfgJuR7n55d/7IV8MO+rpwQ=" - [mod."github.com/muesli/termenv"] - version = "v0.16.0" - hash = "sha256-hGo275DJlyLtcifSLpWnk8jardOksdeX9lH4lBeE3gI=" - [mod."github.com/munnerz/goautoneg"] - version = "v0.0.0-20191010083416-a7dc8b61c822" - hash = "sha256-79URDDFenmGc9JZu+5AXHToMrtTREHb3BC84b/gym9Q=" - [mod."github.com/pelletier/go-toml/v2"] - version = "v2.2.3" - hash = "sha256-fE++SVgnCGdnFZoROHWuYjIR7ENl7k9KKxQrRTquv/o=" - [mod."github.com/pkg/errors"] - version = "v0.9.1" - hash = "sha256-mNfQtcrQmu3sNg/7IwiieKWOgFQOVVe2yXgKBpe/wZw=" - [mod."github.com/pmezard/go-difflib"] - version = "v1.0.0" - hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA=" - [mod."github.com/rivo/uniseg"] - version = "v0.4.7" - hash = "sha256-rDcdNYH6ZD8KouyyiZCUEy8JrjOQoAkxHBhugrfHjFo=" - [mod."github.com/sagikazarmark/locafero"] - version = "v0.7.0" - hash = "sha256-ZmaGOKHDw18jJqdkwQwSpUT11F9toR6KPs3241TONeY=" - [mod."github.com/sourcegraph/conc"] - version = "v0.3.0" - hash = "sha256-mIdMs9MLAOBKf3/0quf1iI3v8uNWydy7ae5MFa+F2Ko=" - [mod."github.com/spf13/afero"] - version = "v1.12.0" - hash = "sha256-TX3DcyAdrXqf+TxmEz4TilWQo2Y4hcBXOeRY6BjDp+s=" - [mod."github.com/spf13/cast"] - version = "v1.7.1" - hash = "sha256-BjX0aY/PC37gIdMc7JhMgvhWFsksGdAcp2FgzpuvkPo=" - [mod."github.com/spf13/cobra"] - version = "v1.9.1" - hash = "sha256-dzEqquABE3UqZmJuj99244QjvfojS8cFlsPr/MXQGj0=" - [mod."github.com/spf13/pflag"] - version = "v1.0.6" - hash = "sha256-NjrK0FZPIfO/p2xtL1J7fOBQNTZAPZOC6Cb4aMMvhxI=" - [mod."github.com/spf13/viper"] - version = "v1.20.1" - hash = "sha256-gbCM0k7RAlvn7jpSuWB2LX5Nip9vgwsPNGbDXTI7JvM=" - [mod."github.com/subosito/gotenv"] - version = "v1.6.0" - hash = "sha256-LspbjTniiq2xAICSXmgqP7carwlNaLqnCTQfw2pa80A=" - [mod."github.com/x448/float16"] - version = "v0.8.4" - hash = "sha256-VKzMTMS9pIB/cwe17xPftCSK9Mf4Y6EuBEJlB4by5mE=" - [mod."github.com/xo/terminfo"] - version = "v0.0.0-20220910002029-abceb7e1c41e" - hash = "sha256-GyCDxxMQhXA3Pi/TsWXpA8cX5akEoZV7CFx4RO3rARU=" - [mod."go.opentelemetry.io/otel"] - version = "v1.37.0" - hash = "sha256-zWpyp9K8/Te86uhNjamchZctTdAnmHhoVw9m4ACfSoo=" - [mod."go.opentelemetry.io/otel/sdk/metric"] - version = "v1.37.0" - hash = "sha256-dm6Aa5UDFgQCVexayiWu85A1ir1lpNn5rIN9tj9KVDs=" - [mod."go.opentelemetry.io/proto/otlp"] - version = "v1.7.0" - hash = "sha256-wqmUA6XGjODfuTCRQs32tYFkvO717ZGyZYZFcbNHvLw=" - [mod."go.uber.org/multierr"] - version = "v1.11.0" - hash = "sha256-Lb6rHHfR62Ozg2j2JZy3MKOMKdsfzd1IYTR57r3Mhp0=" - [mod."go.yaml.in/yaml/v2"] - version = "v2.4.2" - hash = "sha256-oC8RWdf1zbMYCtmR0ATy/kCkhIwPR9UqFZSMOKLVF/A=" - [mod."go.yaml.in/yaml/v3"] - version = "v3.0.4" - hash = "sha256-NkGFiDPoCxbr3LFsI6OCygjjkY0rdmg5ggvVVwpyDQ4=" - [mod."golang.org/x/net"] - version = "v0.43.0" - hash = "sha256-bf3iQFrsC8BoarVaS0uSspEFAcr1zHp1uziTtBpwV34=" - [mod."golang.org/x/oauth2"] - version = "v0.30.0" - hash = "sha256-btD7BUtQpOswusZY5qIU90uDo38buVrQ0tmmQ8qNHDg=" - [mod."golang.org/x/sync"] - version = "v0.16.0" - hash = "sha256-sqKDRESeMzLe0jWGWltLZL/JIgrn0XaIeBWCzVN3Bks=" - [mod."golang.org/x/sys"] - version = "v0.35.0" - hash = "sha256-ZKM8pesQE6NAFZeKQ84oPn5JMhGr8g4TSwLYAsHMGSI=" - [mod."golang.org/x/term"] - version = "v0.34.0" - hash = "sha256-faLolF6EUSSaC0ZwRiKH5JF/TmtcMQ+m+RWWl6Pk1PU=" - [mod."golang.org/x/text"] - version = "v0.28.0" - hash = "sha256-8UlJniGK+km4Hmrw6XMxELnExgrih7+z8tU26Cntmto=" - [mod."golang.org/x/time"] - version = "v0.9.0" - hash = "sha256-ipaWVIk1+DZg0rfCzBSkz/Y6DEnB7xkX2RRYycHkhC0=" - [mod."google.golang.org/genproto/googleapis/api"] - version = "v0.0.0-20250528174236-200df99c418a" - hash = "sha256-VO7Rko8b/zO2sm6vML7hhxi9laPilt6JEab8xl4qIN8=" - [mod."google.golang.org/genproto/googleapis/rpc"] - version = "v0.0.0-20250811230008-5f3141c8851a" - hash = "sha256-hbGMdlN/vwPIOJhYv6CAEnpQqTXbQ1GlXabiQUOv3sc=" - [mod."google.golang.org/grpc"] - version = "v1.74.2" - hash = "sha256-tvYMdfu/ZQZRPZNmnQI4CZpg46CM8+mD49hw0gFheGs=" - [mod."google.golang.org/protobuf"] - version = "v1.36.7" - hash = "sha256-6xCU+t2AVPcscMKenVs4etGqutYGPDXCQ3DCD3PpTq4=" - [mod."gopkg.in/evanphx/json-patch.v4"] - version = "v4.12.0" - hash = "sha256-rUOokb3XW30ftpHp0fsF2WiJln1S0FSt2El7fTHq3CM=" - [mod."gopkg.in/inf.v0"] - version = "v0.9.1" - hash = "sha256-z84XlyeWLcoYOvWLxPkPFgLkpjyb2Y4pdeGMyySOZQI=" - [mod."gopkg.in/yaml.v3"] - version = "v3.0.1" - hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU=" - [mod."k8s.io/api"] - version = "v0.34.2" - hash = "sha256-8TpPB07Dh6REOxYzT78OGLCV1iTZlMVzEQCzmpA4Q3U=" - [mod."k8s.io/apimachinery"] - version = "v0.34.2" - hash = "sha256-y6GVugdaX81lHwVWgDgFoIi/v6K9A3YC2kKlGUritVU=" - [mod."k8s.io/client-go"] - version = "v0.34.2" - hash = "sha256-ELhaXsMrieNn0ms84n5kL9QlNDXQ2u8xpbny+bTvdZg=" - [mod."k8s.io/klog/v2"] - version = "v2.130.1" - hash = "sha256-n5vls1o1a0V0KYv+3SULq4q3R2Is15K8iDHhFlsSH4o=" - [mod."k8s.io/kube-openapi"] - version = "v0.0.0-20250710124328-f3f2b991d03b" - hash = "sha256-0v0MN67pora3UCA8vXSJDgkosx/1RaB2HkjUnwdVLbY=" - [mod."k8s.io/utils"] - version = "v0.0.0-20250604170112-4c0f3b243397" - hash = "sha256-USPKRYYKfbhQWU0CgT/8V1hdBirWjmhTZvJCuuUlNFo=" - [mod."sigs.k8s.io/json"] - version = "v0.0.0-20241014173422-cfa47c3a1cc8" - hash = "sha256-dkegDkyjp/niYirIdhbQrBYt/uttCZQAfsBzKSzOMh0=" - [mod."sigs.k8s.io/randfill"] - version = "v1.0.0" - hash = "sha256-xldQxDwW84hmlihdSOFfjXyauhxEWV9KmIDLZMTcYNo=" - [mod."sigs.k8s.io/structured-merge-diff/v6"] - version = "v6.3.0" - hash = "sha256-2EqUZSaHUhwTrdjoZuv+Z99tZYrX1E6rxf2ejeKd2BM=" - [mod."sigs.k8s.io/yaml"] - version = "v1.6.0" - hash = "sha256-49hg7IVPzwxeovp+HTMiWa/10NMMTSTjAdCmIv6p9dw=" + [mod.'github.com/NimbleMarkets/ntcharts'] + version = 'v0.3.1' + hash = 'sha256-ZybI8VrsxgK890OYFJazgFnNR6bGi4suowVHd1+XJlM=' + + [mod.'github.com/alecthomas/chroma/v2'] + version = 'v2.20.0' + hash = 'sha256-rL1mJS8fCltcii9w/efyyE4iyzogyjP+N80lE3CuzVE=' + + [mod.'github.com/atotto/clipboard'] + version = 'v0.1.4' + hash = 'sha256-ZZ7U5X0gWOu8zcjZcWbcpzGOGdycwq0TjTFh/eZHjXk=' + + [mod.'github.com/aymanbagabas/go-osc52/v2'] + version = 'v2.0.1' + hash = 'sha256-6Bp0jBZ6npvsYcKZGHHIUSVSTAMEyieweAX2YAKDjjg=' + + [mod.'github.com/aymerick/douceur'] + version = 'v0.2.0' + hash = 'sha256-NiBX8EfOvLXNiK3pJaZX4N73YgfzdrzRXdiBFe3X3sE=' + + [mod.'github.com/charmbracelet/bubbles'] + version = 'v0.21.0' + hash = 'sha256-cfjUHgy9eq5SretTHuYuRaeeT6QmJYQBB9dsI8QSnW0=' + + [mod.'github.com/charmbracelet/bubbletea'] + version = 'v1.3.6' + hash = 'sha256-79MpQk+92RDXnNc2hSct8EDf0AhV6MZpvFMmmrVC/bs=' + + [mod.'github.com/charmbracelet/colorprofile'] + version = 'v0.2.3-0.20250311203215-f60798e515dc' + hash = 'sha256-D9E/bMOyLXAUVOHA1/6o3i+vVmLfwIMOWib6sU7A6+Q=' + + [mod.'github.com/charmbracelet/glamour'] + version = 'v1.0.0' + hash = 'sha256-1sGgVA3QXJ2P5c/DNMb7R0HFG1X5vz9nmDhLrzKdgMw=' + + [mod.'github.com/charmbracelet/lipgloss'] + version = 'v1.1.1-0.20250404203927-76690c660834' + hash = 'sha256-CwS1AyoRsPOFA4SrC0CTtwLnbg57FmmSW/mvVQUFYcc=' + + [mod.'github.com/charmbracelet/x/ansi'] + version = 'v0.10.2' + hash = 'sha256-+JMWFbgv/5Wl2UERKxY4p5oayCRgJEDZpt2ACu3oG1o=' + + [mod.'github.com/charmbracelet/x/cellbuf'] + version = 'v0.0.13' + hash = 'sha256-ubgBd82jcS5L4i6rCFLOB1BXGrDEu6wWlciJA0gsH2g=' + + [mod.'github.com/charmbracelet/x/exp/slice'] + version = 'v0.0.0-20250327172914-2fdc97757edf' + hash = 'sha256-C1tksnevc/RdytJRQg5LQ0+VVSWlTwbNGic649m6E1Q=' + + [mod.'github.com/charmbracelet/x/term'] + version = 'v0.2.1' + hash = 'sha256-VBkCZLI90PhMasftGw3403IqoV7d3E5WEGAIVrN5xQM=' + + [mod.'github.com/davecgh/go-spew'] + version = 'v1.1.1' + hash = 'sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI=' + + [mod.'github.com/dlclark/regexp2'] + version = 'v1.11.5' + hash = 'sha256-jN5+2ED+YbIoPIuyJ4Ou5pqJb2w1uNKzp5yTjKY6rEQ=' + + [mod.'github.com/emicklei/go-restful/v3'] + version = 'v3.12.2' + hash = 'sha256-eQ0qtVH7c5jgqB7F9B17GhZujYelBA2g9KwpPuSS0sE=' + + [mod.'github.com/erikgeiser/coninput'] + version = 'v0.0.0-20211004153227-1c3628e74d0f' + hash = 'sha256-OWSqN1+IoL73rWXWdbbcahZu8n2al90Y3eT5Z0vgHvU=' + + [mod.'github.com/fsnotify/fsnotify'] + version = 'v1.9.0' + hash = 'sha256-WtpE1N6dpHwEvIub7Xp/CrWm0fd6PX7MKA4PV44rp2g=' + + [mod.'github.com/fxamacker/cbor/v2'] + version = 'v2.9.0' + hash = 'sha256-/IZK76MRCrz9XCiilieH5tKaLnIWyPJhwxDoVKB8dFc=' + + [mod.'github.com/go-logr/logr'] + version = 'v1.4.3' + hash = 'sha256-Nnp/dEVNMxLp3RSPDHZzGbI8BkSNuZMX0I0cjWKXXLA=' + + [mod.'github.com/go-openapi/jsonpointer'] + version = 'v0.21.0' + hash = 'sha256-bB8XTzo4hzXemi8Ey3tIXia3mfn38bvwIzKYLJYC650=' + + [mod.'github.com/go-openapi/jsonreference'] + version = 'v0.20.2' + hash = 'sha256-klWZKK7LZqSg3HMIrSkjh/NwaZTr+8kTW2ok2+JlioE=' + + [mod.'github.com/go-openapi/swag'] + version = 'v0.23.0' + hash = 'sha256-D5CzsSQ3SYJLwXT6BDahnG66LI8du59Dy1mY4KutA7A=' + + [mod.'github.com/go-viper/mapstructure/v2'] + version = 'v2.4.0' + hash = 'sha256-lLfcV9z4n94hDhgyXJlde4bFB0hfzlbh+polqcJCwGE=' + + [mod.'github.com/gogo/protobuf'] + version = 'v1.3.2' + hash = 'sha256-pogILFrrk+cAtb0ulqn9+gRZJ7sGnnLLdtqITvxvG6c=' + + [mod.'github.com/google/gnostic-models'] + version = 'v0.7.0' + hash = 'sha256-sxShRxqOUVlz9IkAz0C/NP/7CmLBW3ffwoZTdh+rIOc=' + + [mod.'github.com/google/go-cmp'] + version = 'v0.7.0' + hash = 'sha256-JbxZFBFGCh/Rj5XZ1vG94V2x7c18L8XKB0N9ZD5F2rM=' + + [mod.'github.com/google/uuid'] + version = 'v1.6.0' + hash = 'sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw=' + + [mod.'github.com/gorilla/css'] + version = 'v1.0.1' + hash = 'sha256-6JwNHqlY2NpZ0pSQTyYPSpiNqjXOdFHqrUT10sv3y8A=' + + [mod.'github.com/grpc-ecosystem/grpc-gateway/v2'] + version = 'v2.26.3' + hash = 'sha256-j/nyE8OgiwZ1YP0h3gmmxY1Fx4GDMCohi9g9kgNz4U8=' + + [mod.'github.com/hashicorp/golang-lru/v2'] + version = 'v2.0.7' + hash = 'sha256-t1bcXLgrQNOYUVyYEZ0knxcXpsTk4IuJZDjKvyJX75g=' + + [mod.'github.com/inconshreveable/mousetrap'] + version = 'v1.1.0' + hash = 'sha256-XWlYH0c8IcxAwQTnIi6WYqq44nOKUylSWxWO/vi+8pE=' + + [mod.'github.com/jaeyo/go-drain3'] + version = 'v0.1.2' + hash = 'sha256-mBQensNJV8/yKb8cqJTk7QG9B8PwN3NoZcWmvSv3vPo=' + + [mod.'github.com/josharian/intern'] + version = 'v1.0.0' + hash = 'sha256-LJR0QE2vOQ2/2shBbO5Yl8cVPq+NFrE3ers0vu9FRP0=' + + [mod.'github.com/json-iterator/go'] + version = 'v1.1.12' + hash = 'sha256-To8A0h+lbfZ/6zM+2PpRpY3+L6725OPC66lffq6fUoM=' + + [mod.'github.com/lrstanley/bubblezone'] + version = 'v0.0.0-20240914071701-b48c55a5e78e' + hash = 'sha256-FoYUk7NlM5r1yYuhVPVSj72dWVHX1gb6v8Doqw0jx3I=' + + [mod.'github.com/lucasb-eyer/go-colorful'] + version = 'v1.3.0' + hash = 'sha256-6BKrJsfmxie+YFAWzTYVPQfrwjQEXRo+J8LY+50C1BU=' + + [mod.'github.com/mailru/easyjson'] + version = 'v0.7.7' + hash = 'sha256-NVCz8MURpxgOjHXqxOZExqV4bnpHggpeAOyZDArjcy4=' + + [mod.'github.com/mattn/go-isatty'] + version = 'v0.0.20' + hash = 'sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ=' + + [mod.'github.com/mattn/go-localereader'] + version = 'v0.0.1' + hash = 'sha256-JlWckeGaWG+bXK8l8WEdZqmSiTwCA8b1qbmBKa/Fj3E=' + + [mod.'github.com/mattn/go-runewidth'] + version = 'v0.0.17' + hash = 'sha256-Xeb3F+WZhyhEWNKuWRm/umc2FolXYHtbi3QoPfDPtaA=' + + [mod.'github.com/microcosm-cc/bluemonday'] + version = 'v1.0.27' + hash = 'sha256-EZSya9FLPQ83CL7N2cZy21fdS35hViTkiMK5f3op8Es=' + + [mod.'github.com/modern-go/concurrent'] + version = 'v0.0.0-20180306012644-bacd9c7ef1dd' + hash = 'sha256-OTySieAgPWR4oJnlohaFTeK1tRaVp/b0d1rYY8xKMzo=' + + [mod.'github.com/modern-go/reflect2'] + version = 'v1.0.3-0.20250322232337-35a7c28c31ee' + hash = 'sha256-0pkWWZRB3lGFyzmlxxrm0KWVQo9HNXNafaUu3k+rE1g=' + + [mod.'github.com/muesli/ansi'] + version = 'v0.0.0-20230316100256-276c6243b2f6' + hash = 'sha256-qRKn0Bh2yvP0QxeEMeZe11Vz0BPFIkVcleKsPeybKMs=' + + [mod.'github.com/muesli/cancelreader'] + version = 'v0.2.2' + hash = 'sha256-uEPpzwRJBJsQWBw6M71FDfgJuR7n55d/7IV8MO+rpwQ=' + + [mod.'github.com/muesli/reflow'] + version = 'v0.3.0' + hash = 'sha256-Pou2ybE9SFSZG6YfZLVV1Eyfm+X4FuVpDPLxhpn47Cc=' + + [mod.'github.com/muesli/termenv'] + version = 'v0.16.0' + hash = 'sha256-hGo275DJlyLtcifSLpWnk8jardOksdeX9lH4lBeE3gI=' + + [mod.'github.com/munnerz/goautoneg'] + version = 'v0.0.0-20191010083416-a7dc8b61c822' + hash = 'sha256-79URDDFenmGc9JZu+5AXHToMrtTREHb3BC84b/gym9Q=' + + [mod.'github.com/pelletier/go-toml/v2'] + version = 'v2.2.3' + hash = 'sha256-fE++SVgnCGdnFZoROHWuYjIR7ENl7k9KKxQrRTquv/o=' + + [mod.'github.com/pkg/errors'] + version = 'v0.9.1' + hash = 'sha256-mNfQtcrQmu3sNg/7IwiieKWOgFQOVVe2yXgKBpe/wZw=' + + [mod.'github.com/pmezard/go-difflib'] + version = 'v1.0.0' + hash = 'sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA=' + + [mod.'github.com/rivo/uniseg'] + version = 'v0.4.7' + hash = 'sha256-rDcdNYH6ZD8KouyyiZCUEy8JrjOQoAkxHBhugrfHjFo=' + + [mod.'github.com/sagikazarmark/locafero'] + version = 'v0.7.0' + hash = 'sha256-ZmaGOKHDw18jJqdkwQwSpUT11F9toR6KPs3241TONeY=' + + [mod.'github.com/sourcegraph/conc'] + version = 'v0.3.0' + hash = 'sha256-mIdMs9MLAOBKf3/0quf1iI3v8uNWydy7ae5MFa+F2Ko=' + + [mod.'github.com/spf13/afero'] + version = 'v1.12.0' + hash = 'sha256-TX3DcyAdrXqf+TxmEz4TilWQo2Y4hcBXOeRY6BjDp+s=' + + [mod.'github.com/spf13/cast'] + version = 'v1.7.1' + hash = 'sha256-BjX0aY/PC37gIdMc7JhMgvhWFsksGdAcp2FgzpuvkPo=' + + [mod.'github.com/spf13/cobra'] + version = 'v1.9.1' + hash = 'sha256-dzEqquABE3UqZmJuj99244QjvfojS8cFlsPr/MXQGj0=' + + [mod.'github.com/spf13/pflag'] + version = 'v1.0.6' + hash = 'sha256-NjrK0FZPIfO/p2xtL1J7fOBQNTZAPZOC6Cb4aMMvhxI=' + + [mod.'github.com/spf13/viper'] + version = 'v1.20.1' + hash = 'sha256-gbCM0k7RAlvn7jpSuWB2LX5Nip9vgwsPNGbDXTI7JvM=' + + [mod.'github.com/stretchr/testify'] + version = 'v1.11.1' + hash = 'sha256-sWfjkuKJyDllDEtnM8sb/pdLzPQmUYWYtmeWz/5suUc=' + + [mod.'github.com/subosito/gotenv'] + version = 'v1.6.0' + hash = 'sha256-LspbjTniiq2xAICSXmgqP7carwlNaLqnCTQfw2pa80A=' + + [mod.'github.com/x448/float16'] + version = 'v0.8.4' + hash = 'sha256-VKzMTMS9pIB/cwe17xPftCSK9Mf4Y6EuBEJlB4by5mE=' + + [mod.'github.com/xo/terminfo'] + version = 'v0.0.0-20220910002029-abceb7e1c41e' + hash = 'sha256-GyCDxxMQhXA3Pi/TsWXpA8cX5akEoZV7CFx4RO3rARU=' + + [mod.'github.com/yuin/goldmark'] + version = 'v1.7.13' + hash = 'sha256-vBCxZrPYPc8x/nvAAv3Au59dCCyfS80Vw3/a9EXK7TE=' + + [mod.'github.com/yuin/goldmark-emoji'] + version = 'v1.0.6' + hash = 'sha256-+d6bZzOPE+JSFsZbQNZMCWE+n3jgcQnkPETVk47mxSY=' + + [mod.'go.opentelemetry.io/proto/otlp'] + version = 'v1.7.0' + hash = 'sha256-wqmUA6XGjODfuTCRQs32tYFkvO717ZGyZYZFcbNHvLw=' + + [mod.'go.uber.org/multierr'] + version = 'v1.11.0' + hash = 'sha256-Lb6rHHfR62Ozg2j2JZy3MKOMKdsfzd1IYTR57r3Mhp0=' + + [mod.'go.yaml.in/yaml/v2'] + version = 'v2.4.2' + hash = 'sha256-oC8RWdf1zbMYCtmR0ATy/kCkhIwPR9UqFZSMOKLVF/A=' + + [mod.'go.yaml.in/yaml/v3'] + version = 'v3.0.4' + hash = 'sha256-NkGFiDPoCxbr3LFsI6OCygjjkY0rdmg5ggvVVwpyDQ4=' + + [mod.'golang.org/x/net'] + version = 'v0.48.0' + hash = 'sha256-oZpddsiJwWCH3Aipa+XXpy7G/xHY5fEagUSok7T0bXE=' + + [mod.'golang.org/x/oauth2'] + version = 'v0.34.0' + hash = 'sha256-5eqpGGxJ7FJsPmfRek6roeGmkWHBMJaWYXyz8gXJsS4=' + + [mod.'golang.org/x/sync'] + version = 'v0.19.0' + hash = 'sha256-RbRZ+sKZUurOczGhhzOoY/sojTlta3H9XjL4PXX/cno=' + + [mod.'golang.org/x/sys'] + version = 'v0.39.0' + hash = 'sha256-dxTBu/JAWUkPbjFIXXRFdhQWyn+YyEpIC+tWqGo0Y6U=' + + [mod.'golang.org/x/term'] + version = 'v0.38.0' + hash = 'sha256-46mX91XGJqii6SUESiU5mLVJDBOfKYCp+408m91hrMw=' + + [mod.'golang.org/x/text'] + version = 'v0.32.0' + hash = 'sha256-9PXtWBKKY9rG4AgjSP4N+I1DhepXhy8SF/vWSIDIoWs=' + + [mod.'golang.org/x/time'] + version = 'v0.9.0' + hash = 'sha256-ipaWVIk1+DZg0rfCzBSkz/Y6DEnB7xkX2RRYycHkhC0=' + + [mod.'google.golang.org/genproto/googleapis/api'] + version = 'v0.0.0-20251202230838-ff82c1b0f217' + hash = 'sha256-z0OqsPMjhZ8fNmUbcDfi5Q49hBcutKX8lyzW1mLBTec=' + + [mod.'google.golang.org/genproto/googleapis/rpc'] + version = 'v0.0.0-20251202230838-ff82c1b0f217' + hash = 'sha256-I3ZNpNjKKvTq4DVNw3wLKrCuORabZ0oYj0KKhOMI/MA=' + + [mod.'google.golang.org/grpc'] + version = 'v1.79.3' + hash = 'sha256-mO9ZI8ONBMEBTlrwIPeQMfb3C9ru5zPb26P+uG/pIzY=' + + [mod.'google.golang.org/protobuf'] + version = 'v1.36.10' + hash = 'sha256-gUrj1qSpjcpRKCBnrYlKMm+P0OSh7B/8EBREstwhD1w=' + + [mod.'gopkg.in/evanphx/json-patch.v4'] + version = 'v4.12.0' + hash = 'sha256-rUOokb3XW30ftpHp0fsF2WiJln1S0FSt2El7fTHq3CM=' + + [mod.'gopkg.in/inf.v0'] + version = 'v0.9.1' + hash = 'sha256-z84XlyeWLcoYOvWLxPkPFgLkpjyb2Y4pdeGMyySOZQI=' + + [mod.'gopkg.in/yaml.v3'] + version = 'v3.0.1' + hash = 'sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU=' + + [mod.'k8s.io/api'] + version = 'v0.34.2' + hash = 'sha256-8TpPB07Dh6REOxYzT78OGLCV1iTZlMVzEQCzmpA4Q3U=' + + [mod.'k8s.io/apimachinery'] + version = 'v0.34.2' + hash = 'sha256-y6GVugdaX81lHwVWgDgFoIi/v6K9A3YC2kKlGUritVU=' + + [mod.'k8s.io/client-go'] + version = 'v0.34.2' + hash = 'sha256-ELhaXsMrieNn0ms84n5kL9QlNDXQ2u8xpbny+bTvdZg=' + + [mod.'k8s.io/klog/v2'] + version = 'v2.130.1' + hash = 'sha256-n5vls1o1a0V0KYv+3SULq4q3R2Is15K8iDHhFlsSH4o=' + + [mod.'k8s.io/kube-openapi'] + version = 'v0.0.0-20250710124328-f3f2b991d03b' + hash = 'sha256-0v0MN67pora3UCA8vXSJDgkosx/1RaB2HkjUnwdVLbY=' + + [mod.'k8s.io/utils'] + version = 'v0.0.0-20250604170112-4c0f3b243397' + hash = 'sha256-USPKRYYKfbhQWU0CgT/8V1hdBirWjmhTZvJCuuUlNFo=' + + [mod.'nhooyr.io/websocket'] + version = 'v1.8.17' + hash = 'sha256-YIYdIz3KR3j3ykT/TSOoftwkCoZdX1/bUtlXm3RjfoQ=' + + [mod.'sigs.k8s.io/json'] + version = 'v0.0.0-20241014173422-cfa47c3a1cc8' + hash = 'sha256-dkegDkyjp/niYirIdhbQrBYt/uttCZQAfsBzKSzOMh0=' + + [mod.'sigs.k8s.io/randfill'] + version = 'v1.0.0' + hash = 'sha256-xldQxDwW84hmlihdSOFfjXyauhxEWV9KmIDLZMTcYNo=' + + [mod.'sigs.k8s.io/structured-merge-diff/v6'] + version = 'v6.3.0' + hash = 'sha256-2EqUZSaHUhwTrdjoZuv+Z99tZYrX1E6rxf2ejeKd2BM=' + + [mod.'sigs.k8s.io/yaml'] + version = 'v1.6.0' + hash = 'sha256-49hg7IVPzwxeovp+HTMiWa/10NMMTSTjAdCmIv6p9dw=' From 0b9dd56fd231a64414a353ac62328a18eadbc72d Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 4 May 2026 16:37:40 -0500 Subject: [PATCH 4/4] Fix remaining (all) build targets with stub for dist --- Makefile | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 237bb40..b21b72c 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,11 @@ web-clean: ## Clean web build artifacts @rm -rf web/dist web/node_modules @echo "$(GREEN)✓ Web artifacts cleaned$(NC)" +# Ensure web/dist exists for go:embed (stub if not built) +ensure-web-dist: + @mkdir -p web/dist + @test -f web/dist/index.html || echo '' > web/dist/index.html + # Build targets build: deps web-build ## Build binary with embedded web dashboard @echo "$(BLUE)Building Gonzo with web dashboard...$(NC)" @@ -91,17 +96,15 @@ build-signed: build ## Build and codesign for macOS (set CODESIGN_IDENTITY for d @echo "$(GREEN)✓ Signed $(BUILD_DIR)/$(BINARY_NAME)$(NC)" @codesign -dvv $(BUILD_DIR)/$(BINARY_NAME) 2>&1 | head -5 -build-tui: deps ## Build TUI-only binary (placeholder web dashboard) +build-tui: deps ensure-web-dist ## Build TUI-only binary (placeholder web dashboard) @echo "$(BLUE)Building TUI-only version...$(NC)" @mkdir -p $(BUILD_DIR) - @mkdir -p web/dist - @test -f web/dist/index.html || echo '

Run make build to embed the full web dashboard

' > web/dist/index.html CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) \ $(GO) build $(BUILD_FLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) $(CMD_DIR) @echo "$(GREEN)✓ Built $(BUILD_DIR)/$(BINARY_NAME) (TUI only)$(NC)" # Cross-platform builds -cross-build: clean deps ## Build for multiple platforms +cross-build: clean deps ensure-web-dist ## Build for multiple platforms @echo "$(BLUE)Building for multiple platforms...$(NC)" @mkdir -p $(DIST_DIR) @@ -147,11 +150,11 @@ deps-tidy: ## Clean up dependencies @$(GO) mod tidy # Testing -test: deps ## Run tests +test: deps ensure-web-dist ## Run tests @echo "$(BLUE)Running tests...$(NC)" @$(GO) test -v ./... -test-race: deps ## Run tests with race detection +test-race: deps ensure-web-dist ## Run tests with race detection @echo "$(BLUE)Running tests with race detection...$(NC)" @$(GO) test -race -v ./... @@ -181,10 +184,8 @@ fmt: ## Format code @echo "$(BLUE)Formatting code...$(NC)" @$(GO) fmt ./... -vet: ## Run go vet +vet: ensure-web-dist ## Run go vet @echo "$(BLUE)Running go vet...$(NC)" - @mkdir -p web/dist - @test -f web/dist/index.html || echo '' > web/dist/index.html @$(GO) vet ./... lint: ## Run linter (requires golangci-lint)