Compare commits

...

49 Commits

Author SHA1 Message Date
Amir Raminfar 492c5bfd32 chore: release v8.12.2 2025-03-31 13:49:52 -07:00
Amir Raminfar 397f42a223 chore: fixes wording 2025-03-31 13:48:50 -07:00
Amir Raminfar ddcc5ccbe1 fix(docs): updates docs for shell 2025-03-31 13:41:33 -07:00
Amir Raminfar 9ae7c4cbd8 fix: fixes cursor color on shell (#3738) 2025-03-31 20:36:04 +00:00
Amir Raminfar 076f5d0fc9 feat: adds shell behind a flag or env var (#3737) 2025-03-31 13:31:42 -07:00
Peter Dave Hello b8a8781a9b fix: Update and improve zh-tw Traditional Chinese locale (#3736) 2025-03-31 16:49:13 +00:00
renovate[bot] d5abf1868a fix(deps): update all non-major dependencies (#3735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 16:31:09 +00:00
Amir Raminfar d5a2841e02 chore: release v8.12.1 2025-03-31 08:54:29 -07:00
Amir Raminfar 410f5f305c fix: only shows attach and shell for docker (#3731)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-03-30 10:19:35 -07:00
Amir Raminfar 48d9958e0c chore: release v8.12.0 2025-03-30 08:34:01 -07:00
Amir Raminfar ea2132efc9 feat: adds terminal mode to attach or exec shell on a container (#3726) 2025-03-30 08:32:59 -07:00
Amir Raminfar 7a6914c6cf fix: fixes rotate degree on close button for drawer (#3729)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-03-29 18:25:39 -07:00
renovate[bot] da66540952 fix(deps): update all non-major dependencies (#3725)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-29 20:15:55 +00:00
renovate[bot] f863fc1bd3 fix(deps): update all non-major dependencies (#3722)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 16:12:57 +00:00
renovate[bot] 91b9c26c3a fix(deps): update dependency vite to v6.2.3 [security] (#3721)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 20:16:37 +00:00
renovate[bot] ae483639bb fix(deps): update module github.com/rs/zerolog to v1.34.0 (#3718)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 16:18:23 +00:00
renovate[bot] 38383e8924 fix(deps): update all non-major dependencies (#3717)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 16:22:04 +00:00
renovate[bot] c66bbc520c fix(deps): update all non-major dependencies (#3715)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 22:35:25 +00:00
renovate[bot] 7c2b7a5dcb fix(deps): update all non-major dependencies (#3714)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 14:44:50 +00:00
Amir Raminfar 0c96821f15 chore: moves sortables to async import (#3710)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-03-17 12:22:56 -07:00
renovate[bot] d1a225adc1 chore(deps): update pnpm to v10.6.4 (#3707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 16:35:43 +00:00
Amir Raminfar 3a7efe9ada chore: updates vue plugin (#3709) 2025-03-17 09:30:58 -07:00
Amir Raminfar 3e3bb02b5f chore: fixes code suggestions (#3708) 2025-03-17 09:20:04 -07:00
Amir Raminfar c3ee2da33c fix: fixes theme color in dark and light mode (#3706)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
2025-03-16 23:38:43 +00:00
Amir Raminfar a488e2e7ee chore: updates vue-tsc to alpha (#3705) 2025-03-16 15:57:17 -07:00
renovate[bot] 46fdb88b81 fix(deps): update dependency splitpanes to ^4.0.3 (#3704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-16 22:36:54 +00:00
renovate[bot] c88429c854 fix(deps): update all non-major dependencies (#3702)
Test / Go Staticcheck (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-15 16:42:00 +00:00
renovate[bot] 9e2d053ec8 fix(deps): update all non-major dependencies (#3700)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / JavaScript Tests (push) Waiting to run
Test / Typecheck (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-13 21:21:01 +00:00
renovate[bot] 97ece4c5ac fix(deps): update all non-major dependencies (#3699)
Test / JavaScript Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-12 15:54:18 +00:00
Amir Raminfar 4da50bbbd9 chore: release v8.11.9
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-03-10 09:21:20 -07:00
renovate[bot] ababa789c3 fix(deps): update vueuse monorepo to v13 (major) (#3697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 09:20:45 -07:00
renovate[bot] 60100dfdbb chore(deps): update pnpm to v10.6.2 (#3696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 16:16:18 +00:00
Amir Raminfar 0370759f69 chore: fixes deprecated mobile-web-app 2025-03-10 09:15:51 -07:00
Amir Raminfar 1b01710aa8 chore: adds a new test for everything complex (#3695)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Go Staticcheck (push) Waiting to run
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Integration Tests (push) Waiting to run
2025-03-09 19:59:02 +00:00
Amir Raminfar 0ffdd53568 feat: adds inset for left and right (#3694)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Typecheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
2025-03-09 17:51:19 +00:00
Amir Raminfar d9c9d20fff chore: fixes test cases 2025-03-09 08:35:25 -07:00
Stas Darevskiy e0797d7675 feat: safe area styling for ios pwa (#3693) 2025-03-09 08:32:59 -07:00
Amir Raminfar 25d8ba413a chore: release v8.11.8 2025-03-08 16:42:10 -08:00
Amir Raminfar 80bbb317aa feat: adds pwa in production mode (#3691) 2025-03-08 16:41:21 -08:00
Amir Raminfar c1c61c0ce6 fix: fixes broken mobile splitpanes (#3692) 2025-03-08 23:43:31 +00:00
Amir Raminfar 792a9f1187 fix: fixes analytics tab when mixed logs are found (#3690) 2025-03-08 14:19:51 -08:00
Amir Raminfar a3a6c03f11 chore: unignores splitplanes 2025-03-08 13:54:13 -08:00
Amir Raminfar 46b4fc0994 chore: updates splitpanes (#3689) 2025-03-08 13:52:57 -08:00
Amir Raminfar 4878144592 Revert "chore: npmrc clean up"
This reverts commit 99f203a23e.
2025-03-07 11:11:01 -08:00
Amir Raminfar 99f203a23e chore: npmrc clean up 2025-03-07 11:03:24 -08:00
renovate[bot] 211a442147 fix(deps): update all non-major dependencies (#3682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 19:01:25 +00:00
Amir Raminfar 02aafac2a9 chore: updates mods (#3684) 2025-03-07 18:56:14 +00:00
Amir Raminfar 3b260bef27 chore: ignores splitpanes 2025-03-07 10:55:14 -08:00
renovate[bot] fe0902e62a fix(deps): update dependency vue-i18n to v11.1.2 [security] (#3685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 16:46:48 +00:00
52 changed files with 1522 additions and 797 deletions
+3 -3
View File
@@ -31,7 +31,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: 1.22.x
go-version: 1.24.1
check-latest: true
- name: Checkout code
uses: actions/checkout@v4
@@ -92,12 +92,12 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the Container registry
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
+2 -2
View File
@@ -15,12 +15,12 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.4.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the Container registry
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
+2 -2
View File
@@ -54,7 +54,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "1.23"
go-version: "1.24.1"
check-latest: true
- name: Checkout code
uses: actions/checkout@v4
@@ -77,7 +77,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
go-version: "1.24.1"
check-latest: true
- name: Generate dependencies
run: make fake_assets shared_key.pem shared_cert.pem
+1 -1
View File
@@ -1,5 +1,5 @@
# Build assets
FROM --platform=$BUILDPLATFORM node:23.9.0-alpine AS node
FROM --platform=$BUILDPLATFORM node:23.10.0-alpine AS node
RUN corepack enable
+1 -20
View File
@@ -115,26 +115,7 @@ If you do not want to be tracked at all, see the `--no-analytics` flag below.
## Environment variables and configuration
Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can use the CLI flags or environment variables. The table below outlines all supported options and their respective env vars.
| Flag | Env Variable | Default |
| --------------------- | -------------------------- | -------------- |
| `--addr` | `DOZZLE_ADDR` | `:8080` |
| `--base` | `DOZZLE_BASE` | `/` |
| `--hostname` | `DOZZLE_HOSTNAME` | `""` |
| `--level` | `DOZZLE_LEVEL` | `info` |
| `--auth-provider` | `DOZZLE_AUTH_PROVIDER` | `none` |
| `--auth-header-user` | `DOZZLE_AUTH_HEADER_USER` | `Remote-User` |
| `--auth-header-email` | `DOZZLE_AUTH_HEADER_EMAIL` | `Remote-Email` |
| `--auth-header-name` | `DOZZLE_AUTH_HEADER_NAME` | `Remote-Name` |
| `--enable-actions` | `DOZZLE_ENABLE_ACTIONS` | `false` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | `false` |
| `--mode` | `DOZZLE_MODE` | `server` |
| `--remote-host` | `DOZZLE_REMOTE_HOST` | |
| `--remote-agent` | `DOZZLE_REMOTE_AGENT` | |
| `--timeout` | `DOZZLE_TIMEOUT` | `10s` |
| `--namespace` | `DOZZLE_NAMESPACE` | `""` |
Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can use the CLI flags or environment variables. See documentation at [https://dozzle.dev/guide/supported-env-vars](https://dozzle.dev/guide/supported-env-vars) for more details.
## Support
+1
View File
@@ -15,6 +15,7 @@ watchEffect(() => {
if (theme === "auto") {
theme = mode.value;
}
document.querySelector('meta[name="theme-color"]')?.setAttribute("content", theme == "dark" ? "#121212" : "#3FA68F");
document.documentElement.setAttribute("data-theme", theme);
});
</script>
+2
View File
@@ -31,6 +31,7 @@ declare global {
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']
@@ -421,6 +422,7 @@ declare module 'vue' {
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createRef: UnwrapRef<typeof import('@vueuse/core')['createRef']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
+3
View File
@@ -62,6 +62,7 @@ declare module 'vue' {
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
'MaterialSymbols:terminal': typeof import('~icons/material-symbols/terminal')['default']
'Mdi:account': typeof import('~icons/mdi/account')['default']
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
@@ -103,6 +104,7 @@ declare module 'vue' {
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
Popup: typeof import('./components/Popup.vue')['default']
RandomColorTag: typeof import('./components/LogViewer/RandomColorTag.vue')['default']
'Ri:terminalWindowFill': typeof import('~icons/ri/terminal-window-fill')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
@@ -121,6 +123,7 @@ declare module 'vue' {
StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default']
SwarmMenu: typeof import('./components/SwarmMenu.vue')['default']
Tag: typeof import('./components/common/Tag.vue')['default']
Terminal: typeof import('./components/Terminal.vue')['default']
TimedButton: typeof import('./components/common/TimedButton.vue')['default']
ToastModal: typeof import('./components/common/ToastModal.vue')['default']
Toggle: typeof import('./components/common/Toggle.vue')['default']
@@ -134,6 +134,22 @@
</button>
</li>
</template>
<template v-if="enableShell && host.type === 'local'">
<li class="line"></li>
<li>
<a @click.prevent="showDrawer(Terminal, { container, action: 'attach' }, 'lg')">
<ri:terminal-window-fill /> Attach
<KeyShortcut char="a" :modifiers="['shift', 'meta']" />
</a>
</li>
<li>
<a @click.prevent="showDrawer(Terminal, { container, action: 'exec' }, 'lg')">
<material-symbols:terminal /> Shell
<KeyShortcut char="e" :modifiers="['shift', 'meta']" />
</a>
</li>
</template>
</ul>
</div>
</template>
@@ -141,16 +157,19 @@
<script lang="ts" setup>
import { Container } from "@/models/Container";
import { allLevels } from "@/composable/logContext";
const { hosts } = useHosts();
import LogAnalytics from "../LogViewer/LogAnalytics.vue";
import Terminal from "@/components/Terminal.vue";
const { showSearch } = useSearchFilter();
const { enableActions } = config;
const { enableActions, enableShell } = config;
const { streamConfig, hasComplexLogs, levels } = useLoggingContext();
const showDrawer = useDrawer();
const { container } = defineProps<{ container: Container }>();
const clear = defineEmit();
const { actionStates, start, stop, restart } = useContainerActions(toRef(() => container));
const host = computed(() => hosts.value[container.host]);
onKeyStroke("f", (e) => {
if (hasComplexLogs.value) {
@@ -160,6 +179,21 @@ onKeyStroke("f", (e) => {
}
}
});
if (enableShell) {
onKeyStroke("a", (e) => {
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
showDrawer(Terminal, { container, action: "attach" }, "lg");
e.preventDefault();
}
});
onKeyStroke("e", (e) => {
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
showDrawer(Terminal, { container, action: "exec" }, "lg");
e.preventDefault();
}
});
}
const downloadParams = computed(() =>
Object.entries(toValue(streamConfig))
@@ -62,9 +62,9 @@ const store = useContainerStore();
const { containers: allContainers } = storeToRefs(store);
const otherContainers = computed(() =>
[...allContainers.value.filter((c) => c.name === container.name && c.id !== container.id)].sort(
(a, b) => +b.created - +a.created,
),
allContainers.value
.filter((c) => c.name === container.name && c.id !== container.id)
.sort((a, b) => +b.created - +a.created),
);
const wrapper = useTemplateRef("wrapper");
+4 -3
View File
@@ -72,7 +72,6 @@
<script setup lang="ts">
import { ComplexLogEntry } from "@/models/LogEntry";
import { useSortable } from "@vueuse/integrations/useSortable";
import { UseClipboard } from "@vueuse/components";
const { entry } = defineProps<{ entry: ComplexLogEntry }>();
@@ -82,6 +81,8 @@ const container = currentContainer(toRef(() => entry.containerID));
const visibleKeys = persistentVisibleKeysForContainer(container);
const { hosts } = useHosts();
const { useSortable } = await import("@vueuse/integrations/useSortable");
function toggleField(key: string[]) {
if (visibleKeys.value.size === 0) {
visibleKeys.value = new Map<string[], boolean>(fields.value.map(({ key }) => [key, true]));
@@ -146,8 +147,8 @@ function syntaxHighlight(json: any) {
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|\b\d+\b)/g,
function (match: string) {
var cls = "json-number";
if (/^"/.test(match)) {
if (/:$/.test(match)) {
if (match.startsWith('"')) {
if (match.endsWith(":")) {
cls = "json-key";
} else {
cls = "json-string";
@@ -29,7 +29,7 @@ defineProps<{
const colorize = (value: string) => ansiConvertor.toHtml(value);
const urlPattern =
/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_+.~#?&\/=]*/g;
/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_+.~#?&/=]*/g;
const linkify = (text: string) =>
text.replace(urlPattern, (url) => `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`);
</script>
+95
View File
@@ -0,0 +1,95 @@
<template>
<aside>
<header class="flex items-center gap-4">
<material-symbols:terminal class="size-8" />
<h1 class="text-2xl max-md:hidden">{{ container.name }}</h1>
<h2 class="text-sm">Started <DistanceTime :date="container.created" /></h2>
</header>
<div class="mt-8 flex flex-col gap-2">
<section>
<div ref="host" class="shell"></div>
</section>
</div>
</aside>
</template>
<script setup lang="ts">
import { Container } from "@/models/Container";
import "@xterm/xterm/css/xterm.css";
const { container, action } = defineProps<{ container: Container; action: "attach" | "exec" }>();
const { Terminal } = await import("@xterm/xterm");
const { WebLinksAddon } = await import("@xterm/addon-web-links");
const host = useTemplateRef<HTMLDivElement>("host");
const terminal = new Terminal({
cursorBlink: true,
cursorStyle: "block",
});
terminal.loadAddon(new WebLinksAddon());
let ws: WebSocket | null = null;
onMounted(() => {
terminal.open(host.value!);
terminal.resize(100, 40);
ws = new WebSocket(withBase(`/api/hosts/${container.host}/containers/${container.id}/${action}`));
ws.onopen = () => {
terminal.writeln(`Attaching to ${container.name} 🚀`);
if (action === "attach") {
ws?.send("\r");
}
terminal.onData((data) => {
ws?.send(data);
});
terminal.focus();
};
ws.onmessage = (event) => terminal.write(event.data);
ws.addEventListener("close", () => {
terminal.writeln("⚠️ Connection closed");
});
});
onUnmounted(() => {
console.log("Closing WebSocket");
terminal.dispose();
ws?.close();
});
</script>
<style scoped>
@import "@/main.css" reference;
.shell {
& :deep(.terminal) {
@apply overflow-hidden rounded border-1 p-2;
&:is(.focus) {
@apply border-primary;
}
}
& :deep(.xterm-viewport) {
@apply bg-base-200!;
}
& :deep(.xterm-rows) {
@apply text-base-content;
}
& :deep(.xterm-cursor-block.xterm-cursor-blink) {
animation-name: blink !important;
}
}
@keyframes blink {
0% {
background-color: var(--color-base-content);
color: #000000;
}
50% {
background-color: inherit;
color: var(--color-base-content);
}
}
</style>
+1 -1
View File
@@ -2,7 +2,7 @@
<dialog ref="panel" class="modal-right modal items-start outline-hidden backdrop:bg-none">
<div class="modal-box" :width="width">
<form method="dialog">
<button class="swap swap-rotate hover:swap-active absolute top-4 right-4 outline-hidden">
<button class="swap hover:swap-active absolute top-4 right-4 outline-hidden">
<mdi:keyboard-esc class="swap-off" />
<mdi:close class="swap-on" />
</button>
+1 -1
View File
@@ -29,7 +29,7 @@ export function useProfileStorage<K extends keyof Profile>(
if (transformer) {
storage.value = transformer.from(config.profile[key]);
} else if (storage.value instanceof Set && config.profile[key] instanceof Array) {
storage.value = new Set([...(config.profile[key] as Iterable<any>)]) as unknown as NonNullable<Profile[K]>;
storage.value = new Set(config.profile[key] as Iterable<any>) as unknown as NonNullable<Profile[K]>;
} else if (config.profile[key] instanceof Array) {
storage.value = config.profile[key] as NonNullable<Profile[K]>;
} else if (config.profile[key] instanceof Object) {
+5 -5
View File
@@ -5,7 +5,7 @@
<Pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav && !forceMenuHidden">
<SidePanel @search="showFuzzySearch" />
</Pane>
<Pane min-size="10">
<Pane min-size="10" :size="100 - menuWidth">
<Splitpanes>
<Pane class="router-view min-h-screen">
<router-view></router-view>
@@ -91,9 +91,9 @@ function showFuzzySearch() {
open.value = true;
}
function onResized(e: any) {
if (e.length == 2) {
menuWidth.value = e[0].size;
function onResized({ panes }: { panes: { size: number }[] }) {
if (panes.length == 2) {
menuWidth.value = Math.min(panes[0].size, 50);
}
}
</script>
@@ -102,7 +102,7 @@ function onResized(e: any) {
@import "@/main.css" reference;
:deep(.splitpanes--vertical > .splitpanes__splitter) {
@apply bg-base-100 hover:bg-secondary min-w-[3px];
@apply bg-base-100 hover:bg-secondary min-w-[5px];
}
@media screen and (max-width: 768px) {
+11
View File
@@ -126,3 +126,14 @@
}
}
}
nav {
padding-top: env(safe-area-inset-top);
}
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
+1
View File
@@ -11,6 +11,7 @@ export interface Config {
hosts: Host[];
authProvider: "simple" | "none" | "forward-proxy";
enableActions: boolean;
enableShell: boolean;
user?: {
username: string;
email: string;
+1 -1
View File
@@ -95,7 +95,7 @@ services:
playwright:
container_name: playwright
image: mcr.microsoft.com/playwright:v1.50.1-jammy
image: mcr.microsoft.com/playwright:v1.51.1-jammy
working_dir: /app
volumes:
- .:/app
+1
View File
@@ -72,6 +72,7 @@ export default defineConfig({
items: [
{ text: "Authentication", link: "/guide/authentication" },
{ text: "Actions", link: "/guide/actions" },
{ text: "Shell Access", link: "/guide/shell" },
{ text: "Agent Mode", link: "/guide/agent" },
{ text: "Changing Base", link: "/guide/changing-base" },
{ text: "Container Names", link: "/guide/container-names" },
+1 -1
View File
@@ -4,7 +4,7 @@ title: Container Actions
# Using Container Actions
Dozzle supports Container Actions, which allows you to `start`, `stop` and `restart` containers from within the UI in the dropdown menu. This feature is **disabled** by default and can be enabled by setting the environment variable `DOZZLE_ENABLE_ACTIONS` to `true`.
Dozzle supports container actions, which allows you to `start`, `stop` and `restart` containers from the dropdown menu on the right next to the container stats. This feature is **disabled** by default and can be enabled by setting the environment variable `DOZZLE_ENABLE_ACTIONS` to `true`.
::: code-group
+27
View File
@@ -0,0 +1,27 @@
---
title: Container Shell Access
---
# Attaching and Executing Commands <Badge type="info" text="new" />
Dozzle supports attaching or executing commands within containers. It provides a web-based interface to interact with Docker containers, allowing users to attach to running containers and execute commands directly from the browser. This feature is particularly useful for debugging and troubleshooting containerized applications. This feature is **disabled** by default as it may pose security risks. To enable it, set the `DOZZLE_ENABLE_SHELL` environment variable to `true`.
::: code-group
```sh
docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --enable-shell
```
```yaml [docker-compose.yml]
services:
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 8080:8080
environment:
DOZZLE_ENABLE_SHELL: true
```
:::
+1
View File
@@ -17,6 +17,7 @@ Configurations can be done with flags or environment variables. The table below
| `--auth-header-email` | `DOZZLE_AUTH_HEADER_EMAIL` | `Remote-Email` |
| `--auth-header-name` | `DOZZLE_AUTH_HEADER_NAME` | `Remote-Name` |
| `--enable-actions` | `DOZZLE_ENABLE_ACTIONS` | `false` |
| `--enable-shell` | `DOZZLE_ENABLE_SHELL` | `false` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | `false` |
| `--mode` | `DOZZLE_MODE` | `server` |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

+14 -13
View File
@@ -4,7 +4,7 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alexflint/go-arg v1.5.1
github.com/beme/abide v0.0.0-20190723115211-635a09831760
github.com/docker/docker v28.0.1+incompatible
github.com/docker/docker v28.0.4+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
@@ -15,31 +15,31 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/spf13/afero v1.12.0
github.com/spf13/afero v1.14.0
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.10.0
golang.org/x/net v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/sys v0.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/PuerkitoBio/goquery v1.10.2
github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/jwtauth/v5 v5.3.2
github.com/go-chi/jwtauth/v5 v5.3.3
github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/rs/zerolog v1.33.0
github.com/rs/zerolog v1.34.0
github.com/samber/lo v1.49.1
github.com/wk8/go-ordered-map/v2 v2.1.8
github.com/yuin/goldmark v1.7.8
golang.org/x/crypto v0.35.0
golang.org/x/crypto v0.36.0
golang.org/x/sync v0.12.0
google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.5
k8s.io/api v0.32.2
k8s.io/apimachinery v0.32.2
k8s.io/client-go v0.32.2
k8s.io/metrics v0.32.2
google.golang.org/protobuf v1.36.6
k8s.io/api v0.32.3
k8s.io/apimachinery v0.32.3
k8s.io/client-go v0.32.3
k8s.io/metrics v0.32.3
)
require (
@@ -66,6 +66,7 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
@@ -93,8 +94,8 @@ require (
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/oauth2 v0.26.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.10.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
+31
View File
@@ -34,6 +34,10 @@ github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR1
github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.0.2+incompatible h1:9BILleFwug5FSSqWBgVevgL3ewDJfWWWyZVqlDMttE8=
github.com/docker/docker v28.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=
github.com/docker/docker v28.0.4+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=
@@ -50,6 +54,8 @@ github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs=
github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
github.com/go-chi/jwtauth/v5 v5.3.3 h1:50Uzmacu35/ZP9ER2Ht6SazwPsnLQ9LRJy6zTZJpHEo=
github.com/go-chi/jwtauth/v5 v5.3.3/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -84,6 +90,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -154,8 +162,11 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPK
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
@@ -166,6 +177,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -222,6 +235,8 @@ golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
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=
@@ -277,6 +292,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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=
@@ -288,6 +305,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
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=
@@ -299,6 +318,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
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=
@@ -327,6 +348,8 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -345,16 +368,24 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
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/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=
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas=
k8s.io/metrics v0.32.2 h1:7t/rZzTHFrGa9f94XcgLlm3ToAuJtdlHANcJEHlYl9g=
k8s.io/metrics v0.32.2/go.mod h1:VL3nJpzcgB6L5nSljkkzoE0nilZhVgcjCfNRgoylaIQ=
k8s.io/metrics v0.32.3 h1:2vsBvw0v8rIIlczZ/lZ8Kcqk9tR6Fks9h+dtFNbc2a4=
k8s.io/metrics v0.32.3/go.mod h1:9R1Wk5cb+qJpCQon9h52mgkVCcFeYxcY+YkumfwHVCU=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
+8
View File
@@ -395,6 +395,14 @@ func (c *Client) ContainerAction(ctx context.Context, containerId string, action
return err
}
func (c *Client) ContainerAttach(ctx context.Context, containerId string) (io.WriteCloser, io.Reader, error) {
panic("not implemented")
}
func (c *Client) ContainerExec(ctx context.Context, containerId string, cmd []string) (io.WriteCloser, io.Reader, error) {
panic("not implemented")
}
func (c *Client) Close() error {
return c.conn.Close()
}
+2
View File
@@ -38,4 +38,6 @@ type Client interface {
Ping(context.Context) error
Host() Host
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error)
ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error)
}
+52
View File
@@ -33,6 +33,10 @@ type DockerCLI interface {
ContainerStart(ctx context.Context, containerID string, options docker.StartOptions) error
ContainerStop(ctx context.Context, containerID string, options docker.StopOptions) error
ContainerRestart(ctx context.Context, containerID string, options docker.StopOptions) error
ContainerAttach(ctx context.Context, containerID string, options docker.AttachOptions) (types.HijackedResponse, error)
ContainerExecCreate(ctx context.Context, containerID string, options docker.ExecOptions) (docker.ExecCreateResponse, error)
ContainerExecAttach(ctx context.Context, execID string, config docker.ExecAttachOptions) (types.HijackedResponse, error)
ContainerExecResize(ctx context.Context, execID string, options docker.ResizeOptions) error
Info(ctx context.Context) (system.Info, error)
}
@@ -297,6 +301,54 @@ func (d *DockerClient) Host() container.Host {
return d.host
}
func (d *DockerClient) ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error) {
log.Debug().Str("id", id).Str("host", d.host.Name).Msg("Attaching to container")
options := docker.AttachOptions{
Stream: true,
Stdin: true,
Stdout: true,
Stderr: true,
}
waiter, err := d.cli.ContainerAttach(ctx, id, options)
if err != nil {
return nil, nil, err
}
return waiter.Conn, waiter.Reader, nil
}
func (d *DockerClient) ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error) {
log.Debug().Str("id", id).Str("host", d.host.Name).Msg("Executing command in container")
options := docker.ExecOptions{
AttachStdout: true,
AttachStderr: true,
AttachStdin: true,
Cmd: cmd,
Tty: true,
}
execID, err := d.cli.ContainerExecCreate(ctx, id, options)
if err != nil {
return nil, nil, err
}
waiter, err := d.cli.ContainerExecAttach(ctx, execID.ID, docker.ExecAttachOptions{})
if err != nil {
return nil, nil, err
}
if err = d.cli.ContainerExecResize(ctx, execID.ID, docker.ResizeOptions{
Width: 100,
Height: 40,
}); err != nil {
return nil, nil, err
}
return waiter.Conn, waiter.Reader, nil
}
func newContainer(c docker.Summary, host string) container.Container {
name := "no name"
if c.Labels["dev.dozzle.name"] != "" {
+9 -2
View File
@@ -241,8 +241,15 @@ func (k *K8sClient) Host() container.Host {
}
func (k *K8sClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
// Implementation for container actions (start, stop, restart, etc.)
return nil
panic("not implemented")
}
func (k *K8sClient) ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error) {
panic("not implemented")
}
func (k *K8sClient) ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error) {
panic("not implemented")
}
// Helper function to parse pod and container names from container ID
+1
View File
@@ -22,6 +22,7 @@ type Args struct {
AuthHeaderName string `arg:"--auth-header-name,env:DOZZLE_AUTH_HEADER_NAME" default:"Remote-Name" help:"sets the HTTP Header to use for name in Forward Proxy configuration."`
AuthHeaderFilter string `arg:"--auth-header-filter,env:DOZZLE_AUTH_HEADER_FILTER" default:"Remote-Filter" help:"sets the HTTP Header to use for filtering in Forward Proxy configuration."`
EnableActions bool `arg:"--enable-actions,env:DOZZLE_ENABLE_ACTIONS" default:"false" help:"enables essential actions on containers from the web interface."`
EnableShell bool `arg:"--enable-shell,env:DOZZLE_ENABLE_SHELL" default:"false" help:"enables shell access to containers from the web interface."`
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
Filter map[string][]string `arg:"-"`
RemoteHost []string `arg:"env:DOZZLE_REMOTE_HOST,--remote-host,separate" help:"list of hosts to connect remotely"`
@@ -70,3 +70,11 @@ func (d *agentService) SubscribeContainersStarted(ctx context.Context, container
func (a *agentService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
return a.client.ContainerAction(ctx, container.ID, action)
}
func (a *agentService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
panic("not implemented")
}
func (a *agentService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
panic("not implemented")
}
+9 -5
View File
@@ -16,13 +16,17 @@ type ClientService interface {
Host(ctx context.Context) (container.Host, error)
ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error
LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error)
RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error)
RawLogs(context.Context, container.Container, time.Time, time.Time, container.StdType) (io.ReadCloser, error)
// Subscriptions
SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat)
SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent)
SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container)
SubscribeStats(context.Context, chan<- container.ContainerStat)
SubscribeEvents(context.Context, chan<- container.ContainerEvent)
SubscribeContainersStarted(context.Context, chan<- container.Container)
// Blocking streaming functions that should be used in a goroutine
StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error
StreamLogs(context.Context, container.Container, time.Time, container.StdType, chan<- *container.LogEvent) error
// Terminal
Attach(context.Context, container.Container, io.Reader, io.Writer) error
Exec(context.Context, container.Container, []string, io.Reader, io.Writer) error
}
@@ -35,3 +35,11 @@ func (c *ContainerService) StreamLogs(ctx context.Context, from time.Time, stdTy
func (c *ContainerService) Action(ctx context.Context, action container.ContainerAction) error {
return c.clientService.ContainerAction(ctx, c.Container, action)
}
func (c *ContainerService) Attach(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
return c.clientService.Attach(ctx, c.Container, stdin, stdout)
}
func (c *ContainerService) Exec(ctx context.Context, cmd []string, stdin io.Reader, stdout io.Writer) error {
return c.clientService.Exec(ctx, c.Container, cmd, stdin, stdout)
}
+73
View File
@@ -3,6 +3,7 @@ package docker_support
import (
"context"
"io"
"sync"
"time"
"github.com/amir20/dozzle/internal/container"
@@ -109,3 +110,75 @@ func (d *DockerClientService) SubscribeEvents(ctx context.Context, events chan<-
func (d *DockerClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
d.store.SubscribeNewContainers(ctx, containers)
}
func (d *DockerClientService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
cancelCtx, cancel := context.WithCancel(ctx)
containerWriter, containerReader, err := d.client.ContainerAttach(cancelCtx, container.ID)
if err != nil {
cancel()
return err
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
if _, err := io.Copy(containerWriter, stdin); err != nil {
log.Error().Err(err).Msg("error while reading from ws")
}
cancel()
containerWriter.Close()
}()
go func() {
defer wg.Done()
if container.Tty {
if _, err := io.Copy(stdout, containerReader); err != nil {
log.Error().Err(err).Msg("error while writing to ws")
}
} else {
if _, err := stdcopy.StdCopy(stdout, stdout, containerReader); err != nil {
log.Error().Err(err).Msg("error while writing to ws")
}
}
cancel()
}()
wg.Wait()
return nil
}
func (d *DockerClientService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
cancelCtx, cancel := context.WithCancel(ctx)
containerWriter, containerReader, err := d.client.ContainerExec(cancelCtx, container.ID, cmd)
if err != nil {
cancel()
return err
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
if _, err := io.Copy(containerWriter, stdin); err != nil {
log.Error().Err(err).Msg("error while reading from ws")
}
cancel()
containerWriter.Close()
}()
go func() {
defer wg.Done()
if _, err := stdcopy.StdCopy(stdout, stdout, containerReader); err != nil {
log.Error().Err(err).Msg("error while writing to ws")
}
cancel()
}()
wg.Wait()
return nil
}
+8
View File
@@ -90,3 +90,11 @@ func (k *K8sClientService) SubscribeEvents(ctx context.Context, events chan<- co
func (k *K8sClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
k.store.SubscribeNewContainers(ctx, containers)
}
func (k *K8sClientService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
panic("not implemented")
}
func (k *K8sClientService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
panic("not implemented")
}
+4
View File
@@ -84,6 +84,10 @@ Content-Type: text/html
{"m":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout","c":"123456"}
{"m":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr","c":"123456"}
/* snapshot: Test_handler_between_dates_with_everything_complex */
{"m":{"msg":"a complex log message"},"ts":1589396197772,"id":62280847,"l":"unknown","s":"stdout","c":"123456"}
/* snapshot: Test_handler_between_dates_with_fill */
{"m":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout","c":"123456"}
{"m":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr","c":"123456"}
+1
View File
@@ -60,6 +60,7 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
config["hostname"] = h.config.Hostname
config["hosts"] = hosts
config["enableActions"] = h.config.EnableActions
config["enableShell"] = h.config.EnableShell
}
if user != nil {
+14 -18
View File
@@ -129,34 +129,30 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
for event := range events {
if everything {
if _, ok := event.Message.(string); onlyComplex && ok {
continue
}
if err := encoder.Encode(event); err != nil {
log.Error().Err(err).Msg("error encoding log event")
}
continue
}
} else {
if regex != nil {
if !search.Search(regex, event) {
continue
}
}
if onlyComplex {
if _, ok := event.Message.(string); ok {
if _, ok := levels[event.Level]; !ok {
continue
}
}
if regex != nil {
if !search.Search(regex, event) {
continue
if lastSeenId != 0 && event.Id == lastSeenId {
log.Debug().Uint32("lastSeenId", lastSeenId).Msg("found last seen id")
break
}
}
if _, ok := levels[event.Level]; !ok {
continue
buffer.Push(event)
}
if lastSeenId != 0 && event.Id == lastSeenId {
log.Debug().Uint32("lastSeenId", lastSeenId).Msg("found last seen id")
break
}
buffer.Push(event)
}
if everything || from.Before(containerService.Container.Created) {
+40
View File
@@ -300,6 +300,46 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
mockedClient.AssertExpectations(t)
}
func Test_handler_between_dates_with_everything_complex(t *testing.T) {
id := "123456"
req, err := http.NewRequest("GET", "/api/hosts/localhost/containers/"+id+"/logs", nil)
require.NoError(t, err, "NewRequest should not return an error.")
q := req.URL.Query()
q.Add("jsonOnly", "true")
q.Add("stdout", "true")
q.Add("stderr", "true")
q.Add("everything", "true")
q.Add("levels", "info")
req.URL.RawQuery = q.Encode()
mockedClient := new(MockedClient)
first := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing stdout logs...\n", container.STDOUT)
second := makeMessage("2020-05-13T18:56:37.772853839Z {\"msg\":\"a complex log message\"}\n", container.STDOUT)
data := append(first, second...)
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, mock.Anything, mock.Anything, container.STDALL).
Return(io.NopCloser(bytes.NewReader(data)), nil).
Once()
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id}, nil)
mockedClient.On("Host").Return(container.Host{
ID: "localhost",
})
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
{ID: id, Name: "test", Host: "localhost", State: "running"},
}, nil)
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
handler := createDefaultHandler(mockedClient)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
reader := strings.NewReader(regexp.MustCompile(`"time":"[^"]*"`).ReplaceAllString(rr.Body.String(), `"time":"<removed>"`))
abide.AssertReader(t, t.Name(), reader)
mockedClient.AssertExpectations(t)
}
func makeMessage(message string, stream container.StdType) []byte {
data := make([]byte, 8)
binary.BigEndian.PutUint32(data[4:], uint32(len(message)))
+5
View File
@@ -35,6 +35,7 @@ type Config struct {
Dev bool
Authorization Authorization
EnableActions bool
EnableShell bool
Labels container.ContainerLabels
}
@@ -116,6 +117,10 @@ func createRouter(h *handler) *chi.Mux {
if h.config.EnableActions {
r.Post("/hosts/{host}/containers/{id}/actions/{action}", h.containerActions)
}
if h.config.EnableShell {
r.Get("/hosts/{host}/containers/{id}/attach", h.attach)
r.Get("/hosts/{host}/containers/{id}/exec", h.exec)
}
r.Get("/releases", h.releases)
r.Get("/profile/avatar", h.avatar)
r.Patch("/profile", h.updateProfile)
+120
View File
@@ -0,0 +1,120 @@
package web
import (
"net/http"
"github.com/amir20/dozzle/internal/auth"
"github.com/go-chi/chi/v5"
"github.com/gorilla/websocket"
"github.com/rs/zerolog/log"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func (h *handler) attach(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error().Err(err).Msg("error while trying to upgrade connection")
return
}
defer conn.Close()
id := chi.URLParam(r, "id")
userLabels := h.config.Labels
if h.config.Authorization.Provider != NONE {
user := auth.UserFromContext(r.Context())
if user.ContainerLabels.Exists() {
userLabels = user.ContainerLabels
}
}
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
if err != nil {
log.Error().Err(err).Msg("error while trying to find container")
return
}
wsReader := &webSocketReader{conn: conn}
wsWriter := &webSocketWriter{conn: conn}
if err = containerService.Attach(r.Context(), wsReader, wsWriter); err != nil {
log.Error().Err(err).Msg("error while trying to attach to container")
conn.WriteMessage(websocket.TextMessage, []byte("🚨 Error while trying to attach to container\r\n"))
return
}
}
func (h *handler) exec(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error().Err(err).Msg("error while trying to upgrade connection")
return
}
defer conn.Close()
id := chi.URLParam(r, "id")
userLabels := h.config.Labels
if h.config.Authorization.Provider != NONE {
user := auth.UserFromContext(r.Context())
if user.ContainerLabels.Exists() {
userLabels = user.ContainerLabels
}
}
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
if err != nil {
log.Error().Err(err).Msg("error while trying to find container")
return
}
wsReader := &webSocketReader{conn: conn}
wsWriter := &webSocketWriter{conn: conn}
if err = containerService.Exec(r.Context(), []string{"sh", "-c", "command -v bash >/dev/null 2>&1 && exec bash || exec sh"}, wsReader, wsWriter); err != nil {
log.Error().Err(err).Msg("error while trying to attach to container")
conn.WriteMessage(websocket.TextMessage, []byte("🚨 Error while trying to attach to container\r\n"))
return
}
}
type webSocketWriter struct {
conn *websocket.Conn
}
func (w *webSocketWriter) Write(p []byte) (int, error) {
err := w.conn.WriteMessage(websocket.TextMessage, p)
return len(p), err
}
type webSocketReader struct {
conn *websocket.Conn
buffer []byte
}
func (r *webSocketReader) Read(p []byte) (n int, err error) {
if len(r.buffer) > 0 {
n = copy(p, r.buffer)
r.buffer = r.buffer[n:]
return n, nil
}
// Otherwise, read a new message
_, message, err := r.conn.ReadMessage()
if err != nil {
return 0, err
}
n = copy(p, message)
// If we couldn't copy the entire message, store the rest in our buffer
if n < len(message) {
r.buffer = message[n:]
}
return n, nil
}
+55 -52
View File
@@ -1,24 +1,21 @@
toolbar:
clear: 清除
download: 下載
search: 查詢
search: 搜尋
show: 僅顯示 {std}
show-all: 顯示全部資料流
show-all: 顯示全部
stop: 停止
start: 啟動
restart: 重新啟動
show-hostname: 顯示主機名稱
show-container-name: 顯示容器名稱
label:
containers: 容器
container: 無容器 | 1 個容器 | {count} 個容器
service: 無服務 | 1 個服務 | {count} 個服務
running-containers: 運行中的容器
services: 服務
running-containers: 運作中的容器
all-containers: 所有容器
total-containers: 總容器數
running: 運行中
total-cpu-usage: 總 CPU 使用率
total-mem-usage: 總記憶體使用率
dozzle-version: Dozzle 版本
all: 全部
host: 主機
hosts: 主機
password: 密碼
@@ -26,38 +23,44 @@ label:
container-name: 容器名稱
status: 狀態
created: 建立時間
avg-cpu: 平均 CPU (%)
avg-mem: 平均記憶體 (%)
avg-cpu: 平均 CPU 使用率 (%)
avg-mem: 平均記憶體使用率 (%)
pinned: 已釘選
per-page: 每頁顯示行
swarm-mode: Swarm 模式
services: 服務
custom-groups: 自訂群組
per-page: 每頁
host-menu: 主機和容器
swarm-menu: 服務和堆疊
group-menu: 定義群組
group-menu: 群組
tooltip:
search: 查詢容器 (⌘ + k, ⌃k)
pin-column: 固定此列
merge-services: 合併所有服務到一個視圖
merge-containers: 合併所有容器到一個視圖
merge-hosts: 將此主機上的所有容器合併到一個視圖中
search: 搜尋容器 (⌘ + k, ⌃k)
pin-column: 釘選為欄位
merge-services: 所有服務合併至單一檢視
merge-containers: 所有容器合併至單一檢視
merge-hosts: 將此主機上的所有容器合併至單一檢視
error:
page-not-found: 此頁面不存在
invalid-auth: 使用者名稱或密碼不正確
logs-skipped: 已略過 {total} 筆記錄
container-not-found: 找不到容器
events-stream:
title: 意外錯誤
message: >-
Dozzle UI 無法連接到 API。請檢查您的網路設定。
如果您使用反向代理,請確保其正確配置
Dozzle UI 無法連線至 API。請檢查您的網路設定。
如果您使用反向代理伺服器,請確認其設定正確
events-timeout:
title: 發生錯誤
message: >-
Dozzle UI 連 API 時時。請檢查網路連接並重試
page-not-found: 此頁面不存在。
invalid-auth: 使用者名稱和密碼不正確。
logs-skipped: 略過 {total} 筆記錄
container-not-found: 找不到容器
Dozzle UI 連線至 API 時時。請檢查網路連線後再試一次
alert:
redirected:
title: 已重新導向至新容器
message: Dozzle 已自動將您重新導向至新容器 {containerId}
similar-container-found:
title: 找到相似的容器
message: >-
Dozzle 在相同主機上找到一個相似的容器 {containerId}
除非您點選「取消」,否則系統將自動切換至該容器。
title:
page-not-found: 頁面不存在
page-not-found: 找不到頁面
login: 需要身份驗證
dashboard: 1 個容器 | {count} 個容器
settings: 設定
@@ -68,49 +71,49 @@ button:
cancel: 取消
redirect: 重新導向
placeholder:
search-containers: 查詢容器 (⌘ + k, ⌃k)
search: 查詢
search-containers: 搜尋容器 (⌘ + k, ⌃k)
search: 搜尋
settings:
display: 顯示
locale: 覆寫語言
locale: 變更語言
small-scrollbars: 使用較小的捲軸
show-timesamps: 顯示時間戳記
soft-wrap: 自動換行
12-24-format: >-
預設情況下,Dozzle 會使用您瀏覽器的區域設定來格式化時間。您可以強制使用 12 或 24 小時制。
font-size: 日誌的字型大小
color-scheme: 顏色配置
datetime-format: 變更日期與時間格式
font-size: 日誌字型大小
color-scheme: 顏色設定
options: 選項
show-stopped-containers: 顯示已停止的容器
about: 關於
search: Dozzle 進行查詢
using-version: 正在使用 Dozzle {version}。
search: 啟用 Dozzle 搜尋功能
using-version: 目前使用的是 Dozzle {version}。
update-available: >-
有新版本可用!請更新至 <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>。
show-std: 顯示 stdout 和 stderr 標籤
compact: 緊湊模式
有新版本可用!請更新至 <a href="{href}" target="_blank"
rel="noreferrer noopener">{nextVersion}</a>。
show-std: 顯示標準輸出(stdout)與標準錯誤(stderr)標籤
automatic-redirect: 自動重新導向至相同名稱的新容器
compact: 啟用緊湊模式
log:
preview: 這是日誌的預覽
warning: 警告日誌看起來是這樣的
complex: 這是一個複雜的 JSON 格式日誌
simple: 這是一條非常非常長的訊息,默認情況下會自動換行。用自動換行會使其禁用。Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
complex: 這是一 JSON 格式的複雜日誌
simple: 這是一非常長的訊息,預設情況下會自動換行。用自動換行後便不會換行。Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
multi-line-error:
start-line: 這是一多行錯誤訊息
middle-line: 第二行訊息
start-line: 這是一多行錯誤訊息
middle-line: 這是第二行
end-line: 最後是第三行。
releases:
features: 新功能 | {count} 新功能
bugFixes: 錯誤修 | {count} 個修復
breaking: 重大變更 | {count} 重大變更
three_parts: "{first}、{second} {third}"
features: 新功能 | {count} 新功能
bugFixes: 錯誤修 | {count} 項修正
breaking: 重大變更 | {count} 重大變更
three_parts: "{first}、{second} {third}"
two_parts: "{first} 與 {second}"
latest: 最新
latest: 最新版本
no_releases: 您已經是最新版本
log_actions:
copy_log: 複製日誌
jump_to_context: 轉至上下文
jump_to_context: 至相關內容
toasts:
copied:
title: 已複製
message: 日誌已複製到剪貼簿
message: 日誌已複製到剪貼簿
+1
View File
@@ -191,6 +191,7 @@ func createServer(args cli.Args, hostService web.HostService) *http.Server {
TTL: authTTL,
},
EnableActions: args.EnableActions,
EnableShell: args.EnableShell,
Labels: args.Filter,
}
+28 -24
View File
@@ -1,12 +1,12 @@
{
"name": "dozzle",
"version": "8.11.7",
"version": "8.12.2",
"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.5.2",
"packageManager": "pnpm@10.7.0",
"type": "module",
"repository": {
"type": "git",
@@ -33,19 +33,21 @@
"@iconify-json/carbon": "^1.2.8",
"@iconify-json/cil": "^1.2.2",
"@iconify-json/ic": "^1.2.2",
"@iconify-json/material-symbols": "^1.2.15",
"@iconify-json/material-symbols": "^1.2.17",
"@iconify-json/mdi": "^1.2.3",
"@iconify-json/mdi-light": "^1.2.2",
"@iconify-json/octicon": "^1.2.5",
"@iconify-json/ph": "^1.2.2",
"@intlify/unplugin-vue-i18n": "^6.0.3",
"@intlify/unplugin-vue-i18n": "^6.0.5",
"@oddbird/css-anchor-positioning": "^0.4.0",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "4.0.9",
"@vueuse/components": "^12.8.2",
"@vueuse/core": "^12.8.2",
"@vueuse/integrations": "^12.8.2",
"@vueuse/router": "^12.8.2",
"@tailwindcss/vite": "4.0.17",
"@vueuse/components": "^13.0.0",
"@vueuse/core": "^13.0.0",
"@vueuse/integrations": "^13.0.0",
"@vueuse/router": "^13.0.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/xterm": "^5.5.0",
"ansi-to-html": "^0.7.2",
"d3-array": "^3.2.4",
"d3-ease": "^3.0.1",
@@ -53,34 +55,36 @@
"d3-selection": "^3.0.0",
"d3-shape": "^3.2.0",
"d3-transition": "^3.0.1",
"daisyui": "5.0.0",
"daisyui": "5.0.9",
"date-fns": "^4.1.0",
"entities": "^6.0.0",
"fuse.js": "^7.1.0",
"lodash.debounce": "^4.0.8",
"pinia": "^3.0.1",
"sortablejs": "^1.15.6",
"splitpanes": "^3.1.8",
"splitpanes": "^4.0.3",
"strip-ansi": "^7.1.0",
"tailwindcss": "4.0.9",
"unplugin-auto-import": "^19.1.1",
"tailwindcss": "4.0.17",
"unplugin-auto-import": "^19.1.2",
"unplugin-icons": "^22.1.0",
"unplugin-vue-components": "^28.4.1",
"unplugin-vue-macros": "^2.14.5",
"unplugin-vue-router": "^0.12.0",
"vite": "6.2.0",
"vite": "6.2.4",
"vite-plugin-compression2": "^1.3.3",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-svg-loader": "^5.1.0",
"vitepress": "1.6.3",
"vue": "^3.5.13",
"vue-i18n": "^11.1.1",
"vue-i18n": "^11.1.2",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@apache-arrow/esnext-esm": "^19.0.1",
"@iconify-json/material-symbols-light": "^1.2.17",
"@iconify-json/ri": "^1.2.5",
"@pinia/testing": "^1.0.0",
"@playwright/test": "^1.50.1",
"@playwright/test": "^1.51.1",
"@types/d3-array": "^3.2.1",
"@types/d3-ease": "^3.0.2",
"@types/d3-scale": "^4.0.9",
@@ -88,24 +92,24 @@
"@types/d3-shape": "^3.1.7",
"@types/d3-transition": "^3.0.9",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^22.13.9",
"@vitejs/plugin-vue": "5.2.1",
"@types/node": "^22.13.14",
"@vitejs/plugin-vue": "5.2.3",
"@vue/compiler-sfc": "^3.5.13",
"@vue/test-utils": "^2.4.6",
"bumpp": "^10.0.3",
"bumpp": "^10.1.0",
"c8": "^10.1.3",
"concurrently": "^9.1.2",
"eventsourcemock": "^2.0.0",
"jsdom": "^26.0.0",
"lint-staged": "^15.4.3",
"lint-staged": "^15.5.0",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"simple-git-hooks": "^2.11.1",
"simple-git-hooks": "^2.12.1",
"ts-node": "^10.9.2",
"typescript": "^5.8.2",
"vitest": "^3.0.7",
"vue-component-type-helpers": "^2.2.8",
"vue-tsc": "^2.2.8"
"vitest": "^3.1.1",
"vue-component-type-helpers": "3.0.0-alpha.2",
"vue-tsc": "3.0.0-alpha.2"
},
"lint-staged": {
"*.{js,vue,css,ts,html,md}": [
+812 -635
View File
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -2,7 +2,10 @@
<html class="bg-base-200 text-base-content">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="theme-color" content="" />
<title>Dozzle</title>
<script type="application/json" id="config__json">
{{ marshal .Config }}
@@ -16,9 +19,10 @@
<link rel="stylesheet" href="{{ .Base }}/{{ index $css 0}}" />
<script type="module" src="{{ .Base }}/{{ $js }}"></script>
{{- end }}
<meta name="description" content="Dozzle is a log viewer for Docker" />
<meta name="description" content="A log viewer for containers" />
<meta name="robots" content="noindex" />
<link rel="apple-touch-icon" href="{{ .Base }}/apple-touch-icon.png" sizes="512x512" />
<link rel="manifest" href="{{ .Base }}/manifest.webmanifest" />
</head>
<body>
+10
View File
@@ -0,0 +1,10 @@
{
"name": "Dozzle",
"short_name": "Dozzle",
"start_url": "/",
"display": "standalone",
"lang": "en",
"scope": "/",
"description": "A log viewer for containers",
"icons": [{ "src": "/apple-touch-icon.png", "sizes": "512x512", "type": "image/png" }]
}
-1
View File
@@ -68,7 +68,6 @@ export default defineConfig(() => ({
}),
VueI18nPlugin({
runtimeOnly: true,
compositionOnly: true,
strictMessage: false,
include: [path.resolve(__dirname, "locales/**")],
}),