mirror of
https://github.com/amir20/dozzle.git
synced 2026-08-07 19:01:58 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da32dc9171 | |||
| 653fc8878a | |||
| 3abe70b402 | |||
| 6097bd49b7 | |||
| 8462ee9454 | |||
| 8e6f7f16ba | |||
| 4605a4a59d | |||
| c7615d01a3 | |||
| aba8746dd0 | |||
| 9e7de63f26 | |||
| 313cde3ad1 | |||
| bf0148d131 | |||
| 72d1ecbcc6 | |||
| 50ce41eca3 | |||
| cffd4bd4e3 | |||
| 0f1e9cf0e6 | |||
| a761129aff | |||
| 16de645a89 | |||
| 1c6f30bcd3 | |||
| 9f29aa5923 | |||
| c2a17888a7 | |||
| 176c173ac7 | |||
| ce436d9697 | |||
| b752afb352 | |||
| 50dc54f4bb | |||
| e6eb7eeebd | |||
| 0cd62745f6 | |||
| 911f785e2c | |||
| 074f402d0f | |||
| 9485fa738b | |||
| bb56154811 | |||
| 78e63c39a0 | |||
| c1c8e8cccf | |||
| f9bc915ace | |||
| 938c6229f8 | |||
| b945c35bfa | |||
| 47adf9845a | |||
| 6951c1cc71 | |||
| 9dc27fa06c | |||
| f5786ba388 | |||
| b7b9ea7c6f | |||
| d7b6e9ee50 | |||
| 34f68849f4 | |||
| fa78467698 |
@@ -0,0 +1,57 @@
|
||||
name: Claude Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
# Optional: Only run on specific file changes
|
||||
# paths:
|
||||
# - "src/**/*.ts"
|
||||
# - "src/**/*.tsx"
|
||||
# - "src/**/*.js"
|
||||
# - "src/**/*.jsx"
|
||||
|
||||
jobs:
|
||||
claude-review:
|
||||
# Optional: Filter by PR author
|
||||
# if: |
|
||||
# github.event.pull_request.user.login == 'external-contributor' ||
|
||||
# github.event.pull_request.user.login == 'new-developer' ||
|
||||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code Review
|
||||
id: claude-review
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
prompt: |
|
||||
REPO: ${{ github.repository }}
|
||||
PR NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
Please review this pull request and provide feedback on:
|
||||
- Code quality and best practices
|
||||
- Potential bugs or issues
|
||||
- Performance considerations
|
||||
- Security concerns
|
||||
- Test coverage
|
||||
|
||||
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
||||
|
||||
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
|
||||
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
||||
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
# This is an optional setting that allows Claude to read CI results on PRs
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
|
||||
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
||||
# prompt: 'Update the pull request description to include a summary of changes.'
|
||||
|
||||
# Optional: Add claude_args to customize behavior and configuration
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
|
||||
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
||||
@@ -11,15 +11,15 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||
- name: Install dependencies
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.2
|
||||
go-version: 1.25.4
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
@@ -53,15 +53,15 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||
- name: Install dependencies
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
*.cache-to=type=gha,mode=max
|
||||
- name: Run Playwright tests
|
||||
run: docker compose up --exit-code-from playwright
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
- name: Release to Github
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -29,9 +29,9 @@ jobs:
|
||||
fetch-depth: 0 # Not needed if lastUpdated is not enabled
|
||||
- uses: pnpm/action-setup@v2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
cache: pnpm # or pnpm / yarn
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
+13
-17
@@ -14,15 +14,15 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||
- name: Install dependencies
|
||||
@@ -36,15 +36,11 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||
- name: Install dependencies
|
||||
@@ -58,7 +54,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25.2"
|
||||
go-version: "1.25.4"
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
@@ -81,7 +77,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.25.2"
|
||||
go-version: "1.25.4"
|
||||
check-latest: true
|
||||
- name: Generate dependencies
|
||||
run: make fake_assets shared_key.pem shared_cert.pem
|
||||
@@ -97,15 +93,15 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: latest
|
||||
node-version: 24.11.0
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: "**/pnpm-lock.yaml"
|
||||
- name: Install dependencies
|
||||
@@ -124,7 +120,7 @@ jobs:
|
||||
*.cache-to=type=gha,mode=max
|
||||
- name: Run Playwright tests
|
||||
run: docker compose up --exit-code-from playwright
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
|
||||
@@ -15,3 +15,4 @@ coverage.out
|
||||
*.pem
|
||||
*.csr
|
||||
tmp
|
||||
.claude
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
typed-router.d.ts
|
||||
docs/.vitepress/cache
|
||||
docs/.vitepress/dist
|
||||
dist
|
||||
|
||||
@@ -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
@@ -1,5 +1,5 @@
|
||||
# Build assets
|
||||
FROM --platform=$BUILDPLATFORM node:23.11.1-alpine AS node
|
||||
FROM --platform=$BUILDPLATFORM node:24.11.0-alpine AS node
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
|
||||
Vendored
+4
-1
@@ -75,6 +75,7 @@ declare global {
|
||||
const locale: typeof import('./stores/settings')['locale']
|
||||
const loggingContextKey: typeof import('./composable/logContext')['loggingContextKey']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const manualResetRef: typeof import('@vueuse/core')['manualResetRef']
|
||||
const mapActions: typeof import('pinia')['mapActions']
|
||||
const mapGetters: typeof import('pinia')['mapGetters']
|
||||
const mapState: typeof import('pinia')['mapState']
|
||||
@@ -122,6 +123,7 @@ declare global {
|
||||
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||
const refManualReset: typeof import('@vueuse/core')['refManualReset']
|
||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
@@ -472,6 +474,7 @@ declare module 'vue' {
|
||||
readonly locale: UnwrapRef<typeof import('./stores/settings')['locale']>
|
||||
readonly loggingContextKey: UnwrapRef<typeof import('./composable/logContext')['loggingContextKey']>
|
||||
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
||||
readonly manualResetRef: UnwrapRef<typeof import('@vueuse/core')['manualResetRef']>
|
||||
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||
@@ -519,11 +522,11 @@ declare module 'vue' {
|
||||
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
|
||||
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
|
||||
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
|
||||
readonly refManualReset: UnwrapRef<typeof import('@vueuse/core')['refManualReset']>
|
||||
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
|
||||
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
|
||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
||||
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
||||
readonly scrollContextKey: UnwrapRef<typeof import('./composable/scrollContext')['scrollContextKey']>
|
||||
readonly search: UnwrapRef<typeof import('./stores/settings')['search']>
|
||||
readonly sessionHost: UnwrapRef<typeof import('./composable/storage')['sessionHost']>
|
||||
|
||||
Vendored
+4
-1
@@ -1,8 +1,11 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// biome-ignore lint: disable
|
||||
// oxlint-disable
|
||||
// ------
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
// biome-ignore lint: disable
|
||||
|
||||
export {}
|
||||
|
||||
/* prettier-ignore */
|
||||
|
||||
@@ -29,10 +29,15 @@ describe("<ContainerEventSource />", () => {
|
||||
global.EventSource = EventSource;
|
||||
// @ts-ignore
|
||||
window.scrollTo = vi.fn();
|
||||
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
|
||||
observe: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
}));
|
||||
global.IntersectionObserver = class IntersectionObserver {
|
||||
observe = vi.fn();
|
||||
disconnect = vi.fn();
|
||||
unobserve = vi.fn();
|
||||
takeRecords = vi.fn();
|
||||
root = null;
|
||||
rootMargin = "";
|
||||
thresholds = [];
|
||||
} as any;
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1560336942459);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans" v-show="container">
|
||||
<div
|
||||
class="dropdown dropdown-hover absolute -left-2 z-10 font-sans"
|
||||
:class="shouldShowBelow ? 'dropdown-right' : 'dropdown-right dropdown-end'"
|
||||
v-show="container"
|
||||
ref="dropdownRef"
|
||||
@mouseenter="checkDropdownPosition"
|
||||
>
|
||||
<router-link
|
||||
v-if="isSearching"
|
||||
@click="resetSearch()"
|
||||
@@ -130,4 +136,14 @@ function hideMenu(e: MouseEvent) {
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
const dropdownRef = useTemplateRef<HTMLDivElement>("dropdownRef");
|
||||
const shouldShowBelow = ref(false);
|
||||
|
||||
function checkDropdownPosition() {
|
||||
if (!dropdownRef.value) return;
|
||||
|
||||
const rect = dropdownRef.value.getBoundingClientRect();
|
||||
shouldShowBelow.value = rect.top < 150;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<LogItem :logEntry>
|
||||
<div
|
||||
class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap"
|
||||
class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-pre"
|
||||
v-html="colorize(logEntry.message)"
|
||||
></div>
|
||||
</LogItem>
|
||||
|
||||
@@ -7,7 +7,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<div class="dropdown dropdown-hover absolute -left-2 z-10 font-sans dropdown-right dropdown-end"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
|
||||
@@ -28,7 +28,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-pre">foo bar</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
@@ -41,7 +41,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<div class="dropdown dropdown-hover absolute -left-2 z-10 font-sans dropdown-right dropdown-end"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
|
||||
@@ -62,7 +62,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-pre">foo bar</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
@@ -75,7 +75,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<div class="dropdown dropdown-hover absolute -left-2 z-10 font-sans dropdown-right dropdown-end"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
|
||||
@@ -96,7 +96,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">This is a message.</div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-pre">This is a message.</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Vendored
+139
-38
@@ -1,10 +1,15 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection ES6UnusedImports
|
||||
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
|
||||
// It's recommended to commit this file.
|
||||
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
|
||||
|
||||
declare module 'vue-router/auto-resolver' {
|
||||
export type ParamParserCustom = never
|
||||
}
|
||||
|
||||
declare module 'vue-router/auto-routes' {
|
||||
import type {
|
||||
RouteRecordInfo,
|
||||
@@ -18,23 +23,95 @@ declare module 'vue-router/auto-routes' {
|
||||
* Route name map generated by unplugin-vue-router
|
||||
*/
|
||||
export interface RouteNamedMap {
|
||||
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
||||
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
|
||||
'/container/[id]': RouteRecordInfo<'/container/[id]', '/container/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
||||
'/container/[id].time.[datetime]': RouteRecordInfo<'/container/[id].time.[datetime]', '/container/:id/time/:datetime', { id: ParamValue<true>, datetime: ParamValue<true> }, { id: ParamValue<false>, datetime: ParamValue<false> }>,
|
||||
'/group/[name]': RouteRecordInfo<'/group/[name]', '/group/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
|
||||
'/host/[id]': RouteRecordInfo<'/host/[id]', '/host/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
||||
'/login': RouteRecordInfo<'/login', '/login', Record<never, never>, Record<never, never>>,
|
||||
'/merged/[ids]': RouteRecordInfo<'/merged/[ids]', '/merged/:ids', { ids: ParamValue<true> }, { ids: ParamValue<false> }>,
|
||||
'/service/[name]': RouteRecordInfo<'/service/[name]', '/service/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
|
||||
'/settings': RouteRecordInfo<'/settings', '/settings', Record<never, never>, Record<never, never>>,
|
||||
'/show': RouteRecordInfo<'/show', '/show', Record<never, never>, Record<never, never>>,
|
||||
'/stack/[name]': RouteRecordInfo<'/stack/[name]', '/stack/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
|
||||
'/': RouteRecordInfo<
|
||||
'/',
|
||||
'/',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/[...all]': RouteRecordInfo<
|
||||
'/[...all]',
|
||||
'/:all(.*)',
|
||||
{ all: ParamValue<true> },
|
||||
{ all: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/container/[id]': RouteRecordInfo<
|
||||
'/container/[id]',
|
||||
'/container/:id',
|
||||
{ id: ParamValue<true> },
|
||||
{ id: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/container/[id].time.[datetime]': RouteRecordInfo<
|
||||
'/container/[id].time.[datetime]',
|
||||
'/container/:id/time/:datetime',
|
||||
{ id: ParamValue<true>, datetime: ParamValue<true> },
|
||||
{ id: ParamValue<false>, datetime: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/group/[name]': RouteRecordInfo<
|
||||
'/group/[name]',
|
||||
'/group/:name',
|
||||
{ name: ParamValue<true> },
|
||||
{ name: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/host/[id]': RouteRecordInfo<
|
||||
'/host/[id]',
|
||||
'/host/:id',
|
||||
{ id: ParamValue<true> },
|
||||
{ id: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/login': RouteRecordInfo<
|
||||
'/login',
|
||||
'/login',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/merged/[ids]': RouteRecordInfo<
|
||||
'/merged/[ids]',
|
||||
'/merged/:ids',
|
||||
{ ids: ParamValue<true> },
|
||||
{ ids: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/service/[name]': RouteRecordInfo<
|
||||
'/service/[name]',
|
||||
'/service/:name',
|
||||
{ name: ParamValue<true> },
|
||||
{ name: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/settings': RouteRecordInfo<
|
||||
'/settings',
|
||||
'/settings',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/show': RouteRecordInfo<
|
||||
'/show',
|
||||
'/show',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/stack/[name]': RouteRecordInfo<
|
||||
'/stack/[name]',
|
||||
'/stack/:name',
|
||||
{ name: ParamValue<true> },
|
||||
{ name: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
}
|
||||
|
||||
/**
|
||||
* Route file to route info map by unplugin-vue-router.
|
||||
* Used by the volar plugin to automatically type useRoute()
|
||||
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
|
||||
*
|
||||
* Each key is a file path relative to the project root with 2 properties:
|
||||
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
|
||||
@@ -44,58 +121,82 @@ declare module 'vue-router/auto-routes' {
|
||||
*/
|
||||
export interface _RouteFileInfoMap {
|
||||
'assets/pages/index.vue': {
|
||||
routes: '/'
|
||||
views: never
|
||||
routes:
|
||||
| '/'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/[...all].vue': {
|
||||
routes: '/[...all]'
|
||||
views: never
|
||||
routes:
|
||||
| '/[...all]'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/container/[id].vue': {
|
||||
routes: '/container/[id]'
|
||||
views: never
|
||||
routes:
|
||||
| '/container/[id]'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/container/[id].time.[datetime].vue': {
|
||||
routes: '/container/[id].time.[datetime]'
|
||||
views: never
|
||||
routes:
|
||||
| '/container/[id].time.[datetime]'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/group/[name].vue': {
|
||||
routes: '/group/[name]'
|
||||
views: never
|
||||
routes:
|
||||
| '/group/[name]'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/host/[id].vue': {
|
||||
routes: '/host/[id]'
|
||||
views: never
|
||||
routes:
|
||||
| '/host/[id]'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/login.vue': {
|
||||
routes: '/login'
|
||||
views: never
|
||||
routes:
|
||||
| '/login'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/merged/[ids].vue': {
|
||||
routes: '/merged/[ids]'
|
||||
views: never
|
||||
routes:
|
||||
| '/merged/[ids]'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/service/[name].vue': {
|
||||
routes: '/service/[name]'
|
||||
views: never
|
||||
routes:
|
||||
| '/service/[name]'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/settings.vue': {
|
||||
routes: '/settings'
|
||||
views: never
|
||||
routes:
|
||||
| '/settings'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/show.vue': {
|
||||
routes: '/show'
|
||||
views: never
|
||||
routes:
|
||||
| '/show'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/stack/[name].vue': {
|
||||
routes: '/stack/[name]'
|
||||
views: never
|
||||
routes:
|
||||
| '/stack/[name]'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a union of possible route names in a certain route component file.
|
||||
* Used by the volar plugin to automatically type useRoute()
|
||||
* Used by the \`sfc-typed-router\` Volar plugin to automatically type \`useRoute()\`.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ services:
|
||||
|
||||
playwright:
|
||||
container_name: playwright
|
||||
image: mcr.microsoft.com/playwright:v1.56.0-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.56.1-jammy
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
|
||||
+43
-3
@@ -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
@@ -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
|
||||
|
||||
@@ -4,7 +4,7 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/alexflint/go-arg v1.6.0
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/docker/docker v28.5.1+incompatible
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
@@ -119,4 +119,4 @@ require (
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
go 1.25.2
|
||||
go 1.25.4
|
||||
|
||||
@@ -62,6 +62,8 @@ github.com/docker/docker v28.5.0+incompatible h1:ZdSQoRUE9XxhFI/B8YLvhnEFMmYN9Pp
|
||||
github.com/docker/docker v28.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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...")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func (h *HealthcheckCmd) Run(args Args, embeddedCerts embed.FS) error {
|
||||
return fmt.Errorf("failed to read file: %w", err)
|
||||
}
|
||||
agentAddress := string(data)
|
||||
certs, err := ReadCertificates(embeddedCerts)
|
||||
certs, err := ReadCertificates(embeddedCerts, args.CertPath, args.KeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read certificates: %w", err)
|
||||
}
|
||||
|
||||
+12
-2
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -68,7 +68,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Could not create docker client")
|
||||
}
|
||||
certs, err := cli.ReadCertificates(certs)
|
||||
certs, err := cli.ReadCertificates(certs, args.CertPath, args.KeyPath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Could not read certificates")
|
||||
}
|
||||
|
||||
+29
-29
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "dozzle",
|
||||
"version": "8.14.4",
|
||||
"version": "8.14.7",
|
||||
"description": "Realtime log viewer for docker containers.",
|
||||
"homepage": "https://github.com/amir20/dozzle#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/amir20/dozzle/issues"
|
||||
},
|
||||
"packageManager": "pnpm@10.18.2",
|
||||
"packageManager": "pnpm@10.20.0",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,18 +33,18 @@
|
||||
"@iconify-json/carbon": "^1.2.14",
|
||||
"@iconify-json/cil": "^1.2.3",
|
||||
"@iconify-json/ic": "^1.2.4",
|
||||
"@iconify-json/material-symbols": "^1.2.41",
|
||||
"@iconify-json/material-symbols": "^1.2.44",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@iconify-json/mdi-light": "^1.2.2",
|
||||
"@iconify-json/octicon": "^1.2.16",
|
||||
"@iconify-json/octicon": "^1.2.17",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@intlify/unplugin-vue-i18n": "^11.0.1",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "4.1.14",
|
||||
"@vueuse/components": "^13.9.0",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"@vueuse/integrations": "^13.9.0",
|
||||
"@vueuse/router": "14.0.0-beta.1",
|
||||
"@tailwindcss/vite": "4.1.16",
|
||||
"@vueuse/components": "^14.0.0",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"@vueuse/integrations": "^14.0.0",
|
||||
"@vueuse/router": "14.0.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
@@ -54,35 +54,35 @@
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-shape": "^3.2.0",
|
||||
"d3-transition": "^3.0.1",
|
||||
"daisyui": "5.2.3",
|
||||
"daisyui": "5.4.5",
|
||||
"entities": "^7.0.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia": "^3.0.4",
|
||||
"sortablejs": "^1.15.6",
|
||||
"splitpanes": "^4.0.4",
|
||||
"strip-ansi": "^7.1.2",
|
||||
"tailwindcss": "4.1.14",
|
||||
"tailwindcss": "4.1.16",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-icons": "^22.4.2",
|
||||
"unplugin-vue-components": "^29.1.0",
|
||||
"unplugin-icons": "^22.5.0",
|
||||
"unplugin-vue-components": "^30.0.0",
|
||||
"unplugin-vue-macros": "^2.14.5",
|
||||
"unplugin-vue-router": "^0.15.0",
|
||||
"vite": "7.1.9",
|
||||
"unplugin-vue-router": "^0.16.1",
|
||||
"vite": "7.2.1",
|
||||
"vite-plugin-vue-layouts": "^0.11.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vitepress": "1.6.4",
|
||||
"vue": "^3.5.22",
|
||||
"vue": "^3.5.23",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-router": "^4.5.1"
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apache-arrow/esnext-esm": "^21.1.0",
|
||||
"@iconify-json/ion": "^1.2.6",
|
||||
"@iconify-json/material-symbols-light": "^1.2.41",
|
||||
"@iconify-json/material-symbols-light": "^1.2.44",
|
||||
"@iconify-json/ri": "^1.2.6",
|
||||
"@pinia/testing": "^1.0.2",
|
||||
"@playwright/test": "^1.56.0",
|
||||
"@pinia/testing": "^1.0.3",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@types/d3-array": "^3.2.2",
|
||||
"@types/d3-ease": "^3.0.2",
|
||||
"@types/d3-scale": "^4.0.9",
|
||||
@@ -90,24 +90,24 @@
|
||||
"@types/d3-shape": "^3.1.7",
|
||||
"@types/d3-transition": "^3.0.9",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/node": "^24.10.0",
|
||||
"@vitejs/plugin-vue": "6.0.1",
|
||||
"@vue/compiler-sfc": "^3.5.22",
|
||||
"@vue/compiler-sfc": "^3.5.23",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"bumpp": "^10.3.1",
|
||||
"c8": "^10.1.3",
|
||||
"concurrently": "^9.2.1",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"jsdom": "^27.0.0",
|
||||
"lint-staged": "^16.2.4",
|
||||
"jsdom": "^27.1.0",
|
||||
"lint-staged": "^16.2.6",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^3.2.4",
|
||||
"vue-component-type-helpers": "3.1.1",
|
||||
"vue-tsc": "3.1.1"
|
||||
"vitest": "^4.0.7",
|
||||
"vue-component-type-helpers": "3.1.3",
|
||||
"vue-tsc": "3.1.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,vue,css,ts,html,md}": [
|
||||
|
||||
Generated
+1318
-1074
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user