Compare commits

...

44 Commits

Author SHA1 Message Date
Amir Raminfar da32dc9171 chore: release v8.14.7 2025-11-06 07:01:45 -08:00
Amir Raminfar 653fc8878a fix: fixes filtering of levels to backfill, similar to search (#4228) 2025-11-06 06:31:02 -08:00
renovate[bot] 3abe70b402 fix(deps): update all non-major dependencies (#4229)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-06 06:10:49 +00:00
Amir Raminfar 6097bd49b7 fix: fixes redirect to also check the host of the container. fixes #4226 (#4227) 2025-11-05 17:51:11 +00:00
renovate[bot] 8462ee9454 chore(deps): update node.js to v24 (#4224)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 09:03:02 -08:00
renovate[bot] 8e6f7f16ba fix(deps): update all non-major dependencies (#4223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 00:39:24 +00:00
Amir Raminfar 4605a4a59d docs: updates dtop 2025-10-31 14:53:42 -07:00
renovate[bot] c7615d01a3 chore(deps): update actions/upload-artifact action to v5 (#4219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 08:54:05 -07:00
renovate[bot] aba8746dd0 fix(deps): update all non-major dependencies (#4218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 00:40:41 +00:00
Amir Raminfar 9e7de63f26 fix: fixes dropdown placement. (#4217) 2025-10-25 20:03:21 +00:00
Amir Raminfar 313cde3ad1 chore: release v8.14.6 2025-10-24 10:01:54 -07:00
Amir Raminfar bf0148d131 chore: fixes tests 2025-10-24 09:52:57 -07:00
Amir Raminfar 72d1ecbcc6 fix: fixes linebreaks breaks. Fixes #4197 (#4214) 2025-10-24 09:34:47 -07:00
Amir Raminfar 50ce41eca3 chore: fixes tests 2025-10-24 09:22:23 -07:00
Amir Raminfar cffd4bd4e3 fix: fixes action menu being cut 2025-10-24 09:18:42 -07:00
Amir Raminfar 0f1e9cf0e6 Merge branch 'master' into bugfix/scroll-jump 2025-10-24 09:09:53 -07:00
renovate[bot] a761129aff fix(deps): update vueuse monorepo to v14 (major) (#4213)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-24 08:45:43 -07:00
Amir Raminfar 16de645a89 chore: fixes tests 2025-10-24 08:43:46 -07:00
renovate[bot] 1c6f30bcd3 fix(deps): update all non-major dependencies (#4211)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-24 14:44:47 +00:00
Amir Raminfar 9f29aa5923 Merge branch 'master' into bugfix/scroll-jump 2025-10-24 07:26:37 -07:00
Amir Raminfar c2a17888a7 chore: fix permissions 2025-10-24 07:22:52 -07:00
Akash R 176c173ac7 fix: scroll-jump by making dropdown position dynamic 2025-10-24 09:59:26 +05:30
renovate[bot] ce436d9697 fix(deps): update all non-major dependencies (#4207)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-22 15:49:26 +00:00
renovate[bot] b752afb352 fix(deps): update dependency vite to v7.1.11 [security] (#4203)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 22:07:54 +00:00
renovate[bot] 50dc54f4bb fix(deps): update dependency unplugin-vue-components to v30 (#4201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 10:34:59 -07:00
Amir Raminfar e6eb7eeebd chore: updates d files 2025-10-20 09:36:19 -07:00
renovate[bot] 0cd62745f6 fix(deps): update all non-major dependencies (#4200)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 00:34:15 +00:00
Amir Raminfar 911f785e2c feat: support setting the path to certs (#4198) 2025-10-19 12:15:50 -07:00
renovate[bot] 074f402d0f chore(deps): update dependency jsdom to ^27.0.1 (#4199)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-19 16:45:04 +00:00
Amir Raminfar 9485fa738b chore: release v8.14.5 2025-10-17 10:11:27 -07:00
renovate[bot] bb56154811 chore(deps): update actions/checkout action to v5 (#4196)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 10:10:40 -07:00
renovate[bot] 78e63c39a0 fix(deps): update all non-major dependencies (#4195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-17 16:58:12 +00:00
Amir Raminfar c1c8e8cccf chore: changes node to node-version: 24.x (#4194) 2025-10-17 16:34:56 +00:00
Amir Raminfar f9bc915ace chore: pins node to v24 (#4193) 2025-10-17 16:26:09 +00:00
Amir Raminfar 938c6229f8 fix: fixes a nil pointer that was happening in the agent (#4183) 2025-10-17 08:30:47 -07:00
Amir Raminfar b945c35bfa chore: updates d files 2025-10-16 07:02:32 -07:00
Amir Raminfar 47adf9845a chore: adds claude 2025-10-14 16:24:08 -07:00
Amir Raminfar 6951c1cc71 chore: adds claude 2025-10-14 16:23:00 -07:00
Amir Raminfar 9dc27fa06c chore: auto gen d file 2025-10-14 16:11:14 -07:00
renovate[bot] f5786ba388 chore(deps): update all non-major dependencies (#4184)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 18:42:44 +00:00
Amir Raminfar b7b9ea7c6f chore: fixes typecheck (#4187) 2025-10-14 18:37:51 +00:00
Amir Raminfar d7b6e9ee50 chore: Add Claude Code GitHub Workflow (#4180) 2025-10-14 11:35:32 -07:00
renovate[bot] 34f68849f4 chore(deps): update actions/setup-node action to v6 (#4185)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-14 09:13:18 -07:00
Amir Raminfar fa78467698 chore: updates gitignore 2025-10-14 08:14:38 -07:00
35 changed files with 1996 additions and 1213 deletions
+57
View File
@@ -0,0 +1,57 @@
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Please review this pull request and provide feedback on:
- Code quality and best practices
- Potential bugs or issues
- Performance considerations
- Security concerns
- Test coverage
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
+53
View File
@@ -0,0 +1,53 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
permissions:
id-token: write
contents: read
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'
+11 -11
View File
@@ -11,15 +11,15 @@ jobs:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
name: Install Node
with:
node-version: latest
node-version: 24.11.0
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.0
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
@@ -33,7 +33,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: 1.25.2
go-version: 1.25.4
check-latest: true
- name: Checkout code
uses: actions/checkout@v5
@@ -53,15 +53,15 @@ jobs:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
name: Install Node
with:
node-version: latest
node-version: 24.11.0
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.0
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
@@ -82,7 +82,7 @@ jobs:
*.cache-to=type=gha,mode=max
- name: Run Playwright tests
run: docker compose up --exit-code-from playwright
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v5
if: always()
with:
name: playwright-report
@@ -147,7 +147,7 @@ jobs:
- uses: pnpm/action-setup@v4
name: Install pnpm
- name: Install Node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
- name: Release to Github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+2 -2
View File
@@ -29,9 +29,9 @@ jobs:
fetch-depth: 0 # Not needed if lastUpdated is not enabled
- uses: pnpm/action-setup@v2
- name: Setup Node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.0
cache: pnpm # or pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v5
+13 -17
View File
@@ -14,15 +14,15 @@ jobs:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
name: Install Node
with:
node-version: latest
node-version: 24.11.0
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.0
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
@@ -36,15 +36,11 @@ jobs:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.0
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
@@ -58,7 +54,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: "1.25.2"
go-version: "1.25.4"
check-latest: true
- name: Checkout code
uses: actions/checkout@v5
@@ -81,7 +77,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: "1.25.2"
go-version: "1.25.4"
check-latest: true
- name: Generate dependencies
run: make fake_assets shared_key.pem shared_cert.pem
@@ -97,15 +93,15 @@ jobs:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
name: Install Node
with:
node-version: latest
node-version: 24.11.0
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.0
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
@@ -124,7 +120,7 @@ jobs:
*.cache-to=type=gha,mode=max
- name: Run Playwright tests
run: docker compose up --exit-code-from playwright
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v5
if: always()
with:
name: playwright-report
+1
View File
@@ -15,3 +15,4 @@ coverage.out
*.pem
*.csr
tmp
.claude
+1
View File
@@ -1,5 +1,6 @@
auto-imports.d.ts
components.d.ts
typed-router.d.ts
docs/.vitepress/cache
docs/.vitepress/dist
dist
+225
View File
@@ -0,0 +1,225 @@
# 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
## 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
# Install Go tools (protobuf, air hot-reloader)
make tools
# 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/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
- `container/`: Container 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
- **`main.go`** - Application entry point with mode switching (server/swarm/k8s)
### 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
- `ContainerViewer/`: Container-specific UI
- `common/`: Reusable UI components
- **`assets/stores/`** - Pinia stores (auto-imported)
- `config.ts`: App configuration and feature flags
- `container.ts`: Container state management
- `hosts.ts`: Multi-host state
- `settings.ts`: User preferences
- **`assets/composable/`** - Vue composables (auto-imported)
- `eventStreams.ts`: SSE connection management
- `historicalLogs.ts`: Historical log fetching
- `logContext.ts`: Log filtering and search context
- `storage.ts`: LocalStorage abstractions
- **`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`
### 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
### 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 Labels
- `dev.dozzle.name`: Custom container display name
- `dev.dozzle.group`: Group containers together
- Label-based filtering throughout the application
### Deployment Modes
- **Server mode**: Single or multi-host Docker monitoring
- **Swarm mode**: Automatic discovery of Swarm nodes via Docker API
- **K8s mode**: Pod log monitoring in Kubernetes cluster
- **Agent mode**: Lightweight gRPC agent for remote log collection
+1 -1
View File
@@ -1,5 +1,5 @@
# Build assets
FROM --platform=$BUILDPLATFORM node:23.11.1-alpine AS node
FROM --platform=$BUILDPLATFORM node:24.11.0-alpine AS node
RUN corepack enable
+4 -1
View File
@@ -75,6 +75,7 @@ declare global {
const locale: typeof import('./stores/settings')['locale']
const loggingContextKey: typeof import('./composable/logContext')['loggingContextKey']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const manualResetRef: typeof import('@vueuse/core')['manualResetRef']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
@@ -122,6 +123,7 @@ declare global {
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
const refDebounced: typeof import('@vueuse/core')['refDebounced']
const refDefault: typeof import('@vueuse/core')['refDefault']
const refManualReset: typeof import('@vueuse/core')['refManualReset']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
@@ -472,6 +474,7 @@ declare module 'vue' {
readonly locale: UnwrapRef<typeof import('./stores/settings')['locale']>
readonly loggingContextKey: UnwrapRef<typeof import('./composable/logContext')['loggingContextKey']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly manualResetRef: UnwrapRef<typeof import('@vueuse/core')['manualResetRef']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
@@ -519,11 +522,11 @@ declare module 'vue' {
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
readonly refManualReset: UnwrapRef<typeof import('@vueuse/core')['refManualReset']>
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly scrollContextKey: UnwrapRef<typeof import('./composable/scrollContext')['scrollContextKey']>
readonly search: UnwrapRef<typeof import('./stores/settings')['search']>
readonly sessionHost: UnwrapRef<typeof import('./composable/storage')['sessionHost']>
+4 -1
View File
@@ -1,8 +1,11 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore lint: disable
// oxlint-disable
// ------
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
@@ -29,10 +29,15 @@ describe("<ContainerEventSource />", () => {
global.EventSource = EventSource;
// @ts-ignore
window.scrollTo = vi.fn();
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
disconnect: vi.fn(),
}));
global.IntersectionObserver = class IntersectionObserver {
observe = vi.fn();
disconnect = vi.fn();
unobserve = vi.fn();
takeRecords = vi.fn();
root = null;
rootMargin = "";
thresholds = [];
} as any;
vi.useFakeTimers();
vi.setSystemTime(1560336942459);
});
+17 -1
View File
@@ -1,5 +1,11 @@
<template>
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans" v-show="container">
<div
class="dropdown dropdown-hover absolute -left-2 z-10 font-sans"
:class="shouldShowBelow ? 'dropdown-right' : 'dropdown-right dropdown-end'"
v-show="container"
ref="dropdownRef"
@mouseenter="checkDropdownPosition"
>
<router-link
v-if="isSearching"
@click="resetSearch()"
@@ -130,4 +136,14 @@ function hideMenu(e: MouseEvent) {
}, 50);
}
}
const dropdownRef = useTemplateRef<HTMLDivElement>("dropdownRef");
const shouldShowBelow = ref(false);
function checkDropdownPosition() {
if (!dropdownRef.value) return;
const rect = dropdownRef.value.getBoundingClientRect();
shouldShowBelow.value = rect.top < 150;
}
</script>
@@ -1,7 +1,7 @@
<template>
<LogItem :logEntry>
<div
class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap"
class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-pre"
v-html="colorize(logEntry.message)"
></div>
</LogItem>
@@ -7,7 +7,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
</li>
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<div class="dropdown dropdown-hover absolute -left-2 z-10 font-sans dropdown-right dropdown-end"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
@@ -28,7 +28,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
</div>
</div>
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-pre">foo bar</div>
</div>
</li>
</ul>"
@@ -41,7 +41,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
</li>
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<div class="dropdown dropdown-hover absolute -left-2 z-10 font-sans dropdown-right dropdown-end"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
@@ -62,7 +62,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
</div>
</div>
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-pre">foo bar</div>
</div>
</li>
</ul>"
@@ -75,7 +75,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
</li>
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<div class="dropdown dropdown-hover absolute -left-2 z-10 font-sans dropdown-right dropdown-end"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
@@ -96,7 +96,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
</div>
</div>
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">This is a message.</div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-pre">This is a message.</div>
</div>
</li>
</ul>"
+1 -1
View File
@@ -5,7 +5,7 @@ import { setupLayouts } from "virtual:generated-layouts";
export const router = createRouter({
history: createWebHistory(withBase("/")),
routes: setupLayouts(routes),
routes: setupLayouts([...routes]),
});
export const install = (app: App) => {
+6 -1
View File
@@ -45,7 +45,12 @@ watchEffect(() => {
if (Date.now() - +currentContainer.value.finishedAt > 5 * 60 * 1000) return;
const nextContainer = allContainers.value
.filter((c) => c.startedAt > currentContainer.value.startedAt && c.name === currentContainer.value.name)
.filter(
(c) =>
c.startedAt > currentContainer.value.startedAt &&
c.name === currentContainer.value.name &&
c.host === currentContainer.value.host,
)
.sort((a, b) => +a.created - +b.created)[0];
if (!nextContainer) return;
+139 -38
View File
@@ -1,10 +1,15 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection ES6UnusedImports
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
// It's recommended to commit this file.
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
declare module 'vue-router/auto-resolver' {
export type ParamParserCustom = never
}
declare module 'vue-router/auto-routes' {
import type {
RouteRecordInfo,
@@ -18,23 +23,95 @@ declare module 'vue-router/auto-routes' {
* Route name map generated by unplugin-vue-router
*/
export interface RouteNamedMap {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
'/container/[id]': RouteRecordInfo<'/container/[id]', '/container/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/container/[id].time.[datetime]': RouteRecordInfo<'/container/[id].time.[datetime]', '/container/:id/time/:datetime', { id: ParamValue<true>, datetime: ParamValue<true> }, { id: ParamValue<false>, datetime: ParamValue<false> }>,
'/group/[name]': RouteRecordInfo<'/group/[name]', '/group/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
'/host/[id]': RouteRecordInfo<'/host/[id]', '/host/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/login': RouteRecordInfo<'/login', '/login', Record<never, never>, Record<never, never>>,
'/merged/[ids]': RouteRecordInfo<'/merged/[ids]', '/merged/:ids', { ids: ParamValue<true> }, { ids: ParamValue<false> }>,
'/service/[name]': RouteRecordInfo<'/service/[name]', '/service/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
'/settings': RouteRecordInfo<'/settings', '/settings', Record<never, never>, Record<never, never>>,
'/show': RouteRecordInfo<'/show', '/show', Record<never, never>, Record<never, never>>,
'/stack/[name]': RouteRecordInfo<'/stack/[name]', '/stack/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
'/': RouteRecordInfo<
'/',
'/',
Record<never, never>,
Record<never, never>,
| never
>,
'/[...all]': RouteRecordInfo<
'/[...all]',
'/:all(.*)',
{ all: ParamValue<true> },
{ all: ParamValue<false> },
| never
>,
'/container/[id]': RouteRecordInfo<
'/container/[id]',
'/container/:id',
{ id: ParamValue<true> },
{ id: ParamValue<false> },
| never
>,
'/container/[id].time.[datetime]': RouteRecordInfo<
'/container/[id].time.[datetime]',
'/container/:id/time/:datetime',
{ id: ParamValue<true>, datetime: ParamValue<true> },
{ id: ParamValue<false>, datetime: ParamValue<false> },
| never
>,
'/group/[name]': RouteRecordInfo<
'/group/[name]',
'/group/:name',
{ name: ParamValue<true> },
{ name: ParamValue<false> },
| never
>,
'/host/[id]': RouteRecordInfo<
'/host/[id]',
'/host/:id',
{ id: ParamValue<true> },
{ id: ParamValue<false> },
| never
>,
'/login': RouteRecordInfo<
'/login',
'/login',
Record<never, never>,
Record<never, never>,
| never
>,
'/merged/[ids]': RouteRecordInfo<
'/merged/[ids]',
'/merged/:ids',
{ ids: ParamValue<true> },
{ ids: ParamValue<false> },
| never
>,
'/service/[name]': RouteRecordInfo<
'/service/[name]',
'/service/:name',
{ name: ParamValue<true> },
{ name: ParamValue<false> },
| never
>,
'/settings': RouteRecordInfo<
'/settings',
'/settings',
Record<never, never>,
Record<never, never>,
| never
>,
'/show': RouteRecordInfo<
'/show',
'/show',
Record<never, never>,
Record<never, never>,
| never
>,
'/stack/[name]': RouteRecordInfo<
'/stack/[name]',
'/stack/:name',
{ name: ParamValue<true> },
{ name: ParamValue<false> },
| never
>,
}
/**
* Route file to route info map by unplugin-vue-router.
* Used by the volar plugin to automatically type useRoute()
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
*
* Each key is a file path relative to the project root with 2 properties:
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
@@ -44,58 +121,82 @@ declare module 'vue-router/auto-routes' {
*/
export interface _RouteFileInfoMap {
'assets/pages/index.vue': {
routes: '/'
views: never
routes:
| '/'
views:
| never
}
'assets/pages/[...all].vue': {
routes: '/[...all]'
views: never
routes:
| '/[...all]'
views:
| never
}
'assets/pages/container/[id].vue': {
routes: '/container/[id]'
views: never
routes:
| '/container/[id]'
views:
| never
}
'assets/pages/container/[id].time.[datetime].vue': {
routes: '/container/[id].time.[datetime]'
views: never
routes:
| '/container/[id].time.[datetime]'
views:
| never
}
'assets/pages/group/[name].vue': {
routes: '/group/[name]'
views: never
routes:
| '/group/[name]'
views:
| never
}
'assets/pages/host/[id].vue': {
routes: '/host/[id]'
views: never
routes:
| '/host/[id]'
views:
| never
}
'assets/pages/login.vue': {
routes: '/login'
views: never
routes:
| '/login'
views:
| never
}
'assets/pages/merged/[ids].vue': {
routes: '/merged/[ids]'
views: never
routes:
| '/merged/[ids]'
views:
| never
}
'assets/pages/service/[name].vue': {
routes: '/service/[name]'
views: never
routes:
| '/service/[name]'
views:
| never
}
'assets/pages/settings.vue': {
routes: '/settings'
views: never
routes:
| '/settings'
views:
| never
}
'assets/pages/show.vue': {
routes: '/show'
views: never
routes:
| '/show'
views:
| never
}
'assets/pages/stack/[name].vue': {
routes: '/stack/[name]'
views: never
routes:
| '/stack/[name]'
views:
| never
}
}
/**
* Get a union of possible route names in a certain route component file.
* Used by the volar plugin to automatically type useRoute()
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
*
* @internal
*/
+1 -1
View File
@@ -95,7 +95,7 @@ services:
playwright:
container_name: playwright
image: mcr.microsoft.com/playwright:v1.56.0-jammy
image: mcr.microsoft.com/playwright:v1.56.1-jammy
working_dir: /app
volumes:
- .:/app
+43 -3
View File
@@ -35,7 +35,6 @@ services:
> [!NOTE] Docker Socket Proxy users
> If you are using a remote agent you **CANNOT** add a socket proxy on top of the agent. Dozzle agents **REPLACE** using a proxy, see [Remote Hosts](/guide/remote-hosts.md) for more info and how to use a socket proxy instead of an agent.
The agent will start and listen on port `7007`. You can connect to the agent using the Dozzle UI by providing the agent's IP address and port. The agent will only show the containers that are available on the host where the agent is running.
> [!TIP]
@@ -153,7 +152,9 @@ This will restrict the agent to displaying only containers with the label `color
By default, Dozzle uses self-signed certificates for communication between agents. This is a private certificate which is only valid to other Dozzle instances. This is secure and recommended for most use cases. However, if Dozzle is exposed externally and an attacker knows exactly which port the agent is running on, then they can set up their own Dozzle instance and connect to the agent. To prevent this, you can provide your own certificates.
To provide custom certificates, you need to mount or use secrets to provide the certificates. Here is an example:
To provide custom certificates, you need to mount or use secrets to provide the certificates. By default, Dozzle looks for certificates at `/dozzle_cert.pem` and `/dozzle_key.pem`, but you can customize these paths using the `--cert` and `--key` flags or the `DOZZLE_CERT` and `DOZZLE_KEY` environment variables.
Here is an example using the default paths:
```yml
services:
@@ -176,10 +177,49 @@ secrets:
file: ./key.pem
```
Or using custom paths with environment variables:
```yml
services:
agent:
image: amir20/dozzle:latest
command: agent
environment:
- DOZZLE_CERT=/certs/my-cert.pem
- DOZZLE_KEY=/certs/my-key.pem
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./certs:/certs
ports:
- 7007:7007
```
Or using command-line flags:
::: code-group
```sh
docker run -v /var/run/docker.sock:/var/run/docker.sock -v ./certs:/certs -p 7007:7007 amir20/dozzle:latest agent --cert /certs/my-cert.pem --key /certs/my-key.pem
```
```yaml [docker-compose.yml]
services:
agent:
image: amir20/dozzle:latest
command: agent --cert /certs/my-cert.pem --key /certs/my-key.pem
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./certs:/certs
ports:
- 7007:7007
```
:::
> [!TIP]
> Docker secrets are preferred for providing certificates. They can be created using `docker secret create` command or as the example above using `docker-compose.yml`. The same certificates should be provided to the Dozzle instance connecting to the agent.
This will mount the `cert.pem` and `key.pem` files to the agent. The agent will use these certificates for communication. The same certificates should be provided to the Dozzle instance connecting to the agent.
This will mount the certificate and key files to the agent. The agent will use these certificates for communication. The same certificates should be provided to the Dozzle instance connecting to the agent.
To generate certificates, you can use the following command:
+2 -2
View File
@@ -10,10 +10,10 @@ title: Introducing dtop
## Installation
Full installation instructions can be found at [https://github.com/amir20/dtop](https://github.com/amir20/dtop?tab=readme-ov-file#installation). You can also install using brew:
Full installation instructions can be found at [https://github.com/amir20/dtop](https://github.com/amir20/dtop?tab=readme-ov-file#installation). You can use Docker to quickly run `dtop`:
```bash
brew install amir20/homebrew-dtop/dtop
docker run -v /var/run/docker.sock:/var/run/docker.sock -it ghcr.io/amir20/dtop:latest
```
## Project Status
+2 -2
View File
@@ -4,7 +4,7 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alexflint/go-arg v1.6.0
github.com/beme/abide v0.0.0-20190723115211-635a09831760
github.com/docker/docker v28.5.1+incompatible
github.com/docker/docker v28.5.2+incompatible
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1
@@ -119,4 +119,4 @@ require (
sigs.k8s.io/yaml v1.6.0 // indirect
)
go 1.25.2
go 1.25.4
+2
View File
@@ -62,6 +62,8 @@ github.com/docker/docker v28.5.0+incompatible h1:ZdSQoRUE9XxhFI/B8YLvhnEFMmYN9Pp
github.com/docker/docker v28.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+10 -4
View File
@@ -64,9 +64,11 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
g := container.NewEventGenerator(out.Context(), dockerReader, c)
for event := range g.Events {
out.Send(&pb.StreamLogsResponse{
Event: logEventToPb(event),
})
if event != nil {
out.Send(&pb.StreamLogsResponse{
Event: logEventToPb(event),
})
}
}
select {
@@ -93,7 +95,11 @@ func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentSe
for {
select {
case event := <-g.Events:
case event, ok := <-g.Events:
if !ok {
// Channel closed, exit cleanly
return nil
}
out.Send(&pb.StreamLogsResponse{
Event: logEventToPb(event),
})
+1 -1
View File
@@ -27,7 +27,7 @@ func (a *AgentCmd) Run(args Args, embeddedCerts embed.FS) error {
if err != nil {
return fmt.Errorf("failed to create docker client: %w", err)
}
certs, err := ReadCertificates(embeddedCerts)
certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath)
if err != nil {
return fmt.Errorf("failed to read certificates: %w", err)
}
+1 -1
View File
@@ -14,7 +14,7 @@ type AgentTestCmd struct {
}
func (at *AgentTestCmd) Run(args Args, embeddedCerts embed.FS) error {
certs, err := ReadCertificates(embeddedCerts)
certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath)
if err != nil {
return fmt.Errorf("error reading certificates: %w", err)
}
+2
View File
@@ -36,6 +36,8 @@ type Args struct {
TimeoutString string `arg:"--timeout,env:DOZZLE_TIMEOUT" default:"10s" help:"sets the timeout for docker client"`
Timeout time.Duration `arg:"-"`
Namespace []string `arg:"env:DOZZLE_NAMESPACE" help:"sets the namespace to use in k8s"`
CertPath string `arg:"--cert,env:DOZZLE_CERT" default:"dozzle_cert.pem" help:"path to custom TLS certificate"`
KeyPath string `arg:"--key,env:DOZZLE_KEY" default:"dozzle_key.pem" help:"path to custom TLS key"`
Healthcheck *HealthcheckCmd `arg:"subcommand:healthcheck" help:"checks if the server is running"`
Generate *GenerateCmd `arg:"subcommand:generate" help:"generates a configuration file for simple auth"`
Agent *AgentCmd `arg:"subcommand:agent" help:"starts the agent"`
+4 -4
View File
@@ -8,13 +8,13 @@ import (
"github.com/rs/zerolog/log"
)
func ReadCertificates(certs embed.FS) (tls.Certificate, error) {
if pair, err := tls.LoadX509KeyPair("dozzle_cert.pem", "dozzle_key.pem"); err == nil {
log.Info().Msg("Loaded custom dozzle certificate and key")
func ReadCertificates(certs embed.FS, certPath, keyPath string) (tls.Certificate, error) {
if pair, err := tls.LoadX509KeyPair(certPath, keyPath); err == nil {
log.Info().Str("cert", certPath).Str("key", keyPath).Msg("Loaded custom dozzle certificate and key")
return pair, nil
} else {
if !os.IsNotExist(err) {
log.Fatal().Err(err).Msg("Failed to load custom dozzle certificate and key. Stopping...")
log.Fatal().Err(err).Str("cert", certPath).Str("key", keyPath).Msg("Failed to load custom dozzle certificate and key. Stopping...")
}
}
+1 -1
View File
@@ -51,7 +51,7 @@ func CreateMultiHostService(embeddedCerts embed.FS, args Args) *docker_support.M
go StartEvent(args, "server", localClient, "")
}
certs, err := ReadCertificates(embeddedCerts)
certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath)
if err != nil {
log.Fatal().Err(err).Msg("Could not read certificates")
}
+1 -1
View File
@@ -20,7 +20,7 @@ func (h *HealthcheckCmd) Run(args Args, embeddedCerts embed.FS) error {
return fmt.Errorf("failed to read file: %w", err)
}
agentAddress := string(data)
certs, err := ReadCertificates(embeddedCerts)
certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath)
if err != nil {
return fmt.Errorf("failed to read certificates: %w", err)
}
+12 -2
View File
@@ -303,6 +303,13 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
levels[level] = struct{}{}
}
allLogs := true
for level := range container.SupportedLogLevels {
if _, ok := levels[level]; !ok {
allLogs = false
}
}
if r.URL.Query().Has("filter") {
var err error
regex, err = support_web.ParseRegex(r.URL.Query().Get("filter"))
@@ -310,7 +317,9 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
if !allLogs || regex != nil {
absoluteTime = time.Now()
go func() {
@@ -342,9 +351,10 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
if _, ok := levels[log.Level]; !ok {
continue
}
if support_web.Search(regex, log) {
events = append(events, log)
if regex != nil && !support_web.Search(regex, log) {
continue
}
events = append(events, log)
}
stillRunning = true
+15 -2
View File
@@ -6,6 +6,7 @@ import (
"encoding/binary"
"errors"
"io"
"net/url"
"regexp"
"time"
@@ -20,6 +21,16 @@ import (
"github.com/stretchr/testify/require"
)
func addAllLogLevels(q url.Values) {
q.Add("levels", "error")
q.Add("levels", "warn")
q.Add("levels", "info")
q.Add("levels", "debug")
q.Add("levels", "trace")
q.Add("levels", "fatal")
q.Add("levels", "unknown")
}
func Test_handler_streamLogs_happy(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
@@ -29,7 +40,7 @@ func Test_handler_streamLogs_happy(t *testing.T) {
q := req.URL.Query()
q.Add("stdout", "true")
q.Add("stderr", "true")
q.Add("levels", "info")
addAllLogLevels(q)
req.URL.RawQuery = q.Encode()
require.NoError(t, err, "NewRequest should not return an error.")
@@ -73,7 +84,7 @@ func Test_handler_streamLogs_happy_with_id(t *testing.T) {
q := req.URL.Query()
q.Add("stdout", "true")
q.Add("stderr", "true")
q.Add("levels", "info")
addAllLogLevels(q)
req.URL.RawQuery = q.Encode()
require.NoError(t, err, "NewRequest should not return an error.")
@@ -119,6 +130,7 @@ func Test_handler_streamLogs_happy_container_stopped(t *testing.T) {
q := req.URL.Query()
q.Add("stdout", "true")
q.Add("stderr", "true")
addAllLogLevels(q)
req.URL.RawQuery = q.Encode()
require.NoError(t, err, "NewRequest should not return an error.")
@@ -157,6 +169,7 @@ func Test_handler_streamLogs_error_reading(t *testing.T) {
q := req.URL.Query()
q.Add("stdout", "true")
q.Add("stderr", "true")
addAllLogLevels(q)
req.URL.RawQuery = q.Encode()
require.NoError(t, err, "NewRequest should not return an error.")
+1 -1
View File
@@ -68,7 +68,7 @@ func main() {
if err != nil {
log.Fatal().Err(err).Msg("Could not create docker client")
}
certs, err := cli.ReadCertificates(certs)
certs, err := cli.ReadCertificates(certs, args.CertPath, args.KeyPath)
if err != nil {
log.Fatal().Err(err).Msg("Could not read certificates")
}
+29 -29
View File
@@ -1,12 +1,12 @@
{
"name": "dozzle",
"version": "8.14.4",
"version": "8.14.7",
"description": "Realtime log viewer for docker containers.",
"homepage": "https://github.com/amir20/dozzle#readme",
"bugs": {
"url": "https://github.com/amir20/dozzle/issues"
},
"packageManager": "pnpm@10.18.2",
"packageManager": "pnpm@10.20.0",
"type": "module",
"repository": {
"type": "git",
@@ -33,18 +33,18 @@
"@iconify-json/carbon": "^1.2.14",
"@iconify-json/cil": "^1.2.3",
"@iconify-json/ic": "^1.2.4",
"@iconify-json/material-symbols": "^1.2.41",
"@iconify-json/material-symbols": "^1.2.44",
"@iconify-json/mdi": "^1.2.3",
"@iconify-json/mdi-light": "^1.2.2",
"@iconify-json/octicon": "^1.2.16",
"@iconify-json/octicon": "^1.2.17",
"@iconify-json/ph": "^1.2.2",
"@intlify/unplugin-vue-i18n": "^11.0.1",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "4.1.14",
"@vueuse/components": "^13.9.0",
"@vueuse/core": "^13.9.0",
"@vueuse/integrations": "^13.9.0",
"@vueuse/router": "14.0.0-beta.1",
"@tailwindcss/vite": "4.1.16",
"@vueuse/components": "^14.0.0",
"@vueuse/core": "^14.0.0",
"@vueuse/integrations": "^14.0.0",
"@vueuse/router": "14.0.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0",
"ansi-to-html": "^0.7.2",
@@ -54,35 +54,35 @@
"d3-selection": "^3.0.0",
"d3-shape": "^3.2.0",
"d3-transition": "^3.0.1",
"daisyui": "5.2.3",
"daisyui": "5.4.5",
"entities": "^7.0.0",
"fuse.js": "^7.1.0",
"lodash.debounce": "^4.0.8",
"pinia": "^3.0.3",
"pinia": "^3.0.4",
"sortablejs": "^1.15.6",
"splitpanes": "^4.0.4",
"strip-ansi": "^7.1.2",
"tailwindcss": "4.1.14",
"tailwindcss": "4.1.16",
"unplugin-auto-import": "^20.2.0",
"unplugin-icons": "^22.4.2",
"unplugin-vue-components": "^29.1.0",
"unplugin-icons": "^22.5.0",
"unplugin-vue-components": "^30.0.0",
"unplugin-vue-macros": "^2.14.5",
"unplugin-vue-router": "^0.15.0",
"vite": "7.1.9",
"unplugin-vue-router": "^0.16.1",
"vite": "7.2.1",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-svg-loader": "^5.1.0",
"vitepress": "1.6.4",
"vue": "^3.5.22",
"vue": "^3.5.23",
"vue-i18n": "^11.1.12",
"vue-router": "^4.5.1"
"vue-router": "^4.6.3"
},
"devDependencies": {
"@apache-arrow/esnext-esm": "^21.1.0",
"@iconify-json/ion": "^1.2.6",
"@iconify-json/material-symbols-light": "^1.2.41",
"@iconify-json/material-symbols-light": "^1.2.44",
"@iconify-json/ri": "^1.2.6",
"@pinia/testing": "^1.0.2",
"@playwright/test": "^1.56.0",
"@pinia/testing": "^1.0.3",
"@playwright/test": "^1.56.1",
"@types/d3-array": "^3.2.2",
"@types/d3-ease": "^3.0.2",
"@types/d3-scale": "^4.0.9",
@@ -90,24 +90,24 @@
"@types/d3-shape": "^3.1.7",
"@types/d3-transition": "^3.0.9",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^24.7.2",
"@types/node": "^24.10.0",
"@vitejs/plugin-vue": "6.0.1",
"@vue/compiler-sfc": "^3.5.22",
"@vue/compiler-sfc": "^3.5.23",
"@vue/test-utils": "^2.4.6",
"bumpp": "^10.3.1",
"c8": "^10.1.3",
"concurrently": "^9.2.1",
"eventsourcemock": "^2.0.0",
"jsdom": "^27.0.0",
"lint-staged": "^16.2.4",
"jsdom": "^27.1.0",
"lint-staged": "^16.2.6",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"prettier-plugin-tailwindcss": "^0.7.1",
"simple-git-hooks": "^2.13.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
"vitest": "^3.2.4",
"vue-component-type-helpers": "3.1.1",
"vue-tsc": "3.1.1"
"vitest": "^4.0.7",
"vue-component-type-helpers": "3.1.3",
"vue-tsc": "3.1.3"
},
"lint-staged": {
"*.{js,vue,css,ts,html,md}": [
+1318 -1074
View File
File diff suppressed because it is too large Load Diff