Compare commits

...

66 Commits

Author SHA1 Message Date
Amir Raminfar 91152d7a94 chore: release v8.14.10 2025-12-02 10:36:44 -08:00
Amir Raminfar 109c0e50e0 feat: localizes dropdowns (#4273) 2025-12-02 10:33:34 -08:00
Amir Raminfar a9cfa97704 feat: supports grouping containers always (#4272) 2025-12-02 09:50:50 -08:00
Amir Raminfar 4ac7bd0f05 chore: updates readme 2025-12-02 09:16:15 -08:00
renovate[bot] 512293968e chore(deps): update dependency prettier to ^3.7.3 (#4266)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 01:24:07 +00:00
Amir Raminfar e443dd4c9b chore: adds generated files 2025-11-28 08:32:21 -08:00
renovate[bot] 835ca9fbc6 chore(deps): update pnpm to v10.24.0 (#4262)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-28 16:32:16 +00:00
Amir Raminfar cbf30f3ae0 chore: updates modules (#4264) 2025-11-28 16:27:27 +00:00
renovate[bot] 523761c424 chore(deps): update all non-major dependencies to v3.1.5 (#4260)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 00:37:05 +00:00
renovate[bot] bba95b98a6 fix(deps): update all non-major dependencies (#4258)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 18:23:17 +00:00
renovate[bot] 6d01128092 chore(deps): update actions/checkout action to v6 (#4256)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Amir Raminfar <findamir@gmail.com>
2025-11-20 23:36:06 +00:00
renovate[bot] 4b2235e0da fix(deps): update all non-major dependencies (#4255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-20 20:12:19 +00:00
Amir Raminfar b361f0b772 chore: release v8.14.9 2025-11-19 14:48:21 -08:00
renovate[bot] d84cc574e3 fix(deps): update all non-major dependencies (#4252)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 22:42:21 +00:00
Amir Raminfar bafcbf6d66 feat: enables filters and level filters when downloading (#4251) 2025-11-19 14:35:30 -08:00
renovate[bot] 80df836f1d fix(deps): update all non-major dependencies (#4244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-17 02:17:27 +00:00
Amir Raminfar 1b36d2c79d chore: release v8.14.8 2025-11-15 10:50:12 -08:00
Amir Raminfar b09d73736b feat: adds a dropdown when pages exceed 10 (#4242) 2025-11-15 18:46:34 +00:00
renovate[bot] 604133b591 fix(deps): update all non-major dependencies (#4240)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-14 15:18:29 +00:00
Andrei Micuda 065dd0aea0 feat: update log level retrieval to support CLEF-format logs ( '@l' key) (#4236) 2025-11-10 08:27:18 -08:00
renovate[bot] b1cbd7c23e chore(deps): update pnpm to v10.21.0 (#4234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 02:14:24 +00:00
renovate[bot] 0b8b60afd9 fix(deps): update all non-major dependencies (#4233)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-09 15:50:09 +00:00
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
65 changed files with 2944 additions and 1992 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@v6
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@v6
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:*)'
+16 -16
View File
@@ -8,18 +8,18 @@ jobs:
name: JavaScript Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- 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.1
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.1
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
@@ -33,10 +33,10 @@ 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
uses: actions/checkout@v6
- name: Install Protoc
uses: arduino/setup-protoc@v3
- name: Install gRPC and Go
@@ -50,18 +50,18 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- 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.1
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.1
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
@@ -107,7 +107,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
@@ -141,13 +141,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
- 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 }}
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
+3 -3
View File
@@ -24,14 +24,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
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.1
cache: pnpm # or pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v5
+18 -22
View File
@@ -11,18 +11,18 @@ jobs:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- 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.1
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.1
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
@@ -33,18 +33,14 @@ jobs:
name: JavaScript Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- 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.1
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
@@ -58,10 +54,10 @@ 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
uses: actions/checkout@v6
- name: Install Protoc
uses: arduino/setup-protoc@v3
with:
@@ -77,11 +73,11 @@ jobs:
name: Go Staticcheck
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
- 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
@@ -94,18 +90,18 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- 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.1
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: latest
node-version: 24.11.1
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.1-alpine AS node
RUN corepack enable
+4
View File
@@ -1,3 +1,7 @@
<p align="center">
<img src="assets/logo.svg" alt="Dozzle Logo" width="200"/>
</p>
# Dozzle - [dozzle.dev](https://dozzle.dev/)
Dozzle is a small lightweight application with a web based interface to monitor Docker logs. It doesnt store any log files. It is for live monitoring of your container logs only.
+375 -369
View File
@@ -6,375 +6,379 @@
// biome-ignore lint: disable
export {}
declare global {
const DEFAULT_SETTINGS: typeof import('./stores/settings')['DEFAULT_SETTINGS']
const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const allLevels: typeof import('./composable/logContext')['allLevels']
const arrayEquals: typeof import('./utils/index')['arrayEquals']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const automaticRedirect: typeof import('./stores/settings')['automaticRedirect']
const collapseNav: typeof import('./stores/settings')['collapseNav']
const compact: typeof import('./stores/settings')['compact']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
const computedInject: typeof import('@vueuse/core')['computedInject']
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
const config: typeof import('./stores/config')['default']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createApp: typeof import('vue')['createApp']
const createDrawer: typeof import('./composable/drawer')['createDrawer']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createPinia: typeof import('pinia')['createPinia']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createRef: typeof import('@vueuse/core')['createRef']
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
const customRef: typeof import('vue')['customRef']
const dateLocale: typeof import('./stores/settings')['dateLocale']
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const defineStore: typeof import('pinia')['defineStore']
const drawerContext: typeof import('./composable/drawer')['drawerContext']
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
const effectScope: typeof import('vue')['effectScope']
const extendRef: typeof import('@vueuse/core')['extendRef']
const flattenJSON: typeof import('./utils/index')['flattenJSON']
const flattenJSONToMap: typeof import('./utils/index')['flattenJSONToMap']
const formatBytes: typeof import('./utils/index')['formatBytes']
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
const getDeep: typeof import('./utils/index')['getDeep']
const globalShowPopup: typeof import('./composable/popup')['globalShowPopup']
const h: typeof import('vue')['h']
const hashCode: typeof import('./utils/index')['hashCode']
const hourStyle: typeof import('./stores/settings')['hourStyle']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const inject: typeof import('vue')['inject']
const injectLocal: typeof import('@vueuse/core')['injectLocal']
const isDefined: typeof import('@vueuse/core')['isDefined']
const isMobile: typeof import('./composable/media')['isMobile']
const isObject: typeof import('./utils/index')['isObject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const isShallow: typeof import('vue')['isShallow']
const lightTheme: typeof import('./stores/settings')['lightTheme']
const loadBetween: typeof import('./composable/eventStreams')['loadBetween']
const locale: typeof import('./stores/settings')['locale']
const loggingContextKey: typeof import('./composable/logContext')['loggingContextKey']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const mapActions: typeof import('pinia')['mapActions']
const mapGetters: typeof import('pinia')['mapGetters']
const mapState: typeof import('pinia')['mapState']
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const menuWidth: typeof import('./stores/settings')['menuWidth']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onDeactivated: typeof import('vue')['onDeactivated']
const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onLongPress: typeof import('@vueuse/core')['onLongPress']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const persistentVisibleKeysForContainer: typeof import('./composable/storage')['persistentVisibleKeysForContainer']
const pinnedContainers: typeof import('./composable/storage')['pinnedContainers']
const provide: typeof import('vue')['provide']
const provideLocal: typeof import('@vueuse/core')['provideLocal']
const provideLoggingContext: typeof import('./composable/logContext')['provideLoggingContext']
const provideScrollContext: typeof import('./composable/scrollContext')['provideScrollContext']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
const reactivePick: typeof import('@vueuse/core')['reactivePick']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
const refDebounced: typeof import('@vueuse/core')['refDebounced']
const refDefault: typeof import('@vueuse/core')['refDefault']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const DEFAULT_SETTINGS: typeof import('./stores/settings').DEFAULT_SETTINGS
const EffectScope: typeof import('vue').EffectScope
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
const allLevels: typeof import('./composable/logContext').allLevels
const arrayEquals: typeof import('./utils/index').arrayEquals
const asyncComputed: typeof import('@vueuse/core').asyncComputed
const autoResetRef: typeof import('@vueuse/core').autoResetRef
const automaticRedirect: typeof import('./stores/settings').automaticRedirect
const collapseNav: typeof import('./stores/settings').collapseNav
const compact: typeof import('./stores/settings').compact
const computed: typeof import('vue').computed
const computedAsync: typeof import('@vueuse/core').computedAsync
const computedEager: typeof import('@vueuse/core').computedEager
const computedInject: typeof import('@vueuse/core').computedInject
const computedWithControl: typeof import('@vueuse/core').computedWithControl
const config: typeof import('./stores/config').default
const controlledComputed: typeof import('@vueuse/core').controlledComputed
const controlledRef: typeof import('@vueuse/core').controlledRef
const createApp: typeof import('vue').createApp
const createDrawer: typeof import('./composable/drawer').createDrawer
const createEventHook: typeof import('@vueuse/core').createEventHook
const createGlobalState: typeof import('@vueuse/core').createGlobalState
const createInjectionState: typeof import('@vueuse/core').createInjectionState
const createPinia: typeof import('pinia').createPinia
const createReactiveFn: typeof import('@vueuse/core').createReactiveFn
const createRef: typeof import('@vueuse/core').createRef
const createReusableTemplate: typeof import('@vueuse/core').createReusableTemplate
const createSharedComposable: typeof import('@vueuse/core').createSharedComposable
const createTemplatePromise: typeof import('@vueuse/core').createTemplatePromise
const createUnrefFn: typeof import('@vueuse/core').createUnrefFn
const customRef: typeof import('vue').customRef
const dateLocale: typeof import('./stores/settings').dateLocale
const debouncedRef: typeof import('@vueuse/core').debouncedRef
const debouncedWatch: typeof import('@vueuse/core').debouncedWatch
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
const defineComponent: typeof import('vue').defineComponent
const defineStore: typeof import('pinia').defineStore
const drawerContext: typeof import('./composable/drawer').drawerContext
const eagerComputed: typeof import('@vueuse/core').eagerComputed
const effectScope: typeof import('vue').effectScope
const extendRef: typeof import('@vueuse/core').extendRef
const flattenJSON: typeof import('./utils/index').flattenJSON
const flattenJSONToMap: typeof import('./utils/index').flattenJSONToMap
const formatBytes: typeof import('./utils/index').formatBytes
const getActivePinia: typeof import('pinia').getActivePinia
const getCurrentInstance: typeof import('vue').getCurrentInstance
const getCurrentScope: typeof import('vue').getCurrentScope
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
const getDeep: typeof import('./utils/index').getDeep
const globalShowPopup: typeof import('./composable/popup').globalShowPopup
const groupContainers: typeof import('./stores/settings').groupContainers
const h: typeof import('vue').h
const hashCode: typeof import('./utils/index').hashCode
const hourStyle: typeof import('./stores/settings').hourStyle
const ignorableWatch: typeof import('@vueuse/core').ignorableWatch
const inject: typeof import('vue').inject
const injectLocal: typeof import('@vueuse/core').injectLocal
const isDefined: typeof import('@vueuse/core').isDefined
const isMobile: typeof import('./composable/media').isMobile
const isObject: typeof import('./utils/index').isObject
const isProxy: typeof import('vue').isProxy
const isReactive: typeof import('vue').isReactive
const isReadonly: typeof import('vue').isReadonly
const isRef: typeof import('vue').isRef
const isShallow: typeof import('vue').isShallow
const lightTheme: typeof import('./stores/settings').lightTheme
const loadBetween: typeof import('./composable/eventStreams').loadBetween
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
const mapStores: typeof import('pinia').mapStores
const mapWritableState: typeof import('pinia').mapWritableState
const markRaw: typeof import('vue').markRaw
const menuWidth: typeof import('./stores/settings').menuWidth
const nextTick: typeof import('vue').nextTick
const onActivated: typeof import('vue').onActivated
const onBeforeMount: typeof import('vue').onBeforeMount
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
const onBeforeRouteUpdate: typeof import('vue-router').onBeforeRouteUpdate
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
const onClickOutside: typeof import('@vueuse/core').onClickOutside
const onDeactivated: typeof import('vue').onDeactivated
const onElementRemoval: typeof import('@vueuse/core').onElementRemoval
const onErrorCaptured: typeof import('vue').onErrorCaptured
const onKeyStroke: typeof import('@vueuse/core').onKeyStroke
const onLongPress: typeof import('@vueuse/core').onLongPress
const onMounted: typeof import('vue').onMounted
const onRenderTracked: typeof import('vue').onRenderTracked
const onRenderTriggered: typeof import('vue').onRenderTriggered
const onScopeDispose: typeof import('vue').onScopeDispose
const onServerPrefetch: typeof import('vue').onServerPrefetch
const onStartTyping: typeof import('@vueuse/core').onStartTyping
const onUnmounted: typeof import('vue').onUnmounted
const onUpdated: typeof import('vue').onUpdated
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
const pausableWatch: typeof import('@vueuse/core').pausableWatch
const persistentVisibleKeysForContainer: typeof import('./composable/storage').persistentVisibleKeysForContainer
const pinnedContainers: typeof import('./composable/storage').pinnedContainers
const provide: typeof import('vue').provide
const provideLocal: typeof import('@vueuse/core').provideLocal
const provideLoggingContext: typeof import('./composable/logContext').provideLoggingContext
const provideScrollContext: typeof import('./composable/scrollContext').provideScrollContext
const reactify: typeof import('@vueuse/core').reactify
const reactifyObject: typeof import('@vueuse/core').reactifyObject
const reactive: typeof import('vue').reactive
const reactiveComputed: typeof import('@vueuse/core').reactiveComputed
const reactiveOmit: typeof import('@vueuse/core').reactiveOmit
const reactivePick: typeof import('@vueuse/core').reactivePick
const readonly: typeof import('vue').readonly
const ref: typeof import('vue').ref
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
const resolveRef: typeof import('@vueuse/core').resolveRef
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const scrollContextKey: typeof import('./composable/scrollContext')['scrollContextKey']
const search: typeof import('./stores/settings')['search']
const sessionHost: typeof import('./composable/storage')['sessionHost']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const setTitle: typeof import('./composable/title')['setTitle']
const settings: typeof import('./stores/settings')['settings']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const showAllContainers: typeof import('./stores/settings')['showAllContainers']
const showStd: typeof import('./stores/settings')['showStd']
const showTimestamp: typeof import('./stores/settings')['showTimestamp']
const size: typeof import('./stores/settings')['size']
const smallerScrollbars: typeof import('./stores/settings')['smallerScrollbars']
const softWrap: typeof import('./stores/settings')['softWrap']
const storeToRefs: typeof import('pinia')['storeToRefs']
const stripVersion: typeof import('./utils/index')['stripVersion']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
const templateRef: typeof import('@vueuse/core')['templateRef']
const throttledRef: typeof import('@vueuse/core')['throttledRef']
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toRelativeTime: typeof import('./utils/index')['toRelativeTime']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
const unref: typeof import('vue')['unref']
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useAnnouncements: typeof import('./stores/announcements')['useAnnouncements']
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useCached: typeof import('@vueuse/core')['useCached']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
const useContainerActions: typeof import('./composable/containerActions')['useContainerActions']
const useContainerStore: typeof import('./stores/container')['useContainerStore']
const useContainerStream: typeof import('./composable/eventStreams')['useContainerStream']
const useCountdown: typeof import('@vueuse/core')['useCountdown']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVar: typeof import('@vueuse/core')['useCssVar']
const useCssVars: typeof import('vue')['useCssVars']
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
const useCycleList: typeof import('@vueuse/core')['useCycleList']
const useDark: typeof import('@vueuse/core')['useDark']
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useDraggable: typeof import('@vueuse/core')['useDraggable']
const useDrawer: typeof import('./composable/drawer')['useDrawer']
const useDropZone: typeof import('@vueuse/core')['useDropZone']
const useDuckDB: typeof import('./composable/duckdb')['useDuckDB']
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
const useElementHover: typeof import('@vueuse/core')['useElementHover']
const useElementSize: typeof import('@vueuse/core')['useElementSize']
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
const useEventBus: typeof import('@vueuse/core')['useEventBus']
const useEventListener: typeof import('@vueuse/core')['useEventListener']
const useEventSource: typeof import('@vueuse/core')['useEventSource']
const useExponentialMovingAverage: typeof import('./utils/index')['useExponentialMovingAverage']
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
const useFocus: typeof import('@vueuse/core')['useFocus']
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
const useFps: typeof import('@vueuse/core')['useFps']
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useGroupedStream: typeof import('./composable/eventStreams')['useGroupedStream']
const useHead: typeof import('@vueuse/head')['useHead']
const useHistoricalContainerLog: typeof import('./composable/historicalLogs')['useHistoricalContainerLog']
const useHostStream: typeof import('./composable/eventStreams')['useHostStream']
const useHosts: typeof import('./stores/hosts')['useHosts']
const useI18n: typeof import('vue-i18n')['useI18n']
const useId: typeof import('vue')['useId']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useInterval: typeof import('@vueuse/core')['useInterval']
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useLoggingContext: typeof import('./composable/logContext')['useLoggingContext']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMemoize: typeof import('@vueuse/core')['useMemoize']
const useMemory: typeof import('@vueuse/core')['useMemory']
const useMergedStream: typeof import('./composable/eventStreams')['useMergedStream']
const useModel: typeof import('vue')['useModel']
const useMounted: typeof import('@vueuse/core')['useMounted']
const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNow: typeof import('@vueuse/core')['useNow']
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const useParentElement: typeof import('@vueuse/core')['useParentElement']
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePinnedLogsStore: typeof import('./stores/pinned')['usePinnedLogsStore']
const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useProfileStorage: typeof import('./composable/profileStorage')['useProfileStorage']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
const useScroll: typeof import('@vueuse/core')['useScroll']
const useScrollContext: typeof import('./composable/scrollContext')['useScrollContext']
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
const useSearchFilter: typeof import('./composable/search')['useSearchFilter']
const useSeoMeta: typeof import('@vueuse/head')['useSeoMeta']
const useServiceStream: typeof import('./composable/eventStreams')['useServiceStream']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSimpleRefHistory: typeof import('./utils/index')['useSimpleRefHistory']
const useSlots: typeof import('vue')['useSlots']
const useSorted: typeof import('@vueuse/core')['useSorted']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStackStream: typeof import('./composable/eventStreams')['useStackStream']
const useStepper: typeof import('@vueuse/core')['useStepper']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwarmStore: typeof import('./stores/swarm')['useSwarmStore']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToNumber: typeof import('@vueuse/core')['useToNumber']
const useToString: typeof import('@vueuse/core')['useToString']
const useToast: typeof import('./composable/toast')['useToast']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
const useVModel: typeof import('@vueuse/core')['useVModel']
const useVModels: typeof import('@vueuse/core')['useVModels']
const useVibrate: typeof import('@vueuse/core')['useVibrate']
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
const useVisibleFilter: typeof import('./composable/visible')['useVisibleFilter']
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
const watch: typeof import('vue')['watch']
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchDeep: typeof import('@vueuse/core')['watchDeep']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
const withBase: typeof import('./stores/config')['withBase']
const scrollContextKey: typeof import('./composable/scrollContext').scrollContextKey
const search: typeof import('./stores/settings').search
const sessionHost: typeof import('./composable/storage').sessionHost
const setActivePinia: typeof import('pinia').setActivePinia
const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
const setTitle: typeof import('./composable/title').setTitle
const settings: typeof import('./stores/settings').settings
const shallowReactive: typeof import('vue').shallowReactive
const shallowReadonly: typeof import('vue').shallowReadonly
const shallowRef: typeof import('vue').shallowRef
const showAllContainers: typeof import('./stores/settings').showAllContainers
const showStd: typeof import('./stores/settings').showStd
const showTimestamp: typeof import('./stores/settings').showTimestamp
const size: typeof import('./stores/settings').size
const smallerScrollbars: typeof import('./stores/settings').smallerScrollbars
const softWrap: typeof import('./stores/settings').softWrap
const storeToRefs: typeof import('pinia').storeToRefs
const stripVersion: typeof import('./utils/index').stripVersion
const syncRef: typeof import('@vueuse/core').syncRef
const syncRefs: typeof import('@vueuse/core').syncRefs
const templateRef: typeof import('@vueuse/core').templateRef
const throttledRef: typeof import('@vueuse/core').throttledRef
const throttledWatch: typeof import('@vueuse/core').throttledWatch
const toRaw: typeof import('vue').toRaw
const toReactive: typeof import('@vueuse/core').toReactive
const toRef: typeof import('vue').toRef
const toRefs: typeof import('vue').toRefs
const toRelativeTime: typeof import('./utils/index').toRelativeTime
const toValue: typeof import('vue').toValue
const triggerRef: typeof import('vue').triggerRef
const tryOnBeforeMount: typeof import('@vueuse/core').tryOnBeforeMount
const tryOnBeforeUnmount: typeof import('@vueuse/core').tryOnBeforeUnmount
const tryOnMounted: typeof import('@vueuse/core').tryOnMounted
const tryOnScopeDispose: typeof import('@vueuse/core').tryOnScopeDispose
const tryOnUnmounted: typeof import('@vueuse/core').tryOnUnmounted
const unref: typeof import('vue').unref
const unrefElement: typeof import('@vueuse/core').unrefElement
const until: typeof import('@vueuse/core').until
const useActiveElement: typeof import('@vueuse/core').useActiveElement
const useAnimate: typeof import('@vueuse/core').useAnimate
const useAnnouncements: typeof import('./stores/announcements').useAnnouncements
const useArrayDifference: typeof import('@vueuse/core').useArrayDifference
const useArrayEvery: typeof import('@vueuse/core').useArrayEvery
const useArrayFilter: typeof import('@vueuse/core').useArrayFilter
const useArrayFind: typeof import('@vueuse/core').useArrayFind
const useArrayFindIndex: typeof import('@vueuse/core').useArrayFindIndex
const useArrayFindLast: typeof import('@vueuse/core').useArrayFindLast
const useArrayIncludes: typeof import('@vueuse/core').useArrayIncludes
const useArrayJoin: typeof import('@vueuse/core').useArrayJoin
const useArrayMap: typeof import('@vueuse/core').useArrayMap
const useArrayReduce: typeof import('@vueuse/core').useArrayReduce
const useArraySome: typeof import('@vueuse/core').useArraySome
const useArrayUnique: typeof import('@vueuse/core').useArrayUnique
const useAsyncQueue: typeof import('@vueuse/core').useAsyncQueue
const useAsyncState: typeof import('@vueuse/core').useAsyncState
const useAttrs: typeof import('vue').useAttrs
const useBase64: typeof import('@vueuse/core').useBase64
const useBattery: typeof import('@vueuse/core').useBattery
const useBluetooth: typeof import('@vueuse/core').useBluetooth
const useBreakpoints: typeof import('@vueuse/core').useBreakpoints
const useBroadcastChannel: typeof import('@vueuse/core').useBroadcastChannel
const useBrowserLocation: typeof import('@vueuse/core').useBrowserLocation
const useCached: typeof import('@vueuse/core').useCached
const useClipboard: typeof import('@vueuse/core').useClipboard
const useClipboardItems: typeof import('@vueuse/core').useClipboardItems
const useCloned: typeof import('@vueuse/core').useCloned
const useColorMode: typeof import('@vueuse/core').useColorMode
const useConfirmDialog: typeof import('@vueuse/core').useConfirmDialog
const useContainerActions: typeof import('./composable/containerActions').useContainerActions
const useContainerStore: typeof import('./stores/container').useContainerStore
const useContainerStream: typeof import('./composable/eventStreams').useContainerStream
const useCountdown: typeof import('@vueuse/core').useCountdown
const useCounter: typeof import('@vueuse/core').useCounter
const useCssModule: typeof import('vue').useCssModule
const useCssVar: typeof import('@vueuse/core').useCssVar
const useCssVars: typeof import('vue').useCssVars
const useCurrentElement: typeof import('@vueuse/core').useCurrentElement
const useCycleList: typeof import('@vueuse/core').useCycleList
const useDark: typeof import('@vueuse/core').useDark
const useDateFormat: typeof import('@vueuse/core').useDateFormat
const useDebounce: typeof import('@vueuse/core').useDebounce
const useDebounceFn: typeof import('@vueuse/core').useDebounceFn
const useDebouncedRefHistory: typeof import('@vueuse/core').useDebouncedRefHistory
const useDeviceMotion: typeof import('@vueuse/core').useDeviceMotion
const useDeviceOrientation: typeof import('@vueuse/core').useDeviceOrientation
const useDevicePixelRatio: typeof import('@vueuse/core').useDevicePixelRatio
const useDevicesList: typeof import('@vueuse/core').useDevicesList
const useDisplayMedia: typeof import('@vueuse/core').useDisplayMedia
const useDocumentVisibility: typeof import('@vueuse/core').useDocumentVisibility
const useDownloadUrl: typeof import('./composable/downloadUrl').useDownloadUrl
const useDraggable: typeof import('@vueuse/core').useDraggable
const useDrawer: typeof import('./composable/drawer').useDrawer
const useDropZone: typeof import('@vueuse/core').useDropZone
const useDuckDB: typeof import('./composable/duckdb').useDuckDB
const useElementBounding: typeof import('@vueuse/core').useElementBounding
const useElementByPoint: typeof import('@vueuse/core').useElementByPoint
const useElementHover: typeof import('@vueuse/core').useElementHover
const useElementSize: typeof import('@vueuse/core').useElementSize
const useElementVisibility: typeof import('@vueuse/core').useElementVisibility
const useEventBus: typeof import('@vueuse/core').useEventBus
const useEventListener: typeof import('@vueuse/core').useEventListener
const useEventSource: typeof import('@vueuse/core').useEventSource
const useExponentialMovingAverage: typeof import('./utils/index').useExponentialMovingAverage
const useEyeDropper: typeof import('@vueuse/core').useEyeDropper
const useFavicon: typeof import('@vueuse/core').useFavicon
const useFetch: typeof import('@vueuse/core').useFetch
const useFileDialog: typeof import('@vueuse/core').useFileDialog
const useFileSystemAccess: typeof import('@vueuse/core').useFileSystemAccess
const useFocus: typeof import('@vueuse/core').useFocus
const useFocusWithin: typeof import('@vueuse/core').useFocusWithin
const useFps: typeof import('@vueuse/core').useFps
const useFullscreen: typeof import('@vueuse/core').useFullscreen
const useGamepad: typeof import('@vueuse/core').useGamepad
const useGeolocation: typeof import('@vueuse/core').useGeolocation
const useGroupedStream: typeof import('./composable/eventStreams').useGroupedStream
const useHead: typeof import('@vueuse/head').useHead
const useHistoricalContainerLog: typeof import('./composable/historicalLogs').useHistoricalContainerLog
const useHostStream: typeof import('./composable/eventStreams').useHostStream
const useHosts: typeof import('./stores/hosts').useHosts
const useI18n: typeof import('vue-i18n').useI18n
const useId: typeof import('vue').useId
const useIdle: typeof import('@vueuse/core').useIdle
const useImage: typeof import('@vueuse/core').useImage
const useInfiniteScroll: typeof import('@vueuse/core').useInfiniteScroll
const useIntersectionObserver: typeof import('@vueuse/core').useIntersectionObserver
const useInterval: typeof import('@vueuse/core').useInterval
const useIntervalFn: typeof import('@vueuse/core').useIntervalFn
const useKeyModifier: typeof import('@vueuse/core').useKeyModifier
const useLastChanged: typeof import('@vueuse/core').useLastChanged
const useLocalStorage: typeof import('@vueuse/core').useLocalStorage
const useLoggingContext: typeof import('./composable/logContext').useLoggingContext
const useMagicKeys: typeof import('@vueuse/core').useMagicKeys
const useManualRefHistory: typeof import('@vueuse/core').useManualRefHistory
const useMediaControls: typeof import('@vueuse/core').useMediaControls
const useMediaQuery: typeof import('@vueuse/core').useMediaQuery
const useMemoize: typeof import('@vueuse/core').useMemoize
const useMemory: typeof import('@vueuse/core').useMemory
const useMergedStream: typeof import('./composable/eventStreams').useMergedStream
const useModel: typeof import('vue').useModel
const useMounted: typeof import('@vueuse/core').useMounted
const useMouse: typeof import('@vueuse/core').useMouse
const useMouseInElement: typeof import('@vueuse/core').useMouseInElement
const useMousePressed: typeof import('@vueuse/core').useMousePressed
const useMutationObserver: typeof import('@vueuse/core').useMutationObserver
const useNavigatorLanguage: typeof import('@vueuse/core').useNavigatorLanguage
const useNetwork: typeof import('@vueuse/core').useNetwork
const useNow: typeof import('@vueuse/core').useNow
const useObjectUrl: typeof import('@vueuse/core').useObjectUrl
const useOffsetPagination: typeof import('@vueuse/core').useOffsetPagination
const useOnline: typeof import('@vueuse/core').useOnline
const usePageLeave: typeof import('@vueuse/core').usePageLeave
const useParallax: typeof import('@vueuse/core').useParallax
const useParentElement: typeof import('@vueuse/core').useParentElement
const usePerformanceObserver: typeof import('@vueuse/core').usePerformanceObserver
const usePermission: typeof import('@vueuse/core').usePermission
const usePinnedLogsStore: typeof import('./stores/pinned').usePinnedLogsStore
const usePointer: typeof import('@vueuse/core').usePointer
const usePointerLock: typeof import('@vueuse/core').usePointerLock
const usePointerSwipe: typeof import('@vueuse/core').usePointerSwipe
const usePreferredColorScheme: typeof import('@vueuse/core').usePreferredColorScheme
const usePreferredContrast: typeof import('@vueuse/core').usePreferredContrast
const usePreferredDark: typeof import('@vueuse/core').usePreferredDark
const usePreferredLanguages: typeof import('@vueuse/core').usePreferredLanguages
const usePreferredReducedMotion: typeof import('@vueuse/core').usePreferredReducedMotion
const usePreferredReducedTransparency: typeof import('@vueuse/core').usePreferredReducedTransparency
const usePrevious: typeof import('@vueuse/core').usePrevious
const useProfileStorage: typeof import('./composable/profileStorage').useProfileStorage
const useRafFn: typeof import('@vueuse/core').useRafFn
const useRefHistory: typeof import('@vueuse/core').useRefHistory
const useResizeObserver: typeof import('@vueuse/core').useResizeObserver
const useRoute: typeof import('vue-router').useRoute
const useRouter: typeof import('vue-router').useRouter
const useSSRWidth: typeof import('@vueuse/core').useSSRWidth
const useScreenOrientation: typeof import('@vueuse/core').useScreenOrientation
const useScreenSafeArea: typeof import('@vueuse/core').useScreenSafeArea
const useScriptTag: typeof import('@vueuse/core').useScriptTag
const useScroll: typeof import('@vueuse/core').useScroll
const useScrollContext: typeof import('./composable/scrollContext').useScrollContext
const useScrollLock: typeof import('@vueuse/core').useScrollLock
const useSearchFilter: typeof import('./composable/search').useSearchFilter
const useSeoMeta: typeof import('@vueuse/head').useSeoMeta
const useServiceStream: typeof import('./composable/eventStreams').useServiceStream
const useSessionStorage: typeof import('@vueuse/core').useSessionStorage
const useShare: typeof import('@vueuse/core').useShare
const useSimpleRefHistory: typeof import('./utils/index').useSimpleRefHistory
const useSlots: typeof import('vue').useSlots
const useSorted: typeof import('@vueuse/core').useSorted
const useSpeechRecognition: typeof import('@vueuse/core').useSpeechRecognition
const useSpeechSynthesis: typeof import('@vueuse/core').useSpeechSynthesis
const useStackStream: typeof import('./composable/eventStreams').useStackStream
const useStepper: typeof import('@vueuse/core').useStepper
const useStorage: typeof import('@vueuse/core').useStorage
const useStorageAsync: typeof import('@vueuse/core').useStorageAsync
const useStyleTag: typeof import('@vueuse/core').useStyleTag
const useSupported: typeof import('@vueuse/core').useSupported
const useSwarmStore: typeof import('./stores/swarm').useSwarmStore
const useSwipe: typeof import('@vueuse/core').useSwipe
const useTemplateRef: typeof import('vue').useTemplateRef
const useTemplateRefsList: typeof import('@vueuse/core').useTemplateRefsList
const useTextDirection: typeof import('@vueuse/core').useTextDirection
const useTextSelection: typeof import('@vueuse/core').useTextSelection
const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize
const useThrottle: typeof import('@vueuse/core').useThrottle
const useThrottleFn: typeof import('@vueuse/core').useThrottleFn
const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory
const useTimeAgo: typeof import('@vueuse/core').useTimeAgo
const useTimeAgoIntl: typeof import('@vueuse/core').useTimeAgoIntl
const useTimeout: typeof import('@vueuse/core').useTimeout
const useTimeoutFn: typeof import('@vueuse/core').useTimeoutFn
const useTimeoutPoll: typeof import('@vueuse/core').useTimeoutPoll
const useTimestamp: typeof import('@vueuse/core').useTimestamp
const useTitle: typeof import('@vueuse/core').useTitle
const useToNumber: typeof import('@vueuse/core').useToNumber
const useToString: typeof import('@vueuse/core').useToString
const useToast: typeof import('./composable/toast').useToast
const useToggle: typeof import('@vueuse/core').useToggle
const useTransition: typeof import('@vueuse/core').useTransition
const useUrlSearchParams: typeof import('@vueuse/core').useUrlSearchParams
const useUserMedia: typeof import('@vueuse/core').useUserMedia
const useVModel: typeof import('@vueuse/core').useVModel
const useVModels: typeof import('@vueuse/core').useVModels
const useVibrate: typeof import('@vueuse/core').useVibrate
const useVirtualList: typeof import('@vueuse/core').useVirtualList
const useVisibleFilter: typeof import('./composable/visible').useVisibleFilter
const useWakeLock: typeof import('@vueuse/core').useWakeLock
const useWebNotification: typeof import('@vueuse/core').useWebNotification
const useWebSocket: typeof import('@vueuse/core').useWebSocket
const useWebWorker: typeof import('@vueuse/core').useWebWorker
const useWebWorkerFn: typeof import('@vueuse/core').useWebWorkerFn
const useWindowFocus: typeof import('@vueuse/core').useWindowFocus
const useWindowScroll: typeof import('@vueuse/core').useWindowScroll
const useWindowSize: typeof import('@vueuse/core').useWindowSize
const watch: typeof import('vue').watch
const watchArray: typeof import('@vueuse/core').watchArray
const watchAtMost: typeof import('@vueuse/core').watchAtMost
const watchDebounced: typeof import('@vueuse/core').watchDebounced
const watchDeep: typeof import('@vueuse/core').watchDeep
const watchEffect: typeof import('vue').watchEffect
const watchIgnorable: typeof import('@vueuse/core').watchIgnorable
const watchImmediate: typeof import('@vueuse/core').watchImmediate
const watchOnce: typeof import('@vueuse/core').watchOnce
const watchPausable: typeof import('@vueuse/core').watchPausable
const watchPostEffect: typeof import('vue').watchPostEffect
const watchSyncEffect: typeof import('vue').watchSyncEffect
const watchThrottled: typeof import('@vueuse/core').watchThrottled
const watchTriggerable: typeof import('@vueuse/core').watchTriggerable
const watchWithFilter: typeof import('@vueuse/core').watchWithFilter
const whenever: typeof import('@vueuse/core').whenever
const withBase: typeof import('./stores/config').withBase
}
// for type re-export
declare global {
@@ -453,6 +457,7 @@ declare module 'vue' {
readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
readonly globalShowPopup: UnwrapRef<typeof import('./composable/popup')['globalShowPopup']>
readonly groupContainers: UnwrapRef<typeof import('./stores/settings')['groupContainers']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly hashCode: UnwrapRef<typeof import('./utils/index')['hashCode']>
readonly hourStyle: UnwrapRef<typeof import('./stores/settings')['hourStyle']>
@@ -519,11 +524,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']>
@@ -613,6 +618,7 @@ declare module 'vue' {
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDownloadUrl: UnwrapRef<typeof import('./composable/downloadUrl')['useDownloadUrl']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDrawer: UnwrapRef<typeof import('./composable/drawer')['useDrawer']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
+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 */
+10 -4
View File
@@ -104,7 +104,7 @@
</table>
</div>
<div class="p-4 text-center">
<nav class="join" v-if="isPaginated">
<nav class="join" v-if="isPaginated && totalPages <= 15">
<input
class="btn btn-square join-item"
type="radio"
@@ -114,6 +114,12 @@
v-for="i in totalPages"
/>
</nav>
<DropdownMenu
v-else-if="isPaginated"
class="btn-sm"
v-model="currentPage"
:options="Array.from({ length: totalPages }, (_, i) => ({ label: `${i + 1}`, value: i + 1 }))"
/>
</div>
</div>
</template>
@@ -167,10 +173,10 @@ const perPage = useStorage("DOZZLE_TABLE_PAGE_SIZE", 15);
const pageSizes = [15, 30, 50, 100];
const storage = useStorage<{ column: keys; direction: 1 | -1 }>("DOZZLE_TABLE_CONTAINERS_SORT", {
column: "created",
direction: -1,
column: "created" as keys,
direction: -1 as 1 | -1,
});
const { column: sortField, direction } = toRefs(storage);
const { column: sortField, direction } = toRefs(storage.value);
const counter = useInterval(10000);
const filteredContainers = computed(() =>
containers.filter((c) => selectedHost.value === null || c.host === selectedHost.value),
@@ -16,7 +16,10 @@
</a>
</li>
<li v-if="enableDownload">
<a :href="downloadUrl" download> <octicon:download-24 /> {{ $t("toolbar.download") }} </a>
<a :href="downloadUrl" download>
<octicon:download-24 />
{{ isFiltered ? $t("toolbar.download-filtered") : $t("toolbar.download") }}
</a>
</li>
<li v-if="!historical">
<a @click="showSearch = true">
@@ -199,17 +202,8 @@ if (enableShell) {
});
}
const downloadParams = computed(() =>
Object.entries(toValue(streamConfig))
.filter(([, value]) => value)
.reduce((acc, [key]) => ({ ...acc, [key]: "1" }), {}),
);
const downloadUrl = computed(() =>
withBase(
`/api/containers/${container.host}~${container.id}/download?${new URLSearchParams(downloadParams.value).toString()}`,
),
);
const containerRef = computed(() => [container]);
const { downloadUrl, isFiltered } = useDownloadUrl(containerRef, streamConfig, levels);
const disableRestart = computed(() => actionStates.stop || actionStates.start || actionStates.restart);
+9 -4
View File
@@ -121,7 +121,7 @@
<script lang="ts" setup>
import { Container } from "@/models/Container";
import { sessionHost } from "@/composable/storage";
import { showAllContainers } from "@/stores/settings";
import { showAllContainers, groupContainers } from "@/stores/settings";
// @ts-ignore
import Pin from "~icons/ph/map-pin-simple";
@@ -183,7 +183,7 @@ const menuItems = computed(() => {
for (const item of sortedContainers.value) {
const namespace = item.namespace;
if (debouncedPinnedContainers.value.has(item.name)) {
if (debouncedPinnedContainers.value?.has(item.name)) {
pinned.push(item);
} else if (namespace) {
namespaced[namespace] ||= [];
@@ -198,10 +198,15 @@ const menuItems = computed(() => {
items.push({ label: "label.pinned", containers: pinned, icon: Pin });
}
for (const [label, containers] of Object.entries(namespaced).sort(([a], [b]) => a.localeCompare(b))) {
if (containers.length > 1) {
const shouldGroup =
groupContainers.value === "always" || (groupContainers.value === "at-least-2" && containers.length > 1);
if (shouldGroup) {
items.push({ label, containers, icon: Stack });
} else {
singular.push(containers[0]);
for (const container of containers) {
singular.push(container);
}
}
}
@@ -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>
@@ -16,7 +16,10 @@
</a>
</li>
<li v-if="enableDownload">
<a :href="downloadUrl" download> <octicon:download-24 /> {{ $t("toolbar.download") }} </a>
<a :href="downloadUrl" download>
<octicon:download-24 />
{{ isFiltered ? $t("toolbar.download-filtered") : $t("toolbar.download") }}
</a>
</li>
<li>
<a @click="showSearch = true">
@@ -91,19 +94,9 @@ const { showSearch } = useSearchFilter();
const { enableDownload } = config;
const clear = defineEmit();
const { streamConfig, showHostname, showContainerName, containers } = useLoggingContext();
const { streamConfig, showHostname, showContainerName, containers, levels } = useLoggingContext();
const downloadParams = computed(() =>
Object.entries(toValue(streamConfig))
.filter(([, value]) => value)
.reduce((acc, [key]) => ({ ...acc, [key]: "1" }), {}),
);
const downloadUrl = computed(() =>
withBase(
`/api/containers/${containers.value.map((c) => c.host + "~" + c.id).join(",")}/download?${new URLSearchParams(downloadParams.value).toString()}`,
),
);
const { downloadUrl, isFiltered } = useDownloadUrl(containers, streamConfig, levels);
const hideMenu = (e: MouseEvent) => {
if (e.target instanceof HTMLAnchorElement) {
@@ -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>"
+45
View File
@@ -0,0 +1,45 @@
import { Container } from "@/models/Container";
import { allLevels } from "@/composable/logContext";
export function useDownloadUrl(
containers: Ref<Container[]> | ComputedRef<Container[]>,
streamConfig: { stdout: boolean; stderr: boolean } | Ref<{ stdout: boolean; stderr: boolean }>,
levels: Ref<Set<string>>,
) {
const { debouncedSearchFilter } = useSearchFilter();
const downloadUrl = computed(() => {
const params = new URLSearchParams();
const config = toValue(streamConfig);
// Add stdout/stderr
if (config.stdout) params.append("stdout", "1");
if (config.stderr) params.append("stderr", "1");
// Add filter if search is active
if (debouncedSearchFilter.value) {
params.append("filter", debouncedSearchFilter.value);
}
// Add levels (multiple values) only if filtered
const selectedLevels = Array.from(levels.value);
if (selectedLevels.length > 0 && selectedLevels.length < allLevels.length) {
selectedLevels.forEach((level) => params.append("levels", level));
}
const containerIds = toValue(containers)
.map((c) => c.host + "~" + c.id)
.join(",");
return withBase(`/api/containers/${containerIds}/download?${params.toString()}`);
});
const isFiltered = computed(
() => debouncedSearchFilter.value || (levels.value.size > 0 && levels.value.size < allLevels.length),
);
return {
downloadUrl,
isFiltered,
};
}
+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;
+28 -12
View File
@@ -88,9 +88,9 @@
<DropdownMenu
v-model="hourStyle"
:options="[
{ label: 'Auto', value: 'auto' },
{ label: '12', value: '12' },
{ label: '24', value: '24' },
{ label: $t('settings.hour.auto'), value: 'auto' },
{ label: $t('settings.hour.12'), value: '12' },
{ label: $t('settings.hour.24'), value: '24' },
]"
/>
</div>
@@ -105,9 +105,9 @@
<DropdownMenu
v-model="size"
:options="[
{ label: 'Small', value: 'small' },
{ label: 'Medium', value: 'medium' },
{ label: 'Large', value: 'large' },
{ label: $t('settings.size.small'), value: 'small' },
{ label: $t('settings.size.medium'), value: 'medium' },
{ label: $t('settings.size.large'), value: 'large' },
]"
/>
</template>
@@ -121,9 +121,9 @@
<DropdownMenu
v-model="lightTheme"
:options="[
{ label: 'Auto', value: 'auto' },
{ label: 'Dark', value: 'dark' },
{ label: 'Light', value: 'light' },
{ label: $t('settings.theme.auto'), value: 'auto' },
{ label: $t('settings.theme.dark'), value: 'dark' },
{ label: $t('settings.theme.light'), value: 'light' },
]"
/>
</template>
@@ -151,9 +151,24 @@
<DropdownMenu
v-model="automaticRedirect"
:options="[
{ label: 'Instant', value: 'instant' },
{ label: 'Delayed', value: 'delayed' },
{ label: 'None', value: 'none' },
{ label: $t('settings.redirect.instant'), value: 'instant' },
{ label: $t('settings.redirect.delayed'), value: 'delayed' },
{ label: $t('settings.redirect.none'), value: 'none' },
]"
/>
</template>
</LabeledInput>
<LabeledInput>
<template #label>
{{ $t("settings.group-containers") }}
</template>
<template #input>
<DropdownMenu
v-model="groupContainers"
:options="[
{ label: $t('settings.grouping.always'), value: 'always' },
{ label: $t('settings.grouping.at-least-2'), value: 'at-least-2' },
{ label: $t('settings.grouping.never'), value: 'never' },
]"
/>
</template>
@@ -185,6 +200,7 @@ import {
smallerScrollbars,
softWrap,
locale,
groupContainers,
} from "@/stores/settings";
import { availableLocales, i18n } from "@/modules/i18n";
+4 -1
View File
@@ -16,6 +16,7 @@ export type Settings = {
collapseNav: boolean;
automaticRedirect: "instant" | "delayed" | "none";
locale: string;
groupContainers: "always" | "at-least-2" | "never";
};
export const DEFAULT_SETTINGS: Settings = {
search: true,
@@ -33,6 +34,7 @@ export const DEFAULT_SETTINGS: Settings = {
collapseNav: false,
automaticRedirect: "delayed",
locale: "",
groupContainers: "at-least-2",
};
export const settings = useProfileStorage("settings", DEFAULT_SETTINGS);
@@ -61,4 +63,5 @@ export const {
search,
locale,
automaticRedirect,
} = toRefs(settings);
groupContainers,
} = toRefs(settings.value);
+140 -39
View File
@@ -1,10 +1,15 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
// 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.57.0-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
+20 -20
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
@@ -17,13 +17,13 @@ require (
github.com/spf13/afero v1.15.0
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.11.1
golang.org/x/net v0.45.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sys v0.38.0 // indirect
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/PuerkitoBio/goquery v1.10.3
github.com/PuerkitoBio/goquery v1.11.0
github.com/andybalholm/brotli v1.2.0
github.com/go-chi/chi/v5 v5.2.3
github.com/go-chi/jwtauth/v5 v5.3.3
@@ -34,14 +34,14 @@ require (
github.com/samber/lo v1.52.0
github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/yuin/goldmark v1.7.13
golang.org/x/crypto v0.43.0
golang.org/x/sync v0.17.0
google.golang.org/grpc v1.76.0
golang.org/x/crypto v0.45.0
golang.org/x/sync v0.18.0
google.golang.org/grpc v1.77.0
google.golang.org/protobuf v1.36.10
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/client-go v0.34.1
k8s.io/metrics v0.34.1
k8s.io/api v0.34.2
k8s.io/apimachinery v0.34.2
k8s.io/client-go v0.34.2
k8s.io/metrics v0.34.2
)
require (
@@ -93,19 +93,19 @@ require (
github.com/segmentio/asm v1.2.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
golang.org/x/time v0.10.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gotest.tools/v3 v3.0.3 // indirect
@@ -119,4 +119,4 @@ require (
sigs.k8s.io/yaml v1.6.0 // indirect
)
go 1.25.2
go 1.25.4
+42
View File
@@ -4,6 +4,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRSo=
@@ -62,6 +64,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=
@@ -276,6 +280,8 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
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/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
@@ -286,6 +292,8 @@ go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q=
@@ -298,6 +306,8 @@ go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCRE
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
@@ -310,6 +320,8 @@ go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKr
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
@@ -338,6 +350,10 @@ golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -368,6 +384,10 @@ golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
@@ -376,6 +396,8 @@ golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -395,6 +417,8 @@ golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -423,6 +447,8 @@ golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -444,6 +470,8 @@ golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -467,6 +495,8 @@ golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -496,6 +526,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
@@ -514,6 +546,8 @@ google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
@@ -556,6 +590,8 @@ k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE=
k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug=
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY=
k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
@@ -572,6 +608,8 @@ k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0=
k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=
@@ -588,6 +626,8 @@ k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo=
k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY=
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M=
k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg=
@@ -612,6 +652,8 @@ k8s.io/metrics v0.34.0 h1:nYSfG2+tnL6/MRC2I+sGHjtNEGoEoM/KktgGOoQFwws=
k8s.io/metrics v0.34.0/go.mod h1:KCuXmotE0v4AvoARKUP8NC4lUnbK/Du1mluGdor5h4M=
k8s.io/metrics v0.34.1 h1:374Rexmp1xxgRt64Bi0TsjAM8cA/Y8skwCoPdjtIslE=
k8s.io/metrics v0.34.1/go.mod h1:Drf5kPfk2NJrlpcNdSiAAHn/7Y9KqxpRNagByM7Ei80=
k8s.io/metrics v0.34.2 h1:zao91FNDVPRGIiHLO2vqqe21zZVPien1goyzn0hsz90=
k8s.io/metrics v0.34.2/go.mod h1:Ydulln+8uZZctUM8yrUQX4rfq/Ay6UzsuXf24QJ37Vc=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
+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),
})
+8 -2
View File
@@ -81,7 +81,11 @@ func guessLogLevel(logEvent *LogEvent) string {
return "unknown"
}
if level, ok := value.Get("level"); ok {
if level, ok := value.Get("@l"); ok {
if level, ok := level.(string); ok {
return normalizeLogLevel(level)
}
} else if level, ok := value.Get("level"); ok {
if level, ok := level.(string); ok {
return normalizeLogLevel(level)
}
@@ -95,7 +99,9 @@ func guessLogLevel(logEvent *LogEvent) string {
if value == nil {
return "unknown"
}
if level, ok := value.Get("level"); ok {
if level, ok := value.Get("@l"); ok {
return normalizeLogLevel(level)
} else if level, ok := value.Get("level"); ok {
return normalizeLogLevel(level)
} else if severity, ok := value.Get("severity"); ok {
return normalizeLogLevel(severity)
+18
View File
@@ -65,6 +65,24 @@ func TestGuessLogLevel(t *testing.T) {
orderedmap.Pair[string, any]{Key: "severity", Value: "info"},
),
), "info"},
{orderedmap.New[string, string](
orderedmap.WithInitialData(
orderedmap.Pair[string, string]{Key: "key", Value: "value"},
orderedmap.Pair[string, string]{Key: "@l", Value: "info"},
),
), "info"},
{orderedmap.New[string, any](
orderedmap.WithInitialData(
orderedmap.Pair[string, any]{Key: "key", Value: "value"},
orderedmap.Pair[string, any]{Key: "@l", Value: "debug"},
),
), "debug"},
{orderedmap.New[string, string](
orderedmap.WithInitialData(
orderedmap.Pair[string, string]{Key: "@l", Value: "error"},
orderedmap.Pair[string, string]{Key: "@t", Value: "2024-01-01T00:00:00Z"},
),
), "error"},
{nilOrderedMap, "unknown"},
{nil, "unknown"},
}
+1
View File
@@ -35,6 +35,7 @@ type Settings struct {
HourStyle string `json:"hourStyle,omitempty"`
DateLocale string `json:"dateLocale,omitempty"`
Locale string `json:"locale"`
GroupContainers string `json:"groupContainers,omitempty"`
}
type Profile struct {
+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)
}
+95 -23
View File
@@ -5,11 +5,14 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
"github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/container"
container_support "github.com/amir20/dozzle/internal/support/container"
support_web "github.com/amir20/dozzle/internal/support/web"
"github.com/go-chi/chi/v5"
"github.com/rs/zerolog/log"
)
@@ -54,15 +57,34 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
return
}
// Set headers for zip file
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=container-logs-%s.zip", nowFmt))
w.Header().Set("Content-Type", "application/zip")
// Parse filter regex if provided
var regex *regexp.Regexp
var err error
if r.URL.Query().Has("filter") {
regex, err = support_web.ParseRegex(r.URL.Query().Get("filter"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
}
// Create zip writer
zw := zip.NewWriter(w)
defer zw.Close()
// Parse level filters if provided
levels := make(map[string]struct{})
if r.URL.Query().Has("levels") {
for _, level := range r.URL.Query()["levels"] {
levels[level] = struct{}{}
}
}
// Validate all containers before starting to write response
type containerInfo struct {
hostId string
host string
id string
containerService *container_support.ContainerService
}
containers := make([]containerInfo, 0, len(hostIds))
// Process each container
for _, hostId := range hostIds {
parts := strings.Split(hostId, "~")
if len(parts) != 2 {
@@ -80,29 +102,79 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
return
}
containers = append(containers, containerInfo{
hostId: hostId,
host: host,
id: id,
containerService: containerService,
})
}
// Set headers for zip file
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=container-logs-%s.zip", nowFmt))
w.Header().Set("Content-Type", "application/zip")
// Create zip writer
zw := zip.NewWriter(w)
defer zw.Close()
// Process each container - errors after this point are logged only since response has started
for _, c := range containers {
// Create new file in zip for this container's logs
fileName := fmt.Sprintf("%s-%s.log", containerService.Container.Name, nowFmt)
fileName := fmt.Sprintf("%s-%s.log", c.containerService.Container.Name, nowFmt)
f, err := zw.Create(fileName)
if err != nil {
log.Error().Err(err).Msgf("error creating zip entry for container %s", id)
http.Error(w, fmt.Sprintf("error creating zip entry: %v", err), http.StatusInternalServerError)
log.Error().Err(err).Msgf("error creating zip entry for container %s", c.id)
return
}
// Get container logs
reader, err := containerService.RawLogs(r.Context(), time.Time{}, now, stdTypes)
if err != nil {
log.Error().Err(err).Msgf("error getting logs for container %s", id)
http.Error(w, fmt.Sprintf("error getting logs for container %s: %v", id, err), http.StatusInternalServerError)
return
}
// Get container logs - use LogsBetweenDates if filtering is needed, otherwise use RawLogs
if regex != nil || len(levels) > 0 {
// Fetch parsed log events for filtering
events, err := c.containerService.LogsBetweenDates(r.Context(), time.Time{}, now, stdTypes)
if err != nil {
log.Error().Err(err).Msgf("error getting logs for container %s", c.id)
return
}
// Copy logs to zip file
_, err = io.Copy(f, reader)
if err != nil {
log.Error().Err(err).Msgf("error copying logs for container %s", id)
http.Error(w, fmt.Sprintf("error copying logs for container %s: %v", id, err), http.StatusInternalServerError)
return
// Filter and write events
for event := range events {
// Apply regex filter if provided
if regex != nil && !support_web.Search(regex, event) {
continue
}
// Apply level filter if provided
if len(levels) > 0 {
if _, ok := levels[event.Level]; !ok {
continue
}
}
// Format timestamp in UTC
timestamp := time.UnixMilli(event.Timestamp).UTC().Format(time.RFC3339Nano)
// Write timestamp followed by message
_, err = fmt.Fprintf(f, "%s %s\n", timestamp, event.RawMessage)
if err != nil {
log.Error().Err(err).Msgf("error writing log for container %s", c.id)
return
}
}
} else {
// No filtering needed, use raw logs for better performance
reader, err := c.containerService.RawLogs(r.Context(), time.Time{}, now, stdTypes)
if err != nil {
log.Error().Err(err).Msgf("error getting logs for container %s", c.id)
return
}
// Copy logs to zip file
_, err = io.Copy(f, reader)
if err != nil {
log.Error().Err(err).Msgf("error copying logs for container %s", c.id)
return
}
}
}
}
+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.")
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Ryd
download: Download
download-filtered: Download Filtrerede Logs
search: Søg
show: Vis kun {std}
show-all: Vis alle streams
@@ -94,6 +95,7 @@ settings:
color-scheme: Farve skema
options: Muligheder
show-stopped-containers: Vis stoppet containere
group-containers: Gruppér containere efter namespace
about: Omkring
search: Aktiver søgning med Dozzle ved brug af
using-version: Du bruger <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -102,6 +104,26 @@ settings:
show-std: Vis stdout og stderr mærker
automatic-redirect: Automatisk omdiriger til nye containere med det samme navn
compact: Slå kompakt tilstand til for logs
size:
small: Lille
medium: Mellem
large: Stor
theme:
auto: Auto
dark: Mørk
light: Lys
redirect:
instant: Øjeblikkelig
delayed: Forsinket
none: Ingen
grouping:
always: Altid
at-least-2: Mindst 2
never: Aldrig
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Dette er en forhåndsvisning af loggene
warning: En advarselslog ser sådan ud
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Leeren
download: Herunterladen
download-filtered: Gefilterte Logs Herunterladen
search: Suchen
show: Zeige nur {std}
show-all: Zeige alle Streams
@@ -94,6 +95,7 @@ settings:
color-scheme: Farbschema
options: Optionen
show-stopped-containers: Zeige gestoppte Container
group-containers: Container nach Namespace gruppieren
about: Über
search: Aktiviere die Suche mit Dozzle mit
using-version: Du verwendest <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -102,6 +104,26 @@ settings:
show-std: Zeige stdout und stderr Labels
automatic-redirect: Automatische Weiterleitung zu neuen Containern mit demselben Namen
compact: Aktiviere den kompakten Modus für Logs
size:
small: Klein
medium: Mittel
large: Groß
theme:
auto: Auto
dark: Dunkel
light: Hell
redirect:
instant: Sofort
delayed: Verzögert
none: Keine
grouping:
always: Immer
at-least-2: Mindestens 2
never: Nie
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Dies ist eine Vorschau auf die Protokolle
warning: Ein Warnprotokoll sieht wie folgt aus
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Clear
download: Download
download-filtered: Download Filtered Logs
search: Search
show: Show {std}
show-all: Show all
@@ -99,6 +100,7 @@ settings:
color-scheme: Color scheme
options: Options
show-stopped-containers: Show stopped containers
group-containers: Group containers by namespace
about: About
search: Enable searching with Dozzle using
using-version: You are using <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -108,6 +110,26 @@ settings:
show-std: Show stdout and stderr labels
automatic-redirect: Automatically redirect to new containers with the same name
compact: Enable compact mode for logs
size:
small: Small
medium: Medium
large: Large
theme:
auto: Auto
dark: Dark
light: Light
redirect:
instant: Instant
delayed: Delayed
none: None
grouping:
always: Always
at-least-2: At least 2
never: Never
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: This is a preview of the logs
warning: A warning log looks like this
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Limpiar
download: Descargar
download-filtered: Descargar Logs Filtrados
search: Buscar
show: Mostrar {std}
show-all: Mostrar todo
@@ -94,6 +95,7 @@ settings:
color-scheme: Esquema de colores
options: Opciones
show-stopped-containers: Mostrar contenedores parados
group-containers: Agrupar contenedores por namespace
about: Acerca de
search: Activar la búsqueda con Dozzle mediante
using-version: Estás usando <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -102,6 +104,26 @@ settings:
show-std: Mostrar etiquetas de salida estándar y salida de error estándar
automatic-redirect: Redireccionar automáticamente a nuevos contenedores con el mismo nombre
compact: Modo compacto
size:
small: Pequeño
medium: Mediano
large: Grande
theme:
auto: Auto
dark: Oscuro
light: Claro
redirect:
instant: Instantáneo
delayed: Retrasado
none: Ninguno
grouping:
always: Siempre
at-least-2: Al menos 2
never: Nunca
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Esta es una vista previa de los registros
warning: Un registro de advertencia se ve así
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Effacer
download: Téléchargement
download-filtered: Télécharger Logs Filtrés
search: Chercher
show: Montrer seulement {std}
show-all: Afficher tous les flux
@@ -94,6 +95,7 @@ settings:
color-scheme: Schéma de couleurs
options: Options
show-stopped-containers: Afficher les conteneurs arrêtés
group-containers: Regrouper les conteneurs par namespace
about: A propos de
search: Activer la recherche avec Dozzle en utilisant
using-version: Vous utilisez <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -102,6 +104,26 @@ settings:
show-std: Afficher les étiquettes stdout et stderr
automatic-redirect: Redirection automatique vers de nouveaux conteneurs portant le même nom
compact: Activer le mode compact pour les journaux
size:
small: Petit
medium: Moyen
large: Grand
theme:
auto: Auto
dark: Sombre
light: Clair
redirect:
instant: Instantané
delayed: Différé
none: Aucun
grouping:
always: Toujours
at-least-2: Au moins 2
never: Jamais
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Voici un aperçu des logs
warning: Un log d'avertissement ressemble à ceci
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Bersihkan
download: Unduh
download-filtered: Unduh Log yang Difilter
search: Cari
show: Tampilkan {std}
show-all: Tampilkan semua
@@ -102,6 +103,7 @@ settings:
color-scheme: Skema warna
options: Opsi
show-stopped-containers: Tampilkan kontainer yang dihentikan
group-containers: Kelompokkan kontainer berdasarkan namespace
about: Tentang
search: Aktifkan pencarian dengan Dozzle menggunakan
using-version: Anda menggunakan <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -110,6 +112,26 @@ settings:
show-std: Tampilkan label stdout dan stderr
automatic-redirect: Alihkan otomatis ke kontainer baru dengan nama yang sama
compact: Aktifkan mode ringkas untuk log
size:
small: Kecil
medium: Sedang
large: Besar
theme:
auto: Auto
dark: Gelap
light: Terang
redirect:
instant: Instan
delayed: Tertunda
none: Tidak ada
grouping:
always: Selalu
at-least-2: Setidaknya 2
never: Tidak pernah
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Ini adalah pratinjau log
warning: Log peringatan seperti ini
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Pulisci
download: Scarica
download-filtered: Scarica Log Filtrati
search: Cerca
show: Mostra solo {std}
show-all: Mostra tutto
@@ -94,6 +95,7 @@ settings:
color-scheme: Tema colori
options: Opzioni
show-stopped-containers: Visualizza i container arrestati
group-containers: Raggruppa i container per namespace
about: Circa
search: Abilita la ricerca attraverso Dozzle
using-version: Stai utilizzando <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -102,6 +104,26 @@ settings:
show-std: Visualizza stdout e stderr
automatic-redirect: Reindirizza automaticamente al nuovo container con lo stesso nome
compact: Abilita la modalità compatta per i log
size:
small: Piccolo
medium: Medio
large: Grande
theme:
auto: Auto
dark: Scuro
light: Chiaro
redirect:
instant: Immediato
delayed: Ritardato
none: Nessuno
grouping:
always: Sempre
at-least-2: Almeno 2
never: Mai
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Questa è un'anteprima dei log
warning: Un log di avviso appare così
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: 지우기
download: 다운로드
download-filtered: 필터링된 로그 다운로드
search: 검색
show: "{std} 보기"
show-all: 전체 보기
@@ -97,6 +98,7 @@ settings:
color-scheme: 색상 테마
options: 옵션
show-stopped-containers: 중지된 컨테이너 보기
group-containers: 네임스페이스별로 컨테이너 그룹화
about: 정보
search: Dozzle 검색 기능 사용
using-version: <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}을 사용하고 있습니다.
@@ -106,6 +108,26 @@ settings:
show-std: stdout 및 stderr 라벨 표시
automatic-redirect: 같은 이름의 새 컨테이너로 자동 이동
compact: 로그 간단하게 보기
size:
small: 작게
medium: 중간
large: 크게
theme:
auto: 자동
dark: 어두움
light: 밝음
redirect:
instant: 즉시
delayed: 지연
none: 안 함
grouping:
always: 항상
at-least-2: 최소 2개
never: 안 함
hour:
auto: 자동
12: "12"
24: "24"
log:
preview: 로그 미리보기입니다
warning: 경고 로그는 이렇게 표시됩니다
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Wissen
download: Downloaden
download-filtered: Gefilterde Logs Downloaden
search: Zoeken
show: Toon {std}
show-all: Toon alles
@@ -95,6 +96,7 @@ settings:
color-scheme: Kleurmodus
options: Opties
show-stopped-containers: Gestopte containers tonen
group-containers: Groepeer containers op namespace
about: Over
search: Doorzoeken inschakelen via Dozzle
using-version: Je gebruikt <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -103,6 +105,26 @@ settings:
show-std: Toon stdout- en stderr-labels
automatic-redirect: Automatisch omleiden naar nieuwe containers met dezelfde naam
compact: Compacte modus inschakelen voor logs
size:
small: Klein
medium: Middel
large: Groot
theme:
auto: Auto
dark: Donker
light: Licht
redirect:
instant: Direct
delayed: Vertraagd
none: Geen
grouping:
always: Altijd
at-least-2: Minimaal 2
never: Nooit
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Voorbeeld van de logweergave
warning: Een waarschuwingslog ziet er zo uit
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Wyczyść
download: Pobierz
download-filtered: Pobierz Filtrowane Logi
search: Szukaj
show: Pokaż tylko {std}
show-all: Pokaż wszystko
@@ -100,6 +101,7 @@ settings:
color-scheme: Motyw
options: Opcje
show-stopped-containers: Pokaż zatrzymane kontenery
group-containers: Grupuj kontenery według przestrzeni nazw
about: Informacje
search: Włącz szukanie z dozzle
using-version: Używasz <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> w wersji {version}.
@@ -109,6 +111,26 @@ settings:
show-std: Pokaż etykiety dla stdout i stderr
automatic-redirect: Automatycznie przekieruj na kontener o tej samej nazwie
compact: Włącz tryb kompaktowy dla logów
size:
small: Mały
medium: Średni
large: Duży
theme:
auto: Auto
dark: Ciemny
light: Jasny
redirect:
instant: Natychmiastowe
delayed: Opóźnione
none: Brak
grouping:
always: Zawsze
at-least-2: Co najmniej 2
never: Nigdy
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: To jest podgląd logów
warning: Log ostrzeżenia wygląda tak
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Limpar
download: Descarregar
download-filtered: Descarregar Logs Filtrados
search: Pesquisa
show: Mostrar apenas {std}
show-all: Mostrar todos os fluxos
@@ -102,6 +103,7 @@ settings:
options: Opções
datetime-format: Formato de data e hora
show-stopped-containers: Mostrar contentores parados
group-containers: Agrupar contentores por namespace
about: Acerca de
search: Habilitar a pesquisa com Dozzle usando
using-version: Está a usar <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -111,6 +113,26 @@ settings:
show-std: Mostrar etiquetas de saída padrão e saída de erro padrão
automatic-redirect: Redirecionar automaticamente para novos contentores com o mesmo nome
compact: Compactar a interface do utilizador
size:
small: Pequeno
medium: Médio
large: Grande
theme:
auto: Auto
dark: Escuro
light: Claro
redirect:
instant: Instantâneo
delayed: Atrasado
none: Nenhum
grouping:
always: Sempre
at-least-2: Pelo menos 2
never: Nunca
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Esta é uma prévia dos logs
warning: Um log de aviso se parece com isto
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Limpar
download: Baixar
download-filtered: Baixar Logs Filtrados
search: Pesquisar
show: Mostrar {std}
show-all: Mostrar tudo
@@ -94,6 +95,7 @@ settings:
color-scheme: Esquema de cores
options: Opções
show-stopped-containers: Mostrar containers parados
group-containers: Agrupar containers por namespace
about: Sobre
search: Habilitar pesquisa com Dozzle usando
using-version: Você está usando <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -102,6 +104,26 @@ settings:
show-std: Mostrar rótulos stdout e stderr
automatic-redirect: Redirecionar automaticamente para novos containers com o mesmo nome
compact: Ativar modo compacto para logs
size:
small: Pequeno
medium: Médio
large: Grande
theme:
auto: Auto
dark: Escuro
light: Claro
redirect:
instant: Instantâneo
delayed: Atrasado
none: Nenhum
grouping:
always: Sempre
at-least-2: Pelo menos 2
never: Nunca
hour:
auto: Auto
12: "12"
24: "24"
log:
preview: Esta é uma prévia dos logs
warning: Um log de aviso se parece com isto
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Очистить
download: Скачать
download-filtered: Скачать Отфильтрованные Логи
search: Поиск
show: Показать только {std}
show-all: Показать все потоки
@@ -94,6 +95,7 @@ settings:
color-scheme: Цветовая схема
options: Опции
show-stopped-containers: Показывать остановленные контейнеры
group-containers: Группировать контейнеры по пространству имён
about: Информация
search: Включить поиск с помощью Dozzle, используя
using-version: Вы используете <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
@@ -102,6 +104,26 @@ settings:
show-std: Показывать метки stdout и stderr
automatic-redirect: Автоматическое перенаправление на новые контейнеры с тем же именем.
compact: Компактный режим
size:
small: Маленький
medium: Средний
large: Большой
theme:
auto: Авто
dark: Тёмная
light: Светлая
redirect:
instant: Мгновенное
delayed: Отложенное
none: Нет
grouping:
always: Всегда
at-least-2: Минимум 2
never: Никогда
hour:
auto: Авто
12: "12"
24: "24"
log:
preview: Это предварительный просмотр логов
warning: Так выглядит лог предупреждения
+33 -11
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Počisti
download: Prenesi
download-filtered: Prenesi Filtrirane Dnevnike
search: Iskanje
show: Prikaži {std}
show-all: Prikaži vse
@@ -52,12 +53,12 @@ error:
logs-skipped: Prikaži {total} skritih vnosov
events-stream:
title: Nepričakovana napaka
message: Uporabniški vmesnik Dozzle se ni mogel povezati z API-jem.
Preverite omrežne nastavitve. Če uporabljate obratni proxy, se
message: Uporabniški vmesnik Dozzle se ni mogel povezati z API-jem.
Preverite omrežne nastavitve. Če uporabljate obratni proxy, se
prepričajte, da je pravilno konfiguriran.
events-timeout:
title: Nekaj ni v redu
message: Časovna omejitev uporabniškega vmesnika Dozzle je potekla med
message: Časovna omejitev uporabniškega vmesnika Dozzle je potekla med
povezovanjem z API-jem. Preverite omrežno povezavo in poskusite znova.
container-not-found: Zabojnika ni bilo mogoče najti
alert:
@@ -66,8 +67,8 @@ alert:
message: Dozzle vas je samodejno preusmeril v nov zabojnik {containerId}.
similar-container-found:
title: Najden podoben zabojnik
message: Dozzle je našel podoben zabojnik {containerId}, ki se izvaja na
istem gostitelju in bo samodejno preklopil nanj, razen če kliknete
message: Dozzle je našel podoben zabojnik {containerId}, ki se izvaja na
istem gostitelju in bo samodejno preklopil nanj, razen če kliknete
»Prekliči«.
title:
page-not-found: Strani ni bilo mogoče najti
@@ -94,12 +95,33 @@ settings:
color-scheme: Barvna shema
options: Možnosti
show-stopped-containers: Prikaži ustavljene zabojnike
group-containers: Združi zabojnike po imenskem prostoru
about: O programu
search: Omogočite iskanje z Dozzle z uporabo
update-available: Nova različica je na voljo! Posodobi na <a href="{href}"
update-available: Nova različica je na voljo! Posodobi na <a href="{href}"
target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
show-std: Prikaži oznake stdout in stderr
compact: Omogoči kompaktni način za dnevnike
size:
small: Majhna
medium: Srednja
large: Velika
theme:
auto: Samodejno
dark: Temna
light: Svetla
redirect:
instant: Takojšnje
delayed: Zakasnelo
none: Brez
grouping:
always: Vedno
at-least-2: Vsaj 2
never: Nikoli
hour:
auto: Samodejno
12: "12"
24: "24"
log:
preview: To je predogled dnevnikov
warning: Dnevnik opozoril izgleda tako
@@ -108,11 +130,11 @@ settings:
start-line: To je večvrstično sporočilo o napaki
middle-line: z drugo vrstico
end-line: in nazadnje tretja vrstica.
simple: To je zelo zelo dolgo sporočilo, ki bi se privzeto prelomilo. Če
onemogočite mehke ovoje, bi to onemogočili. Lorem ipsum dolor sit amet,
simple: To je zelo zelo dolgo sporočilo, ki bi se privzeto prelomilo. Če
onemogočite mehke ovoje, bi to onemogočili. Lorem ipsum dolor sit amet,
consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua.
using-version: Uporabljaš <a href="https://dozzle.dev/" target="_blank"
using-version: Uporabljaš <a href="https://dozzle.dev/" target="_blank"
rel="noreferrer noopener">Dozzle</a> {version}.
help-support: "Prosimo, podprite Dozzle z donacijo ali sponzoriranjem na GitHubu.
Vaši prispevki nam pomagajo izboljšati Dozzle za vse. Hvala! 🙏🏼\n"
@@ -121,8 +143,8 @@ releases:
features: ena nova funkcija | {count} funkcij
bugFixes: en popravek napake | {counr} popravkov
breaking: ena ključna sprememba | {count} ključnih sprememb
three_parts: '{first}, {second} in {third}'
two_parts: '{first} s/z {second}'
three_parts: "{first}, {second} in {third}"
two_parts: "{first} s/z {second}"
latest: Najnovejše
no_releases: Imate najnovejšo različico
log_actions:
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: Temizle
download: İndir
download-filtered: Filtrelenmiş Logları İndir
search: Ara
show: Sadece {std} göster
show-all: Tüm akışları göster
@@ -103,6 +104,7 @@ settings:
color-scheme: Renk düzeni
options: Seçenekler
show-stopped-containers: Durdurulan konteynerleri göster
group-containers: Konteynerleri ad alanına göre grupla
about: Hakkında
search: Dozzle kullanarak aramayı etkinleştir
using-version: <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version} kullanıyorsunuz.
@@ -112,6 +114,26 @@ settings:
show-std: stdout ve stderr etiketlerini göster
automatic-redirect: Aynı ada sahip yeni konteynerlere otomatik olarak yönlendir
compact: Günlükler için kompakt modu etkinleştir
size:
small: Küçük
medium: Orta
large: Büyük
theme:
auto: Otomatik
dark: Karanlık
light: Açık
redirect:
instant: Anında
delayed: Gecikmeli
none: Hiçbiri
grouping:
always: Her zaman
at-least-2: En az 2
never: Asla
hour:
auto: Otomatik
12: "12"
24: "24"
releases:
features: bir yeni özellik | {count} özellik
bugFixes: bir hata düzeltmesi | {count} düzeltme
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: 清除
download: 下載
download-filtered: 下載篩選的日誌
search: 搜尋
show: 僅顯示 {std}
show-all: 顯示全部
@@ -96,6 +97,7 @@ settings:
color-scheme: 顏色設定
options: 選項
show-stopped-containers: 顯示已停止的容器
group-containers: 依命名空間分組容器
about: 關於
search: 啟用 Dozzle 搜尋功能
using-version: 您目前使用的是 <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}。
@@ -105,6 +107,26 @@ settings:
show-std: 顯示標準輸出(stdout)與標準錯誤(stderr)標籤
automatic-redirect: 自動重新導向至相同名稱的新容器
compact: 啟用緊湊模式
size:
small:
medium:
large:
theme:
auto: 自動
dark: 深色
light: 淺色
redirect:
instant: 立即
delayed: 延遲
none:
grouping:
always: 總是
at-least-2: 至少 2 個
never: 從不
hour:
auto: 自動
12: "12"
24: "24"
log:
preview: 這是日誌的預覽
warning: 警告日誌看起來是這樣的
+22
View File
@@ -1,6 +1,7 @@
toolbar:
clear: 清除
download: 下载
download-filtered: 下载筛选的日志
search: 搜索
show: 显示 {std}
show-all: 显示全部
@@ -94,6 +95,7 @@ settings:
color-scheme: 颜色方案
options: 选项
show-stopped-containers: 显示已停止的容器
group-containers: 按命名空间分组容器
about: 关于
search: 使用Dozzle启用搜索
using-version: 您正在使用<a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}。
@@ -102,6 +104,26 @@ settings:
show-std: 显示stdout和stderr标签
automatic-redirect: 自动重定向到同名的新容器
compact: 紧凑模式
size:
small:
medium:
large:
theme:
auto: 自动
dark: 深色
light: 浅色
redirect:
instant: 即时
delayed: 延迟
none:
grouping:
always: 始终
at-least-2: 至少 2 个
never: 从不
hour:
auto: 自动
12: "12"
24: "24"
log:
preview: 这是默认日志样式
warning: 警告日志看起来像这样
+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")
}
+34 -34
View File
@@ -1,12 +1,12 @@
{
"name": "dozzle",
"version": "8.14.4",
"version": "8.14.10",
"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.24.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.48",
"@iconify-json/mdi": "^1.2.3",
"@iconify-json/mdi-light": "^1.2.2",
"@iconify-json/octicon": "^1.2.16",
"@iconify-json/octicon": "^1.2.19",
"@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.17",
"@vueuse/components": "^14.1.0",
"@vueuse/core": "^14.1.0",
"@vueuse/integrations": "^14.1.0",
"@vueuse/router": "14.1.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.5.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",
"unplugin-auto-import": "^20.2.0",
"unplugin-icons": "^22.4.2",
"unplugin-vue-components": "^29.1.0",
"tailwindcss": "4.1.17",
"unplugin-auto-import": "^20.3.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.18.0",
"vite": "7.2.4",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-svg-loader": "^5.1.0",
"vitepress": "1.6.4",
"vue": "^3.5.22",
"vue-i18n": "^11.1.12",
"vue-router": "^4.5.1"
"vue": "^3.5.25",
"vue-i18n": "^11.2.2",
"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.48",
"@iconify-json/ri": "^1.2.6",
"@pinia/testing": "^1.0.2",
"@playwright/test": "^1.56.0",
"@pinia/testing": "^1.0.3",
"@playwright/test": "^1.57.0",
"@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",
"@vitejs/plugin-vue": "6.0.1",
"@vue/compiler-sfc": "^3.5.22",
"@types/node": "^24.10.1",
"@vitejs/plugin-vue": "6.0.2",
"@vue/compiler-sfc": "^3.5.25",
"@vue/test-utils": "^2.4.6",
"bumpp": "^10.3.1",
"bumpp": "^10.3.2",
"c8": "^10.1.3",
"concurrently": "^9.2.1",
"eventsourcemock": "^2.0.0",
"jsdom": "^27.0.0",
"lint-staged": "^16.2.4",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"jsdom": "^27.2.0",
"lint-staged": "^16.2.7",
"prettier": "^3.7.3",
"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.14",
"vue-component-type-helpers": "3.1.5",
"vue-tsc": "3.1.5"
},
"lint-staged": {
"*.{js,vue,css,ts,html,md}": [
+1200 -1367
View File
File diff suppressed because it is too large Load Diff