# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Comment Style **Always use ultra-brief mode for all PR reviews and responses.** Format: - Critical issues only (bugs, security, blockers) - Brief bullet points, no lengthy explanations - Skip verbose sections (no "Strengths", "Summary", etc.) - Include file:line references when relevant - Maximum ~10-15 lines per response ## Testing Unreleased PRs When replying to a GitHub issue or discussion where the fix lives in an open PR, ask the reporter to test the pre-built image: `amir20/dozzle:pr-XXX` (XXX = PR number). CI builds a tagged image per PR, so reporters can verify without waiting for the next release. ## GitHub Tone (issues, PRs, comments, discussions) When posting anything to GitHub, write like a human maintainer, not an AI assistant. Avoid telltale LLM patterns: - No em dashes or en dashes. Use commas, periods, or parentheses instead. - No "Not X, but Y" rhetorical contrasts. - No throat-clearing openers ("Great point", "Makes sense", "Thanks for the detailed write-up"). - No closing summaries or recap sentences. - No bolded inline labels mid-paragraph ("**Why:**", "**Note:**"). - Drop hedges ("essentially", "basically", "essentially just"). Say it plain. - Lowercase casual tone is fine. Contractions are fine. Short sentences are fine. - Don't over-explain tradeoffs. State the decision, give one reason, stop. ## Project Overview Dozzle is a lightweight, web-based Docker log viewer with real-time monitoring capabilities. It's a hybrid application with: - **Backend**: Go (HTTP server, Docker API client, WebSocket streaming) - **Frontend**: Vue 3 (SPA with Vite, TypeScript) The application supports multiple deployment modes: standalone server, Docker Swarm, and Kubernetes (k8s). ## Development Commands ### Setup ```bash # Install dependencies pnpm install # Generate certificates and protobuf files make generate ``` ### Development ```bash # Run full development environment (backend + frontend with hot reload) make dev # Alternative: Run backend and frontend separately pnpm run watch:backend # Go backend with air (port 3100) pnpm run watch:frontend # Vite dev server (port 3100) # Run in agent mode for development pnpm run agent:dev ``` ### Building ```bash # Build frontend assets pnpm build # or make dist # Build entire application (includes frontend build) make build # Build Docker image make docker ``` ### Testing ```bash # Run Go tests make test # Run frontend tests (Vitest) pnpm test # Run in watch mode TZ=UTC pnpm test --watch # Type checking pnpm typecheck ``` ### Preview & Other ```bash # Preview production build locally pnpm preview # or make preview # Run integration tests (Playwright) make int ``` ## Architecture ### Backend (Go) The Go backend is organized into these key packages: - **`internal/web/`** - HTTP server and routing layer - Routes defined in `routes.go` using chi router - WebSocket/SSE handlers for log streaming (`logs.go`) - Authentication middleware and token management (`auth.go`) - Container action handlers (`actions.go`) - **`internal/docker/`** - Docker API client implementation - `client.go`: Main Docker client wrapper with container operations - `log_reader.go`: Streaming container logs - `stats_collector.go`: Real-time container stats collection - **`internal/agent/`** - gRPC agent for multi-host support - Uses Protocol Buffers (protos defined in `protos/`) - Enables distributed log collection across Docker hosts - **`internal/cloud/`** - Dozzle Cloud integration (tool execution engine) - `client.go`: Bidirectional gRPC stream client with auto-reconnect and exponential backoff - `tools.go`: Tool registration, dispatch (`executeTool`), and `ToolHostService` interface - `tools_containers.go`: Container listing, finding, stats, and inspection tools - `tools_logs.go`: Log fetching with level/query/regex filtering (max 100 lines) - `tools_actions.go`: Container start/stop/restart actions (gated by `enableActions`) - `tools_helpers.go`: Proto conversion utilities and host name resolution - Uses `protos/cloud.proto` for service and message definitions - **`internal/k8s/`** - Kubernetes client support - Alternative to Docker client for k8s deployments - **`internal/support/`** - Support utilities - `cli/`: Command-line argument parsing and validation - `docker/`: Multi-host Docker management and Swarm support (`docker_service.go`, client managers) - `k8s/`: Kubernetes service abstractions - `web/`: Web service utilities - **`internal/auth/`** - Authentication providers - Simple file-based auth (`simple.go`) - Forward proxy auth (`proxy.go`) - Role-based authorization (`roles.go`) - **`internal/container/`** - Container domain models and interfaces - `event_generator.go`: Log parsing and grouping logic (multi-line, JSON detection) - **`internal/notification/`** - Alert and notification system - `manager.go`: Notification rule evaluation and dispatching - `log_listener.go`: Log pattern matching for alerts - `dispatcher/`: Notification channel implementations (email, webhook, etc.) - **`main.go`** - Application entry point with mode switching (server/swarm/k8s/agent) ### Frontend (Vue 3) The frontend uses file-based routing with these conventions: - **`assets/pages/`** - File-based routes (unplugin-vue-router) - `container/[id].vue`: Single container view - `merged/[ids].vue`: Multi-container merged view - `host/[id].vue`: Host-level logs - `service/[name].vue`: Swarm service logs - `stack/[name].vue`: Docker stack logs - `group/[name].vue`: Custom grouped logs - **`assets/components/`** - Vue components (auto-imported) - `LogViewer/`: Core log viewing components - `SimpleLogItem.vue`: Single-line log entries - `ComplexLogItem.vue`: JSON/structured log entries - `GroupedLogItem.vue`: Multi-line grouped log entries - `ContainerEventLogItem.vue`: Container lifecycle events - `SkippedEntriesLogItem.vue`: Placeholder for skipped logs - `LoadMoreLogItem.vue`: Load more historical logs - `ContainerViewer/`: Container-specific UI - `common/`: Reusable UI components - `BarChart.vue`: Lightweight bar chart with automatic downsampling - `HostCard.vue`: Host overview card with metrics - `MetricCard.vue`: Reusable metric display component - `ContainerTable.vue`: Container table with historical stat visualization - **`assets/stores/`** - Pinia stores (auto-imported) - `config.ts`: App configuration and feature flags (injected from backend HTML, frozen immutable) - `container.ts`: Container state management with EventSource streaming (`/api/events/stream`) - `hosts.ts`: Multi-host state - `settings.ts`: User preferences (localStorage-backed via profileStorage) - `pinned.ts`: Pinned container logs for side-by-side viewing - `swarm.ts`, `k8s.ts`: Deployment mode-specific state - `announcements.ts`: Feature announcements - **`assets/composable/`** - Vue composables (auto-imported) - `eventStreams.ts`: SSE connection management with buffer-based flushing (250ms debounce) - `historicalLogs.ts`: Historical log fetching - `logContext.ts`: Log filtering and search context (provide/inject pattern) - `scrollContext.ts`: Scroll state management (paused, progress, currentDate) - `storage.ts`: LocalStorage abstractions with reactivity - `visible.ts`: Log filtering by visible keys for complex logs - `containerActions.ts`: Container control operations - `duckdb.ts`: DuckDB WASM for SQL queries on logs - **`assets/modules/`** - Vue plugins - `router.ts`: Vue Router configuration - `pinia.ts`: Pinia store setup - `i18n.ts`: Internationalization ### Communication Flow 1. **Real-time Logs**: Frontend establishes SSE connections to `/api/hosts/{host}/containers/{id}/logs/stream` 2. **Container Events**: SSE stream at `/api/events/stream` pushes container lifecycle events 3. **Stats**: Real-time CPU/memory stats streamed via SSE alongside events 4. **Actions**: POST to `/api/hosts/{host}/containers/{id}/actions/{action}` (start/stop/restart) 5. **Terminal**: WebSocket connections for container attach/exec at `/api/hosts/{host}/containers/{id}/attach` ### Build System - **Frontend**: Vite builds to `dist/` with manifest - **Backend**: Embeds `dist/` using Go embed directive - **Hot Reload**: In development, `DEV=true` disables embedded assets, `LIVE_FS=true` serves from filesystem - **Makefile**: Orchestrates builds and dependency generation ## Important Development Notes ### Frontend - Auto-imports are configured for Vue composables, components, and Pinia stores (see `vite.config.ts`) - Icons use unplugin-icons with multiple icon sets (mdi, carbon, material-symbols, etc.) - Tailwind CSS with DaisyUI for styling - TypeScript definitions auto-generated in `assets/auto-imports.d.ts` and `assets/components.d.ts` - **Log Entry Types**: Three types of log messages supported - `SimpleLogEntry`: Single-line text logs (`string`) - `ComplexLogEntry`: Structured JSON logs (`JSONObject`) - `GroupedLogEntry`: Multi-line grouped logs (`string[]`) - **Type consistency**: Use `LogMessage` type alias instead of `string | string[] | JSONObject` for log entry messages - **Log Entry Factory Pattern**: Use `LogEntry.create(logEvent)` to instantiate the correct entry type based on `logEvent.t` field - **EventSource Buffering**: Log streams use buffer-based flushing (250ms debounce, 1000ms max) to batch UI updates - **Charts/Visualizations**: Custom lightweight implementations (no D3.js) - `BarChart.vue`: Self-contained bar chart with responsive downsampling - Downsampling algorithm: Averages data into buckets based on available screen width - All stat history tracked in `Container.statsHistory` (max 300 items via rolling window) - `chartData` is always a rolling window of max 300 items — array length stays constant - Uses `ref` (not `computed`) for `downsampledBars` to enable in-place mutation of the last bar, avoiding full re-renders - Component instance is reused when switching containers; after init the chart only patches the last bar per tick, so on a wholesale `chartData` replacement (container switch) the parent must call the exposed `recalculate()`. `MultiContainerStat` holds refs to its `BarChart`s and calls it in the `containers` watch. (Note: `Container` carries Vue `ref`s, so VueTestUtils `setProps` cannot retrigger such a watch — tests must swap the container via a parent `ref` re-render.) ### Backend - The application uses Go 1.25+ with module support - Certificate generation is required (`make generate` creates shared_key.pem and shared_cert.pem) - Protocol buffer generation happens via `go generate` directive in `main.go` - Docker client uses API version negotiation for compatibility - **Service Layer Architecture**: - `ClientService` interface abstracts Docker/K8s/Agent backends - `MultiHostService` orchestrates multi-host operations - `ClientManager` implementations: `RetriableClientManager` (server mode), `SwarmClientManager` (swarm mode) ### Authentication - Three modes: none, simple (file-based users.yml), forward-proxy (e.g., Authelia) - JWT tokens for simple auth with configurable TTL - User file location: `./data/users.yml` or `./data/users.yaml` ### Testing - Go tests use standard `testing` package with testify assertions - Frontend uses Vitest with `@vue/test-utils` - Integration tests with Playwright in `e2e/` - Tests must run with `TZ=UTC` for consistent timestamps ### Container Stats & Metrics - Stats are tracked using exponential moving average (EMA) with alpha=0.2 - History stored in rolling window (300 items max) via `useSimpleRefHistory` - CPU metrics normalized by core count (respects `cpuLimit` or falls back to host `nCPU`) - Memory metrics include both percentage and absolute usage (`memoryUsage` vs `memory`) - Stats visualization uses adaptive downsampling for performance ### Container Labels - `dev.dozzle.name`: Custom container display name - `dev.dozzle.group`: Group containers together - Label-based filtering throughout the application ### Deployment Modes - **Server mode** (default): Single or multi-host Docker monitoring - Uses `RetriableClientManager` with local + remote agent clients - **Swarm mode**: Automatic discovery of Swarm nodes via Docker API - Creates gRPC agent server on each node (port 7007) - Uses `SwarmClientManager` for node discovery - **K8s mode**: Pod log monitoring in Kubernetes cluster - Implements `container.Client` interface via Kubernetes API - **Agent mode**: Lightweight gRPC agent for remote log collection - Run with `dozzle agent` or `pnpm run agent:dev` - Listens on port 7007 with TLS certificate authentication ## Key Architectural Patterns ### Backend Abstraction Layers The backend follows a clean layered architecture: ``` HTTP Handlers (internal/web) ↓ HostService Interface (MultiHostService) ↓ ClientService Interface (per host) ↓ container.Client Interface ↓ Implementation (DockerClient, K8sClient, AgentClient) ``` **When adding new container operations:** 1. Define method in `container.Client` interface (`internal/container/client.go`) 2. Implement in `internal/docker/client.go` (and `internal/k8s/client.go` if applicable) 3. Add wrapper method in `ClientService` interface (`internal/support/docker/docker_service.go`) 4. Add HTTP handler in `internal/web/` with appropriate route ### Frontend Data Flow **Real-time Log Viewing:** 1. User navigates to `/container/{id}` route 2. Page component calls `useContainerStream(container)` composable 3. Composable creates EventSource connection to `/api/hosts/{host}/containers/{id}/logs/stream` 4. Backend streams `LogEvent` objects via SSE 5. Frontend buffers events (250ms debounce, max 1000ms) 6. Batched buffer flushes update reactive `messages` array 7. `LogViewer.vue` renders using appropriate component (`SimpleLogItem`, `ComplexLogItem`, `GroupedLogItem`) 8. When messages exceed `maxLogs` (400), oldest entries replaced or marked as `SkippedLogsEntry` **Stats Streaming:** 1. `container.ts` store connects to `/api/events/stream` on app init 2. Backend multiplexes container events and stats into single SSE stream 3. `container-stat` events update `Container._stat` and append to `_statsHistory` 4. EMA calculation provides smoothed `movingAverageStat` (alpha=0.2) 5. `ContainerTable.vue` displays mini bar charts using `statsHistory` with downsampling ### Protocol Buffer Flow (Agent Mode) 1. Main server creates `agent.NewClient(endpoint, certs)` for each remote host 2. AgentClient implements `container.Client` interface 3. Method calls translate to gRPC requests defined in `protos/rpc.proto` 4. Remote agent receives gRPC call, delegates to local `DockerClient` 5. Streaming RPCs (logs, stats, events) use bidirectional channels 6. Responses converted back to domain models via `FromProto()` methods ### Cloud Tool Execution Flow 1. `cloud.Client.Run()` blocks until `Notify()` signals a cloud dispatcher is configured 2. `connect()` establishes bidirectional gRPC stream (`ToolStream` RPC) to cloud endpoint 3. Cloud sends `ToolRequest` (ListTools or CallTool), client dispatches via `executeTool()` 4. Tool calls run concurrently (max 5 via weighted semaphore), responses sent back on stream 5. On disconnect, exponential backoff (1s→30s with jitter) triggers reconnection 6. `PermissionDenied` errors stop retrying permanently (invalid API key / no pro plan) 7. Tool definitions cached via `sync.Once`; zero overhead for non-cloud users ### Log Parsing Pipeline 1. Docker API returns multiplexed stream (8-byte headers + payload) 2. `log_reader.go` parses headers, extracts stdout/stderr type 3. `event_generator.go` receives raw log lines 4. Detection logic identifies: - JSON structure → `ComplexLogEntry` - Multi-line patterns (stack traces) → `GroupedLogEntry` - Single lines → `SimpleLogEntry` 5. Log level extraction via regex patterns 6. `LogEvent` serialized to JSON and sent via SSE 7. Frontend deserializes and renders with appropriate component ## Adding New Features ### Adding a New HTTP Route 1. Define route in `internal/web/routes.go` using chi router: ```go r.Get("/api/custom-endpoint", h.customHandler) ``` 2. Implement handler method in appropriate file (e.g., `actions.go`, `logs.go`) 3. Use `hostService` to find container/host via `FindContainer()` or `FindHost()` 4. Return JSON response or establish SSE/WebSocket stream ### Adding a New Log View Type 1. Create route file in `assets/pages/` (e.g., `custom/[id].vue`) 2. Create composable in `assets/composable/eventStreams.ts` (e.g., `useCustomStream()`) 3. Composable should: - Build API URL with appropriate filters - Create EventSource connection - Handle buffering and message batching - Return reactive `messages` array and control methods 4. Use `LogViewer.vue` component to render messages 5. Add backend API endpoint if needed (see above) ### Adding Container Stats/Metrics 1. Add field to `Stat` type in `internal/container/types.go` 2. Update `stats_collector.go` to extract metric from Docker API response 3. Add calculation logic in `docker/calculation.go` if needed 4. Ensure protobuf definition includes field in `protos/rpc.proto` 5. Frontend automatically receives updates via existing SSE stream 6. Update `Container` model in `assets/models/Container.ts` if UI needs access ### Working with Notifications/Alerts **Backend** (`internal/notification/`): - `manager.go`: Rule evaluation engine, manages alert state - `log_listener.go`: Subscribes to container log streams, evaluates rules against incoming logs - `types.go`: Alert rule definitions (log pattern matching, thresholds) - `dispatcher/`: Notification channel implementations **Frontend** (`assets/pages/notifications.vue`, `assets/components/Notification/`): - `AlertForm.vue`, `DestinationForm.vue`: UI for creating rules - Rules persisted to `./data/notifications.yml` via `internal/notification/persist.go` - Alert state displayed in notification cards **Adding a new notification channel:** 1. Implement dispatcher interface in `internal/notification/dispatcher/` 2. Register in `manager.go` dispatcher factory 3. Add UI form in `assets/components/Notification/DestinationForm.vue` ### Adding a New Cloud Tool 1. Define the tool in `AvailableTools()` in `internal/cloud/tools.go` with name, description, and parameter schema 2. Add a response message type in `protos/cloud.proto` and add it to the `CallToolResponse.result` oneof 3. Run `make generate` to regenerate protobuf code 4. Add a case in the `executeTool()` switch in `internal/cloud/tools.go` 5. Implement the execution function in the appropriate `tools_*.go` file 6. Use `ToolHostService` interface methods to access container/host data 7. Add tests in `tools_test.go` ## Common Development Patterns ### Testing - Always run Go tests with race detector: `go test -race` - Frontend tests require `TZ=UTC` for timestamp consistency - Integration tests use Playwright with `make int` (runs docker-compose setup) - Use `testify/assert` for Go test assertions ### Hot Reload Development - `make dev` runs both backend (air) and frontend (vite) with hot reload - `DEV=true` disables embedded asset serving - `LIVE_FS=true` serves assets from filesystem instead of embedded - Backend changes trigger air restart automatically - Frontend changes trigger vite HMR ### Debugging - Backend logs: Set `--level debug` flag or `DOZZLE_LEVEL=debug` env var - Frontend: Vue DevTools browser extension - SSE streams: Browser DevTools Network tab shows EventSource connections