Compare commits

...

35 Commits

Author SHA1 Message Date
Amir Raminfar 4dfc0e3e59 fixes static errors 2025-04-21 11:11:21 -07:00
Amir Raminfar 4ebd0c3624 removes br check inside static logic 2025-04-21 11:03:19 -07:00
Amir Raminfar 10baad1d4a adds gzip to sse 2025-04-21 09:50:14 -07:00
Amir Raminfar cceab9bd29 feat: enables brotli globally 2025-04-21 09:37:39 -07:00
renovate[bot] bea1876c7c fix(deps): update dependency @intlify/unplugin-vue-i18n to ^6.0.7 (#3822)
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 / 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
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 02:34:50 +00:00
Amir Raminfar ae7eee9bca chore: release v8.12.10
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 / 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
2025-04-20 08:02:41 -07:00
Amir Raminfar d98b84e24a fix: fixes sql analytics when error occurs (#3820)
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 / 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
2025-04-19 16:44:40 +00:00
Amir Raminfar 18c87256cc fix: fixes alerts on mobile (#3819) 2025-04-19 09:41:57 -07:00
Amir Raminfar e96631930e chore: adds some shadows and makes colors better (#3818)
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 / 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
2025-04-19 08:04:59 -07:00
Amir Raminfar 76b1229341 chore: release v8.12.9
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 / 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
2025-04-18 14:24:57 -07:00
renovate[bot] 440f9394d6 fix(deps): update all non-major dependencies (#3817)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-18 20:58:05 +00:00
Amir Raminfar ec792318e6 feat: adds better disabled colors and loader while fetching big logs (#3816) 2025-04-18 20:52:53 +00:00
Amir Raminfar 16db90b660 docs: updates docs to Ed25519
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 / 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
2025-04-17 17:21:33 -07:00
Amir Raminfar eb22752b9a chore: updates to Ed25519 2025-04-17 17:20:19 -07:00
Amir Raminfar 642ab45ef7 chore: fixes tests
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 / 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
2025-04-17 14:24:34 -07:00
renovate[bot] 12fadb9348 fix(deps): update all non-major dependencies (#3813)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-17 14:09:33 -07:00
Amir Raminfar 1cf178e37d feat: more improvements to analytic and gzips logs for better performance (#3814) 2025-04-17 14:09:06 -07:00
Amir Raminfar 061cd0c445 feat: creates betters messaging for analytics mode (#3812) 2025-04-17 17:32:20 +00:00
renovate[bot] e8f796a7d6 fix(deps): update all non-major dependencies (#3811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-17 15:42:02 +00:00
Amir Raminfar 78f94f1f16 chore: updates d.ts
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-04-15 15:16:35 -07:00
renovate[bot] cc980361fd fix(deps): update module github.com/yuin/goldmark to v1.7.10 (#3809)
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 / 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
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 18:55:20 +00:00
Amir Raminfar 736ad8ed5b chore: updates modules (#3808) 2025-04-15 11:28:30 -07:00
renovate[bot] d0dbd2051e fix(deps): update all non-major dependencies (#3807)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:54:30 +00:00
Amir Raminfar 40a17b4088 chore: release v8.12.8
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 / 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
2025-04-14 08:33:33 -07:00
renovate[bot] b3da3d70a9 chore(deps): update dependency jsdom to ^26.1.0 (#3806)
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 / 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
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 03:44:40 +00:00
Amir Raminfar 28588fdfd2 chore: fixes center css (#3804) 2025-04-13 15:31:42 -07:00
Amir Raminfar a33f7bf3b2 fix: reverts confirm password 2025-04-13 15:31:28 -07:00
Amir Raminfar febdbff0de fix(docs): updates typos in docs 2025-04-13 15:27:36 -07:00
Amir Raminfar 0fee11e036 fix: fixes broken link on container title (#3803)
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 / 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
2025-04-13 12:54:20 -07:00
Amir Raminfar b768cbfc89 docs: updates generate command 2025-04-13 12:33:10 -07:00
Amir Raminfar d43de49b6d fix: fixes collapse color visually hard to see on light theme (#3801) 2025-04-13 09:42:49 -07:00
Amir Raminfar facc137a93 chore: updates duckdb (#3799)
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 / 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
2025-04-12 21:05:28 +00:00
renovate[bot] a052f55dc6 fix(deps): update all non-major dependencies (#3798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-12 21:00:54 +00:00
Amir Raminfar 481f2bd592 chore: format 2025-04-12 13:44:40 -07:00
Amir Raminfar b33ae4fa4e fix: Use /tmp instead of ./ to ensure that user permissions can be mounted correctly. (#3797) 2025-04-12 13:44:21 -07:00
48 changed files with 1196 additions and 1207 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = true
stop_on_error = false
stop_on_error = true
[color]
app = ""
+1
View File
@@ -61,6 +61,7 @@ RUN mkdir /data
FROM scratch
COPY --from=builder /data /data
COPY --from=builder /tmp /tmp
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /dozzle/dozzle /dozzle
+1 -1
View File
@@ -43,7 +43,7 @@ int:
docker compose up --build --force-recreate --exit-code-from playwright
shared_key.pem:
@openssl genpkey -algorithm RSA -out shared_key.pem -pkeyopt rsa_keygen_bits:2048
@openssl genpkey -algorithm Ed25519 -out shared_key.pem
shared_cert.pem: shared_key.pem
@openssl req -new -key shared_key.pem -out shared_request.csr -subj "/C=US/ST=California/L=San Francisco/O=Dozzle"
+1 -1
View File
@@ -373,7 +373,7 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
// @ts-ignore
export type { DrawerWidth } from './composable/drawer'
@@ -6,15 +6,15 @@
<carbon:star class="swap-off" />
</label>
<div class="inline-flex items-center text-sm">
<div class="breadcrumbs p-0">
<div class="breadcrumbs p-0 font-mono">
<ul>
<li v-if="config.hosts.length > 1" class="font-thin max-md:hidden">
{{ container.hostLabel }}
</li>
<li>
<div v-if="otherContainers.length === 0" class="font-mono">{{ container.name }}</div>
<template v-if="otherContainers.length === 0">{{ container.name }}</template>
<div class="wrapper" ref="wrapper" v-else>
<button popovertarget="popover-container-list" class="btn btn-xs md:btn-sm anchor font-mono">
<button popovertarget="popover-container-list" class="btn btn-xs md:btn-sm anchor">
{{ container.name }} <carbon:caret-down />
</button>
@@ -25,8 +25,8 @@
class="status data-[state=exited]:status-error data-[state=running]:status-success"
:data-state="other.state"
></div>
<div class="font-mono" v-if="other.isSwarm">{{ other.swarmId }}</div>
<div class="font-mono" v-else>{{ other.name }}</div>
<div v-if="other.isSwarm">{{ other.swarmId }}</div>
<div v-else>{{ other.name }}</div>
<div v-if="other.state === 'running'">running</div>
<DistanceTime :date="other.created" strict class="text-base-content/70 text-xs" v-else />
</router-link>
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<div class="dropdown dropdown-open w-full">
<div class="dropdown dropdown-open w-full shadow-md">
<div class="input input-xl input-primary flex w-full items-center">
<mdi:magnify class="flex size-8" />
<input
@@ -2,7 +2,7 @@
<ScrollableView :scrollable="scrollable" v-if="group.containers.length && ready">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<div class="@container flex flex-1 items-center gap-1.5 truncate md:gap-2">
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ $t("label.container", group.containers.length) }}</div>
</div>
+59 -23
View File
@@ -11,18 +11,26 @@
<textarea
v-model="query"
class="textarea textarea-primary w-full font-mono text-lg"
:class="{ 'textarea-error': error }"
:class="{ 'textarea-error!': error }"
:disabled="state === 'downloading'"
></textarea>
<div class="label max-h-48 overflow-y-auto pr-2">
<span class="label-text-alt text-error" v-if="error">{{ error }}</span>
<span class="label-text-alt" v-else>
Total {{ results.numRows }} records
<template v-if="results.numRows > pageLimit"> . Showing first {{ page.numRows }}. </template>
<div class="mt-2">
<span class="text-error" v-if="error">{{ error }}</span>
<span v-else-if="state === 'initializing'">{{ $t("analytics.creating_table") }}</span>
<span v-else-if="state === 'downloading'">{{
$t("analytics.downloading", { size: formatBytes(bytes, { decimals: 1 }) })
}}</span>
<span v-else-if="evaluating">{{ $t("analytics.evaluating_query") }}</span>
<span v-else>
{{ $t("analytics.total_records", { count: results.numRows.toLocaleString() }) }}
<template v-if="results.numRows > pageLimit">{{
$t("analytics.showing_first", { count: page.numRows.toLocaleString() })
}}</template>
</span>
</div>
</label>
</section>
<SQLTable :table="page" :loading="evaluating || !isReady" />
<SQLTable :table="page" :loading="evaluating || state !== 'ready'" />
</div>
</aside>
</template>
@@ -30,12 +38,15 @@
<script setup lang="ts">
import { Container } from "@/models/Container";
import { type Table } from "@apache-arrow/esnext-esm";
const { container } = defineProps<{ container: Container }>();
const query = ref("SELECT * FROM logs LIMIT 100");
const error = ref<string | null>(null);
const debouncedQuery = debouncedRef(query, 500);
const evaluating = ref(false);
const pageLimit = 1000;
const state = ref<"downloading" | "ready" | "initializing">("downloading");
const bytes = ref(0);
const url = withBase(
`/api/hosts/${container.host}/containers/${container.id}/logs?stdout=1&stderr=1&everything&jsonOnly`,
@@ -51,27 +62,49 @@ if (!response.ok) {
const { db, conn } = await useDuckDB();
const empty = await conn.query<Record<string, any>>(`SELECT 1 LIMIT 0`);
const { isReady } = useAsyncState(
async () => {
await db.registerFileBuffer("logs.json", new Uint8Array(await response.arrayBuffer()));
onMounted(async () => {
try {
state.value = "downloading";
const reader = response.body?.getReader();
if (!reader) throw new Error("No reader available from stream");
const chunks: Uint8Array[] = [];
bytes.value = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
bytes.value += value.length;
}
const arrayBuffer = new Uint8Array(bytes.value);
let position = 0;
for (const chunk of chunks) {
arrayBuffer.set(chunk, position);
position += chunk.length;
}
await db.registerFileBuffer("logs.json", arrayBuffer);
state.value = "initializing";
await conn.query(
`CREATE TABLE logs AS SELECT unnest(m) FROM read_json('logs.json', ignore_errors = true, format = 'newline_delimited')`,
);
},
undefined,
{
onError: (e) => {
console.error(e);
if (e instanceof Error) {
error.value = e.message;
}
},
},
);
state.value = "ready";
} catch (e) {
console.error(e);
if (e instanceof Error) {
error.value = e.message;
}
}
});
const results = computedAsync(
async () => {
if (isReady.value) {
if (state.value === "ready") {
return await conn.query<Record<string, any>>(debouncedQuery.value);
} else {
return empty;
@@ -89,7 +122,10 @@ const results = computedAsync(
},
);
whenever(evaluating, () => (error.value = null));
whenever(evaluating, () => {
error.value = null;
state.value = "ready";
});
const page = computed(() =>
results.value.numRows > pageLimit ? results.value.slice(0, pageLimit) : results.value,
) as unknown as ComputedRef<Table<Record<string, any>>>;
@@ -2,7 +2,7 @@
<ScrollableView :scrollable="scrollable" v-if="containers.length && ready">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<div class="@container flex flex-1 items-center gap-1.5 truncate md:gap-2">
<octicon:container-24 />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ containers.length }} containers</div>
@@ -2,7 +2,7 @@
<ScrollableView :scrollable="scrollable" v-if="service.name">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<div class="@container flex flex-1 items-center gap-1.5 truncate md:gap-2">
<ph:stack-simple />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ service.name }}</div>
+1 -1
View File
@@ -2,7 +2,7 @@
<ScrollableView :scrollable="scrollable" v-if="stack.name">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<div class="@container flex flex-1 items-center gap-1.5 truncate md:gap-2">
<ph:stack />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ stack.name }}</div>
+2 -2
View File
@@ -1,7 +1,7 @@
<template>
<div class="toast toast-end whitespace-normal max-md:m-0 max-md:w-full">
<div class="toast toast-end whitespace-normal max-md:end-auto max-md:m-0 max-md:max-w-full">
<div
class="alert max-w-xl max-md:rounded-none"
class="alert max-w-xl shadow-sm max-md:rounded-none"
v-for="{ toast, options: { timed } } in toasts"
:key="toast.id"
:class="{
+2 -2
View File
@@ -25,7 +25,7 @@
</Pane>
</Splitpanes>
<label
class="btn btn-circle swap bg-base-100 swap-rotate fixed bottom-4 -left-12 w-16 transition-all hover:-left-4"
class="btn btn-circle swap bg-base-content/10 swap-rotate border-base-content/20 hover:border-primary fixed bottom-4 -left-12 w-16 shadow-sm transition-all hover:-left-4"
:class="{ '-left-6!': collapseNav }"
v-if="!isMobile && !forceMenuHidden"
>
@@ -34,7 +34,7 @@
<mdi:chevron-left class="swap-off" />
</label>
</div>
<dialog ref="modal" class="modal items-start bg-white/15 backdrop-blur-xs transition-none!" @close="open = false">
<dialog ref="modal" class="modal bg-base-300/50! items-start backdrop-blur-md transition-none!" @close="open = false">
<div class="modal-box max-w-2xl bg-transparent pt-20 shadow-none">
<FuzzySearchModal @close="open = false" v-if="open" />
</div>
+1 -1
View File
@@ -95,7 +95,7 @@ services:
playwright:
container_name: playwright
image: mcr.microsoft.com/playwright:v1.51.1-jammy
image: mcr.microsoft.com/playwright:v1.52.0-jammy
working_dir: /app
volumes:
- .:/app
+1 -1
View File
@@ -180,7 +180,7 @@ This will mount the `cert.pem` and `key.pem` files to the agent. The agent will
To generate certificates, you can use the following command:
```sh
$ openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
$ openssl genpkey -algorithm Ed25519 -out key.pem
$ openssl req -new -key key.pem -out request.csr -subj "/C=US/ST=California/L=San Francisco/O=My Company"
$ openssl x509 -req -in request.csr -signkey key.pem -out cert.pem -days 365
```
+6 -5
View File
@@ -10,9 +10,10 @@ If you do not have an authentication solution, then Dozzle has a simple file-bas
## File-Based User Management
**Dozzle** supports multi-user authentication by setting `--auth-provider` to `simple`. In this mode, Dozzle will attempt to read the users file from `/data/`, prioritizing `users.yml` over `users.yaml` if both files are present. If only one of the files exists, it will be used. The log will indicate which file is being read (e.g., `Reading users.yml file`).
Dozzle supports multi-user authentication by setting `--auth-provider` to `simple`. In this mode, Dozzle will attempt to read the users file from `/data/`, prioritizing `users.yml` over `users.yaml` if both files are present. If only one of the files exists, it will be used. The log will indicate which file is being read (e.g., `Reading users.yml file`).
### Example file paths:
- `/data/users.yml`
- `/data/users.yaml`
@@ -24,7 +25,7 @@ users:
admin:
email: me@email.net
name: Admin
# Generate with docker run amir20/dozzle generate --name Admin --email me@email.net --password secret admin
# Generate with docker run run -it --rm amir20/dozzle generate --name Admin --email me@email.net --password secret admin
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
filter:
```
@@ -32,7 +33,7 @@ users:
Dozzle uses `email` to generate avatars using [Gravatar](https://gravatar.com/). It is optional. The password is hashed using `bcrypt` which can be generated using `docker run amir20/dozzle generate`.
> [!WARNING]
> In previous versions of Dozzle, SHA-256 was used to hash passwords. Bcrypt is now more secure and is recommended for future use. Dozzle will revert to SHA-256 if it does not find a bcrypt hash. It is advisable to update the password hash to bcrypt using `docker run amir20/dozzle generate`. For more details, see [this issue](https://github.com/amir20/dozzle/security/advisories/GHSA-w7qr-q9fh-fj35).
> In previous versions of Dozzle, SHA-256 was used to hash passwords. Bcrypt is now more secure and is recommended for future use. Dozzle will revert to SHA-256 if it does not find a bcrypt hash. It is advisable to update the password hash to bcrypt using `generate`. For more details, see [this issue](https://github.com/amir20/dozzle/security/advisories/GHSA-w7qr-q9fh-fj35).
You will need to mount this file for Dozzle to find it. Here is an example:
@@ -143,10 +144,10 @@ In this example, the `admin` user has no filter, so they can see all containers.
Dozzle has a built-in `generate` command to generate `users.yml`. Here is an example:
```sh
docker run amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" --user-filter name=foo > users.yml
docker run -it --rm amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" --user-filter name=foo > users.yml
```
In this example, `admin` is the username. Email and name are optional but recommended to display accurate avatars. `docker run amir20/dozzle generate --help` displays all options. The `--user-filter` flag is a comma-separated list of filters.
In this example, `admin` is the username. Email and name are optional but recommended to display accurate avatars. `docker run -it --rm amir20/dozzle generate --help` displays all options. The `--user-filter` flag is a comma-separated list of filters.
## Forward Proxy
+1 -1
View File
@@ -34,7 +34,7 @@ Configurations can be done with flags or environment variables. The table below
Dozzle supports generating `users.yml` file. This file is used to authenticate users. Here is an example:
```sh
docker run amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" > users.yml
docker run -it --rm amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" > users.yml
```
In this example, `admin` is the username. Email and name are optional but recommended to display accurate avatars. `docker run amir20/dozzle generate --help` displays all options.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 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: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+6 -4
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.4+incompatible
github.com/docker/docker v28.1.1+incompatible
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1
@@ -17,13 +17,14 @@ require (
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/net v0.39.0 // indirect
golang.org/x/sys v0.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/PuerkitoBio/goquery v1.10.2
github.com/PuerkitoBio/goquery v1.10.3
github.com/andybalholm/brotli v1.1.1
github.com/go-chi/chi/v5 v5.2.1
github.com/go-chi/jwtauth/v5 v5.3.3
github.com/go-faker/faker/v4 v4.6.0
@@ -32,7 +33,7 @@ require (
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
github.com/yuin/goldmark v1.7.10
golang.org/x/crypto v0.37.0
golang.org/x/sync v0.13.0
google.golang.org/grpc v1.71.1
@@ -80,6 +81,7 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+16 -8
View File
@@ -2,12 +2,14 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/PuerkitoBio/goquery v1.10.2 h1:7fh2BdHcG6VFZsK7toXBT/Bh1z5Wmy8Q9MV9HqT2AM8=
github.com/PuerkitoBio/goquery v1.10.2/go.mod h1:0guWGjcLu9AYC7C1GHnpysHy056u9aEkUHwhdnePMCU=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -32,8 +34,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
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/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+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=
@@ -128,6 +130,10 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -189,11 +195,13 @@ github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
@@ -244,8 +252,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+2 -2
View File
@@ -15,7 +15,6 @@ import (
"github.com/rs/zerolog/log"
)
type AgentCmd struct {
Addr string `arg:"--agent-addr,env:DOZZLE_AGENT_ADDR" default:":7007" help:"sets the host:port to bind for the agent"`
}
@@ -37,11 +36,12 @@ func (a *AgentCmd) Run(args Args, embeddedCerts embed.FS) error {
if err != nil {
return fmt.Errorf("failed to listen: %w", err)
}
tempFile, err := os.CreateTemp("./", "agent-*.addr")
tempFile, err := os.CreateTemp("", "agent-*.addr")
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
}
io.WriteString(tempFile, listener.Addr().String())
log.Debug().Str("file", tempFile.Name()).Msg("Created temp file")
go StartEvent(args, "", client, "agent")
server, err := agent.NewServer(client, certs, args.Version(), args.Filter)
if err != nil {
+5 -15
View File
@@ -11,12 +11,11 @@ import (
)
type GenerateCmd struct {
Username string `arg:"positional"`
Password string `arg:"--password, -p" help:"sets the password for the user"`
SkipConfirm bool `arg:"--skip-confirm" help:"skip password confirmation prompt"`
Name string `arg:"--name, -n" help:"sets the display name for the user"`
Email string `arg:"--email, -e" help:"sets the email for the user"`
Filter string `arg:"--user-filter" help:"sets the filter for the user. This can be a comma separated list of filters."`
Username string `arg:"positional"`
Password string `arg:"--password, -p" help:"sets the password for the user"`
Name string `arg:"--name, -n" help:"sets the display name for the user"`
Email string `arg:"--email, -e" help:"sets the email for the user"`
Filter string `arg:"--user-filter" help:"sets the filter for the user. This can be a comma separated list of filters."`
}
func (g *GenerateCmd) Run(args Args, embeddedCerts embed.FS) error {
@@ -27,15 +26,6 @@ func (g *GenerateCmd) Run(args Args, embeddedCerts embed.FS) error {
return fmt.Errorf("username and password are required")
}
if !args.Generate.SkipConfirm {
fmt.Print("Confirm password: ")
var confirmPassword string
fmt.Scanln(&confirmPassword)
if confirmPassword != args.Generate.Password {
return fmt.Errorf("passwords do not match")
}
}
buffer := auth.GenerateUsers(auth.User{
Username: args.Generate.Username,
Password: args.Generate.Password,
+12 -27
View File
@@ -8,43 +8,28 @@ import (
"path/filepath"
"github.com/amir20/dozzle/internal/healthcheck"
"github.com/rs/zerolog/log"
)
type HealthcheckCmd struct {
}
type HealthcheckCmd struct{}
func (h *HealthcheckCmd) Run(args Args, embeddedCerts embed.FS) error {
files, err := os.ReadDir(".")
if err != nil {
return fmt.Errorf("failed to read directory: %w", err)
}
agentAddress := ""
for _, file := range files {
if match, _ := filepath.Match("agent-*.addr", file.Name()); match {
data, err := os.ReadFile(file.Name())
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
agentAddress = string(data)
break
if matches, err := filepath.Glob("/tmp/agent-*.addr"); err == nil && len(matches) == 1 {
data, err := os.ReadFile(matches[0])
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
}
if agentAddress == "" {
if err := healthcheck.HttpRequest(args.Addr, args.Base); err != nil {
return fmt.Errorf("failed to make request: %w", err)
}
} else {
agentAddress := string(data)
certs, err := ReadCertificates(embeddedCerts)
if err != nil {
return fmt.Errorf("failed to read certificates: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), args.Timeout)
defer cancel()
if err := healthcheck.RPCRequest(ctx, agentAddress, certs); err != nil {
return fmt.Errorf("failed to make request: %w", err)
}
log.Info().Str("address", agentAddress).Msg("Making RPC request to agent")
return healthcheck.RPCRequest(ctx, agentAddress, certs)
} else {
log.Info().Str("address", args.Addr).Str("base", args.Base).Msg("Making HTTP request to server")
return healthcheck.HttpRequest(args.Addr, args.Base)
}
return nil
}
+27 -7
View File
@@ -2,26 +2,27 @@ package support_web
import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"strings"
"net/http"
)
type SSEWriter struct {
w io.Writer
f http.Flusher
w http.ResponseWriter
}
type HasId interface {
MessageId() int64
}
func NewSSEWriter(ctx context.Context, w http.ResponseWriter) (*SSEWriter, error) {
f, ok := w.(http.Flusher)
if !ok {
func NewSSEWriter(ctx context.Context, w http.ResponseWriter, r *http.Request) (*SSEWriter, error) {
if _, ok := w.(http.Flusher); !ok {
return nil, http.ErrNotSupported
}
@@ -31,9 +32,15 @@ func NewSSEWriter(ctx context.Context, w http.ResponseWriter) (*SSEWriter, error
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
var writer io.Writer = w
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
writer = gzip.NewWriter(w)
}
sse := &SSEWriter{
f: f,
w: w,
w: writer,
f: w.(http.Flusher),
}
return sse, nil
@@ -50,6 +57,13 @@ func (s *SSEWriter) Write(data []byte) (int, error) {
return written, err
}
if f, ok := s.w.(*gzip.Writer); ok {
err := f.Flush()
if err != nil {
return written, err
}
}
s.f.Flush()
return written, nil
@@ -60,6 +74,12 @@ func (s *SSEWriter) Ping() error {
return err
}
func (s *SSEWriter) Close() {
if closer, ok := s.w.(io.Closer); ok && s.w != nil {
closer.Close()
}
}
func (s *SSEWriter) Message(data any) error {
encoded, err := json.Marshal(data)
+37
View File
@@ -0,0 +1,37 @@
package web
import (
"io"
"net/http"
"strings"
"github.com/andybalholm/brotli"
)
type brotliWriter struct {
io.Writer
http.ResponseWriter
}
func (w brotliWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
func Brotli(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if client supports Brotli
if !strings.Contains(r.Header.Get("Accept-Encoding"), "br") {
next.ServeHTTP(w, r)
return
}
w.Header().Set("Content-Encoding", "br")
w.Header().Add("Vary", "Accept-Encoding")
brWriter := brotli.NewWriter(w)
defer brWriter.Close()
bw := brotliWriter{Writer: brWriter, ResponseWriter: w}
next.ServeHTTP(bw, r)
})
}
+3 -1
View File
@@ -13,7 +13,9 @@ import (
)
func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
sseWriter, err := support_web.NewSSEWriter(r.Context(), w)
sseWriter, err := support_web.NewSSEWriter(r.Context(), w, r)
defer sseWriter.Close()
if err != nil {
log.Error().Err(err).Msg("error creating sse writer")
http.Error(w, err.Error(), http.StatusInternalServerError)
+1 -11
View File
@@ -3,10 +3,7 @@ package web
import (
"html/template"
"io"
"mime"
"path/filepath"
"sort"
"strings"
"encoding/json"
@@ -24,14 +21,7 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
_, err := h.content.Open(path)
if err == nil && req.URL.Path != "" && req.URL.Path != "/" {
w.Header().Set("Cache-Control", "max-age=31536000, immutable")
// if brotli is enabled, then just send over the compressed file
if file, err := h.content.Open(path + ".br"); strings.Contains(req.Header.Get("Accept-Encoding"), "br") && err == nil {
w.Header().Set("Content-Encoding", "br")
w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(path)))
io.Copy(w, file)
} else {
fileServer.ServeHTTP(w, req)
}
fileServer.ServeHTTP(w, req)
} else {
h.executeTemplate(w, req)
}
+9 -1
View File
@@ -1,6 +1,7 @@
package web
import (
"compress/gzip"
"context"
"errors"
"regexp"
@@ -112,6 +113,12 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
}
encoder := json.NewEncoder(w)
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
writer := gzip.NewWriter(w)
defer writer.Close()
encoder = json.NewEncoder(writer)
}
for {
if buffer.Len() > minimum {
@@ -238,7 +245,8 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
return
}
sseWriter, err := support_web.NewSSEWriter(r.Context(), w)
sseWriter, err := support_web.NewSSEWriter(r.Context(), w, r)
defer sseWriter.Close()
if err != nil {
log.Error().Err(err).Msg("error creating sse writer")
http.Error(w, err.Error(), http.StatusInternalServerError)
+1 -1
View File
@@ -140,7 +140,7 @@ func createRouter(h *handler) *chi.Mux {
r.Get("/healthcheck", h.healthcheck)
defaultHandler := http.StripPrefix(strings.Replace(base+"/", "//", "/", 1), http.HandlerFunc(h.index))
r.Get("/*", func(w http.ResponseWriter, req *http.Request) {
r.With(Brotli).Get("/*", func(w http.ResponseWriter, req *http.Request) {
defaultHandler.ServeHTTP(w, req)
})
})
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: Kopieret
message: Log kopieret til clipboard
analytics:
creating_table: Opretter midlertidig tabel...
downloading: Henter containerlogfiler... ({size})
evaluating_query: Evaluerer forespørgsel...
total_records: I alt {count} poster.
showing_first: Viser de første {count}.
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: Kopiert
message: Log in Zwischenablage kopiert
analytics:
creating_table: Temporäre Tabelle wird erstellt...
downloading: Container-Logs werden abgerufen... ({size})
evaluating_query: Abfrage wird ausgewertet...
total_records: Insgesamt {count} Datensätze.
showing_first: Zeige die ersten {count}.
+6
View File
@@ -119,3 +119,9 @@ toasts:
copied:
title: Copied
message: Log copied to clipboard
analytics:
creating_table: Creating temporary table...
downloading: Fetching container logs... ({size})
evaluating_query: Evaluating query...
total_records: Total {count} records.
showing_first: Showing first {count}.
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: Copiado
message: Registro copiado al portapapeles
analytics:
creating_table: Creando tabla temporal...
downloading: Obteniendo registros de contenedores... ({size})
evaluating_query: Evaluando consulta...
total_records: Total {count} registros.
showing_first: Mostrando los primeros {count}.
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: Copié
message: Journal copié dans le presse-papiers
analytics:
creating_table: Création d'une table temporaire...
downloading: Récupération des journaux des conteneurs... ({size})
evaluating_query: Évaluation de la requête...
total_records: Total de {count} enregistrements.
showing_first: Affichage des {count} premiers.
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: Copiato
message: Log copiato nella clipboard
analytics:
creating_table: Creazione tabella temporanea...
downloading: Recupero dei log dei container... ({size})
evaluating_query: Valutazione della query...
total_records: Totale {count} record.
showing_first: Mostrati i primi {count}.
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: Skopiowany
message: Log skopiowany do schowka
analytics:
creating_table: Tworzenie tymczasowej tabeli...
downloading: Pobieranie logów kontenerów... ({size})
evaluating_query: Przetwarzanie zapytania...
total_records: Razem {count} rekordów.
showing_first: Pokazywanie pierwszych {count}.
+7
View File
@@ -109,3 +109,10 @@ settings:
start-line: Esta é uma mensagem de erro multi-linha
middle-line: com uma segunda linha
end-line: e finalmente uma terceira linha.
analytics:
creating_table: A criar tabela temporária...
downloading: A obter registos dos contentores... ({size})
evaluating_query: A avaliar consulta...
total_records: Total de {count} registos.
showing_first: A mostrar os primeiros {count}.
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: Copiado
message: Log copiado para a área de transferência
analytics:
creating_table: Criando tabela temporária...
downloading: Buscando logs dos containers... ({size})
evaluating_query: Avaliando consulta...
total_records: Total de {count} registros.
showing_first: Mostrando os primeiros {count}.
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: Copied
message: Log copied to clipboard
analytics:
creating_table: Создание временной таблицы...
downloading: Получение логов контейнеров... ({size})
evaluating_query: Выполнение запроса...
total_records: Всего {count} записей.
showing_first: Показаны первые {count}.
+6
View File
@@ -107,3 +107,9 @@ toasts:
copied:
title: Kopyalandı
message: Günlük panoya kopyalandı
analytics:
creating_table: Geçici tablo oluşturuluyor...
downloading: Konteyner günlükleri alınıyor... ({size})
evaluating_query: Sorgu değerlendiriliyor...
total_records: Toplam {count} kayıt.
showing_first: İlk {count} gösteriliyor.
+7 -1
View File
@@ -116,4 +116,10 @@ log_actions:
toasts:
copied:
title: 已複製
message: 日誌已複製到剪貼簿
message: 日誌已複製到剪貼簿
analytics:
creating_table: 正在建立臨時資料表...
downloading: 正在取得容器日誌... ({size})
evaluating_query: 正在評估查詢...
total_records: 共計 {count} 筆記錄。
showing_first: 顯示前 {count} 筆。
+6
View File
@@ -114,3 +114,9 @@ toasts:
copied:
title: 已复制
message: 日志已复制到剪贴板
analytics:
creating_table: 正在创建临时表...
downloading: 正在获取容器日志... ({size})
evaluating_query: 正在评估查询...
total_records: 总共 {count} 条记录。
showing_first: 显示前 {count} 条。
+15 -16
View File
@@ -1,12 +1,12 @@
{
"name": "dozzle",
"version": "8.12.7",
"version": "8.12.10",
"description": "Realtime log viewer for docker containers.",
"homepage": "https://github.com/amir20/dozzle#readme",
"bugs": {
"url": "https://github.com/amir20/dozzle/issues"
},
"packageManager": "pnpm@10.8.0",
"packageManager": "pnpm@10.8.1",
"type": "module",
"repository": {
"type": "git",
@@ -29,19 +29,19 @@
"docs:preview": "vitepress preview docs"
},
"dependencies": {
"@duckdb/duckdb-wasm": "1.29.0",
"@duckdb/duckdb-wasm": "1.29.1-dev132.0",
"@iconify-json/carbon": "^1.2.8",
"@iconify-json/cil": "^1.2.2",
"@iconify-json/ic": "^1.2.2",
"@iconify-json/material-symbols": "^1.2.18",
"@iconify-json/material-symbols": "^1.2.20",
"@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.5",
"@intlify/unplugin-vue-i18n": "^6.0.7",
"@oddbird/css-anchor-positioning": "^0.5.1",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "4.1.3",
"@tailwindcss/vite": "4.1.4",
"@vueuse/components": "^13.1.0",
"@vueuse/core": "^13.1.0",
"@vueuse/integrations": "^13.1.0",
@@ -55,7 +55,7 @@
"d3-selection": "^3.0.0",
"d3-shape": "^3.2.0",
"d3-transition": "^3.0.1",
"daisyui": "5.0.18",
"daisyui": "5.0.27",
"date-fns": "^4.1.0",
"entities": "^6.0.0",
"fuse.js": "^7.1.0",
@@ -64,14 +64,13 @@
"sortablejs": "^1.15.6",
"splitpanes": "^4.0.3",
"strip-ansi": "^7.1.0",
"tailwindcss": "4.1.3",
"tailwindcss": "4.1.4",
"unplugin-auto-import": "^19.1.2",
"unplugin-icons": "^22.1.0",
"unplugin-vue-components": "^28.4.1",
"unplugin-vue-components": "^28.5.0",
"unplugin-vue-macros": "^2.14.5",
"unplugin-vue-router": "^0.12.0",
"vite": "6.2.6",
"vite-plugin-compression2": "^1.3.3",
"vite": "6.3.2",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-svg-loader": "^5.1.0",
"vitepress": "1.6.3",
@@ -81,10 +80,10 @@
},
"devDependencies": {
"@apache-arrow/esnext-esm": "^19.0.1",
"@iconify-json/material-symbols-light": "^1.2.18",
"@iconify-json/material-symbols-light": "^1.2.20",
"@iconify-json/ri": "^1.2.5",
"@pinia/testing": "^1.0.1",
"@playwright/test": "^1.51.1",
"@playwright/test": "^1.52.0",
"@types/d3-array": "^3.2.1",
"@types/d3-ease": "^3.0.2",
"@types/d3-scale": "^4.0.9",
@@ -92,7 +91,7 @@
"@types/d3-shape": "^3.1.7",
"@types/d3-transition": "^3.0.9",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^22.14.0",
"@types/node": "^22.14.1",
"@vitejs/plugin-vue": "5.2.3",
"@vue/compiler-sfc": "^3.5.13",
"@vue/test-utils": "^2.4.6",
@@ -100,8 +99,8 @@
"c8": "^10.1.3",
"concurrently": "^9.1.2",
"eventsourcemock": "^2.0.0",
"jsdom": "^26.0.0",
"lint-staged": "^15.5.0",
"jsdom": "^26.1.0",
"lint-staged": "^15.5.1",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"simple-git-hooks": "^2.12.1",
+896 -1063
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -9,7 +9,6 @@ import IconsResolver from "unplugin-icons/resolver";
import VueRouter from "unplugin-vue-router/vite";
import Layouts from "vite-plugin-vue-layouts";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
import { compression } from "vite-plugin-compression2";
import { VueRouterAutoImports } from "unplugin-vue-router";
import svgLoader from "vite-svg-loader";
import tailwindcss from "@tailwindcss/vite";
@@ -71,7 +70,6 @@ export default defineConfig(() => ({
strictMessage: false,
include: [path.resolve(__dirname, "locales/**")],
}),
compression({ algorithm: "brotliCompress", exclude: [/\.(html)$/] }),
svgLoader({}),
tailwindcss(),
],