mirror of
https://github.com/amir20/dozzle.git
synced 2026-08-07 14:34:45 +00:00
Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 480fdea272 | |||
| 573ee5ed03 | |||
| cb6267f9ae | |||
| 3aa3b879de | |||
| 79bf0f01f9 | |||
| 6b442d26a9 | |||
| 67348ab514 | |||
| cb9ab3f944 | |||
| 0a722bd2da | |||
| 3195bc0d80 | |||
| 7bf455ae6b | |||
| 285a8bf251 | |||
| 65d690074c | |||
| 264af62bff | |||
| c3ac804e7a | |||
| e8e1c3ea6b | |||
| 29f06eb00d | |||
| 020342a1bd | |||
| 3b0b3ed5fc | |||
| 6b121633e9 | |||
| 443084964a | |||
| dc1314ddc3 | |||
| c4060b2206 | |||
| 5675389049 | |||
| bc3c812c74 | |||
| 5aea48f3ca | |||
| ab86a6c59e | |||
| f667e5a271 | |||
| 58146d1ac5 | |||
| a6b65a1341 | |||
| a6003e4c1c | |||
| 4e59bb2f8f | |||
| 220dcd9416 | |||
| fb359cb9d5 | |||
| 395d8641ad | |||
| 84ec399e50 | |||
| 2b0c9124d7 | |||
| e75b4d03bc | |||
| 5f24657b41 | |||
| 54799b420c | |||
| 23ad32d05c | |||
| 6e4a5238d3 | |||
| a1cedd4b16 | |||
| 84edb6bdab | |||
| a8c673544f | |||
| b2c3363659 | |||
| 3f376a0bce | |||
| 16fc75b563 | |||
| 7ed0ec498d | |||
| 11d12d1f6a | |||
| e88c412d62 | |||
| 96a02bf213 | |||
| 35b2c919cc | |||
| 85396d7631 | |||
| a3a6a8ec5c | |||
| 1168c03fc9 | |||
| 10597521de | |||
| da7730cffd | |||
| ed5090a9ec | |||
| c97a9cc34f | |||
| 31d7fe5c67 | |||
| ed8ce856ba | |||
| 940ba30633 | |||
| a4b0b05978 | |||
| 146ff180e0 | |||
| db4f11c68e | |||
| e702b62324 | |||
| cf97100b71 | |||
| b5cc4bd514 | |||
| f6e3d487ac | |||
| a2cc759538 | |||
| cfd757e876 | |||
| 01c564228b | |||
| 0b401aacd1 | |||
| d7335a8cbe | |||
| 6df66dade9 | |||
| ae083e955a | |||
| 99faac6e8e | |||
| 16495a66a0 | |||
| 03a2eb2f3b | |||
| bf18fdd1d8 | |||
| 912595f781 | |||
| 2a5a0b0c55 | |||
| 4e9667e8cf | |||
| 8130889484 | |||
| 013f7c5cde | |||
| 37c1fd032f | |||
| cd4f98fd66 | |||
| b78dab6da1 | |||
| 7be0515648 | |||
| af93918f8c | |||
| 9fa0b5256c | |||
| f037b81c92 | |||
| a97d976c4f | |||
| 6f87c42d2b | |||
| 1064af3175 | |||
| 9939b0001e | |||
| a54dd42531 | |||
| ba9faa7691 | |||
| e044ecd4a1 | |||
| da2d554bee | |||
| 9f0791a38b | |||
| eb20f9e4fa | |||
| 3ca6495404 | |||
| 44d30b8451 | |||
| 1f4410f257 | |||
| bd3e789d4c | |||
| 5d32e04af8 | |||
| 26c85a42b5 | |||
| 4aa06778d7 | |||
| 5755afcedd | |||
| 482c5ae445 | |||
| 8506f6123e | |||
| 4d5f94c2bb |
@@ -5,7 +5,7 @@ tmp_dir = "tmp"
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/dozzle"
|
||||
cmd = "go build -race -o ./tmp/dozzle ."
|
||||
cmd = "GOEXPERIMENT=jsonv2 go build -race -o ./tmp/dozzle ."
|
||||
delay = 1000
|
||||
exclude_dir = [
|
||||
"assets",
|
||||
|
||||
@@ -8,14 +8,16 @@ jobs:
|
||||
name: JavaScript Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: latest
|
||||
cache: "pnpm"
|
||||
@@ -29,12 +31,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.24.5
|
||||
go-version: 1.25.2
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v3
|
||||
- name: Install gRPC and Go
|
||||
@@ -48,14 +50,16 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: latest
|
||||
cache: "pnpm"
|
||||
@@ -92,18 +96,18 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@v3.6.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
@@ -137,11 +141,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
- name: Release to Github
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -15,18 +15,18 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@v3.6.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
|
||||
@@ -24,12 +24,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Not needed if lastUpdated is not enabled
|
||||
- uses: pnpm/action-setup@v2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: latest
|
||||
cache: pnpm # or pnpm / yarn
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
pnpm docs:build
|
||||
touch docs/.vitepress/dist/.nojekyll
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: docs/.vitepress/dist
|
||||
|
||||
|
||||
@@ -15,6 +15,6 @@ jobs:
|
||||
name: Validate PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
- uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+21
-15
@@ -11,14 +11,16 @@ jobs:
|
||||
name: Typecheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: latest
|
||||
cache: "pnpm"
|
||||
@@ -31,14 +33,16 @@ jobs:
|
||||
name: JavaScript Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: latest
|
||||
cache: "pnpm"
|
||||
@@ -52,12 +56,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24.5"
|
||||
go-version: "1.25.2"
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Install Protoc
|
||||
uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
@@ -73,11 +77,11 @@ jobs:
|
||||
name: Go Staticcheck
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24.5"
|
||||
go-version: "1.25.2"
|
||||
check-latest: true
|
||||
- name: Generate dependencies
|
||||
run: make fake_assets shared_key.pem shared_cert.pem
|
||||
@@ -90,14 +94,16 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
- uses: actions/setup-node@v5
|
||||
name: Install Node
|
||||
with:
|
||||
node-version: latest
|
||||
- run: corepack enable
|
||||
- run: pnpm --version
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: latest
|
||||
cache: "pnpm"
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ COPY public ./public
|
||||
# Build assets
|
||||
RUN pnpm build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
||||
|
||||
# install gRPC dependencies
|
||||
RUN apk add --no-cache ca-certificates protoc protobuf-dev\
|
||||
@@ -54,7 +54,7 @@ ARG TARGETOS TARGETARCH
|
||||
RUN go generate
|
||||
|
||||
# Build binary
|
||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/amir20/dozzle/internal/support/cli.Version=$TAG" -o dozzle
|
||||
RUN GOEXPERIMENT=jsonv2 GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/amir20/dozzle/internal/support/cli.Version=$TAG" -o dozzle
|
||||
|
||||
RUN mkdir /data
|
||||
|
||||
|
||||
Vendored
+7
-1
@@ -53,6 +53,7 @@ declare global {
|
||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
|
||||
const getDeep: typeof import('./utils/index')['getDeep']
|
||||
const globalShowPopup: typeof import('./composable/popup')['globalShowPopup']
|
||||
const h: typeof import('vue')['h']
|
||||
@@ -68,6 +69,7 @@ declare global {
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const isShallow: typeof import('vue')['isShallow']
|
||||
const lightTheme: typeof import('./stores/settings')['lightTheme']
|
||||
const loadBetween: typeof import('./composable/eventStreams')['loadBetween']
|
||||
const locale: typeof import('./stores/settings')['locale']
|
||||
@@ -330,6 +332,7 @@ declare global {
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||
@@ -376,7 +379,7 @@ declare global {
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, Slot, Slots, 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, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
// @ts-ignore
|
||||
export type { DrawerWidth } from './composable/drawer'
|
||||
@@ -447,6 +450,7 @@ declare module 'vue' {
|
||||
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||
readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
|
||||
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
|
||||
readonly globalShowPopup: UnwrapRef<typeof import('./composable/popup')['globalShowPopup']>
|
||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
@@ -462,6 +466,7 @@ declare module 'vue' {
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||
readonly isShallow: UnwrapRef<typeof import('vue')['isShallow']>
|
||||
readonly lightTheme: UnwrapRef<typeof import('./stores/settings')['lightTheme']>
|
||||
readonly loadBetween: UnwrapRef<typeof import('./composable/eventStreams')['loadBetween']>
|
||||
readonly locale: UnwrapRef<typeof import('./stores/settings')['locale']>
|
||||
@@ -724,6 +729,7 @@ declare module 'vue' {
|
||||
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
||||
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
||||
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
|
||||
readonly useTimeAgoIntl: UnwrapRef<typeof import('@vueuse/core')['useTimeAgoIntl']>
|
||||
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
|
||||
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
|
||||
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
|
||||
|
||||
Vendored
+1
@@ -68,6 +68,7 @@ declare module 'vue' {
|
||||
'MaterialSymbols:eyeTracking': typeof import('~icons/material-symbols/eye-tracking')['default']
|
||||
'MaterialSymbols:link': typeof import('~icons/material-symbols/link')['default']
|
||||
'MaterialSymbols:logout': typeof import('~icons/material-symbols/logout')['default']
|
||||
'MaterialSymbols:person': typeof import('~icons/material-symbols/person')['default']
|
||||
'MaterialSymbols:terminal': typeof import('~icons/material-symbols/terminal')['default']
|
||||
'MaterialSymbolsLight:collapseAll': typeof import('~icons/material-symbols-light/collapse-all')['default']
|
||||
'Mdi:account': typeof import('~icons/mdi/account')['default']
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<Dropdown class="dropdown-end" @closed="releaseSeen = mostRecent?.tag ?? config.version">
|
||||
<Dropdown
|
||||
class="dropdown-end"
|
||||
@click="config.releaseCheckMode === 'manual' && fetchReleases()"
|
||||
@closed="releaseSeen = mostRecent?.tag ?? config.version"
|
||||
>
|
||||
<template #trigger>
|
||||
<mdi:announcement class="size-6 -rotate-12" />
|
||||
<template v-if="announcements.length > 0 && releaseSeen != mostRecent?.tag">
|
||||
@@ -63,7 +67,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useAnnouncements } from "@/stores/announcements";
|
||||
|
||||
const { announcements, mostRecent } = useAnnouncements();
|
||||
const { announcements, mostRecent, fetchReleases } = useAnnouncements();
|
||||
const { t } = useI18n();
|
||||
|
||||
const releaseSeen = useProfileStorage("releaseSeen", config.version);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<KeyShortcut char="k" :modifiers="['shift', 'meta']" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li v-if="enableDownload">
|
||||
<a :href="downloadUrl" download> <octicon:download-24 /> {{ $t("toolbar.download") }} </a>
|
||||
</li>
|
||||
<li v-if="!historical">
|
||||
@@ -167,7 +167,7 @@ import LogAnalytics from "../LogViewer/LogAnalytics.vue";
|
||||
import Terminal from "@/components/Terminal.vue";
|
||||
|
||||
const { showSearch } = useSearchFilter();
|
||||
const { enableActions, enableShell } = config;
|
||||
const { enableActions, enableShell, enableDownload } = config;
|
||||
const { streamConfig, hasComplexLogs, levels } = useLoggingContext();
|
||||
const showDrawer = useDrawer();
|
||||
|
||||
|
||||
+23
-12
@@ -14,22 +14,27 @@
|
||||
|
||||
<dropdown class="dropdown-end" v-if="config.user">
|
||||
<template #trigger>
|
||||
<img
|
||||
class="ring-base-content/60 size-6 max-w-none rounded-full p-px ring-1"
|
||||
:src="withBase('/api/profile/avatar')"
|
||||
/>
|
||||
<template v-if="config.disableAvatars || !config.user.email">
|
||||
<material-symbols:person class="size-6" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<img
|
||||
class="ring-base-content/60 size-6 max-w-none rounded-full p-px ring-1"
|
||||
:src="withBase('/api/profile/avatar')"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="p-2">
|
||||
<div class="font-bold">
|
||||
{{ config.user.name }}
|
||||
</div>
|
||||
<div class="text-sm font-light">
|
||||
<div v-if="config.user.email" class="text-sm font-light">
|
||||
{{ config.user.email }}
|
||||
</div>
|
||||
</div>
|
||||
<ul class="menu mt-4 p-0">
|
||||
<li v-if="config.authProvider === 'simple'">
|
||||
<ul v-if="config.authProvider === 'simple' || config.logoutUrl" class="menu mt-4 p-0">
|
||||
<li>
|
||||
<button @click.prevent="logout()" class="text-primary p-2">
|
||||
<material-symbols:logout />
|
||||
{{ $t("button.logout") }}
|
||||
@@ -41,11 +46,17 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
async function logout() {
|
||||
await fetch(withBase("/api/token"), {
|
||||
method: "DELETE",
|
||||
});
|
||||
const { logoutUrl } = config;
|
||||
|
||||
location.reload();
|
||||
async function logout() {
|
||||
if (logoutUrl) {
|
||||
location.href = logoutUrl;
|
||||
} else {
|
||||
await fetch(withBase("/api/token"), {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="dropdown dropdown-start dropdown-hover absolute -left-2 z-10 font-sans" v-show="container">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans" v-show="container">
|
||||
<router-link
|
||||
v-if="isSearching"
|
||||
@click="resetSearch()"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Tag size="small" class="items-start!">
|
||||
<Tag class="items-start!">
|
||||
<DateTime :date="date" class="text-blue whitespace-nowrap" />
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
@@ -82,7 +82,7 @@ ul {
|
||||
}
|
||||
|
||||
&.large {
|
||||
@apply text-lg;
|
||||
@apply text-[1em];
|
||||
}
|
||||
|
||||
&.compact {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Tag size="small" :std="std" class="items-start!">
|
||||
<Tag :std="std" class="items-start!">
|
||||
{{ std }}
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<KeyShortcut char="k" :modifiers="['shift', 'meta']" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<li v-if="enableDownload">
|
||||
<a :href="downloadUrl" download> <octicon:download-24 /> {{ $t("toolbar.download") }} </a>
|
||||
</li>
|
||||
<li>
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { showSearch } = useSearchFilter();
|
||||
|
||||
const { enableDownload } = config;
|
||||
const clear = defineEmit();
|
||||
|
||||
const { streamConfig, showHostname, showContainerName, containers } = useLoggingContext();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="hover:text-secondary relative" @mouseenter="mouseOver = true" @mouseleave="mouseOver = false">
|
||||
<div class="hover:text-primary relative" @mouseenter="mouseOver = true" @mouseleave="mouseOver = false">
|
||||
<div class="border-primary overflow-hidden rounded-xs border px-px pt-1 pb-px max-md:hidden">
|
||||
<StatSparkline :data="data" @selected-point="onSelectedPoint" />
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-start dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
|
||||
@@ -23,7 +23,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
<div class="flex gap-x-2 gap-y-1 group-[.compact]:gap-y-0 has-[>_*:nth-of-type(2)]:flex-col-reverse md:flex-row!">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none" size="small">
|
||||
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none">
|
||||
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,7 +41,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-start dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
|
||||
@@ -57,7 +57,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
<div class="flex gap-x-2 gap-y-1 group-[.compact]:gap-y-0 has-[>_*:nth-of-type(2)]:flex-col-reverse md:flex-row!">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none" size="small">
|
||||
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none">
|
||||
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42</time></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,7 +75,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-start dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
|
||||
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
|
||||
@@ -91,7 +91,7 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
||||
<div class="flex gap-x-2 gap-y-1 group-[.compact]:gap-y-0 has-[>_*:nth-of-type(2)]:flex-col-reverse md:flex-row!">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none" size="small">
|
||||
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none">
|
||||
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<transition name="slide">
|
||||
<div
|
||||
class="fixed z-10 flex w-full justify-end p-2"
|
||||
class="fixed z-50 flex w-full justify-end p-2"
|
||||
v-show="showSearch"
|
||||
v-if="search"
|
||||
ref="container"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="ready" data-testid="side-menu" class="flex min-h-0 flex-col">
|
||||
<div v-if="ready" data-testid="side-menu" class="flex min-h-0 flex-col w-full">
|
||||
<Carousel v-model="selectedCard" class="flex-1">
|
||||
<CarouselItem :title="$t('label.host-menu')" id="host">
|
||||
<HostMenu />
|
||||
|
||||
@@ -80,7 +80,11 @@ export class Container {
|
||||
}
|
||||
|
||||
get namespace() {
|
||||
return this.labels["com.docker.stack.namespace"] || this.labels["com.docker.compose.project"];
|
||||
return (
|
||||
this.labels["dev.dozzle.group"] ||
|
||||
this.labels["com.docker.stack.namespace"] ||
|
||||
this.labels["com.docker.compose.project"]
|
||||
);
|
||||
}
|
||||
|
||||
get customGroup() {
|
||||
|
||||
@@ -12,7 +12,25 @@ type Announcement = {
|
||||
breaking: number;
|
||||
};
|
||||
|
||||
const { data: releases } = useFetch(withBase("/api/releases")).get().json<Announcement[]>();
|
||||
const releases = ref<Announcement[]>([]);
|
||||
let fetched = false;
|
||||
|
||||
async function fetchReleases() {
|
||||
if (fetched) return;
|
||||
fetched = true;
|
||||
|
||||
try {
|
||||
const { data } = await useFetch(withBase("/api/releases")).get().json<Announcement[]>();
|
||||
releases.value = data.value || [];
|
||||
} catch (error) {
|
||||
console.error("Error while fetching releases:\n", error);
|
||||
fetched = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.releaseCheckMode === "automatic") {
|
||||
fetchReleases();
|
||||
}
|
||||
|
||||
const otherAnnouncements = [] as Announcement[];
|
||||
|
||||
@@ -33,5 +51,6 @@ export function useAnnouncements() {
|
||||
announcements,
|
||||
latestRelease,
|
||||
hasRelease,
|
||||
fetchReleases,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,8 +10,12 @@ export interface Config {
|
||||
hostname: string;
|
||||
hosts: Host[];
|
||||
authProvider: "simple" | "none" | "forward-proxy";
|
||||
logoutUrl?: string;
|
||||
enableActions: boolean;
|
||||
enableShell: boolean;
|
||||
enableDownload: boolean;
|
||||
disableAvatars: boolean;
|
||||
releaseCheckMode: "automatic" | "manual";
|
||||
user?: {
|
||||
username: string;
|
||||
email: string;
|
||||
|
||||
Vendored
+72
@@ -31,4 +31,76 @@ declare module 'vue-router/auto-routes' {
|
||||
'/show': RouteRecordInfo<'/show', '/show', Record<never, never>, Record<never, never>>,
|
||||
'/stack/[name]': RouteRecordInfo<'/stack/[name]', '/stack/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
|
||||
}
|
||||
|
||||
/**
|
||||
* Route file to route info map by unplugin-vue-router.
|
||||
* Used by the volar plugin to automatically type useRoute()
|
||||
*
|
||||
* Each key is a file path relative to the project root with 2 properties:
|
||||
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
|
||||
* - views: names of nested views (can be passed to <RouterView name="...">)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface _RouteFileInfoMap {
|
||||
'assets/pages/index.vue': {
|
||||
routes: '/'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/[...all].vue': {
|
||||
routes: '/[...all]'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/container/[id].vue': {
|
||||
routes: '/container/[id]'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/container/[id].time.[datetime].vue': {
|
||||
routes: '/container/[id].time.[datetime]'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/group/[name].vue': {
|
||||
routes: '/group/[name]'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/host/[id].vue': {
|
||||
routes: '/host/[id]'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/login.vue': {
|
||||
routes: '/login'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/merged/[ids].vue': {
|
||||
routes: '/merged/[ids]'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/service/[name].vue': {
|
||||
routes: '/service/[name]'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/settings.vue': {
|
||||
routes: '/settings'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/show.vue': {
|
||||
routes: '/show'
|
||||
views: never
|
||||
}
|
||||
'assets/pages/stack/[name].vue': {
|
||||
routes: '/stack/[name]'
|
||||
views: never
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a union of possible route names in a certain route component file.
|
||||
* Used by the volar plugin to automatically type useRoute()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export type _RouteNamesForFilePath<FilePath extends string> =
|
||||
_RouteFileInfoMap extends Record<FilePath, infer Info>
|
||||
? Info['routes']
|
||||
: keyof RouteNamedMap
|
||||
}
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ services:
|
||||
|
||||
playwright:
|
||||
container_name: playwright
|
||||
image: mcr.microsoft.com/playwright:v1.54.1-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.56.0-jammy
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
|
||||
@@ -28,6 +28,7 @@ users:
|
||||
# Generate with docker run -it --rm amir20/dozzle generate --name Admin --email me@email.net --password secret admin
|
||||
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
|
||||
filter:
|
||||
roles:
|
||||
```
|
||||
|
||||
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`.
|
||||
@@ -139,15 +140,44 @@ In this example, the `admin` user has no filter, so they can see all containers.
|
||||
> [!NOTE]
|
||||
> Filters can also be set [globally](/guide/filters) with the `--filter` flag. This flag is applied to all users. If a user has a filter set, it will override the global filter.
|
||||
|
||||
### Setting specific roles for users
|
||||
|
||||
Dozzle allows assigning roles to users. Roles define what actions a user can perform on containers. Roles are configured in the users.yml file.
|
||||
|
||||
```yaml
|
||||
users:
|
||||
admin:
|
||||
email:
|
||||
name: Admin
|
||||
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
|
||||
roles:
|
||||
|
||||
guest:
|
||||
email:
|
||||
name: Guest
|
||||
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
|
||||
roles: shell
|
||||
```
|
||||
|
||||
In this example, the `admin` user has no roles specified, so they have full access to all container actions. The `guest` user has the shell role, meaning they can only open a shell in the containers. Roles make it easy to control and restrict what users can do in Dozzle.
|
||||
|
||||
Dozzle supports the following roles:
|
||||
|
||||
- **shell** - allows attach and exec in the container
|
||||
- **actions** - allows performing container actions (start, stop, restart)
|
||||
- **download** - allows downloading container logs
|
||||
- **none** - denies all actions
|
||||
- **all** - allows all actions (default)
|
||||
|
||||
## Generating users.yml
|
||||
|
||||
Dozzle has a built-in `generate` command to generate `users.yml`. Here is an example:
|
||||
|
||||
```sh
|
||||
docker run -it --rm 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 --user-roles shell > users.yml
|
||||
```
|
||||
|
||||
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.
|
||||
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. The `--user-roles` flag is a comma-separated list of roles.
|
||||
|
||||
## Forward Proxy
|
||||
|
||||
@@ -179,11 +209,21 @@ In this mode, Dozzle expects the following headers:
|
||||
- `Remote-Email` to map to the user's email address. This email is also used to find the right [Gravatar](https://gravatar.com/) for the user.
|
||||
- `Remote-Name` to be a display name like `John Doe`
|
||||
- `Remote-Filter` to be a comma-separated list of filters allowed for user.
|
||||
- `Remote-Roles` to be a comma-separated list of roles allowed for user.
|
||||
|
||||
Additionally, you can configure a logout URL with:
|
||||
|
||||
```yaml
|
||||
DOZZLE_AUTH_LOGOUT_URL: http://oauth2.example.ru/oauth2/sign_out
|
||||
```
|
||||
|
||||
### Setting up Dozzle with Authelia
|
||||
|
||||
[Authelia](https://www.authelia.com/) is an open-source authentication and authorization server and portal fulfilling the identity and access management. While setting up Authelia is out of scope for this section, the configuration can be shared as an example for setting up Dozzle with Authelia.
|
||||
|
||||
<details>
|
||||
<summary>➡️ Click to expand Authelia example</summary>
|
||||
|
||||
::: code-group
|
||||
|
||||
```yaml [docker-compose.yml]
|
||||
@@ -318,6 +358,8 @@ notifier:
|
||||
|
||||
Valid SSL keys are required because Authelia only supports SSL.
|
||||
|
||||
</details>
|
||||
|
||||
### Setting up Dozzle with Cloudflare Zero Trust
|
||||
|
||||
Cloudflare Zero Trust is a service for authenticated access to self-hosted software. This section defines how Dozzle can be set up to use Cloudflare Zero Trust for authentication.
|
||||
@@ -341,3 +383,86 @@ services:
|
||||
```
|
||||
|
||||
After running the Dozzle container, configure the Application in Cloudflare Zero Trust dashboard by following the [guide](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/self-hosted-apps/).
|
||||
|
||||
### Setting up Dozzle with Pocket ID
|
||||
|
||||
You must first setup a container to pass OpenID Connect authentication through your reverse proxy.
|
||||
|
||||
Below is an example using [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy).
|
||||
|
||||
<details>
|
||||
<summary>➡️ Click to expand oauth2-proxy example</summary>
|
||||
|
||||
1. Create a new OIDC client in Pocket ID for Dozzle:
|
||||
- **Name:** `Dozzle`
|
||||
- **Callback URLs:** `https://dozzle.example.com/oauth2/callback`
|
||||
- **PKCE:** `Enabled`
|
||||
|
||||
Copy the **Client ID** and **Client Secret** values for use later.
|
||||
|
||||
2. Add the following to your existing Dozzle compose:
|
||||
|
||||
```yml
|
||||
environment:
|
||||
DOZZLE_AUTH_PROVIDER: forward-proxy
|
||||
DOZZLE_AUTH_HEADER_USER: X-Forwarded-User
|
||||
DOZZLE_AUTH_HEADER_EMAIL: X-Forwarded-Email
|
||||
DOZZLE_AUTH_HEADER_NAME: X-Forwarded-Preferred-Username
|
||||
```
|
||||
|
||||
Comment out the Dozzle ports, as we will redirect these through the new authentication container.
|
||||
|
||||
This method should not require any changes to your reverse proxy configuration.
|
||||
|
||||
```yml
|
||||
# ports:
|
||||
# - 8080:8080
|
||||
```
|
||||
|
||||
3. Add a new oauth2-proxy container service to your existing Dozzle compose:
|
||||
|
||||
```yml
|
||||
services:
|
||||
# ...
|
||||
oauth2-proxy:
|
||||
image: quay.io/oauth2-proxy/oauth2-proxy:latest
|
||||
restart: unless-stopped
|
||||
container_name: dozzle-oidc
|
||||
command: --config /oauth2-proxy.cfg
|
||||
volumes:
|
||||
- "./oauth2-proxy.cfg:/oauth2-proxy.cfg"
|
||||
ports:
|
||||
- 8080:4180
|
||||
```
|
||||
|
||||
4. Create the oauth2-proxy config file.
|
||||
|
||||
In the directory beside your compose file, create `oauth2-proxy.cfg` :
|
||||
|
||||
```toml
|
||||
client_id = "xxx" # from Pocket ID
|
||||
client_secret = "xxx" # from Pocket ID
|
||||
cookie_secret = "xxx" # generate with openssl rand -base64 32 | tr -- '+/' '-_'
|
||||
upstreams = "http://dozzle:8080" # upstream to Dozzle containers internal port
|
||||
code_challenge_method = "S256" # PKCE challenges plain or S256
|
||||
cookie_expire = "0" # seconds, 0 for session
|
||||
cookie_name = "__Host-oauth2-proxy" # or __Secure-oauth2-proxy (less secure)
|
||||
cookie_secure = true # uses the secure HTTPS cookie
|
||||
email_domains = ["*"] # allows any email domain to authenticate
|
||||
http_address = "0.0.0.0:4180" # port oauth2-proxy listens on
|
||||
oidc_issuer_url = "https://id.example.com" # your Pocket base URL
|
||||
provider_display_name = "Pocket ID" # display name for OIDC login
|
||||
provider = "oidc" # use OpenID connect
|
||||
reverse_proxy = true # reverse proxy the traffic
|
||||
scope = "openid email profile groups" # passthru these OIDC scopes
|
||||
```
|
||||
|
||||
Fill in the variables per the comments.
|
||||
|
||||
5. Finally - restart your Docker compose stack.
|
||||
|
||||
Your reverse proxy should now authenticate you to Dozzle via oauth2-proxy.
|
||||
|
||||
Check logs for troubleshooting.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -12,7 +12,7 @@ By default, containers are grouped by their stack name in host mode. If `com.doc
|
||||
|
||||
## Custom Groups
|
||||
|
||||
Additionally, you can create custom groups by adding a label to your container. The label is `dev.dozzle.group` and the value is the name of the group. All containers with the same group name will be joined together in the UI. For example, if you have a group named `myapp`, all containers with the label `dozzle.group=myapp` will be joined together.
|
||||
Additionally, you can create custom groups by adding a label to your container. The label is `dev.dozzle.group` and the value is the name of the group. All containers with the same group name will be joined together in the UI. For example, if you have a group named `myapp`, all containers with the label `dev.dozzle.group=myapp` will be joined together.
|
||||
|
||||
Here is an example using Docker Compose or Docker CLI:
|
||||
|
||||
|
||||
+3
-3
@@ -104,7 +104,7 @@ To verify that the API is running, you can run the following command:
|
||||
kubectl top pod
|
||||
```
|
||||
|
||||
For now this is required to use Dozzle in Kubernetes.
|
||||
For now, this is required to use Dozzle in Kubernetes.
|
||||
|
||||
## Namespaces and Filters
|
||||
|
||||
@@ -140,11 +140,11 @@ spec:
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> Dozzle supports multiple namespaces, you can set the `DOZZLE_NAMESPACES` environment variable to a comma separated list of namespaces. When multiple namespaces are specified, Dozzle will monitor each namespace separately and combine the results.
|
||||
> Dozzle supports multiple namespaces, you can set the `DOZZLE_NAMESPACE` environment variable to a comma-separated list of namespaces. When multiple namespaces are specified, Dozzle will monitor each namespace separately and combine the results.
|
||||
|
||||
### Labels and Filters
|
||||
|
||||
`DOZZLE_FILTER` behave similarlty to Docker filters. You can limit the scope of Dozzle using the `DOZZLE_FILTER` environment variable. For example, to scope only to `env=prod`:
|
||||
`DOZZLE_FILTER` behave similarly to Docker filters. You can limit the scope of Dozzle using the `DOZZLE_FILTER` environment variable. For example, to scope only to `env=prod`:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
|
||||
@@ -8,7 +8,7 @@ Dozzle supports connecting to remote Docker hosts. This is useful when running D
|
||||
|
||||
However, with Dozzle agents, you can connect to remote hosts without exposing the Docker socket. See the [agent](/guide/agent) page for more information.
|
||||
|
||||
Dozzle agents remove the need to remotely expose the Docker socket but cannot be uses with a Docker Socket proxy inside the Dozzle agent stack. If you wish to use a Socket Proxy on it's own without an agent see the [connecting with a socket proxy](#connecting-with-a-socket-proxy) section.
|
||||
Dozzle agents remove the need to remotely expose the Docker socket but cannot be used with a Docker Socket proxy inside the Dozzle agent stack. If you wish to use a Socket Proxy on it's own without an agent see the [connecting with a socket proxy](#connecting-with-a-socket-proxy) section.
|
||||
|
||||
> [!WARNING]
|
||||
> Remote hosts have been replaced with agents. Agents provide a more secure way to connect to remote hosts. Although remote hosts are still supported, it is recommended to use agents. See the [agent](/guide/agent) page for more information and examples. For comparison, see the [comparing agents with remote connections](/guide/agent#comparing-agents-with-remote-connection) section. I won't be able to investigate user's issues with remote hosts as it is very time consuming.
|
||||
|
||||
@@ -6,25 +6,30 @@ title: Environment Variables and Subcommands
|
||||
|
||||
Configurations can be done with 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` |
|
||||
| `--enable-shell` | `DOZZLE_ENABLE_SHELL` | `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` | `""` |
|
||||
| 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` |
|
||||
| `--auth-header-filter` | `DOZZLE_AUTH_HEADER_FILTER` | `Remote-Filter` |
|
||||
| `--auth-header-roles` | `DOZZLE_AUTH_HEADER_ROLES` | `Remote-Roles` |
|
||||
| `--auth-logout-url` | `DOZZLE_AUTH_LOGOUT_URL` | `""` |
|
||||
| `--enable-actions` | `DOZZLE_ENABLE_ACTIONS` | `false` |
|
||||
| `--enable-shell` | `DOZZLE_ENABLE_SHELL` | `false` |
|
||||
| `--disable-avatars` | `DOZZLE_DISABLE_AVATARS` | `false` |
|
||||
| `--filter` | `DOZZLE_FILTER` | `""` |
|
||||
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | `false` |
|
||||
| `--mode` | `DOZZLE_MODE` | `server` |
|
||||
| `--release-check-mode` | `DOZZLE_RELEASE_CHECK_MODE` | `automatic` |
|
||||
| `--remote-host` | `DOZZLE_REMOTE_HOST` | |
|
||||
| `--remote-agent` | `DOZZLE_REMOTE_AGENT` | |
|
||||
| `--timeout` | `DOZZLE_TIMEOUT` | `10s` |
|
||||
| `--namespace` | `DOZZLE_NAMESPACE` | `""` |
|
||||
|
||||
> [!TIP]
|
||||
> Some flags like `--remote-host` or `--remote-agent` can be used multiple times. For example, `--remote-agent 167.99.1.1:7007 --remote-agent 167.99.1.2:7007` or comma-separated `DOZZLE_REMOTE_AGENT=167.99.1.1:7007,167.99.1.2:7007`.
|
||||
@@ -34,16 +39,18 @@ 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 -it --rm 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" --user-filter name=foo --user-roles shell > 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.
|
||||
|
||||
| Flag | Description | Default |
|
||||
| ------------ | ---------------- | ------- |
|
||||
| `--password` | User's password | |
|
||||
| `--email` | User's email | |
|
||||
| `--name` | User's full name | |
|
||||
| Flag | Description | Default |
|
||||
|-----------------|------------------| ------- |
|
||||
| `--password` | User's password | |
|
||||
| `--email` | User's email | |
|
||||
| `--name` | User's full name | |
|
||||
| `--user-filter` | User's filters | |
|
||||
| `--user-roles` | User's roles | |
|
||||
|
||||
See [authentication](/guide/authentication) for more information.
|
||||
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@
|
||||
"dozzle": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/playfair-display": "^5.2.6"
|
||||
"@fontsource-variable/playfair-display": "^5.2.8"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+5
-5
@@ -12,8 +12,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@fontsource-variable/playfair-display':
|
||||
specifier: ^5.2.6
|
||||
version: 5.2.6
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
devDependencies:
|
||||
dozzle:
|
||||
specifier: workspace:*
|
||||
@@ -21,9 +21,9 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@fontsource-variable/playfair-display@5.2.6':
|
||||
resolution: {integrity: sha512-Uedpo4WwsWtobi1KMYGm9tlKcZJcD18EiBtPWhbbccT2u58qM7vwkOZGdrmdWVDhcEhjwNrqRUyZijwIFtsCcQ==}
|
||||
'@fontsource-variable/playfair-display@5.2.8':
|
||||
resolution: {integrity: sha512-ZzVIXPOrL85yyOvZYoBzUszIJM+xKkHqni4IYn2CVLaGQQdJR8sBeC8yFNgjxSJ7ludTwta8qpULeOFuk5X75A==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@fontsource-variable/playfair-display@5.2.6': {}
|
||||
'@fontsource-variable/playfair-display@5.2.8': {}
|
||||
|
||||
@@ -4,7 +4,7 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/alexflint/go-arg v1.6.0
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/docker/docker v28.3.2+incompatible
|
||||
github.com/docker/docker v28.5.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
|
||||
@@ -14,34 +14,34 @@ 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.14.0
|
||||
github.com/spf13/afero v1.15.0
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/net v0.45.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.10.3
|
||||
github.com/andybalholm/brotli v1.2.0
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||
github.com/go-faker/faker/v4 v4.6.1
|
||||
github.com/go-faker/faker/v4 v4.7.0
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
github.com/puzpuzpuz/xsync/v4 v4.1.0
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/samber/lo v1.51.0
|
||||
github.com/samber/lo v1.52.0
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/yuin/goldmark v1.7.12
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/sync v0.16.0
|
||||
google.golang.org/grpc v1.73.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
k8s.io/api v0.33.2
|
||||
k8s.io/apimachinery v0.33.2
|
||||
k8s.io/client-go v0.33.2
|
||||
k8s.io/metrics v0.33.2
|
||||
github.com/yuin/goldmark v1.7.13
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/sync v0.17.0
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/protobuf v1.36.10
|
||||
k8s.io/api v0.34.1
|
||||
k8s.io/apimachinery v0.34.1
|
||||
k8s.io/client-go v0.34.1
|
||||
k8s.io/metrics v0.34.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -56,17 +56,17 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
@@ -85,7 +85,7 @@ require (
|
||||
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/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
@@ -95,25 +95,28 @@ require (
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/oauth2 v0.28.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/term v0.36.0 // indirect
|
||||
golang.org/x/text v0.30.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
go 1.24.5
|
||||
go 1.25.2
|
||||
|
||||
@@ -54,6 +54,14 @@ github.com/docker/docker v28.3.1+incompatible h1:20+BmuA9FXlCX4ByQ0vYJcUEnOmRM6X
|
||||
github.com/docker/docker v28.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA=
|
||||
github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.0+incompatible h1:ZdSQoRUE9XxhFI/B8YLvhnEFMmYN9Pp8Egd2qcaFk1E=
|
||||
github.com/docker/docker v28.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/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=
|
||||
@@ -62,23 +70,35 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
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/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
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-faker/faker/v4 v4.6.0 h1:6aOPzNptRiDwD14HuAnEtlTa+D1IfFuEHO8+vEFwjTs=
|
||||
github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0=
|
||||
github.com/go-faker/faker/v4 v4.6.1 h1:xUyVpAjEtB04l6XFY0V/29oR332rOSPWV4lU8RwDt4k=
|
||||
github.com/go-faker/faker/v4 v4.6.1/go.mod h1:arSdxNCSt7mOhdk8tEolvHeIJ7eX4OX80wXjKKvkKBY=
|
||||
github.com/go-faker/faker/v4 v4.6.2 h1:IR1uQUYotFZnuTL7Iuy0FDGtHM5Rt1Q+2nipH9gnqKs=
|
||||
github.com/go-faker/faker/v4 v4.6.2/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk=
|
||||
github.com/go-faker/faker/v4 v4.7.0 h1:VboC02cXHl/NuQh5lM2W8b87yp4iFXIu59x4w0RZi4E=
|
||||
github.com/go-faker/faker/v4 v4.7.0/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk=
|
||||
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=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
@@ -98,6 +118,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
|
||||
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@@ -167,6 +189,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
@@ -191,6 +215,8 @@ github.com/puzpuzpuz/xsync/v4 v4.0.0 h1:F1za+MBXzDQtQq+OVgFsojSX4w66rsNDmQNebPFA
|
||||
github.com/puzpuzpuz/xsync/v4 v4.0.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.1.0 h1:x9eHRl4QhZFIPJ17yl4KKW9xLyVWbb3/Yq4SXpjF71U=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.1.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
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.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
@@ -202,6 +228,8 @@ github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
||||
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
||||
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
||||
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
@@ -210,6 +238,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.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
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=
|
||||
@@ -223,6 +253,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
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=
|
||||
@@ -238,6 +272,8 @@ github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo=
|
||||
github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
|
||||
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
@@ -246,6 +282,10 @@ go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q=
|
||||
@@ -254,6 +294,10 @@ go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
@@ -262,8 +306,16 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -280,6 +332,12 @@ golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/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=
|
||||
@@ -304,12 +362,20 @@ 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/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
|
||||
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -327,6 +393,8 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -349,6 +417,12 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/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=
|
||||
@@ -364,6 +438,12 @@ golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/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=
|
||||
@@ -381,6 +461,12 @@ golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/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=
|
||||
@@ -404,6 +490,12 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
|
||||
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
|
||||
@@ -414,8 +506,24 @@ google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
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=
|
||||
@@ -440,6 +548,14 @@ k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw=
|
||||
k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
|
||||
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
|
||||
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
|
||||
k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
|
||||
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
|
||||
k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk=
|
||||
k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc=
|
||||
k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE=
|
||||
k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug=
|
||||
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
|
||||
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
|
||||
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
|
||||
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
|
||||
@@ -448,6 +564,14 @@ k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
|
||||
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
|
||||
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
|
||||
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s=
|
||||
k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0=
|
||||
k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
|
||||
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
|
||||
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
|
||||
k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=
|
||||
@@ -456,12 +580,22 @@ k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4=
|
||||
k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
|
||||
k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
|
||||
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
|
||||
k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
|
||||
k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
|
||||
k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw=
|
||||
k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY=
|
||||
k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo=
|
||||
k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY=
|
||||
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
|
||||
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
|
||||
k8s.io/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/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
|
||||
k8s.io/metrics v0.32.3 h1:2vsBvw0v8rIIlczZ/lZ8Kcqk9tR6Fks9h+dtFNbc2a4=
|
||||
k8s.io/metrics v0.32.3/go.mod h1:9R1Wk5cb+qJpCQon9h52mgkVCcFeYxcY+YkumfwHVCU=
|
||||
k8s.io/metrics v0.33.0 h1:sKe5sC9qb1RakMhs8LWYNuN2ne6OTCWexj8Jos3rO2Y=
|
||||
@@ -470,8 +604,18 @@ k8s.io/metrics v0.33.1 h1:Ypd5ITCf+fM+LDNFk7hESXTc3vh02CQYGiwRoVRaGsM=
|
||||
k8s.io/metrics v0.33.1/go.mod h1:wK8cFTK5ykBdhL0Wy4RZwLH28XM7j/Klc+NQrMRWVxg=
|
||||
k8s.io/metrics v0.33.2 h1:gNCBmtnUMDMCRg9Ly5ehxP3OdKISMsOnh1vzk01iCgE=
|
||||
k8s.io/metrics v0.33.2/go.mod h1:yxoAosKGRsZisv3BGekC5W6T1J8XSV+PoUEevACRv7c=
|
||||
k8s.io/metrics v0.33.3 h1:9CcqBz15JZfISqwca33gdHS8I6XfsK1vA8WUdEnG70g=
|
||||
k8s.io/metrics v0.33.3/go.mod h1:Aw+cdg4AYHw0HvUY+lCyq40FOO84awrqvJRTw0cmXDs=
|
||||
k8s.io/metrics v0.33.4 h1:eJ6UdTpKTUQVZbKpUdm5ve39aPpAvvNwLrs13oQcWKc=
|
||||
k8s.io/metrics v0.33.4/go.mod h1:NO/lgFtyIPTurz56debdSh5qRqRfpO8MlkMpau1Ue8U=
|
||||
k8s.io/metrics v0.34.0 h1:nYSfG2+tnL6/MRC2I+sGHjtNEGoEoM/KktgGOoQFwws=
|
||||
k8s.io/metrics v0.34.0/go.mod h1:KCuXmotE0v4AvoARKUP8NC4lUnbK/Du1mluGdor5h4M=
|
||||
k8s.io/metrics v0.34.1 h1:374Rexmp1xxgRt64Bi0TsjAM8cA/Y8skwCoPdjtIslE=
|
||||
k8s.io/metrics v0.34.1/go.mod h1:Drf5kPfk2NJrlpcNdSiAAHn/7Y9KqxpRNagByM7Ei80=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
@@ -481,5 +625,9 @@ sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
@@ -45,7 +46,10 @@ func NewClient(endpoint string, certificates tls.Certificate, opts ...grpc.DialO
|
||||
// Create the gRPC transport credentials
|
||||
creds := credentials.NewTLS(tlsConfig)
|
||||
|
||||
opts = append(opts, grpc.WithTransportCredentials(creds))
|
||||
opts = append(opts,
|
||||
grpc.WithTransportCredentials(creds),
|
||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(10*1024*1024), grpc.UseCompressor(gzip.Name)),
|
||||
)
|
||||
conn, err := grpc.NewClient(endpoint, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to %s: %w", endpoint, err)
|
||||
@@ -88,7 +92,7 @@ func rpcErrToErr(err error) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since time.Time, until time.Time, std container.StdType) (<-chan *container.LogEvent, error) {
|
||||
func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since time.Time, until time.Time, std container.StdType) (<-chan container.LogEvent, error) {
|
||||
stream, err := c.client.LogsBetweenDates(ctx, &pb.LogsBetweenDatesRequest{
|
||||
ContainerId: containerID,
|
||||
Since: timestamppb.New(since),
|
||||
@@ -100,7 +104,7 @@ func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make(chan *container.LogEvent)
|
||||
events := make(chan container.LogEvent)
|
||||
|
||||
go func() {
|
||||
sendLogs(stream, events)
|
||||
@@ -110,7 +114,7 @@ func (c *Client) LogsBetweenDates(ctx context.Context, containerID string, since
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, since time.Time, std container.StdType, events chan<- *container.LogEvent) error {
|
||||
func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, since time.Time, std container.StdType, events chan<- container.LogEvent) error {
|
||||
stream, err := c.client.StreamLogs(ctx, &pb.StreamLogsRequest{
|
||||
ContainerId: containerID,
|
||||
Since: timestamppb.New(since),
|
||||
@@ -124,7 +128,7 @@ func (c *Client) StreamContainerLogs(ctx context.Context, containerID string, si
|
||||
return sendLogs(stream, events)
|
||||
}
|
||||
|
||||
func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *container.LogEvent) error {
|
||||
func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- container.LogEvent) error {
|
||||
for {
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
@@ -150,7 +154,7 @@ func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *container.
|
||||
continue
|
||||
}
|
||||
|
||||
events <- &container.LogEvent{
|
||||
events <- container.LogEvent{
|
||||
Id: resp.Event.Id,
|
||||
ContainerID: resp.Event.ContainerId,
|
||||
Message: message,
|
||||
|
||||
@@ -65,7 +65,7 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
|
||||
|
||||
for event := range g.Events {
|
||||
out.Send(&pb.StreamLogsResponse{
|
||||
Event: logEventToPb(event),
|
||||
Event: logEventToPb(&event),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentSe
|
||||
select {
|
||||
case event := <-g.Events:
|
||||
out.Send(&pb.StreamLogsResponse{
|
||||
Event: logEventToPb(event),
|
||||
Event: logEventToPb(&event),
|
||||
})
|
||||
case e := <-g.Errors:
|
||||
return e
|
||||
@@ -290,10 +290,8 @@ func (s *server) ContainerExec(stream pb.AgentService_ContainerExecServer) error
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
defer cancel()
|
||||
defer containerWriter.Close()
|
||||
for {
|
||||
@@ -306,10 +304,9 @@ func (s *server) ContainerExec(stream pb.AgentService_ContainerExecServer) error
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
defer cancel()
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
@@ -322,7 +319,7 @@ func (s *server) ContainerExec(stream pb.AgentService_ContainerExecServer) error
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ type proxyAuthContext struct {
|
||||
headerEmail string
|
||||
headerName string
|
||||
headerFilter string
|
||||
headerRoles string
|
||||
}
|
||||
|
||||
func hashEmail(email string) string {
|
||||
@@ -30,12 +31,13 @@ func hashEmail(email string) string {
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func NewForwardProxyAuth(userHeader, emailHeader, nameHeader, filterHeader string) *proxyAuthContext {
|
||||
func NewForwardProxyAuth(userHeader, emailHeader, nameHeader, filterHeader, rolesHeader string) *proxyAuthContext {
|
||||
return &proxyAuthContext{
|
||||
headerUser: userHeader,
|
||||
headerEmail: emailHeader,
|
||||
headerName: nameHeader,
|
||||
headerFilter: filterHeader,
|
||||
headerRoles: rolesHeader,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +48,11 @@ func (p *proxyAuthContext) AuthMiddleware(next http.Handler) http.Handler {
|
||||
if err != nil {
|
||||
log.Fatal().Str("filter", r.Header.Get(p.headerFilter)).Msg("Failed to parse container filter")
|
||||
}
|
||||
user := newUser(r.Header.Get(p.headerUser), r.Header.Get(p.headerEmail), r.Header.Get(p.headerName), containerFilter)
|
||||
userRoles := All
|
||||
if strings.TrimSpace(r.Header.Get(p.headerRoles)) != "" {
|
||||
userRoles = ParseRole(r.Header.Get(p.headerRoles))
|
||||
}
|
||||
user := newUser(r.Header.Get(p.headerUser), r.Header.Get(p.headerEmail), r.Header.Get(p.headerName), containerFilter, userRoles)
|
||||
ctx := context.WithValue(r.Context(), remoteUser, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Role int
|
||||
|
||||
const (
|
||||
None Role = 0
|
||||
Shell Role = 1 << iota
|
||||
Actions
|
||||
Download
|
||||
)
|
||||
|
||||
const All = Shell | Actions | Download
|
||||
|
||||
// ParseRole parses a comma-separated string of roles and returns the corresponding Role.
|
||||
func ParseRole(input string) Role {
|
||||
var roles Role
|
||||
var parts []string
|
||||
|
||||
// Check if input is valid JSON
|
||||
trimmed := strings.TrimSpace(input)
|
||||
if json.Valid([]byte(trimmed)) {
|
||||
var jsonRoles []string
|
||||
if err := json.Unmarshal([]byte(trimmed), &jsonRoles); err == nil {
|
||||
parts = jsonRoles
|
||||
} else {
|
||||
log.Warn().Str("input", input).Msg("failed to parse JSON roles")
|
||||
return None
|
||||
}
|
||||
} else {
|
||||
// Split by both commas and pipes
|
||||
parts = strings.FieldsFunc(input, func(c rune) bool {
|
||||
return c == ',' || c == '|'
|
||||
})
|
||||
}
|
||||
|
||||
for _, r := range parts {
|
||||
role := strings.TrimSpace(strings.ToLower(r))
|
||||
switch role {
|
||||
case "shell", "dozzle_shell":
|
||||
roles |= Shell
|
||||
case "actions", "dozzle_actions":
|
||||
roles |= Actions
|
||||
case "download", "dozzle_download":
|
||||
roles |= Download
|
||||
case "none", "dozzle_none":
|
||||
return None
|
||||
case "all", "dozzle_all":
|
||||
return All
|
||||
default:
|
||||
log.Debug().Str("role", role).Msg("invalid role")
|
||||
}
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
func (roles Role) Has(role Role) bool {
|
||||
return roles&role != 0
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseRole(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input string
|
||||
expected Role
|
||||
}{
|
||||
// Single role tests
|
||||
{"Single shell role", "shell", Shell},
|
||||
{"Single actions role", "actions", Actions},
|
||||
{"Single download role", "download", Download},
|
||||
{"None role", "none", None},
|
||||
{"All role", "all", All},
|
||||
|
||||
// Single dozzle_ prefixed role tests
|
||||
{"Single dozzle_shell role", "dozzle_shell", Shell},
|
||||
{"Single dozzle_actions role", "dozzle_actions", Actions},
|
||||
{"Single dozzle_download role", "dozzle_download", Download},
|
||||
{"Dozzle_none role", "dozzle_none", None},
|
||||
{"Dozzle_all role", "dozzle_all", All},
|
||||
|
||||
// Case insensitive tests
|
||||
{"Shell uppercase", "SHELL", Shell},
|
||||
{"Actions mixed case", "AcTiOnS", Actions},
|
||||
{"Download with spaces", " download ", Download},
|
||||
{"Dozzle_shell uppercase", "DOZZLE_SHELL", Shell},
|
||||
{"Dozzle_actions mixed case", "DoZzLe_AcTiOnS", Actions},
|
||||
{"Dozzle_download with spaces", " dozzle_download ", Download},
|
||||
|
||||
// Multiple roles with comma separator
|
||||
{"Shell and actions", "shell,actions", Shell | Actions},
|
||||
{"All three roles", "shell,actions,download", Shell | Actions | Download},
|
||||
{"Roles with spaces", "shell , actions , download", Shell | Actions | Download},
|
||||
{"Dozzle roles with comma", "dozzle_shell,dozzle_actions", Shell | Actions},
|
||||
{"Mixed dozzle and regular", "shell,dozzle_actions,download", Shell | Actions | Download},
|
||||
|
||||
// Multiple roles with pipe separator
|
||||
{"Shell and actions with pipe", "shell|actions", Shell | Actions},
|
||||
{"All three with pipe", "shell|actions|download", Shell | Actions | Download},
|
||||
{"Mixed separators", "shell,actions|download", Shell | Actions | Download},
|
||||
{"Dozzle roles with pipe", "dozzle_shell|dozzle_actions", Shell | Actions},
|
||||
|
||||
// JSON format tests
|
||||
{"JSON single role", `["shell"]`, Shell},
|
||||
{"JSON multiple roles", `["shell", "actions"]`, Shell | Actions},
|
||||
{"JSON all roles", `["shell", "actions", "download"]`, Shell | Actions | Download},
|
||||
{"JSON with spaces", ` ["shell", "actions"] `, Shell | Actions},
|
||||
{"JSON single dozzle role", `["dozzle_shell"]`, Shell},
|
||||
{"JSON multiple dozzle roles", `["dozzle_shell", "dozzle_actions"]`, Shell | Actions},
|
||||
{"JSON mixed dozzle and regular", `["shell", "dozzle_actions"]`, Shell | Actions},
|
||||
|
||||
// Edge cases
|
||||
{"Empty string", "", None},
|
||||
{"Whitespace only", " ", None},
|
||||
{"Invalid role", "invalid", None},
|
||||
{"Mixed valid and invalid", "shell,invalid,actions", Shell | Actions},
|
||||
{"None overrides others", "shell,none,actions", None},
|
||||
{"All overrides others", "shell,all,actions", All},
|
||||
{"Dozzle_none overrides others", "dozzle_shell,dozzle_none,dozzle_actions", None},
|
||||
{"Dozzle_all overrides others", "dozzle_shell,dozzle_all,dozzle_actions", All},
|
||||
|
||||
// Invalid JSON
|
||||
{"Invalid JSON format", `["shell"`, None},
|
||||
{"Malformed JSON", `{shell: "test"}`, None},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := ParseRole(tc.input)
|
||||
if result != tc.expected {
|
||||
t.Errorf("ParseRole(%q) = %d, expected %d", tc.input, int(result), int(tc.expected))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ func NewSimpleAuth(userDatabase UserDatabase, ttl time.Duration) *simpleAuthCont
|
||||
h := sha256.New()
|
||||
for _, user := range userDatabase.Users {
|
||||
h.Write([]byte(user.Password))
|
||||
h.Write([]byte(user.RolesConfigured))
|
||||
}
|
||||
|
||||
tokenAuth := jwtauth.New("HS256", h.Sum(nil), nil)
|
||||
@@ -38,7 +39,7 @@ func (a *simpleAuthContext) CreateToken(username, password string) (string, erro
|
||||
return "", ErrInvalidCredentials
|
||||
}
|
||||
|
||||
claims := map[string]interface{}{"username": user.Username, "email": user.Email, "name": user.Name, "filter": user.Filter}
|
||||
claims := map[string]interface{}{"username": user.Username, "email": user.Email, "name": user.Name, "filter": user.Filter, "roles": user.Roles}
|
||||
jwtauth.SetIssuedNow(claims)
|
||||
|
||||
if a.ttl > 0 {
|
||||
|
||||
+19
-2
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
@@ -24,7 +25,9 @@ type User struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Password string `json:"-" yaml:"password"`
|
||||
Filter string `json:"-" yaml:"filter"`
|
||||
RolesConfigured string `json:"-" yaml:"roles"`
|
||||
ContainerLabels container.ContainerLabels `json:"-" yaml:"-"`
|
||||
Roles Role `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
func (u User) AvatarURL() string {
|
||||
@@ -35,12 +38,13 @@ func (u User) AvatarURL() string {
|
||||
return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name))
|
||||
}
|
||||
|
||||
func newUser(username, email, name string, labels container.ContainerLabels) User {
|
||||
func newUser(username, email, name string, labels container.ContainerLabels, roles Role) User {
|
||||
return User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Name: name,
|
||||
ContainerLabels: labels,
|
||||
Roles: roles,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +113,12 @@ func decodeUsersFromFile(path string) (UserDatabase, error) {
|
||||
if user.Name == "" {
|
||||
user.Name = username
|
||||
}
|
||||
|
||||
if strings.TrimSpace(user.RolesConfigured) == "" {
|
||||
user.RolesConfigured = "all"
|
||||
}
|
||||
|
||||
user.Roles = ParseRole(user.RolesConfigured)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
@@ -198,14 +208,21 @@ func UserFromContext(ctx context.Context) *User {
|
||||
email := claims["email"].(string)
|
||||
name := claims["name"].(string)
|
||||
containerFilter := container.ContainerLabels{}
|
||||
|
||||
if filter, ok := claims["filter"].(string); ok {
|
||||
containerFilter, err = container.ParseContainerFilter(filter)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Str("filter", filter).Msg("Failed to parse container filter")
|
||||
}
|
||||
}
|
||||
roles := None
|
||||
if r, ok := claims["roles"].(float64); ok {
|
||||
roles = Role(r)
|
||||
} else {
|
||||
log.Warn().Interface("roles", claims["roles"]).Msg("Failed to parse roles from JWT claims")
|
||||
}
|
||||
|
||||
user := newUser(username, email, name, containerFilter)
|
||||
user := newUser(username, email, name, containerFilter, roles)
|
||||
return &user
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -17,11 +17,11 @@ import (
|
||||
)
|
||||
|
||||
type EventGenerator struct {
|
||||
Events chan *LogEvent
|
||||
Events chan LogEvent
|
||||
Errors chan error
|
||||
reader LogReader
|
||||
next *LogEvent
|
||||
buffer chan *LogEvent
|
||||
buffer chan LogEvent
|
||||
wg sync.WaitGroup
|
||||
containerID string
|
||||
ctx context.Context
|
||||
@@ -36,9 +36,9 @@ type LogReader interface {
|
||||
func NewEventGenerator(ctx context.Context, reader LogReader, container Container) *EventGenerator {
|
||||
generator := &EventGenerator{
|
||||
reader: reader,
|
||||
buffer: make(chan *LogEvent, 100),
|
||||
buffer: make(chan LogEvent, 100),
|
||||
Errors: make(chan error, 1),
|
||||
Events: make(chan *LogEvent),
|
||||
Events: make(chan LogEvent),
|
||||
containerID: container.ID,
|
||||
ctx: ctx,
|
||||
}
|
||||
@@ -49,24 +49,29 @@ func NewEventGenerator(ctx context.Context, reader LogReader, container Containe
|
||||
}
|
||||
|
||||
func (g *EventGenerator) processBuffer() {
|
||||
var current, next *LogEvent
|
||||
var current, next LogEvent
|
||||
var hasNext bool
|
||||
|
||||
loop:
|
||||
for {
|
||||
if g.next != nil {
|
||||
current = g.next
|
||||
current = *g.next
|
||||
g.next = nil
|
||||
next = g.peek()
|
||||
next, hasNext = g.peek()
|
||||
} else {
|
||||
event, ok := <-g.buffer
|
||||
if !ok {
|
||||
break loop
|
||||
}
|
||||
current = event
|
||||
next = g.peek()
|
||||
next, hasNext = g.peek()
|
||||
}
|
||||
|
||||
checkPosition(current, next)
|
||||
if hasNext {
|
||||
checkPosition(¤t, &next)
|
||||
} else {
|
||||
checkPosition(¤t, nil)
|
||||
}
|
||||
|
||||
select {
|
||||
case g.Events <- current:
|
||||
@@ -101,23 +106,23 @@ func (g *EventGenerator) consumeReader() {
|
||||
g.wg.Done()
|
||||
}
|
||||
|
||||
func (g *EventGenerator) peek() *LogEvent {
|
||||
func (g *EventGenerator) peek() (LogEvent, bool) {
|
||||
if g.next != nil {
|
||||
return g.next
|
||||
return *g.next, true
|
||||
}
|
||||
select {
|
||||
case event := <-g.buffer:
|
||||
g.next = event
|
||||
return g.next
|
||||
g.next = &event
|
||||
return event, true
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
return nil
|
||||
return LogEvent{}, false
|
||||
}
|
||||
}
|
||||
|
||||
func createEvent(message string, streamType StdType) *LogEvent {
|
||||
func createEvent(message string, streamType StdType) LogEvent {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(message))
|
||||
logEvent := &LogEvent{Id: h.Sum32(), Message: message, Stream: streamType.String()}
|
||||
logEvent := LogEvent{Id: h.Sum32(), Message: message, Stream: streamType.String()}
|
||||
if index := strings.IndexAny(message, " "); index != -1 {
|
||||
logId := message[:index]
|
||||
if timestamp, err := time.Parse(time.RFC3339Nano, logId); err == nil {
|
||||
@@ -157,20 +162,20 @@ func createEvent(message string, streamType StdType) *LogEvent {
|
||||
}
|
||||
|
||||
func checkPosition(currentEvent *LogEvent, nextEvent *LogEvent) {
|
||||
currentLevel := guessLogLevel(currentEvent)
|
||||
currentLevel := guessLogLevel(*currentEvent)
|
||||
if nextEvent != nil {
|
||||
if currentEvent.IsCloseToTime(nextEvent) && currentLevel != "unknown" && !nextEvent.HasLevel() {
|
||||
if currentEvent.IsCloseToTime(*nextEvent) && currentLevel != "unknown" && !nextEvent.HasLevel() {
|
||||
currentEvent.Position = Beginning
|
||||
nextEvent.Position = Middle
|
||||
}
|
||||
|
||||
// If next item is not close to current item or has level, set current item position to end
|
||||
if currentEvent.Position == Middle && (nextEvent.HasLevel() || !currentEvent.IsCloseToTime(nextEvent)) {
|
||||
if currentEvent.Position == Middle && (nextEvent.HasLevel() || !currentEvent.IsCloseToTime(*nextEvent)) {
|
||||
currentEvent.Position = End
|
||||
}
|
||||
|
||||
// If next item is close to current item and has no level, set next item position to middle
|
||||
if currentEvent.Position == Middle && !nextEvent.HasLevel() && currentEvent.IsCloseToTime(nextEvent) {
|
||||
if currentEvent.Position == Middle && !nextEvent.HasLevel() && currentEvent.IsCloseToTime(*nextEvent) {
|
||||
nextEvent.Position = Middle
|
||||
}
|
||||
// Set next item level to current item level
|
||||
|
||||
@@ -96,14 +96,14 @@ func Test_createEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *LogEvent
|
||||
want LogEvent
|
||||
}{
|
||||
{
|
||||
name: "empty message",
|
||||
args: args{
|
||||
message: "",
|
||||
},
|
||||
want: &LogEvent{
|
||||
want: LogEvent{
|
||||
Message: "",
|
||||
},
|
||||
}, {
|
||||
@@ -111,7 +111,7 @@ func Test_createEvent(t *testing.T) {
|
||||
args: args{
|
||||
message: "2020-05-13T18:55:37.772853839Z {\"xyz\": \"value\", \"abc\": \"value2\"}",
|
||||
},
|
||||
want: &LogEvent{
|
||||
want: LogEvent{
|
||||
Message: data,
|
||||
},
|
||||
},
|
||||
@@ -120,7 +120,7 @@ func Test_createEvent(t *testing.T) {
|
||||
args: args{
|
||||
message: "2020-05-13T18:55:37.772853839Z {\"key\"}",
|
||||
},
|
||||
want: &LogEvent{
|
||||
want: LogEvent{
|
||||
Message: "{\"key\"}",
|
||||
},
|
||||
},
|
||||
@@ -129,7 +129,7 @@ func Test_createEvent(t *testing.T) {
|
||||
args: args{
|
||||
message: "2020-05-13T18:55:37.772853839Z 123",
|
||||
},
|
||||
want: &LogEvent{
|
||||
want: LogEvent{
|
||||
Message: "123",
|
||||
},
|
||||
},
|
||||
@@ -138,7 +138,7 @@ func Test_createEvent(t *testing.T) {
|
||||
args: args{
|
||||
message: "2020-05-13T18:55:37.772853839Z sample text with=equal sign",
|
||||
},
|
||||
want: &LogEvent{
|
||||
want: LogEvent{
|
||||
Message: "sample text with=equal sign",
|
||||
},
|
||||
},
|
||||
@@ -147,7 +147,7 @@ func Test_createEvent(t *testing.T) {
|
||||
args: args{
|
||||
message: "2020-05-13T18:55:37.772853839Z null",
|
||||
},
|
||||
want: &LogEvent{
|
||||
want: LogEvent{
|
||||
Message: "",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -41,7 +41,7 @@ func init() {
|
||||
SupportedLogLevels["unknown"] = struct{}{}
|
||||
}
|
||||
|
||||
func guessLogLevel(logEvent *LogEvent) string {
|
||||
func guessLogLevel(logEvent LogEvent) string {
|
||||
switch value := logEvent.Message.(type) {
|
||||
case string:
|
||||
value = stripANSI(value)
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestGuessLogLevel(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
name, _ := json.Marshal(test.input)
|
||||
t.Run(string(name), func(t *testing.T) {
|
||||
actual := guessLogLevel(&LogEvent{Message: test.input})
|
||||
actual := guessLogLevel(LogEvent{Message: test.input})
|
||||
if actual != test.expected {
|
||||
t.Errorf("Expected %s, got %s", test.expected, actual)
|
||||
}
|
||||
|
||||
@@ -176,14 +176,14 @@ type LogEvent struct {
|
||||
ContainerID string `json:"c,omitempty"`
|
||||
}
|
||||
|
||||
func (l *LogEvent) HasLevel() bool {
|
||||
func (l LogEvent) HasLevel() bool {
|
||||
return l.Level != "unknown"
|
||||
}
|
||||
|
||||
func (l *LogEvent) IsCloseToTime(other *LogEvent) bool {
|
||||
func (l LogEvent) IsCloseToTime(other LogEvent) bool {
|
||||
return math.Abs(float64(l.Timestamp-other.Timestamp)) < 10
|
||||
}
|
||||
|
||||
func (l *LogEvent) MessageId() int64 {
|
||||
func (l LogEvent) MessageId() int64 {
|
||||
return l.Timestamp
|
||||
}
|
||||
|
||||
@@ -238,12 +238,10 @@ func (k *K8sClient) ContainerEvents(ctx context.Context, ch chan<- container.Con
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(watchers))
|
||||
|
||||
for _, watcher := range watchers {
|
||||
go func(w watch.Interface) {
|
||||
defer wg.Done()
|
||||
for event := range w.ResultChan() {
|
||||
wg.Go(func() {
|
||||
for event := range watcher.ResultChan() {
|
||||
log.Debug().Interface("event.type", event.Type).Msg("Received kubernetes event")
|
||||
pod, ok := event.Object.(*corev1.Pod)
|
||||
if !ok {
|
||||
@@ -270,7 +268,7 @@ func (k *K8sClient) ContainerEvents(ctx context.Context, ch chan<- container.Con
|
||||
}
|
||||
}
|
||||
}
|
||||
}(watcher)
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@@ -75,10 +75,10 @@ func UpdateFromReader(user auth.User, reader io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return Save(user, existingProfile)
|
||||
return save(user, existingProfile)
|
||||
}
|
||||
|
||||
func Save(user auth.User, profile Profile) error {
|
||||
func save(user auth.User, profile Profile) error {
|
||||
path := filepath.Join(dataPath, user.Username)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err := os.Mkdir(path, 0755); err != nil {
|
||||
|
||||
@@ -21,10 +21,14 @@ type Args struct {
|
||||
AuthHeaderEmail string `arg:"--auth-header-email,env:DOZZLE_AUTH_HEADER_EMAIL" default:"Remote-Email" help:"sets the HTTP Header to use for email in Forward Proxy configuration."`
|
||||
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."`
|
||||
AuthHeaderRoles string `arg:"--auth-header-roles,env:DOZZLE_AUTH_HEADER_ROLES" default:"Remote-Roles" help:"sets the HTTP Header to use for roles in Forward Proxy configuration."`
|
||||
AuthLogoutUrl string `arg:"--auth-logout-url,env:DOZZLE_AUTH_LOGOUT_URL" help:"sets the Logout URL used with Forward Proxy."`
|
||||
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."`
|
||||
DisableAvatars bool `arg:"--disable-avatars,env:DOZZLE_DISABLE_AVATARS" default:"false" help:"disables avatars for authenticated users."`
|
||||
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
|
||||
Filter map[string][]string `arg:"-"`
|
||||
ReleaseCheckMode string `arg:"--release-check-mode,env:DOZZLE_RELEASE_CHECK_MODE" default:"automatic" help:"sets the release check mode. When manual, releases will not be automatically fetched."`
|
||||
RemoteHost []string `arg:"env:DOZZLE_REMOTE_HOST,--remote-host,separate" help:"list of hosts to connect remotely"`
|
||||
RemoteAgent []string `arg:"env:DOZZLE_REMOTE_AGENT,--remote-agent,separate" help:"list of agents to connect remotely"`
|
||||
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
|
||||
|
||||
@@ -11,11 +11,12 @@ import (
|
||||
)
|
||||
|
||||
type GenerateCmd struct {
|
||||
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."`
|
||||
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."`
|
||||
RolesConfigured string `arg:"--user-roles" help:"sets the roles for the user. This can be a comma separated list of roles."`
|
||||
}
|
||||
|
||||
func (g *GenerateCmd) Run(args Args, embeddedCerts embed.FS) error {
|
||||
@@ -27,11 +28,12 @@ func (g *GenerateCmd) Run(args Args, embeddedCerts embed.FS) error {
|
||||
}
|
||||
|
||||
buffer := auth.GenerateUsers(auth.User{
|
||||
Username: args.Generate.Username,
|
||||
Password: args.Generate.Password,
|
||||
Name: args.Generate.Name,
|
||||
Email: args.Generate.Email,
|
||||
Filter: args.Generate.Filter,
|
||||
Username: args.Generate.Username,
|
||||
Password: args.Generate.Password,
|
||||
Name: args.Generate.Name,
|
||||
Email: args.Generate.Email,
|
||||
Filter: args.Generate.Filter,
|
||||
RolesConfigured: args.Generate.RolesConfigured,
|
||||
}, true)
|
||||
|
||||
if _, err := os.Stdout.Write(buffer.Bytes()); err != nil {
|
||||
|
||||
@@ -32,11 +32,11 @@ func (a *agentService) RawLogs(ctx context.Context, container container.Containe
|
||||
return a.client.StreamRawBytes(ctx, container.ID, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (a *agentService) LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
|
||||
func (a *agentService) LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan container.LogEvent, error) {
|
||||
return a.client.LogsBetweenDates(ctx, container.ID, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (a *agentService) StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
|
||||
func (a *agentService) StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- container.LogEvent) error {
|
||||
return a.client.StreamContainerLogs(ctx, container.ID, from, stdTypes, events)
|
||||
}
|
||||
|
||||
@@ -87,24 +87,21 @@ func (a *agentService) Exec(ctx context.Context, container container.Container,
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
if _, err := io.Copy(containerWriter, stdin); err != nil {
|
||||
log.Error().Err(err).Msg("error while reading from ws using agent")
|
||||
}
|
||||
cancel()
|
||||
containerWriter.Close()
|
||||
}()
|
||||
})
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
if _, err := stdcopy.StdCopy(stdout, stdout, containerReader); err != nil {
|
||||
log.Error().Err(err).Msg("error while writing to ws using agent")
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ type ClientService interface {
|
||||
ListContainers(ctx context.Context, filter container.ContainerLabels) ([]container.Container, error)
|
||||
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)
|
||||
LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan container.LogEvent, error)
|
||||
RawLogs(context.Context, container.Container, time.Time, time.Time, container.StdType) (io.ReadCloser, error)
|
||||
|
||||
// Subscriptions
|
||||
@@ -24,7 +24,7 @@ type ClientService interface {
|
||||
SubscribeContainersStarted(context.Context, chan<- container.Container)
|
||||
|
||||
// Blocking streaming functions that should be used in a goroutine
|
||||
StreamLogs(context.Context, container.Container, time.Time, container.StdType, 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
|
||||
|
||||
@@ -24,11 +24,11 @@ func (c *ContainerService) RawLogs(ctx context.Context, from time.Time, to time.
|
||||
return c.clientService.RawLogs(ctx, c.Container, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (c *ContainerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
|
||||
func (c *ContainerService) LogsBetweenDates(ctx context.Context, from time.Time, to time.Time, stdTypes container.StdType) (<-chan container.LogEvent, error) {
|
||||
return c.clientService.LogsBetweenDates(ctx, c.Container, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (c *ContainerService) StreamLogs(ctx context.Context, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
|
||||
func (c *ContainerService) StreamLogs(ctx context.Context, from time.Time, stdTypes container.StdType, events chan<- container.LogEvent) error {
|
||||
return c.clientService.StreamLogs(ctx, c.Container, from, stdTypes, events)
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ func (d *DockerClientService) RawLogs(ctx context.Context, container container.C
|
||||
|
||||
}
|
||||
|
||||
func (d *DockerClientService) LogsBetweenDates(ctx context.Context, c container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
|
||||
func (d *DockerClientService) LogsBetweenDates(ctx context.Context, c container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan container.LogEvent, error) {
|
||||
reader, err := d.client.ContainerLogsBetweenDates(ctx, c.ID, from, to, stdTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -63,7 +63,7 @@ func (d *DockerClientService) LogsBetweenDates(ctx context.Context, c container.
|
||||
return g.Events, nil
|
||||
}
|
||||
|
||||
func (d *DockerClientService) StreamLogs(ctx context.Context, c container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
|
||||
func (d *DockerClientService) StreamLogs(ctx context.Context, c container.Container, from time.Time, stdTypes container.StdType, events chan<- container.LogEvent) error {
|
||||
reader, err := d.client.ContainerLogs(ctx, c.ID, from, stdTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -120,19 +120,16 @@ func (d *DockerClientService) Attach(ctx context.Context, container container.Co
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
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()
|
||||
wg.Go(func() {
|
||||
if container.Tty {
|
||||
if _, err := io.Copy(stdout, containerReader); err != nil {
|
||||
log.Error().Err(err).Msg("error while writing to ws")
|
||||
@@ -143,7 +140,7 @@ func (d *DockerClientService) Attach(ctx context.Context, container container.Co
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@@ -159,24 +156,20 @@ func (d *DockerClientService) Exec(ctx context.Context, container container.Cont
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
wg.Go(func() {
|
||||
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()
|
||||
wg.Go(func() {
|
||||
if _, err := stdcopy.StdCopy(stdout, stdout, containerReader); err != nil {
|
||||
log.Error().Err(err).Msg("error while writing to ws")
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
lop "github.com/samber/lo/parallel"
|
||||
)
|
||||
|
||||
type HostUnavailableError struct {
|
||||
@@ -69,22 +70,35 @@ func (m *MultiHostService) ListContainersForHost(host string, labels container.C
|
||||
}
|
||||
|
||||
func (m *MultiHostService) ListAllContainers(labels container.ContainerLabels) ([]container.Container, []error) {
|
||||
containers := make([]container.Container, 0)
|
||||
clients, errors := m.manager.RetryAndList()
|
||||
|
||||
for _, client := range clients {
|
||||
type result struct {
|
||||
containers []container.Container
|
||||
err error
|
||||
}
|
||||
|
||||
results := lop.Map(clients, func(client container_support.ClientService, _ int) result {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.timeout)
|
||||
defer cancel()
|
||||
|
||||
list, err := client.ListContainers(ctx, labels)
|
||||
if err != nil {
|
||||
host, _ := client.Host(ctx)
|
||||
log.Debug().Err(err).Str("host", host.Name).Msg("error listing containers")
|
||||
host.Available = false
|
||||
errors = append(errors, &HostUnavailableError{Host: host, Err: err})
|
||||
continue
|
||||
return result{nil, &HostUnavailableError{Host: host, Err: err}}
|
||||
}
|
||||
|
||||
containers = append(containers, list...)
|
||||
return result{list, nil}
|
||||
})
|
||||
|
||||
containers := make([]container.Container, 0)
|
||||
for _, r := range results {
|
||||
if r.err != nil {
|
||||
errors = append(errors, r.err)
|
||||
} else {
|
||||
containers = append(containers, r.containers...)
|
||||
}
|
||||
}
|
||||
|
||||
return containers, errors
|
||||
|
||||
@@ -45,7 +45,7 @@ func (k *K8sClientService) ContainerAction(ctx context.Context, container contai
|
||||
return k.client.ContainerActions(ctx, action, container.ID)
|
||||
}
|
||||
|
||||
func (k *K8sClientService) LogsBetweenDates(ctx context.Context, c container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error) {
|
||||
func (k *K8sClientService) LogsBetweenDates(ctx context.Context, c container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan container.LogEvent, error) {
|
||||
reader, err := k.client.ContainerLogsBetweenDates(ctx, c.ID, from, to, stdTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -60,7 +60,7 @@ func (k *K8sClientService) RawLogs(ctx context.Context, container container.Cont
|
||||
return k.client.ContainerLogsBetweenDates(ctx, container.ID, from, to, stdTypes)
|
||||
}
|
||||
|
||||
func (k *K8sClientService) StreamLogs(ctx context.Context, c container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error {
|
||||
func (k *K8sClientService) StreamLogs(ctx context.Context, c container.Container, from time.Time, stdTypes container.StdType, events chan<- container.LogEvent) error {
|
||||
reader, err := k.client.ContainerLogs(ctx, c.ID, from, stdTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -101,24 +101,21 @@ func (k *K8sClientService) Attach(ctx context.Context, container container.Conta
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
wg.Go(func() {
|
||||
defer writer.Close()
|
||||
defer cancel()
|
||||
defer wg.Done()
|
||||
if _, err := io.Copy(writer, stdin); err != nil {
|
||||
log.Error().Err(err).Msg("error copying stdin")
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
go func() {
|
||||
wg.Go(func() {
|
||||
defer cancel()
|
||||
defer wg.Done()
|
||||
if _, err := io.Copy(stdout, reader); err != nil {
|
||||
log.Error().Err(err).Msg("error copying stdout")
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
@@ -133,24 +130,21 @@ func (k *K8sClientService) Exec(ctx context.Context, container container.Contain
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
wg.Go(func() {
|
||||
defer writer.Close()
|
||||
defer cancel()
|
||||
defer wg.Done()
|
||||
if _, err := io.Copy(writer, stdin); err != nil {
|
||||
log.Error().Err(err).Msg("error copying stdin")
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
go func() {
|
||||
wg.Go(func() {
|
||||
defer cancel()
|
||||
defer wg.Done()
|
||||
if _, err := io.Copy(stdout, reader); err != nil {
|
||||
log.Error().Err(err).Msg("error copying stdout")
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
|
||||
@@ -16,7 +16,7 @@ func ParseRegex(search string) (*regexp.Regexp, error) {
|
||||
return CreateRegex(search, search == strings.ToLower(search))
|
||||
}
|
||||
|
||||
func Search(re *regexp.Regexp, logEvent *container.LogEvent) bool {
|
||||
func Search(re *regexp.Regexp, logEvent container.LogEvent) bool {
|
||||
matcher := NewPatternMatcher(re, MarkerStart, MarkerEnd)
|
||||
return matcher.MarkInLogEvent(logEvent)
|
||||
return matcher.MarkInLogEvent(&logEvent)
|
||||
}
|
||||
|
||||
@@ -14,11 +14,19 @@ func (h *handler) containerActions(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
userLabels := h.config.Labels
|
||||
permit := true
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
permit = user.Roles.Has(auth.Actions)
|
||||
}
|
||||
|
||||
if !permit {
|
||||
log.Warn().Msg("user is not permitted to perform actions on container")
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
|
||||
|
||||
@@ -20,7 +20,7 @@ func Test_createRoutes_proxy_missing_headers(t *testing.T) {
|
||||
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/",
|
||||
Authorization: Authorization{
|
||||
Provider: FORWARD_PROXY,
|
||||
Authorizer: auth.NewForwardProxyAuth("Remote-User", "Remote-Email", "Remote-Name", "Remote-Filter"),
|
||||
Authorizer: auth.NewForwardProxyAuth("Remote-User", "Remote-Email", "Remote-Name", "Remote-Filter", "Remote-RolesConfigured"),
|
||||
},
|
||||
})
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
@@ -39,7 +39,7 @@ func Test_createRoutes_proxy_happy(t *testing.T) {
|
||||
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/",
|
||||
Authorization: Authorization{
|
||||
Provider: FORWARD_PROXY,
|
||||
Authorizer: auth.NewForwardProxyAuth("Remote-User", "Remote-Email", "Remote-Name", "Remote-Filter"),
|
||||
Authorizer: auth.NewForwardProxyAuth("Remote-User", "Remote-Email", "Remote-Name", "Remote-Filter", "Remote-RolesConfigured"),
|
||||
},
|
||||
})
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
|
||||
@@ -23,11 +23,19 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
userLabels := h.config.Labels
|
||||
permit := true
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
permit = user.Roles.Has(auth.Download)
|
||||
}
|
||||
|
||||
if !permit {
|
||||
log.Warn().Msg("user is not permitted to download logs from container")
|
||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
+17
-3
@@ -4,6 +4,7 @@ import (
|
||||
"html/template"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/auth"
|
||||
"github.com/amir20/dozzle/internal/profile"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
@@ -45,12 +45,26 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
|
||||
user := auth.UserFromContext(req.Context())
|
||||
|
||||
if h.config.Authorization.Provider == NONE || user != nil {
|
||||
if user != nil {
|
||||
config["enableShell"] = h.config.EnableShell && user.Roles.Has(auth.Shell)
|
||||
config["enableActions"] = h.config.EnableActions && user.Roles.Has(auth.Actions)
|
||||
config["enableDownload"] = user.Roles.Has(auth.Download)
|
||||
} else {
|
||||
config["enableShell"] = h.config.EnableShell
|
||||
config["enableActions"] = h.config.EnableActions
|
||||
config["enableDownload"] = true
|
||||
}
|
||||
|
||||
if h.config.Authorization.Provider == FORWARD_PROXY && strings.TrimSpace(h.config.Authorization.LogoutUrl) != "" {
|
||||
config["logoutUrl"] = strings.TrimSpace(h.config.Authorization.LogoutUrl)
|
||||
}
|
||||
|
||||
config["authProvider"] = h.config.Authorization.Provider
|
||||
config["version"] = h.config.Version
|
||||
config["hostname"] = h.config.Hostname
|
||||
config["hosts"] = hosts
|
||||
config["enableActions"] = h.config.EnableActions
|
||||
config["enableShell"] = h.config.EnableShell
|
||||
config["disableAvatars"] = h.config.DisableAvatars
|
||||
config["releaseCheckMode"] = h.config.ReleaseCheckMode
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
|
||||
@@ -82,7 +82,7 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
minimum := 0
|
||||
buffer := utils.NewRingBuffer[*container.LogEvent](500)
|
||||
buffer := utils.NewRingBuffer[container.LogEvent](500)
|
||||
if r.URL.Query().Has("min") {
|
||||
minimum, err = strconv.Atoi(r.URL.Query().Get("min"))
|
||||
if err != nil {
|
||||
@@ -94,7 +94,7 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
http.Error(w, errors.New("minimum must be between 0 and buffer size").Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
buffer = utils.NewRingBuffer[*container.LogEvent](minimum)
|
||||
buffer = utils.NewRingBuffer[container.LogEvent](minimum)
|
||||
}
|
||||
|
||||
maxStart := math.MaxInt
|
||||
@@ -177,7 +177,7 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
break
|
||||
}
|
||||
|
||||
support_web.EscapeHTMLValues(event)
|
||||
support_web.EscapeHTMLValues(&event)
|
||||
buffer.Push(event)
|
||||
}
|
||||
}
|
||||
@@ -294,9 +294,9 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
||||
|
||||
absoluteTime := time.Time{}
|
||||
var regex *regexp.Regexp
|
||||
liveLogs := make(chan *container.LogEvent)
|
||||
liveLogs := make(chan container.LogEvent)
|
||||
events := make(chan *container.ContainerEvent, 1)
|
||||
backfill := make(chan []*container.LogEvent)
|
||||
backfill := make(chan []container.LogEvent)
|
||||
|
||||
levels := make(map[string]struct{})
|
||||
for _, level := range r.URL.Query()["levels"] {
|
||||
@@ -318,7 +318,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
||||
delta := -10 * time.Second
|
||||
to := absoluteTime
|
||||
for minimum > 0 {
|
||||
events := make([]*container.LogEvent, 0)
|
||||
events := make([]container.LogEvent, 0)
|
||||
stillRunning := false
|
||||
for _, container := range existingContainers {
|
||||
containerService, err := h.hostService.FindContainer(container.Host, container.ID, userLabels)
|
||||
@@ -418,7 +418,7 @@ loop:
|
||||
continue
|
||||
}
|
||||
|
||||
support_web.EscapeHTMLValues(logEvent)
|
||||
support_web.EscapeHTMLValues(&logEvent)
|
||||
sseWriter.Message(logEvent)
|
||||
case c := <-newContainers:
|
||||
if _, err := h.hostService.FindContainer(c.Host, c.ID, userLabels); err == nil {
|
||||
@@ -433,8 +433,8 @@ loop:
|
||||
}
|
||||
|
||||
case backfillEvents := <-backfill:
|
||||
for _, event := range backfillEvents {
|
||||
support_web.EscapeHTMLValues(event)
|
||||
for i := range backfillEvents {
|
||||
support_web.EscapeHTMLValues(&backfillEvents[i])
|
||||
}
|
||||
if err := sseWriter.Event("logs-backfill", backfillEvents); err != nil {
|
||||
log.Error().Err(err).Msg("error encoding container event")
|
||||
|
||||
+23
-11
@@ -17,6 +17,13 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ReleaseCheckMode string
|
||||
|
||||
const (
|
||||
Automatic ReleaseCheckMode = "automatic"
|
||||
Manual ReleaseCheckMode = "manual"
|
||||
)
|
||||
|
||||
type AuthProvider string
|
||||
|
||||
const (
|
||||
@@ -27,22 +34,25 @@ const (
|
||||
|
||||
// Config is a struct for configuring the web service
|
||||
type Config struct {
|
||||
Base string
|
||||
Addr string
|
||||
Version string
|
||||
Hostname string
|
||||
NoAnalytics bool
|
||||
Dev bool
|
||||
Authorization Authorization
|
||||
EnableActions bool
|
||||
EnableShell bool
|
||||
Labels container.ContainerLabels
|
||||
Base string
|
||||
Addr string
|
||||
Version string
|
||||
Hostname string
|
||||
NoAnalytics bool
|
||||
Dev bool
|
||||
Authorization Authorization
|
||||
EnableActions bool
|
||||
EnableShell bool
|
||||
DisableAvatars bool
|
||||
ReleaseCheckMode ReleaseCheckMode
|
||||
Labels container.ContainerLabels
|
||||
}
|
||||
|
||||
type Authorization struct {
|
||||
Provider AuthProvider
|
||||
Authorizer Authorizer
|
||||
TTL time.Duration
|
||||
LogoutUrl string
|
||||
}
|
||||
|
||||
type Authorizer interface {
|
||||
@@ -122,7 +132,9 @@ func createRouter(h *handler) *chi.Mux {
|
||||
r.Get("/hosts/{host}/containers/{id}/exec", h.exec)
|
||||
}
|
||||
r.Get("/releases", h.releases)
|
||||
r.Get("/profile/avatar", h.avatar)
|
||||
if !h.config.DisableAvatars {
|
||||
r.Get("/profile/avatar", h.avatar)
|
||||
}
|
||||
r.Patch("/profile", h.updateProfile)
|
||||
r.Get("/version", h.version)
|
||||
if log.Debug().Enabled() {
|
||||
|
||||
@@ -27,15 +27,22 @@ func (h *handler) attach(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
id := chi.URLParam(r, "id")
|
||||
userLabels := h.config.Labels
|
||||
permit := true
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
permit = user.Roles.Has(auth.Shell)
|
||||
}
|
||||
|
||||
if !permit {
|
||||
log.Warn().Msg("user is not permitted to attach to container")
|
||||
conn.WriteMessage(websocket.TextMessage, []byte("⛔ Access denied: attaching to this container is forbidden\r\n"))
|
||||
return
|
||||
}
|
||||
|
||||
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to find container")
|
||||
return
|
||||
@@ -60,11 +67,19 @@ func (h *handler) exec(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
id := chi.URLParam(r, "id")
|
||||
userLabels := h.config.Labels
|
||||
permit := true
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
permit = user.Roles.Has(auth.Shell)
|
||||
}
|
||||
|
||||
if !permit {
|
||||
log.Warn().Msg("user is not permitted to exec into container")
|
||||
conn.WriteMessage(websocket.TextMessage, []byte("⛔ Access denied: attaching to this container is forbidden\r\n"))
|
||||
return
|
||||
}
|
||||
|
||||
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
|
||||
|
||||
@@ -130,40 +130,51 @@ func main() {
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return err == nil
|
||||
_, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func createServer(args cli.Args, hostService web.HostService) *http.Server {
|
||||
_, dev := os.LookupEnv("DEV")
|
||||
|
||||
var releaseCheckMode web.ReleaseCheckMode = web.Automatic
|
||||
|
||||
switch args.ReleaseCheckMode {
|
||||
case "automatic":
|
||||
releaseCheckMode = web.Automatic
|
||||
case "manual":
|
||||
releaseCheckMode = web.Manual
|
||||
default:
|
||||
log.Fatal().Str("releaseCheckMode", args.ReleaseCheckMode).Msg("Invalid release check mode")
|
||||
}
|
||||
|
||||
var provider web.AuthProvider = web.NONE
|
||||
var authorizer web.Authorizer
|
||||
if args.AuthProvider == "forward-proxy" {
|
||||
log.Debug().Msg("Using forward proxy authentication")
|
||||
provider = web.FORWARD_PROXY
|
||||
authorizer = auth.NewForwardProxyAuth(args.AuthHeaderUser, args.AuthHeaderEmail, args.AuthHeaderName, args.AuthHeaderFilter)
|
||||
authorizer = auth.NewForwardProxyAuth(args.AuthHeaderUser, args.AuthHeaderEmail, args.AuthHeaderName, args.AuthHeaderFilter, args.AuthHeaderRoles)
|
||||
} else if args.AuthProvider == "simple" {
|
||||
log.Debug().Msg("Using simple authentication")
|
||||
provider = web.SIMPLE
|
||||
|
||||
userFilePath := "./data/users.yml"
|
||||
if !fileExists(userFilePath) {
|
||||
userFilePath = "./data/users.yaml"
|
||||
if !fileExists(userFilePath) {
|
||||
log.Fatal().Msg("No users.yaml or users.yml file found.")
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Msgf("Reading %s file", filepath.Base(userFilePath))
|
||||
userFilePath := "./data/users.yml"
|
||||
if !fileExists(userFilePath) {
|
||||
userFilePath = "./data/users.yaml"
|
||||
if !fileExists(userFilePath) {
|
||||
log.Fatal().Msg("No users.yaml or users.yml file found.")
|
||||
}
|
||||
}
|
||||
|
||||
db, err := auth.ReadUsersFromFile(userFilePath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("Could not read users file: %s", userFilePath)
|
||||
}
|
||||
log.Debug().Msgf("Reading %s file", filepath.Base(userFilePath))
|
||||
|
||||
db, err := auth.ReadUsersFromFile(userFilePath)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msgf("Could not read users file: %s", userFilePath)
|
||||
}
|
||||
|
||||
log.Debug().Int("users", len(db.Users)).Msg("Loaded users")
|
||||
ttl := time.Duration(0)
|
||||
@@ -197,10 +208,13 @@ func createServer(args cli.Args, hostService web.HostService) *http.Server {
|
||||
Provider: provider,
|
||||
Authorizer: authorizer,
|
||||
TTL: authTTL,
|
||||
LogoutUrl: args.AuthLogoutUrl,
|
||||
},
|
||||
EnableActions: args.EnableActions,
|
||||
EnableShell: args.EnableShell,
|
||||
Labels: args.Filter,
|
||||
EnableActions: args.EnableActions,
|
||||
EnableShell: args.EnableShell,
|
||||
DisableAvatars: args.DisableAvatars,
|
||||
ReleaseCheckMode: releaseCheckMode,
|
||||
Labels: args.Filter,
|
||||
}
|
||||
|
||||
assets, err := fs.Sub(content, "dist")
|
||||
|
||||
+44
-44
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "dozzle",
|
||||
"version": "8.13.6",
|
||||
"version": "8.14.4",
|
||||
"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.13.1",
|
||||
"packageManager": "pnpm@10.18.2",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -29,22 +29,22 @@
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@duckdb/duckdb-wasm": "1.29.1-dev132.0",
|
||||
"@iconify-json/carbon": "^1.2.10",
|
||||
"@iconify-json/cil": "^1.2.2",
|
||||
"@iconify-json/ic": "^1.2.2",
|
||||
"@iconify-json/material-symbols": "^1.2.29",
|
||||
"@duckdb/duckdb-wasm": "1.30.0",
|
||||
"@iconify-json/carbon": "^1.2.14",
|
||||
"@iconify-json/cil": "^1.2.3",
|
||||
"@iconify-json/ic": "^1.2.4",
|
||||
"@iconify-json/material-symbols": "^1.2.41",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@iconify-json/mdi-light": "^1.2.2",
|
||||
"@iconify-json/octicon": "^1.2.7",
|
||||
"@iconify-json/octicon": "^1.2.16",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.8",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"@vueuse/components": "^13.5.0",
|
||||
"@vueuse/core": "^13.5.0",
|
||||
"@vueuse/integrations": "^13.5.0",
|
||||
"@vueuse/router": "^13.5.0",
|
||||
"@intlify/unplugin-vue-i18n": "^11.0.1",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "4.1.14",
|
||||
"@vueuse/components": "^13.9.0",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"@vueuse/integrations": "^13.9.0",
|
||||
"@vueuse/router": "14.0.0-beta.1",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
@@ -54,60 +54,60 @@
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-shape": "^3.2.0",
|
||||
"d3-transition": "^3.0.1",
|
||||
"daisyui": "5.0.46",
|
||||
"entities": "^6.0.1",
|
||||
"daisyui": "5.2.3",
|
||||
"entities": "^7.0.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"pinia": "^3.0.3",
|
||||
"sortablejs": "^1.15.6",
|
||||
"splitpanes": "^4.0.4",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tailwindcss": "4.1.11",
|
||||
"unplugin-auto-import": "^19.3.0",
|
||||
"unplugin-icons": "^22.1.0",
|
||||
"unplugin-vue-components": "^28.8.0",
|
||||
"strip-ansi": "^7.1.2",
|
||||
"tailwindcss": "4.1.14",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-icons": "^22.4.2",
|
||||
"unplugin-vue-components": "^29.1.0",
|
||||
"unplugin-vue-macros": "^2.14.5",
|
||||
"unplugin-vue-router": "^0.14.0",
|
||||
"vite": "7.0.4",
|
||||
"unplugin-vue-router": "^0.15.0",
|
||||
"vite": "7.1.9",
|
||||
"vite-plugin-vue-layouts": "^0.11.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vitepress": "1.6.3",
|
||||
"vue": "^3.5.17",
|
||||
"vue-i18n": "^11.1.9",
|
||||
"vitepress": "1.6.4",
|
||||
"vue": "^3.5.22",
|
||||
"vue-i18n": "^11.1.12",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apache-arrow/esnext-esm": "^21.0.0",
|
||||
"@iconify-json/ion": "^1.2.4",
|
||||
"@iconify-json/material-symbols-light": "^1.2.29",
|
||||
"@iconify-json/ri": "^1.2.5",
|
||||
"@apache-arrow/esnext-esm": "^21.1.0",
|
||||
"@iconify-json/ion": "^1.2.6",
|
||||
"@iconify-json/material-symbols-light": "^1.2.41",
|
||||
"@iconify-json/ri": "^1.2.6",
|
||||
"@pinia/testing": "^1.0.2",
|
||||
"@playwright/test": "^1.54.1",
|
||||
"@types/d3-array": "^3.2.1",
|
||||
"@playwright/test": "^1.56.0",
|
||||
"@types/d3-array": "^3.2.2",
|
||||
"@types/d3-ease": "^3.0.2",
|
||||
"@types/d3-scale": "^4.0.9",
|
||||
"@types/d3-selection": "^3.0.11",
|
||||
"@types/d3-shape": "^3.1.7",
|
||||
"@types/d3-transition": "^3.0.9",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/node": "^24.0.14",
|
||||
"@vitejs/plugin-vue": "6.0.0",
|
||||
"@vue/compiler-sfc": "^3.5.17",
|
||||
"@types/node": "^24.7.2",
|
||||
"@vitejs/plugin-vue": "6.0.1",
|
||||
"@vue/compiler-sfc": "^3.5.22",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"bumpp": "^10.2.0",
|
||||
"bumpp": "^10.3.1",
|
||||
"c8": "^10.1.3",
|
||||
"concurrently": "^9.2.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"lint-staged": "^16.1.2",
|
||||
"jsdom": "^27.0.0",
|
||||
"lint-staged": "^16.2.4",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
"simple-git-hooks": "^2.13.0",
|
||||
"simple-git-hooks": "^2.13.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^3.2.4",
|
||||
"vue-component-type-helpers": "3.0.1",
|
||||
"vue-tsc": "3.0.1"
|
||||
"vue-component-type-helpers": "3.1.1",
|
||||
"vue-tsc": "3.1.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,vue,css,ts,html,md}": [
|
||||
|
||||
Generated
+1369
-1263
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user