Compare commits
241 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cabe33a70f | |||
| 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 | |||
| 90ca59d283 | |||
| 84e608eab1 | |||
| 1dddbec30a | |||
| 0d9ff396d6 | |||
| dc9c6b727d | |||
| 4bf342da87 | |||
| 44c02f6676 | |||
| f843635060 | |||
| bcc8506d8c | |||
| c106808c1d | |||
| f7ff41881a | |||
| b58648d562 | |||
| 6d829b9cae | |||
| 9250db2c7e | |||
| 0c41da9e65 | |||
| 0c25f0f82e | |||
| 500ffa6b5b | |||
| 0d61493e29 | |||
| f8db11a4df | |||
| f596ebc5f3 | |||
| 46b48b0f1f | |||
| a80337177f | |||
| b6cf75020f | |||
| 135fffabbc | |||
| d0fbebea30 | |||
| ae058d3b09 | |||
| 573afcfd3b | |||
| 5de0e23b9e | |||
| d5b5b92701 | |||
| def47b560e | |||
| 3916e3aec8 | |||
| 743678da33 | |||
| 7291448d90 | |||
| 99d0af05d5 | |||
| a87908d957 | |||
| 8d38f36745 | |||
| 100204022d | |||
| 35dea10a1c | |||
| 176d41aff3 | |||
| cd9a771cab | |||
| 633d794539 | |||
| c1955a3301 | |||
| 3a4f0a6e79 | |||
| 2c50d870d2 | |||
| 229758c1db | |||
| 6ee4a8594c | |||
| e2b35b3c8c | |||
| 2d39629de1 | |||
| 61af442264 | |||
| 8dde2fe9ac | |||
| da83f8af11 | |||
| eadf6c98fe | |||
| 931c85afb6 | |||
| a47be8730a | |||
| bcb859509b | |||
| d98000b35a | |||
| a8dced5c2b | |||
| 3c2a2119d8 | |||
| a5ed9b65d5 | |||
| e01baf7119 | |||
| 064d5c21f2 | |||
| fae248b066 | |||
| f22c51690b | |||
| 373f9ae025 | |||
| d4dab0ad1a | |||
| 098f032d03 | |||
| 9db559c9ce | |||
| 7257e35f1b | |||
| 1c20aba5d7 | |||
| 252d865460 | |||
| 02c28fd001 | |||
| b39f2ae6a8 | |||
| 7e2dd1c69e | |||
| 7e5b413c48 | |||
| ec513adde2 | |||
| 186b4504fc | |||
| 9dc286643e | |||
| 553cfd558d | |||
| 31174a5214 | |||
| 515849e72a | |||
| f442635f55 | |||
| a8f2dc17d6 | |||
| 1faa84abc5 | |||
| bdcb1673e1 | |||
| 4de23926eb | |||
| d4defe680e | |||
| 4c2b459fa8 | |||
| aeadae0d30 | |||
| f6f1b5207e | |||
| cfe7285bf7 | |||
| defaba29c2 | |||
| 2aad18d729 | |||
| 9c52d604bc | |||
| 7f936a9a38 | |||
| 0c9065a210 | |||
| b70c2da290 | |||
| 804199aa9a | |||
| 50c3608a5e | |||
| 65b68ad907 | |||
| fd663b9c0e | |||
| 7a0296fc30 | |||
| b8a3325ea7 | |||
| 0ade181ab2 | |||
| 7d60d951ff | |||
| 66b0dfe280 | |||
| c273cb5320 | |||
| 570ef771dd | |||
| 07014470e9 | |||
| bd3c2f5c24 | |||
| 6f45faf27a | |||
| e94a245440 | |||
| daa02dd899 | |||
| 6a27f9f367 | |||
| 9d7b8f26a6 | |||
| 82576ae452 | |||
| 2ca71a49ba | |||
| 3abeeec814 | |||
| 5619201886 | |||
| 0c06d25ae3 | |||
| 7a070b90ed | |||
| 9c70416aec | |||
| 68ca6d3040 | |||
| 711745eb1f | |||
| c172f6f73f | |||
| 7941bd7b6f | |||
| 595e39a139 | |||
| 8e074df145 |
@@ -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.2
|
||||
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"
|
||||
@@ -90,20 +94,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
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
|
||||
@@ -121,7 +125,7 @@ jobs:
|
||||
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
|
||||
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
with:
|
||||
push: true
|
||||
context: .
|
||||
@@ -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 }}
|
||||
|
||||
@@ -13,20 +13,20 @@ jobs:
|
||||
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle') }}
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
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
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
|
||||
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6.16.0
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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.2"
|
||||
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,16 +77,16 @@ 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.2"
|
||||
go-version: "1.25.2"
|
||||
check-latest: true
|
||||
- name: Generate dependencies
|
||||
run: make fake_assets shared_key.pem shared_cert.pem
|
||||
- name: Stactic checker
|
||||
uses: dominikh/staticcheck-action@v1.3.1
|
||||
uses: dominikh/staticcheck-action@v1.4.0
|
||||
with:
|
||||
install-go: false
|
||||
int-test:
|
||||
@@ -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"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build assets
|
||||
FROM --platform=$BUILDPLATFORM node:23.11.0-alpine AS node
|
||||
FROM --platform=$BUILDPLATFORM node:23.11.1-alpine AS node
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ shared_key.pem:
|
||||
|
||||
shared_cert.pem: shared_key.pem
|
||||
@openssl req -new -key shared_key.pem -out shared_request.csr -subj "/C=US/ST=California/L=San Francisco/O=Dozzle"
|
||||
@openssl x509 -req -in shared_request.csr -signkey shared_key.pem -out shared_cert.pem -days 365
|
||||
@openssl x509 -req -in shared_request.csr -signkey shared_key.pem -out shared_cert.pem -days 1825
|
||||
@rm shared_request.csr
|
||||
|
||||
$(GEN_DIR)/%.pb.go: $(PROTO_DIR)/%.proto
|
||||
|
||||
@@ -9,6 +9,9 @@ https://github.com/user-attachments/assets/66a7b4b2-d6c9-4fca-ab04-aef6cd7c0c31
|
||||
[](https://hub.docker.com/r/amir20/dozzle/)
|
||||

|
||||
|
||||
> [!NOTE]
|
||||
> If you like Dozzle, check out [`dtop`](https://github.com/amir20/dtop) which is a top like application for monitoring Docker containers. It integrates with Dozzle to allow for linking directly to container logs.
|
||||
|
||||
## Features
|
||||
|
||||
- Intelligent fuzzy search for container names 🤖
|
||||
|
||||
@@ -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,7 +69,9 @@ 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']
|
||||
const loggingContextKey: typeof import('./composable/logContext')['loggingContextKey']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
@@ -151,6 +154,7 @@ declare global {
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toRelativeTime: typeof import('./utils/index')['toRelativeTime']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||
@@ -238,6 +242,7 @@ declare global {
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useGroupedStream: typeof import('./composable/eventStreams')['useGroupedStream']
|
||||
const useHead: typeof import('@vueuse/head')['useHead']
|
||||
const useHistoricalContainerLog: typeof import('./composable/historicalLogs')['useHistoricalContainerLog']
|
||||
const useHostStream: typeof import('./composable/eventStreams')['useHostStream']
|
||||
const useHosts: typeof import('./stores/hosts')['useHosts']
|
||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||
@@ -327,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']
|
||||
@@ -373,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'
|
||||
@@ -444,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']>
|
||||
@@ -459,7 +466,9 @@ 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']>
|
||||
readonly loggingContextKey: UnwrapRef<typeof import('./composable/logContext')['loggingContextKey']>
|
||||
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
||||
@@ -542,6 +551,7 @@ declare module 'vue' {
|
||||
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
|
||||
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||
readonly toRelativeTime: UnwrapRef<typeof import('./utils/index')['toRelativeTime']>
|
||||
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
|
||||
@@ -629,6 +639,7 @@ declare module 'vue' {
|
||||
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
||||
readonly useGroupedStream: UnwrapRef<typeof import('./composable/eventStreams')['useGroupedStream']>
|
||||
readonly useHead: UnwrapRef<typeof import('@vueuse/head')['useHead']>
|
||||
readonly useHistoricalContainerLog: UnwrapRef<typeof import('./composable/historicalLogs')['useHistoricalContainerLog']>
|
||||
readonly useHostStream: UnwrapRef<typeof import('./composable/eventStreams')['useHostStream']>
|
||||
readonly useHosts: UnwrapRef<typeof import('./stores/hosts')['useHosts']>
|
||||
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
|
||||
@@ -718,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']>
|
||||
|
||||
@@ -11,7 +11,6 @@ declare module 'vue' {
|
||||
Announcements: typeof import('./components/Announcements.vue')['default']
|
||||
'Carbon:caretDown': typeof import('~icons/carbon/caret-down')['default']
|
||||
'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default']
|
||||
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default']
|
||||
'Carbon:information': typeof import('~icons/carbon/information')['default']
|
||||
'Carbon:logoKubernetes': typeof import('~icons/carbon/logo-kubernetes')['default']
|
||||
'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default']
|
||||
@@ -37,49 +36,59 @@ declare module 'vue' {
|
||||
ContainerTable: typeof import('./components/ContainerTable.vue')['default']
|
||||
ContainerTitle: typeof import('./components/ContainerViewer/ContainerTitle.vue')['default']
|
||||
DateTime: typeof import('./components/common/DateTime.vue')['default']
|
||||
DistanceTime: typeof import('./components/common/DistanceTime.vue')['default']
|
||||
Dropdown: typeof import('./components/common/Dropdown.vue')['default']
|
||||
DropdownMenu: typeof import('./components/common/DropdownMenu.vue')['default']
|
||||
EventSource: typeof import('./components/LogViewer/EventSource.vue')['default']
|
||||
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
|
||||
GroupedLog: typeof import('./components/GroupedViewer/GroupedLog.vue')['default']
|
||||
GroupMenu: typeof import('./components/GroupMenu.vue')['default']
|
||||
HistoricalContainerLog: typeof import('./components/ContainerViewer/HistoricalContainerLog.vue')['default']
|
||||
HostIcon: typeof import('./components/common/HostIcon.vue')['default']
|
||||
HostList: typeof import('./components/HostList.vue')['default']
|
||||
HostLog: typeof import('./components/HostViewer/HostLog.vue')['default']
|
||||
HostMenu: typeof import('./components/HostMenu.vue')['default']
|
||||
'Ic:sharpKeyboardReturn': typeof import('~icons/ic/sharp-keyboard-return')['default']
|
||||
IndeterminateBar: typeof import('./components/common/IndeterminateBar.vue')['default']
|
||||
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
|
||||
'Ion:ellipsisVertical': typeof import('~icons/ion/ellipsis-vertical')['default']
|
||||
KeyShortcut: typeof import('./components/common/KeyShortcut.vue')['default']
|
||||
LabeledInput: typeof import('./components/common/LabeledInput.vue')['default']
|
||||
Links: typeof import('./components/Links.vue')['default']
|
||||
LoadMoreLogItem: typeof import('./components/LogViewer/LoadMoreLogItem.vue')['default']
|
||||
LogActions: typeof import('./components/LogViewer/LogActions.vue')['default']
|
||||
LogAnalytics: typeof import('./components/LogViewer/LogAnalytics.vue')['default']
|
||||
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
|
||||
LogDetails: typeof import('./components/LogViewer/LogDetails.vue')['default']
|
||||
LogItem: typeof import('./components/LogViewer/LogItem.vue')['default']
|
||||
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
|
||||
LogList: typeof import('./components/LogViewer/LogList.vue')['default']
|
||||
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
|
||||
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
|
||||
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
|
||||
'MaterialSymbols:codeBlocksRounded': typeof import('~icons/material-symbols/code-blocks-rounded')['default']
|
||||
'MaterialSymbols:contentCopy': typeof import('~icons/material-symbols/content-copy')['default']
|
||||
'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']
|
||||
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
|
||||
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
|
||||
'Mdi:beer': typeof import('~icons/mdi/beer')['default']
|
||||
'Mdi:check': typeof import('~icons/mdi/check')['default']
|
||||
'Mdi:chevronDoubleDown': typeof import('~icons/mdi/chevron-double-down')['default']
|
||||
'Mdi:chevronLeft': typeof import('~icons/mdi/chevron-left')['default']
|
||||
'Mdi:chevronRight': typeof import('~icons/mdi/chevron-right')['default']
|
||||
'Mdi:close': typeof import('~icons/mdi/close')['default']
|
||||
'Mdi:cog': typeof import('~icons/mdi/cog')['default']
|
||||
'Mdi:contentCopy': typeof import('~icons/mdi/content-copy')['default']
|
||||
'Mdi:docker': typeof import('~icons/mdi/docker')['default']
|
||||
'Mdi:gauge': typeof import('~icons/mdi/gauge')['default']
|
||||
'Mdi:github': typeof import('~icons/mdi/github')['default']
|
||||
'Mdi:hamburgerMenu': typeof import('~icons/mdi/hamburger-menu')['default']
|
||||
'Mdi:hexagonMultiple': typeof import('~icons/mdi/hexagon-multiple')['default']
|
||||
'Mdi:key': typeof import('~icons/mdi/key')['default']
|
||||
'Mdi:keyboardEsc': typeof import('~icons/mdi/keyboard-esc')['default']
|
||||
'Mdi:lightningBolt': typeof import('~icons/mdi/lightning-bolt')['default']
|
||||
'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
|
||||
'Mdi:satelliteVariant': typeof import('~icons/mdi/satellite-variant')['default']
|
||||
MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
|
||||
@@ -105,6 +114,7 @@ declare module 'vue' {
|
||||
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
|
||||
Popup: typeof import('./components/Popup.vue')['default']
|
||||
RandomColorTag: typeof import('./components/LogViewer/RandomColorTag.vue')['default']
|
||||
RelativeTime: typeof import('./components/common/RelativeTime.vue')['default']
|
||||
'Ri:terminalWindowFill': typeof import('~icons/ri/terminal-window-fill')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
|
||||
@@ -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">
|
||||
@@ -22,7 +26,7 @@
|
||||
>
|
||||
{{ release.name }}
|
||||
</a>
|
||||
<span class="ml-1 text-xs"><distance-time :date="release.createdAt" /></span>
|
||||
<span class="ml-1 text-xs"><RelativeTime :date="release.createdAt" /></span>
|
||||
</div>
|
||||
<div class="text-base-content/80 text-sm">
|
||||
{{ release.body }}
|
||||
@@ -39,7 +43,7 @@
|
||||
>
|
||||
{{ release.name }}
|
||||
</a>
|
||||
<span class="ml-1 text-xs"><distance-time :date="release.createdAt" /></span>
|
||||
<span class="ml-1 text-xs"><RelativeTime :date="release.createdAt" /></span>
|
||||
<Tag class="bg-red ml-auto px-1 py-1 text-xs" v-if="release.latest">
|
||||
{{ $t("releases.latest") }}
|
||||
</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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="dropdown">
|
||||
<button tabindex="0" role="button" class="btn btn-xs md:btn-sm"><slot /> <carbon:caret-down /></button>
|
||||
<ul tabindex="0" class="dropdown-content menu rounded-box bg-base-100 shadow-sm">
|
||||
<ul tabindex="0" class="dropdown-content menu rounded-box bg-base-100 border-base-content/20 border shadow-sm">
|
||||
<li v-for="other in containers">
|
||||
<router-link :to="{ name: '/container/[id]', params: { id: other.id } }" class="text-nowrap">
|
||||
<div
|
||||
@@ -10,7 +10,7 @@
|
||||
></div>
|
||||
{{ other.name }}
|
||||
<div v-if="other.state === 'running'">running</div>
|
||||
<DistanceTime :date="other.created" strict class="text-base-content/70 text-xs" v-else />
|
||||
<RelativeTime :date="other.finishedAt" class="text-base-content/70 text-xs" v-else />
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,28 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="font-light capitalize"> STATE </span>
|
||||
<span class="font-semibold uppercase"> {{ container.state }} </span>
|
||||
</div>
|
||||
<div v-if="container.startedAt.getFullYear() > 0">
|
||||
<span class="font-light capitalize"> STARTED </span>
|
||||
<span class="font-semibold">
|
||||
<DistanceTime :date="container.startedAt" strict />
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="container.state != 'running' && container.finishedAt.getFullYear() > 0">
|
||||
<span class="font-light capitalize"> FINISHED </span>
|
||||
<span class="font-semibold">
|
||||
<DistanceTime :date="container.finishedAt" strict />
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="container.state == 'running'">
|
||||
<span class="font-light capitalize"> Load </span>
|
||||
<span class="font-semibold"> {{ container.stat.cpu.toFixed(2) }}% </span>
|
||||
</div>
|
||||
<div v-if="container.state == 'running'">
|
||||
<span class="font-light capitalize"> MEM </span>
|
||||
<span class="font-semibold"> {{ formatBytes(container.stat.memoryUsage) }} </span>
|
||||
</div>
|
||||
<table class="w-full border-separate border-spacing-x-1">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="text-right font-light capitalize">STATE</th>
|
||||
<td class="font-semibold uppercase">{{ container.state }}</td>
|
||||
</tr>
|
||||
<tr v-if="container.startedAt.getFullYear() > 0">
|
||||
<th class="text-right font-light capitalize">STARTED</th>
|
||||
<td class="font-semibold">
|
||||
<RelativeTime :date="container.startedAt" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="container.state != 'running' && container.finishedAt.getFullYear() > 0">
|
||||
<th class="text-right font-light capitalize">FINISHED</th>
|
||||
<td class="font-semibold">
|
||||
<RelativeTime :date="container.finishedAt" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="container.state == 'running'">
|
||||
<th class="text-right font-light capitalize">Load</th>
|
||||
<td class="font-semibold">{{ container.stat.cpu.toFixed(2) }}%</td>
|
||||
</tr>
|
||||
<tr v-if="container.state == 'running'">
|
||||
<th class="text-right font-light capitalize">MEM</th>
|
||||
<td class="font-semibold">{{ formatBytes(container.stat.memoryUsage) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -75,26 +75,28 @@
|
||||
<td v-if="isVisible('host')">{{ container.hostLabel }}</td>
|
||||
<td v-if="isVisible('state')">{{ container.state }}</td>
|
||||
<td v-if="isVisible('created')">
|
||||
<distance-time :date="container.created" strict :suffix="false"></distance-time>
|
||||
<RelativeTime :date="container.created" />
|
||||
</td>
|
||||
<td v-if="isVisible('cpu')">
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<progress
|
||||
class="progress progress-primary"
|
||||
:value="Math.min(container.movingAverage.cpu, 100)"
|
||||
:max="100"
|
||||
class="progress h-3 w-full rounded-3xl"
|
||||
:class="getProgressColorClass(containerAverageCpu(container))"
|
||||
:value="containerAverageCpu(container)"
|
||||
max="100"
|
||||
></progress>
|
||||
<span class="text-sm">{{ container.movingAverage.cpu.toFixed(0) }}%</span>
|
||||
<span class="w-8 text-right text-sm"> {{ containerAverageCpu(container).toFixed(0) }}% </span>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="isVisible('mem')">
|
||||
<div class="flex flex-row items-center gap-1">
|
||||
<progress
|
||||
class="progress progress-primary"
|
||||
class="progress h-3 w-full rounded-3xl"
|
||||
:class="getProgressColorClass(container.movingAverage.memory)"
|
||||
:value="container.movingAverage.memory"
|
||||
max="100"
|
||||
></progress>
|
||||
<span class="text-sm">{{ container.movingAverage.memory.toFixed(0) }}%</span>
|
||||
<span class="w-8 text-right text-sm"> {{ container.movingAverage.memory.toFixed(0) }}% </span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -200,6 +202,27 @@ function sort(field: keys) {
|
||||
function isVisible(field: keys) {
|
||||
return fields[field].mobileVisible || !isMobile.value;
|
||||
}
|
||||
|
||||
function getContainerCores(container: Container): number {
|
||||
if (container.cpuLimit && container.cpuLimit > 0) {
|
||||
return container.cpuLimit;
|
||||
}
|
||||
const hostInfo = hosts.value[container.host];
|
||||
return hostInfo?.nCPU ?? 1;
|
||||
}
|
||||
|
||||
function containerAverageCpu(container: Container): number {
|
||||
const cores = getContainerCores(container);
|
||||
const scaledCpu = container.movingAverage.cpu / cores;
|
||||
return Math.min(scaledCpu, 100);
|
||||
}
|
||||
|
||||
function getProgressColorClass(value: number): string {
|
||||
if (value <= 70) return "progress-success";
|
||||
if (value <= 80) return "progress-secondary";
|
||||
if (value <= 90) return "progress-warning";
|
||||
return "progress-error";
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
<template>
|
||||
<div class="dropdown dropdown-end dropdown-hover">
|
||||
<div class="dropdown dropdown-end dropdown-hover z-20">
|
||||
<label tabindex="0" class="btn btn-ghost btn-sm w-10 gap-0.5 px-2">
|
||||
<carbon:circle-solid class="text-red w-2.5" v-if="streamConfig.stderr" />
|
||||
<carbon:circle-solid class="text-blue w-2.5" v-if="streamConfig.stdout" />
|
||||
</label>
|
||||
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 z-50 w-52 p-1 shadow-sm">
|
||||
<li>
|
||||
<a @click.prevent="clear()">
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 shadow-sm"
|
||||
@click="hideMenu"
|
||||
>
|
||||
<li v-if="!historical">
|
||||
<a @click="clear()">
|
||||
<octicon:trash-24 /> {{ $t("toolbar.clear") }}
|
||||
<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>
|
||||
<a @click.prevent="showSearch = true">
|
||||
<li v-if="!historical">
|
||||
<a @click="showSearch = true">
|
||||
<mdi:magnify /> {{ $t("toolbar.search") }}
|
||||
<KeyShortcut char="f" />
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="hasComplexLogs">
|
||||
<a @click.prevent="showDrawer(LogAnalytics, { container }, 'lg')">
|
||||
<a @click="showDrawer(LogAnalytics, { container }, 'lg')">
|
||||
<ph:file-sql /> SQL Analytics
|
||||
<KeyShortcut char="f" :modifiers="['shift', 'meta']" />
|
||||
</a>
|
||||
@@ -103,7 +107,7 @@
|
||||
</li>
|
||||
|
||||
<!-- Container Actions (Enabled via config) -->
|
||||
<template v-if="enableActions">
|
||||
<template v-if="enableActions && !historical">
|
||||
<li class="line"></li>
|
||||
<li>
|
||||
<button
|
||||
@@ -135,17 +139,19 @@
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<template v-if="enableShell">
|
||||
<template v-if="enableShell && !historical">
|
||||
<li class="line"></li>
|
||||
<li>
|
||||
<a @click.prevent="showDrawer(Terminal, { container, action: 'attach' }, 'lg')">
|
||||
<ri:terminal-window-fill /> Attach
|
||||
<a @click="showDrawer(Terminal, { container, action: 'attach' }, 'lg')">
|
||||
<ri:terminal-window-fill />
|
||||
{{ $t("toolbar.attach") }}
|
||||
<KeyShortcut char="a" :modifiers="['shift', 'meta']" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a @click.prevent="showDrawer(Terminal, { container, action: 'exec' }, 'lg')">
|
||||
<material-symbols:terminal /> Shell
|
||||
<a @click="showDrawer(Terminal, { container, action: 'exec' }, 'lg')">
|
||||
<material-symbols:terminal />
|
||||
{{ $t("toolbar.shell") }}
|
||||
<KeyShortcut char="e" :modifiers="['shift', 'meta']" />
|
||||
</a>
|
||||
</li>
|
||||
@@ -161,11 +167,11 @@ 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();
|
||||
|
||||
const { container } = defineProps<{ container: Container }>();
|
||||
const { container, historical = false } = defineProps<{ container: Container; historical?: boolean }>();
|
||||
const clear = defineEmit();
|
||||
const { actionStates, start, stop, restart } = useContainerActions(toRef(() => container));
|
||||
|
||||
@@ -217,6 +223,16 @@ const toggleAllLevels = computed({
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const hideMenu = (e: MouseEvent) => {
|
||||
if (e.target instanceof HTMLAnchorElement) {
|
||||
setTimeout(() => {
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
<button tabindex="0" role="button" class="btn btn-xs md:btn-sm">
|
||||
{{ container.name }} <carbon:caret-down />
|
||||
</button>
|
||||
<ul tabindex="0" class="dropdown-content menu rounded-box bg-base-100 shadow-sm">
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu rounded-box bg-base-100 border-base-content/20 border shadow-sm"
|
||||
>
|
||||
<li v-for="other in otherContainers">
|
||||
<router-link :to="{ name: '/container/[id]', params: { id: other.id } }">
|
||||
<div
|
||||
@@ -28,7 +31,7 @@
|
||||
<div v-if="other.isSwarm">{{ other.swarmId }}</div>
|
||||
<div v-else>{{ other.name }}</div>
|
||||
<div v-if="other.state === 'running'">running</div>
|
||||
<DistanceTime :date="other.created" strict class="text-base-content/70 text-xs" v-else />
|
||||
<RelativeTime :date="other.finishedAt" class="text-base-content/70 text-xs" v-else />
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<ScrollableView :scrollable="scrollable" v-if="container">
|
||||
<template #header v-if="showTitle">
|
||||
<div class="@container mx-2 flex items-center gap-2 md:ml-4">
|
||||
<ContainerTitle :container="container" />
|
||||
<router-link
|
||||
:to="{ name: '/container/[id]', params: { id: container.id } }"
|
||||
class="btn btn-secondary btn-sm"
|
||||
v-if="container.state === 'running'"
|
||||
>
|
||||
<mdi:lightning-bolt />
|
||||
Live Logs
|
||||
</router-link>
|
||||
|
||||
<ContainerActionsToolbar class="max-md:hidden" :container="container" historical />
|
||||
<a class="btn btn-circle btn-xs" @click="close()" v-if="closable">
|
||||
<mdi:close />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<ViewerWithSource
|
||||
ref="viewer"
|
||||
:stream-source="useHistoricalContainerLog"
|
||||
:entity="historicalContainer"
|
||||
:visible-keys="visibleKeys"
|
||||
/>
|
||||
</template>
|
||||
</ScrollableView>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ViewerWithSource from "@/components/LogViewer/ViewerWithSource.vue";
|
||||
import { HistoricalContainer } from "@/models/Container";
|
||||
import { ComponentExposed } from "vue-component-type-helpers";
|
||||
|
||||
const {
|
||||
id,
|
||||
showTitle = false,
|
||||
scrollable = false,
|
||||
closable = false,
|
||||
date,
|
||||
} = defineProps<{
|
||||
id: string;
|
||||
showTitle?: boolean;
|
||||
scrollable?: boolean;
|
||||
closable?: boolean;
|
||||
date: Date;
|
||||
}>();
|
||||
|
||||
const close = defineEmit();
|
||||
|
||||
const store = useContainerStore();
|
||||
const container = store.currentContainer(toRef(() => id));
|
||||
const historicalContainer = toRef(() => new HistoricalContainer(container.value, date));
|
||||
const visibleKeys = persistentVisibleKeysForContainer(container);
|
||||
const viewer = useTemplateRef<ComponentExposed<typeof ViewerWithSource>>("viewer");
|
||||
|
||||
provideLoggingContext(
|
||||
toRef(() => [container.value]),
|
||||
{ showContainerName: false, showHostname: false, historical: true },
|
||||
);
|
||||
</script>
|
||||
@@ -53,7 +53,7 @@
|
||||
<span data-name v-html="matchedName(result)"></span>
|
||||
</div>
|
||||
|
||||
<DistanceTime :date="result.item.created" class="text-xs font-light" />
|
||||
<RelativeTime :date="result.item.created" class="text-xs font-light" />
|
||||
<span
|
||||
@click.stop.prevent="addColumn(result.item)"
|
||||
:title="$t('tooltip.pin-column')"
|
||||
|
||||
@@ -24,15 +24,21 @@
|
||||
<div class="flex-none">
|
||||
<div class="dropdown dropdown-end dropdown-hover">
|
||||
<label tabindex="0" class="btn btn-square btn-ghost btn-sm">
|
||||
<ph:dots-three-vertical-bold />
|
||||
<ion:ellipsis-vertical />
|
||||
</label>
|
||||
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 z-50 w-52 p-1 shadow-sm">
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 shadow-sm"
|
||||
>
|
||||
<li>
|
||||
<a class="text-sm capitalize" @click="toggleShowAllContainers()">
|
||||
<mdi:check class="w-4" v-if="showAllContainers" />
|
||||
<div v-else class="w-4"></div>
|
||||
|
||||
Show all containers
|
||||
{{ $t("label.show-all-containers") }}
|
||||
</a>
|
||||
<a class="text-sm capitalize" @click="collapseAll()">
|
||||
<material-symbols-light:collapse-all class="w-4" />
|
||||
{{ $t("label.collapse-all") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -141,6 +147,18 @@ const updateCollapsedGroups = (event: Event, label: string) => {
|
||||
} else {
|
||||
collapsedGroups.value.add(label);
|
||||
}
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const collapseAll = () => {
|
||||
menuItems.value.forEach(({ label }) => {
|
||||
collapsedGroups.value.add(label);
|
||||
});
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedPinnedContainers = debouncedRef(pinnedContainers, 200);
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<template>
|
||||
<div ref="root" class="flex min-h-[1px] justify-center">
|
||||
<span class="loading loading-bars loading-md text-primary mt-4" v-show="isLoading"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { onLoadMore = () => Promise.resolve(), enabled } = defineProps<{
|
||||
onLoadMore: () => Promise<void>;
|
||||
enabled: boolean;
|
||||
}>();
|
||||
|
||||
const isLoading = ref(false);
|
||||
const root = ref<HTMLElement>();
|
||||
|
||||
const observer = new IntersectionObserver(async (entries) => {
|
||||
if (entries[0].intersectionRatio <= 0) return;
|
||||
if (onLoadMore && enabled) {
|
||||
const scrollingParent = root.value?.closest("[data-scrolling]") || document.documentElement;
|
||||
const previousHeight = scrollingParent.scrollHeight;
|
||||
isLoading.value = true;
|
||||
await onLoadMore();
|
||||
isLoading.value = false;
|
||||
await nextTick();
|
||||
scrollingParent.scrollTop += scrollingParent.scrollHeight - previousHeight;
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => observer.observe(root.value!));
|
||||
onUnmounted(() => observer.disconnect());
|
||||
</script>
|
||||
@@ -14,23 +14,31 @@
|
||||
|
||||
<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'">
|
||||
<button @click.prevent="logout()" class="text-primary p-2">{{ $t("button.logout") }}</button>
|
||||
<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") }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
@@ -38,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,15 +1,31 @@
|
||||
<template>
|
||||
<LogItem :logEntry @click="showDrawer(LogDetails, { entry: logEntry })" class="clickable">
|
||||
<ul class="space-x-4">
|
||||
<li v-for="(value, name) in validValues" :key="name" class="inline-flex">
|
||||
<span class="text-light">{{ name }}=</span><span class="font-bold" v-if="value === null"><null></span>
|
||||
<template v-else-if="Array.isArray(value)">
|
||||
<span class="font-bold" v-html="JSON.stringify(value)"> </span>
|
||||
</template>
|
||||
<span class="font-bold" v-html="stripAnsi(value.toString())" v-else></span>
|
||||
<DefineTemplate v-slot="{ data }">
|
||||
<ul class="inline-flex flex-wrap space-x-4" @click="preventDefaultOnLinks">
|
||||
<li v-for="(value, name) in data" :key="name" v-if="isObject(data)">
|
||||
<span class="key">{{ name }}=</span>
|
||||
<span class="value" v-if="value === null"><null></span>
|
||||
<ReuseTemplate :data="value" v-else-if="isObject(value) || Array.isArray(value)" />
|
||||
<span v-else class="value" :class="typeof value" v-html="stripAnsi(value.toString())"></span>
|
||||
</li>
|
||||
<li class="text-light" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
|
||||
<li v-else-if="Array.isArray(data)">
|
||||
<ul class="array inline-flex flex-wrap space-x-1">
|
||||
<li
|
||||
v-for="(item, index) in data"
|
||||
:key="index"
|
||||
class="after:text-base-content/70 not-last:after:content-[',']"
|
||||
>
|
||||
<ReuseTemplate :data="item" v-if="isObject(item) || Array.isArray(item)" />
|
||||
<span v-else class="value" :class="typeof item" v-html="stripAnsi(item.toString())"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="key" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
|
||||
</ul>
|
||||
</DefineTemplate>
|
||||
<LogItem :logEntry>
|
||||
<div @click="containers.length > 0 && showDrawer(LogDetails, { entry: logEntry })" class="cursor-pointer">
|
||||
<ReuseTemplate :data="validValues" />
|
||||
</div>
|
||||
</LogItem>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -22,16 +38,37 @@ const { logEntry } = defineProps<{
|
||||
showContainerName?: boolean;
|
||||
}>();
|
||||
|
||||
const { containers } = useLoggingContext();
|
||||
|
||||
const [DefineTemplate, ReuseTemplate] = createReusableTemplate();
|
||||
|
||||
const validValues = computed(() => {
|
||||
return Object.fromEntries(Object.entries(logEntry.message).filter(([_, value]) => value !== undefined));
|
||||
});
|
||||
|
||||
const showDrawer = useDrawer();
|
||||
function preventDefaultOnLinks(event: MouseEvent) {
|
||||
if (event.target instanceof HTMLAnchorElement && event.target.rel?.includes("external")) {
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/main.css";
|
||||
.text-light {
|
||||
@apply text-base-content/70;
|
||||
.key {
|
||||
@apply text-base-content/70 font-light;
|
||||
}
|
||||
|
||||
.value {
|
||||
@apply text-base-content font-bold;
|
||||
}
|
||||
|
||||
.array {
|
||||
@apply before:text-base-content/80 after:text-base-content/80 before:content-['['] after:content-[']'];
|
||||
}
|
||||
|
||||
.string {
|
||||
@apply before:content-['"'] after:content-['"'];
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -65,12 +65,29 @@ describe("<ContainerEventSource />", () => {
|
||||
template: "Test from createLogEventSource",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "/container/[id].time.[datetime]",
|
||||
path: "/container/:id/time/:datetime",
|
||||
component: {
|
||||
template: "Test from createLogEventSource",
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return mount(Component, {
|
||||
global: {
|
||||
plugins: [router, createTestingPinia({ createSpy: vi.fn }), createI18n({})],
|
||||
plugins: [
|
||||
router,
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn,
|
||||
stubActions: false,
|
||||
initialState: {
|
||||
container: { containers: [{ id: "abc", image: "test:v123", host: "localhost" }] },
|
||||
},
|
||||
}),
|
||||
createI18n({}),
|
||||
],
|
||||
components: {
|
||||
LogViewer,
|
||||
},
|
||||
@@ -84,6 +101,7 @@ describe("<ContainerEventSource />", () => {
|
||||
streamConfig: reactive({ stdout: true, stderr: true }),
|
||||
hasComplexLogs: ref(false),
|
||||
levels: new Set<Level>(["info"]),
|
||||
historical: ref(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -138,7 +156,7 @@ describe("<ContainerEventSource />", () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"This is a message.", "id":1}`,
|
||||
data: `{"ts":1560336942459, "m":"This is a message.", "id":1, "rm": "This is a message.", "c": "abc"}`,
|
||||
});
|
||||
|
||||
vi.runAllTimers();
|
||||
@@ -154,39 +172,39 @@ describe("<ContainerEventSource />", () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"This is a message.", "id":1}`,
|
||||
data: `{"ts":1560336942459, "m":"This is a message.", "id":1, "rm": "This is a message.", "c": "abc"}`,
|
||||
});
|
||||
|
||||
vi.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find("ul.events").html()).toMatchSnapshot();
|
||||
expect(wrapper.find("ul[data-logs]").html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("should render dates with 12 hour style", async () => {
|
||||
const wrapper = createLogEventSource({ hourStyle: "12" });
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"foo bar", "id":1}`,
|
||||
data: `{"ts":1560336942459, "m":"foo bar", "id":1, "rm": "foo bar", "c": "abc"}`,
|
||||
});
|
||||
|
||||
vi.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find("ul.events").html()).toMatchSnapshot();
|
||||
expect(wrapper.find("ul[data-logs]").html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("should render dates with 24 hour style", async () => {
|
||||
const wrapper = createLogEventSource({ hourStyle: "24" });
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: `{"ts":1560336942459, "m":"foo bar", "id":1}`,
|
||||
data: `{"ts":1560336942459, "m":"foo bar", "id":1, "c": "abc"}`,
|
||||
});
|
||||
|
||||
vi.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find("ul.events").html()).toMatchSnapshot();
|
||||
expect(wrapper.find("ul[data-logs]").html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<template>
|
||||
<InfiniteLoader :onLoadMore="fetchMore" :enabled="!loadingMore && messages.length > 10" />
|
||||
<ul class="flex animate-pulse flex-col gap-4 p-4" v-if="loading || (noLogs && waitingForMoreLog)">
|
||||
<div class="flex flex-row gap-2" v-for="size in sizes">
|
||||
<div class="bg-base-content/50 h-3 w-40 shrink-0 rounded-full opacity-50"></div>
|
||||
@@ -7,21 +6,26 @@
|
||||
</div>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</ul>
|
||||
<div v-else-if="noLogs && !waitingForMoreLog" class="p-4">Container has no logs yet</div>
|
||||
<div v-else-if="noLogs && !waitingForMoreLog" class="p-4">
|
||||
{{ $t("label.no-logs") }}
|
||||
</div>
|
||||
<slot :messages="messages" v-else></slot>
|
||||
<IndeterminateBar :color />
|
||||
<IndeterminateBar :color v-if="!historical" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup generic="T">
|
||||
import { LogStreamSource } from "@/composable/eventStreams";
|
||||
const route = useRoute();
|
||||
|
||||
const { entity, streamSource } = $defineProps<{
|
||||
streamSource: (t: Ref<T>) => LogStreamSource;
|
||||
entity: T;
|
||||
}>();
|
||||
|
||||
const { messages, loadOlderLogs, isLoadingMore, opened, loading, error, eventSourceURL } = streamSource($$(entity));
|
||||
const { loadingMore } = useLoggingContext();
|
||||
const { historical } = useLoggingContext();
|
||||
|
||||
const { messages, opened, loading, error } = streamSource(toRef(() => entity));
|
||||
|
||||
const color = computed(() => {
|
||||
if (error.value) return "error";
|
||||
if (loading.value) return "secondary";
|
||||
@@ -37,33 +41,36 @@ defineExpose({
|
||||
clear: () => (messages.value = []),
|
||||
});
|
||||
|
||||
const fetchMore = async () => {
|
||||
if (!isLoadingMore.value) {
|
||||
loadingMore.value = true;
|
||||
await loadOlderLogs();
|
||||
loadingMore.value = false;
|
||||
}
|
||||
};
|
||||
if (historical.value && route.query.logId) {
|
||||
watchOnce(messages, async () => {
|
||||
await nextTick();
|
||||
document.getElementById(route.query.logId as string)?.scrollIntoView({ behavior: "instant", block: "center" });
|
||||
});
|
||||
}
|
||||
|
||||
const sizes = computedWithControl(eventSourceURL, () => {
|
||||
const sizeOptions = [
|
||||
"w-2/12",
|
||||
"w-3/12",
|
||||
"w-4/12",
|
||||
"w-5/12",
|
||||
"w-6/12",
|
||||
"w-7/12",
|
||||
"w-8/12",
|
||||
"w-9/12",
|
||||
"w-10/12",
|
||||
"w-11/12",
|
||||
"w-full",
|
||||
];
|
||||
const result = [];
|
||||
const iterations = 18;
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
result.push(sizeOptions[Math.floor(Math.random() * sizeOptions.length)]);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
const sizes = ref<string[]>([]);
|
||||
watch(
|
||||
opened,
|
||||
(value) => {
|
||||
if (value) return;
|
||||
const sizeOptions = [
|
||||
"w-2/12",
|
||||
"w-3/12",
|
||||
"w-4/12",
|
||||
"w-5/12",
|
||||
"w-6/12",
|
||||
"w-7/12",
|
||||
"w-8/12",
|
||||
"w-9/12",
|
||||
"w-10/12",
|
||||
"w-11/12",
|
||||
"w-full",
|
||||
];
|
||||
sizes.value = Array.from({ length: 18 }, () => sizeOptions[Math.floor(Math.random() * sizeOptions.length)]);
|
||||
},
|
||||
{
|
||||
flush: "sync",
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div ref="root" class="flex min-h-[1px] flex-1 content-center justify-center">
|
||||
<span class="loading loading-bars loading-md text-primary m-2" v-show="isLoading"></span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { LoadMoreLogEntry } from "@/models/LogEntry";
|
||||
|
||||
const { logEntry } = defineProps<{
|
||||
logEntry: LoadMoreLogEntry;
|
||||
}>();
|
||||
|
||||
const isLoading = ref(false);
|
||||
const root = ref<HTMLElement>();
|
||||
|
||||
useIntersectionObserver(root, async (entries) => {
|
||||
if (entries[0].intersectionRatio <= 0) return;
|
||||
if (isLoading.value) return;
|
||||
const scrollingParent = root.value?.closest("[data-scrolling]") || document.documentElement;
|
||||
const previousHeight = scrollingParent.scrollHeight;
|
||||
isLoading.value = true;
|
||||
await logEntry.loadMore();
|
||||
isLoading.value = false;
|
||||
await nextTick();
|
||||
if (logEntry.rememberScrollPosition) {
|
||||
scrollingParent.scrollTop += scrollingParent.scrollHeight - previousHeight;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans" v-show="container">
|
||||
<router-link
|
||||
v-if="isSearching"
|
||||
@click="resetSearch()"
|
||||
tabindex="0"
|
||||
class="btn btn-square btn-xs border-base-content/20 bg-base-100 pointer-events-auto! opacity-0 shadow-sm group-hover/entry:opacity-90"
|
||||
:to="{
|
||||
name: '/container/[id].time.[datetime]',
|
||||
params: { id: container.id, datetime: logEntry.date.toISOString() },
|
||||
query: { logId: logEntry.id },
|
||||
}"
|
||||
>
|
||||
<material-symbols:eye-tracking />
|
||||
</router-link>
|
||||
<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"
|
||||
v-else
|
||||
>
|
||||
<ion:ellipsis-vertical />
|
||||
</button>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 text-sm shadow-sm"
|
||||
@click="hideMenu"
|
||||
>
|
||||
<li v-if="isSearching">
|
||||
<router-link
|
||||
@click="resetSearch()"
|
||||
:to="{
|
||||
name: '/container/[id].time.[datetime]',
|
||||
params: { id: container.id, datetime: logEntry.date.toISOString() },
|
||||
query: { logId: logEntry.id },
|
||||
}"
|
||||
>
|
||||
<material-symbols:eye-tracking />
|
||||
{{ $t("action.see-in-context") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="isSupported">
|
||||
<a @click="copyLogMessage()">
|
||||
<material-symbols:content-copy />
|
||||
{{ $t("action.copy-log") }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="isSupported">
|
||||
<a @click="copyPermalink()">
|
||||
<material-symbols:link />
|
||||
{{ $t("action.copy-link") }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="logEntry instanceof ComplexLogEntry">
|
||||
<a @click="showDrawer(LogDetails, { entry: logEntry })">
|
||||
<material-symbols:code-blocks-rounded />
|
||||
{{ $t("action.show-details") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import stripAnsi from "strip-ansi";
|
||||
import { Container } from "@/models/Container";
|
||||
import { LogEntry, SimpleLogEntry, ComplexLogEntry, JSONObject } from "@/models/LogEntry";
|
||||
import LogDetails from "./LogDetails.vue";
|
||||
|
||||
const { logEntry, container } = defineProps<{
|
||||
logEntry: LogEntry<string | JSONObject>;
|
||||
container: Container;
|
||||
}>();
|
||||
|
||||
const { showToast } = useToast();
|
||||
const showDrawer = useDrawer();
|
||||
const router = useRouter();
|
||||
const { isSearching, resetSearch } = useSearchFilter();
|
||||
|
||||
const { copy, isSupported, copied } = useClipboard();
|
||||
const { t } = useI18n();
|
||||
|
||||
async function copyLogMessage() {
|
||||
if (logEntry instanceof ComplexLogEntry) {
|
||||
await copy(stripAnsi(logEntry.rawMessage));
|
||||
} else if (logEntry instanceof SimpleLogEntry) {
|
||||
await copy(stripAnsi(logEntry.rawMessage));
|
||||
}
|
||||
|
||||
if (copied.value) {
|
||||
showToast(
|
||||
{
|
||||
title: t("toasts.copied.title"),
|
||||
message: t("toasts.copied.message"),
|
||||
type: "info",
|
||||
},
|
||||
{ expire: 2000 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function copyPermalink() {
|
||||
const url = router.resolve({
|
||||
name: "/container/[id].time.[datetime]",
|
||||
params: { id: container.id, datetime: logEntry.date.toISOString() },
|
||||
query: { logId: logEntry.id },
|
||||
}).href;
|
||||
|
||||
const resolved = new URL(url, window.location.origin);
|
||||
|
||||
await copy(resolved.href);
|
||||
|
||||
if (copied.value) {
|
||||
showToast(
|
||||
{
|
||||
title: t("toasts.copied.title"),
|
||||
message: t("toasts.copied.message"),
|
||||
type: "info",
|
||||
},
|
||||
{ expire: 2000 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function hideMenu(e: MouseEvent) {
|
||||
if (e.target instanceof HTMLAnchorElement) {
|
||||
setTimeout(() => {
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<aside>
|
||||
<header class="flex items-center gap-4">
|
||||
<h1 class="text-2xl max-md:hidden">{{ container.name }}</h1>
|
||||
<h2 class="text-sm"><DistanceTime :date="container.created" /></h2>
|
||||
<h2 class="text-sm"><RelativeTime :date="container.created" /></h2>
|
||||
</header>
|
||||
|
||||
<div class="mt-8 flex flex-col gap-2">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<header class="flex items-center gap-4">
|
||||
<Tag :data-level="entry.level" class="text-white uppercase" v-if="entry.level">{{ entry.level }}</Tag>
|
||||
<Tag :data-level="entry.level" class="show-unknown text-white uppercase" v-if="entry.level">{{ entry.level }}</Tag>
|
||||
<h1 class="text-lg max-md:hidden">
|
||||
<DateTime :date="entry.date" />
|
||||
</h1>
|
||||
<h2 class="text-sm"><DistanceTime :date="entry.date" /> on {{ entry.std }}</h2>
|
||||
<h2 class="text-sm"><RelativeTime :date="entry.date" /> on {{ entry.std }}</h2>
|
||||
</header>
|
||||
|
||||
<div class="mt-8 flex flex-col gap-10">
|
||||
@@ -29,15 +29,15 @@
|
||||
<div class="flex gap-2">
|
||||
Raw JSON
|
||||
|
||||
<UseClipboard v-slot="{ copy, copied }" :source="JSON.stringify(entry.unfilteredMessage)">
|
||||
<UseClipboard v-slot="{ copy, copied }" :source="entry.rawMessage">
|
||||
<button class="swap outline-hidden" @click="copy()" :class="{ 'hover:swap-active': copied }">
|
||||
<mdi:check class="swap-on" />
|
||||
<mdi:content-copy class="swap-off" />
|
||||
<material-symbols:content-copy class="swap-off" />
|
||||
</button>
|
||||
</UseClipboard>
|
||||
</div>
|
||||
<div class="bg-base-200 max-h-48 overflow-scroll rounded-sm border border-white/20 p-2">
|
||||
<pre v-html="syntaxHighlight(entry.unfilteredMessage)"></pre>
|
||||
<pre v-html="syntaxHighlight(entry.rawMessage)"></pre>
|
||||
</div>
|
||||
</section>
|
||||
<table class="table-pin-rows table table-fixed" v-if="entry instanceof ComplexLogEntry">
|
||||
@@ -59,7 +59,7 @@
|
||||
{{ key.join(".") }}
|
||||
</td>
|
||||
<td class="truncate max-md:hidden">
|
||||
<code v-html="JSON.stringify(value)"></code>
|
||||
<code>{{ JSON.stringify(value) }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" class="toggle toggle-primary" :checked="enabled" @change="toggleField(key)" />
|
||||
@@ -96,14 +96,15 @@ function toggleField(key: string[]) {
|
||||
const fields = computed({
|
||||
get() {
|
||||
const fieldsWithValue: { key: string[]; value: any; enabled: boolean }[] = [];
|
||||
const allFields = flattenJSONToMap(entry.unfilteredMessage);
|
||||
const rawFields = JSON.parse(entry.rawMessage);
|
||||
const allFields = flattenJSONToMap(rawFields);
|
||||
if (visibleKeys.value.size === 0) {
|
||||
for (const [key, value] of allFields) {
|
||||
fieldsWithValue.push({ key, value, enabled: true });
|
||||
}
|
||||
} else {
|
||||
for (const [key, enabled] of visibleKeys.value) {
|
||||
const value = getDeep(entry.unfilteredMessage, key);
|
||||
const value = getDeep(rawFields, key);
|
||||
fieldsWithValue.push({ key, value, enabled });
|
||||
}
|
||||
|
||||
@@ -141,8 +142,8 @@ const toggleAllFields = computed({
|
||||
},
|
||||
});
|
||||
|
||||
function syntaxHighlight(json: any) {
|
||||
json = JSON.stringify(json, null, 2);
|
||||
function syntaxHighlight(json: string) {
|
||||
json = JSON.stringify(JSON.parse(json.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")), null, 2);
|
||||
return json.replace(
|
||||
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|\b\d+\b)/g,
|
||||
function (match: string) {
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
<template>
|
||||
<div class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<LogActions :logEntry :container />
|
||||
|
||||
<LogStd :std="logEntry.std" class="shrink-0 select-none" v-if="showStd" />
|
||||
|
||||
<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!">
|
||||
<RandomColorTag class="w-30 shrink-0 select-none md:w-40" :value="host.name" v-if="showHostname" />
|
||||
<RandomColorTag
|
||||
v-if="showContainerName"
|
||||
class="w-30 shrink-0 select-none group-[.compact]:flex-1 md:w-40"
|
||||
:value="container.name"
|
||||
v-if="showContainerName"
|
||||
truncateRight
|
||||
/>
|
||||
<LogDate :date="logEntry.date" v-if="showTimestamp" class="shrink-0 select-none" />
|
||||
<LogDate
|
||||
v-if="showTimestamp"
|
||||
:date="logEntry.date"
|
||||
class="shrink-0 select-none"
|
||||
:class="{ 'bg-secondary': route.query.logId === logEntry.id.toString() }"
|
||||
/>
|
||||
</div>
|
||||
<LogLevel
|
||||
class="flex select-none"
|
||||
@@ -33,4 +40,6 @@ const { hosts } = useHosts();
|
||||
|
||||
const container = currentContainer(toRef(() => logEntry.containerID));
|
||||
const host = computed(() => hosts.value[container.value.host]);
|
||||
|
||||
const route = useRoute();
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
<template>
|
||||
<div :data-level="level" :data-position="position" class="mt-1.5 size-2.5 flex-none rounded-lg"></div>
|
||||
<div
|
||||
:data-level="level"
|
||||
:data-position="position"
|
||||
class="mt-1.5 size-2.5 flex-none rounded-lg"
|
||||
:class="{ showUnknown }"
|
||||
></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Position } from "@/models/LogEntry";
|
||||
import { Position, Level } from "@/models/LogEntry";
|
||||
|
||||
defineProps<{
|
||||
level?: string;
|
||||
const {
|
||||
level,
|
||||
position,
|
||||
showUnknown = false,
|
||||
} = defineProps<{
|
||||
level?: Level;
|
||||
position?: Position;
|
||||
showUnknown?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -53,4 +63,8 @@ defineProps<{
|
||||
[data-level="warn"] {
|
||||
@apply !bg-orange;
|
||||
}
|
||||
|
||||
[data-level="unknown"].show-unknown {
|
||||
@apply !bg-base-300;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<ul class="events group pt-4" :class="{ 'disable-wrap': !softWrap, [size]: true, compact }">
|
||||
<ul class="group pt-4" :class="{ 'disable-wrap': !softWrap, [size]: true, compact }" data-logs>
|
||||
<li
|
||||
v-for="item in messages"
|
||||
ref="list"
|
||||
:key="item.id"
|
||||
:data-key="item.id"
|
||||
:id="item.id.toString()"
|
||||
:data-time="item.date.getTime()"
|
||||
class="group/entry"
|
||||
>
|
||||
@@ -16,20 +16,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { type JSONObject, LogEntry } from "@/models/LogEntry";
|
||||
|
||||
const { loading, progress, currentDate } = useScrollContext();
|
||||
const { progress, currentDate } = useScrollContext();
|
||||
|
||||
const { messages } = defineProps<{
|
||||
messages: LogEntry<string | JSONObject>[];
|
||||
}>();
|
||||
|
||||
watchEffect(() => {
|
||||
loading.value = messages.length === 0;
|
||||
});
|
||||
|
||||
const { containers } = useLoggingContext();
|
||||
|
||||
const list = ref<HTMLElement[]>([]);
|
||||
|
||||
let previousDate = new Date();
|
||||
useIntersectionObserver(
|
||||
list,
|
||||
(entries) => {
|
||||
@@ -40,9 +37,12 @@ useIntersectionObserver(
|
||||
const time = entry.target.getAttribute("data-time");
|
||||
if (time) {
|
||||
const date = new Date(parseInt(time));
|
||||
if (+date === +previousDate) break;
|
||||
previousDate = date;
|
||||
const diff = new Date().getTime() - container.created.getTime();
|
||||
progress.value = (date.getTime() - container.created.getTime()) / diff;
|
||||
currentDate.value = date;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ useIntersectionObserver(
|
||||
</script>
|
||||
<style scoped>
|
||||
@reference "@/main.css";
|
||||
.events {
|
||||
ul {
|
||||
font-family:
|
||||
ui-monospace,
|
||||
SFMono-Regular,
|
||||
@@ -67,7 +67,7 @@ useIntersectionObserver(
|
||||
monospace;
|
||||
|
||||
> li {
|
||||
@apply has-[.clickable]:hover:bg-primary/10 flex px-2 py-1 break-words last:snap-end odd:bg-gray-400/[0.07] has-[.clickable]:cursor-pointer md:px-4;
|
||||
@apply flex px-2 py-1 break-words last:snap-end odd:bg-gray-400/[0.07] md:px-4;
|
||||
&:last-child {
|
||||
scroll-margin-block-end: 5rem;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ useIntersectionObserver(
|
||||
}
|
||||
|
||||
&.large {
|
||||
@apply text-lg;
|
||||
@apply text-[1em];
|
||||
}
|
||||
|
||||
&.compact {
|
||||
@@ -99,6 +99,10 @@ useIntersectionObserver(
|
||||
@apply bg-secondary inline-block rounded-xs;
|
||||
animation: pops 200ms ease-out;
|
||||
}
|
||||
|
||||
:deep(a[rel~="external"]) {
|
||||
@apply text-primary underline-offset-4 hover:underline;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pops {
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<div
|
||||
class="flex min-w-[0.98rem] items-start justify-end align-bottom hover:cursor-pointer"
|
||||
v-if="isSupported"
|
||||
:title="t('log_actions.copy_log')"
|
||||
>
|
||||
<span
|
||||
class="text-primary rounded-sm bg-slate-800/60 px-1.5 py-1 hover:bg-slate-700"
|
||||
@click.prevent="copyLogMessageToClipBoard()"
|
||||
>
|
||||
<carbon:copy-file />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { LogEntry, JSONObject } from "@/models/LogEntry";
|
||||
|
||||
const { message } = defineProps<{
|
||||
message: () => string;
|
||||
logEntry: LogEntry<string | JSONObject>;
|
||||
}>();
|
||||
|
||||
const { showToast } = useToast();
|
||||
const { copy, isSupported, copied } = useClipboard();
|
||||
const { t } = useI18n();
|
||||
|
||||
async function copyLogMessageToClipBoard() {
|
||||
await copy(message());
|
||||
|
||||
if (copied.value) {
|
||||
showToast(
|
||||
{
|
||||
title: t("toasts.copied.title"),
|
||||
message: t("toasts.copied.message"),
|
||||
type: "info",
|
||||
},
|
||||
{ expire: 2000 },
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Tag size="small" :std="std" class="items-start!">
|
||||
<Tag :std="std" class="items-start!">
|
||||
{{ std }}
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
@@ -13,30 +13,6 @@ const props = defineProps<{
|
||||
const { messages, visibleKeys } = toRefs(props);
|
||||
|
||||
const { filteredPayload } = useVisibleFilter(visibleKeys);
|
||||
const { debouncedSearchFilter } = useSearchFilter();
|
||||
const { streamConfig } = useLoggingContext();
|
||||
|
||||
const visibleMessages = filteredPayload(messages);
|
||||
const router = useRouter();
|
||||
|
||||
watchEffect(() => {
|
||||
const query = {} as Record<string, string>;
|
||||
if (debouncedSearchFilter.value !== "") {
|
||||
query.search = debouncedSearchFilter.value;
|
||||
}
|
||||
|
||||
if (!streamConfig.value.stderr) {
|
||||
query.stderr = streamConfig.value.stderr.toString();
|
||||
}
|
||||
|
||||
if (!streamConfig.value.stdout) {
|
||||
query.stdout = streamConfig.value.stdout.toString();
|
||||
}
|
||||
|
||||
router.push({
|
||||
query,
|
||||
replace: true,
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<template>
|
||||
<div class="dropdown dropdown-end dropdown-hover">
|
||||
<div class="dropdown dropdown-end dropdown-hover z-20">
|
||||
<label tabindex="0" class="btn btn-ghost btn-sm gap-0.5 px-2">
|
||||
<carbon:circle-solid class="text-red w-2.5" v-if="streamConfig.stderr" />
|
||||
<carbon:circle-solid class="text-blue w-2.5" v-if="streamConfig.stdout" />
|
||||
</label>
|
||||
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 z-50 w-52 p-1 shadow-sm">
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 shadow-sm"
|
||||
@click="hideMenu"
|
||||
>
|
||||
<li>
|
||||
<a @click.prevent="clear()">
|
||||
<a @click="clear()">
|
||||
<octicon:trash-24 /> {{ $t("toolbar.clear") }}
|
||||
<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>
|
||||
<a @click.prevent="showSearch = true">
|
||||
<a @click="showSearch = true">
|
||||
<mdi:magnify /> {{ $t("toolbar.search") }}
|
||||
<KeyShortcut char="f" />
|
||||
</a>
|
||||
@@ -84,7 +88,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { showSearch } = useSearchFilter();
|
||||
|
||||
const { enableDownload } = config;
|
||||
const clear = defineEmit();
|
||||
|
||||
const { streamConfig, showHostname, showContainerName, containers } = useLoggingContext();
|
||||
@@ -100,6 +104,16 @@ const downloadUrl = computed(() =>
|
||||
`/api/containers/${containers.value.map((c) => c.host + "~" + c.id).join(",")}/download?${new URLSearchParams(downloadParams.value).toString()}`,
|
||||
),
|
||||
);
|
||||
|
||||
const hideMenu = (e: MouseEvent) => {
|
||||
if (e.target instanceof HTMLAnchorElement) {
|
||||
setTimeout(() => {
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
<template>
|
||||
<LogItem :logEntry>
|
||||
<div
|
||||
class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap"
|
||||
v-html="linkify(colorize(logEntry.message))"
|
||||
class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap"
|
||||
v-html="colorize(logEntry.message)"
|
||||
></div>
|
||||
<LogMessageActions
|
||||
class="absolute -right-1 opacity-0 transition-opacity delay-150 duration-250 group-hover/entry:opacity-100"
|
||||
:message="() => decodeXML(stripAnsi(logEntry.message))"
|
||||
:log-entry="logEntry"
|
||||
/>
|
||||
</LogItem>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { SimpleLogEntry } from "@/models/LogEntry";
|
||||
import { decodeXML } from "entities";
|
||||
import AnsiConvertor from "ansi-to-html";
|
||||
import stripAnsi from "strip-ansi";
|
||||
|
||||
const ansiConvertor = new AnsiConvertor({
|
||||
escapeXML: false,
|
||||
@@ -28,15 +21,4 @@ defineProps<{
|
||||
}>();
|
||||
|
||||
const colorize = (value: string) => ansiConvertor.toHtml(value);
|
||||
const urlPattern =
|
||||
/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_+.~#?&/=]*/g;
|
||||
const linkify = (text: string) =>
|
||||
text.replace(urlPattern, (url) => `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/main.css";
|
||||
.log-wrapper :deep(a) {
|
||||
@apply text-primary underline-offset-4 hover:underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
<div class="my-4 flex-1 text-center">
|
||||
<div class="relative">
|
||||
<ZigZag class="absolute inset-0 mt-2" />
|
||||
<span class="bg-base-200 relative px-4 py-2 font-bold whitespace-pre-wrap">
|
||||
<button class="btn btn-primary btn-xs relative whitespace-pre-wrap" @click="logEntry.loadSkippedEntries()">
|
||||
{{ $t("error.logs-skipped", { total: logEntry.totalSkipped }) }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { SkippedLogsEntry } from "@/models/LogEntry";
|
||||
|
||||
defineProps<{
|
||||
const { logEntry } = defineProps<{
|
||||
logEntry: SkippedLogsEntry;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,79 +1,117 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<ContainerEventSource /> > render html correctly > should render dates with 12 hour style 1`] = `
|
||||
"<ul data-v-cf9ff940="" class="events group pt-4 medium" show-container-name="false">
|
||||
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
"<ul data-v-cf9ff940="" class="group pt-4 medium" data-logs="" show-container-name="false">
|
||||
<li data-v-cf9ff940="" id="1560336942709" data-time="1560336942709" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="flex min-h-[1px] flex-1 content-center justify-center"><span class="loading loading-bars loading-md text-primary m-2" style="display: none;"></span></div>
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<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>
|
||||
</svg></button>
|
||||
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 text-sm shadow-sm">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</ul>
|
||||
</div>
|
||||
<!--v-if-->
|
||||
<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>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div data-v-a49e52d4="" class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
|
||||
<div data-v-a49e52d4="" class="flex gap-2 absolute -right-1 opacity-0 transition-opacity delay-150 duration-250 group-hover/entry:opacity-100">
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<ContainerEventSource /> > render html correctly > should render dates with 24 hour style 1`] = `
|
||||
"<ul data-v-cf9ff940="" class="events group pt-4 medium" show-container-name="false">
|
||||
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
"<ul data-v-cf9ff940="" class="group pt-4 medium" data-logs="" show-container-name="false">
|
||||
<li data-v-cf9ff940="" id="1560336942709" data-time="1560336942709" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="flex min-h-[1px] flex-1 content-center justify-center"><span class="loading loading-bars loading-md text-primary m-2" style="display: none;"></span></div>
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<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>
|
||||
</svg></button>
|
||||
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 text-sm shadow-sm">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</ul>
|
||||
</div>
|
||||
<!--v-if-->
|
||||
<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>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div data-v-a49e52d4="" class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
|
||||
<div data-v-a49e52d4="" class="flex gap-2 absolute -right-1 opacity-0 transition-opacity delay-150 duration-250 group-hover/entry:opacity-100">
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<ContainerEventSource /> > render html correctly > should render messages 1`] = `
|
||||
"<ul data-v-cf9ff940="" class="events group pt-4 medium" show-container-name="false">
|
||||
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
"<ul data-v-cf9ff940="" class="group pt-4 medium" data-logs="" show-container-name="false">
|
||||
<li data-v-cf9ff940="" id="1560336942709" data-time="1560336942709" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="flex min-h-[1px] flex-1 content-center justify-center"><span class="loading loading-bars loading-md text-primary m-2" style="display: none;"></span></div>
|
||||
</li>
|
||||
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
|
||||
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
|
||||
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<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>
|
||||
</svg></button>
|
||||
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 text-sm shadow-sm">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</ul>
|
||||
</div>
|
||||
<!--v-if-->
|
||||
<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>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div data-v-a49e52d4="" class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">This is a message.</div>
|
||||
<div data-v-a49e52d4="" class="flex gap-2 absolute -right-1 opacity-0 transition-opacity delay-150 duration-250 group-hover/entry:opacity-100">
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">This is a message.</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<ContainerEventSource /> > should parse messages 1`] = `
|
||||
SimpleLogEntry {
|
||||
"_message": "This is a message.",
|
||||
"containerID": undefined,
|
||||
"date": 2019-06-12T10:55:42.459Z,
|
||||
"id": 1,
|
||||
LoadMoreLogEntry {
|
||||
"_message": "",
|
||||
"containerID": "",
|
||||
"date": 2019-06-12T10:55:42.709Z,
|
||||
"id": 1560336942709,
|
||||
"level": undefined,
|
||||
"position": undefined,
|
||||
"loader": [Function],
|
||||
"rawMessage": "info",
|
||||
"rememberScrollPosition": true,
|
||||
"std": "stderr",
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-8 px-4 py-4 md:px-8">
|
||||
<div class="flex flex-col gap-5 px-4 py-4 md:px-8">
|
||||
<section>
|
||||
<Links>
|
||||
<template #more-items>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-show="show && (delayedShow || globalShow)"
|
||||
class="border-base-content/20 bg-base-100 fixed z-50 rounded-sm border p-4 shadow-sm"
|
||||
class="ring-base-content/20 bg-base-100 fixed z-50 rounded-sm p-3 shadow-sm ring"
|
||||
ref="content"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<span> % </span>
|
||||
</div>
|
||||
</div>
|
||||
<DistanceTime :date="date" class="text-sm whitespace-nowrap" />
|
||||
<RelativeTime :date="date" class="text-sm whitespace-nowrap" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
@@ -93,7 +93,7 @@ svg {
|
||||
}
|
||||
|
||||
.fadeout-leave-active {
|
||||
@apply transition-opacity;
|
||||
@apply transition-opacity duration-400;
|
||||
}
|
||||
|
||||
.fadeout-leave-to {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<section :class="{ 'h-screen min-h-0': scrollable }" class="flex flex-col">
|
||||
<header
|
||||
v-if="$slots.header"
|
||||
class="border-base-content/10 bg-base-200 sticky top-[calc(55px+env(safe-area-inset-top))] z-2 border-b py-2 shadow-[1px_1px_2px_0_rgb(0,0,0,0.05)] md:top-0"
|
||||
class="border-base-content/10 bg-base-200 sticky top-[calc(55px+env(safe-area-inset-top))] z-20 border-b py-2 shadow-[1px_1px_2px_0_rgb(0,0,0,0.05)] md:top-0"
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
</header>
|
||||
@@ -25,7 +25,7 @@
|
||||
<div ref="scrollObserver" class="h-px"></div>
|
||||
</main>
|
||||
|
||||
<div class="mr-16 text-right">
|
||||
<div class="mr-16 text-right" v-if="!historical">
|
||||
<transition name="fade">
|
||||
<button
|
||||
class="btn btn-primary text-primary-content fixed bottom-8 rounded-sm p-3 shadow-sm transition-colors"
|
||||
@@ -49,28 +49,29 @@ const scrollableContent = ref<HTMLElement>();
|
||||
|
||||
const scrollContext = provideScrollContext();
|
||||
|
||||
const { loadingMore } = useLoggingContext();
|
||||
const { loadingMore, historical } = useLoggingContext();
|
||||
if (!historical.value) {
|
||||
useIntersectionObserver(scrollObserver, ([entry]) => (scrollContext.paused = entry.intersectionRatio == 0), {
|
||||
threshold: [0, 1],
|
||||
rootMargin: "40px 0px",
|
||||
});
|
||||
|
||||
useIntersectionObserver(scrollObserver, ([entry]) => (scrollContext.paused = entry.intersectionRatio == 0), {
|
||||
threshold: [0, 1],
|
||||
rootMargin: "40px 0px",
|
||||
});
|
||||
|
||||
useMutationObserver(
|
||||
scrollableContent,
|
||||
(records) => {
|
||||
if (!scrollContext.paused) {
|
||||
scrollToBottom();
|
||||
} else {
|
||||
const record = records[records.length - 1];
|
||||
const children = (record.target as HTMLElement).children;
|
||||
if (children[children.length - 1] == record.addedNodes[record.addedNodes.length - 1]) {
|
||||
hasMore.value = true;
|
||||
useMutationObserver(
|
||||
scrollableContent,
|
||||
(records) => {
|
||||
if (!scrollContext.paused) {
|
||||
scrollToBottom();
|
||||
} else {
|
||||
const record = records[records.length - 1];
|
||||
const children = (record.target as HTMLElement).children;
|
||||
if (children[children.length - 1] == record.addedNodes[record.addedNodes.length - 1]) {
|
||||
hasMore.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ childList: true, subtree: true },
|
||||
);
|
||||
},
|
||||
{ childList: true, subtree: true },
|
||||
);
|
||||
}
|
||||
|
||||
function scrollToBottom(behavior: "auto" | "smooth" = "auto") {
|
||||
scrollObserver.value?.scrollIntoView({ behavior });
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
<template>
|
||||
<ul class="menu w-full p-0 text-[0.95rem]">
|
||||
<div class="mb-2 flex items-center">
|
||||
<div class="flex-1">
|
||||
{{ $t("label.service", services.length) }}
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<div class="dropdown dropdown-end dropdown-hover">
|
||||
<label tabindex="0" class="btn btn-square btn-ghost btn-sm">
|
||||
<ph:dots-three-vertical-bold />
|
||||
</label>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 shadow-sm"
|
||||
>
|
||||
<li>
|
||||
<a class="text-sm capitalize" @click="collapseAll()">
|
||||
<material-symbols-light:collapse-all class="w-4" />
|
||||
{{ $t("label.collapse-all") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="menu w-full p-0 text-[0.95rem]" ref="menu">
|
||||
<li v-for="{ name, services } in stacks" :key="name">
|
||||
<details open>
|
||||
<summary class="text-base-content/80 font-light">
|
||||
@@ -55,4 +78,11 @@ const store = useSwarmStore();
|
||||
const { stacks, services } = storeToRefs(store);
|
||||
|
||||
const servicesWithoutStacks = computed(() => services.value.filter((service) => !service.stack));
|
||||
|
||||
const menu = useTemplateRef("menu");
|
||||
|
||||
const collapseAll = () => {
|
||||
const details = menu.value?.querySelectorAll("details");
|
||||
details?.forEach((detail) => (detail.open = false));
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<header class="flex items-center gap-4">
|
||||
<material-symbols:terminal class="size-8" />
|
||||
<h1 class="text-2xl max-md:hidden">{{ container.name }}</h1>
|
||||
<h2 class="text-sm">Started <DistanceTime :date="container.created" /></h2>
|
||||
<h2 class="text-sm">Started <RelativeTime :date="container.created" /></h2>
|
||||
</header>
|
||||
|
||||
<div class="mt-8 flex flex-col gap-2">
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<time :datetime="date.toISOString()" data-ci-skip>{{ text }}</time>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
|
||||
import { formatDistanceToNowStrict } from "date-fns/formatDistanceToNowStrict";
|
||||
|
||||
const {
|
||||
date,
|
||||
strict = false,
|
||||
suffix = true,
|
||||
} = defineProps<{
|
||||
date: Date;
|
||||
strict?: boolean;
|
||||
suffix?: boolean;
|
||||
}>();
|
||||
|
||||
const text = ref<string>();
|
||||
|
||||
watch($$(date), updateFromNow, { immediate: true });
|
||||
|
||||
function updateFromNow() {
|
||||
const fn = strict ? formatDistanceToNowStrict : formatDistanceToNow;
|
||||
text.value = fn(date, {
|
||||
addSuffix: suffix,
|
||||
});
|
||||
}
|
||||
useIntervalFn(updateFromNow, 30_000, { immediateCallback: true });
|
||||
</script>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<details class="dropdown" ref="details" v-on-click-outside="close">
|
||||
<details class="dropdown dropdown-end" ref="details" v-on-click-outside="close">
|
||||
<summary class="btn btn-primary flex-nowrap" v-bind="$attrs">
|
||||
<slot name="trigger"> {{ label }} <carbon:caret-down /></slot>
|
||||
</summary>
|
||||
<ul
|
||||
class="menu dropdown-content rounded-box border-base-content/20 bg-base-200 z-50 mt-1 w-52 border p-2 shadow-sm"
|
||||
class="menu dropdown-content rounded-box border-base-content/20 bg-base-200 z-50 mt-1 max-h-72 w-48 flex-nowrap overflow-auto border p-2 shadow-sm"
|
||||
>
|
||||
<slot>
|
||||
<li v-for="item in options">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<label class="label text-base-content cursor-pointer gap-4">
|
||||
<div class="flex-1"><slot name="label" /></div>
|
||||
<div class="flex-1 whitespace-normal"><slot name="label" /></div>
|
||||
<slot name="input" />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<time :datetime="date.toISOString()">{{ text }}</time>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { date } = defineProps<{
|
||||
date: Date;
|
||||
}>();
|
||||
|
||||
const text = ref<string>();
|
||||
|
||||
const updateFromNow = () => {
|
||||
text.value = toRelativeTime(date, locale.value === "" ? undefined : locale.value);
|
||||
};
|
||||
watch(() => date, updateFromNow, { immediate: true });
|
||||
useIntervalFn(updateFromNow, 30_000);
|
||||
</script>
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ContainerEventLogEntry,
|
||||
ComplexLogEntry,
|
||||
SkippedLogsEntry,
|
||||
LoadMoreLogEntry,
|
||||
} from "@/models/LogEntry";
|
||||
import { Service, Stack } from "@/models/Stack";
|
||||
import { Container, GroupedContainers } from "@/models/Container";
|
||||
@@ -22,8 +23,7 @@ function parseMessage(data: string): LogEntry<string | JSONObject> {
|
||||
|
||||
export function useContainerStream(container: Ref<Container>): LogStreamSource {
|
||||
const url = computed(() => `/api/hosts/${container.value.host}/containers/${container.value.id}/logs/stream`);
|
||||
const loadMoreUrl = computed(() => `/api/hosts/${container.value.host}/containers/${container.value.id}/logs`);
|
||||
return useLogStream(url, loadMoreUrl);
|
||||
return useLogStream(url, container);
|
||||
}
|
||||
|
||||
export function useHostStream(host: Ref<Host>): LogStreamSource {
|
||||
@@ -53,13 +53,15 @@ export function useServiceStream(service: Ref<Service>): LogStreamSource {
|
||||
|
||||
export type LogStreamSource = ReturnType<typeof useLogStream>;
|
||||
|
||||
function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
function useLogStream(url: Ref<string>, container?: Ref<Container>) {
|
||||
const messages: ShallowRef<LogEntry<string | JSONObject>[]> = shallowRef([]);
|
||||
const buffer: ShallowRef<LogEntry<string | JSONObject>[]> = shallowRef([]);
|
||||
const opened = ref(false);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const { paused: scrollingPaused } = useScrollContext();
|
||||
const { streamConfig, hasComplexLogs, levels, loadingMore } = useLoggingContext();
|
||||
let initial = true;
|
||||
|
||||
function flushNow() {
|
||||
if (messages.value.length + buffer.value.length > config.maxLogs) {
|
||||
@@ -71,10 +73,9 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
} else {
|
||||
const firstItem = buffer.value.at(0) as LogEntry<string | JSONObject>;
|
||||
const lastItem = buffer.value.at(-1) as LogEntry<string | JSONObject>;
|
||||
|
||||
messages.value = [
|
||||
...messages.value,
|
||||
new SkippedLogsEntry(new Date(), buffer.value.length, firstItem, lastItem),
|
||||
new SkippedLogsEntry(new Date(), buffer.value.length, firstItem, lastItem, loadSkippedLogs),
|
||||
];
|
||||
}
|
||||
buffer.value = [];
|
||||
@@ -87,9 +88,15 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
buffer.value = [];
|
||||
}
|
||||
} else {
|
||||
if (messages.value.length == 0) {
|
||||
if (initial) {
|
||||
// sort the buffer the very first time because of multiple logs in parallel
|
||||
buffer.value.sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||
|
||||
if (container) {
|
||||
const loadMoreItem = new LoadMoreLogEntry(new Date(), loadOlderLogs);
|
||||
messages.value = [loadMoreItem];
|
||||
}
|
||||
initial = false;
|
||||
}
|
||||
messages.value = [...messages.value, ...buffer.value];
|
||||
buffer.value = [];
|
||||
@@ -111,8 +118,6 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
buffer.value = [];
|
||||
}
|
||||
|
||||
const { streamConfig, hasComplexLogs, levels } = useLoggingContext();
|
||||
|
||||
const params = computed(() => {
|
||||
const params = new URLSearchParams();
|
||||
if (streamConfig.value.stdout) params.append("stdout", "1");
|
||||
@@ -132,6 +137,7 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
opened.value = false;
|
||||
loading.value = true;
|
||||
error.value = false;
|
||||
initial = true;
|
||||
es = new EventSource(urlWithParams.value);
|
||||
es.addEventListener("container-event", (e) => {
|
||||
const event = JSON.parse((e as MessageEvent).data) as {
|
||||
@@ -175,46 +181,48 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
|
||||
watch(urlWithParams, () => connect(), { immediate: true });
|
||||
|
||||
const isLoadingMore = ref(false);
|
||||
async function loadOlderLogs(entry: LoadMoreLogEntry) {
|
||||
if (!(messages.value[0] instanceof LoadMoreLogEntry)) throw new Error("No loadMoreLogEntry on first item");
|
||||
if (!container) throw new Error("No container");
|
||||
|
||||
async function loadOlderLogs() {
|
||||
if (!loadMoreUrl) return;
|
||||
if (isLoadingMore.value) return;
|
||||
|
||||
const to = messages.value[0].date;
|
||||
const lastSeenId = messages.value[0].id;
|
||||
const [loader, ...existingLogs] = messages.value;
|
||||
const to = existingLogs[0].date;
|
||||
const lastSeenId = existingLogs[0].id;
|
||||
const last = messages.value[Math.min(messages.value.length - 1, 300)].date;
|
||||
const delta = to.getTime() - last.getTime();
|
||||
const from = new Date(to.getTime() + delta);
|
||||
|
||||
const abortController = new AbortController();
|
||||
const signal = abortController.signal;
|
||||
isLoadingMore.value = true;
|
||||
try {
|
||||
const urlWithMoreParams = computed(() => {
|
||||
const loadMoreParams = new URLSearchParams(params.value);
|
||||
loadMoreParams.append("from", from.toISOString());
|
||||
loadMoreParams.append("to", to.toISOString());
|
||||
loadMoreParams.append("minimum", "100");
|
||||
loadMoreParams.append("lastSeenId", String(lastSeenId));
|
||||
|
||||
return withBase(`${loadMoreUrl.value}?${loadMoreParams.toString()}`);
|
||||
loadingMore.value = true;
|
||||
const { logs: newLogs, signal } = await loadBetween(container, params, from, to, {
|
||||
min: 100,
|
||||
lastSeenId,
|
||||
});
|
||||
const stopWatcher = watchOnce(urlWithMoreParams, () => abortController.abort("stream changed"));
|
||||
const logs = await (await fetch(urlWithMoreParams.value, { signal })).text();
|
||||
stopWatcher();
|
||||
|
||||
if (logs && signal.aborted === false) {
|
||||
const newMessages = logs
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((line) => parseMessage(line));
|
||||
messages.value = [...newMessages, ...messages.value];
|
||||
if (newLogs && signal.aborted === false) {
|
||||
messages.value = [loader, ...newLogs, ...existingLogs];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading older logs", e);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
isLoadingMore.value = false;
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSkippedLogs(entry: SkippedLogsEntry) {
|
||||
if (!container) throw new Error("No container");
|
||||
|
||||
const from = entry.firstSkipped.date;
|
||||
const to = entry.lastSkippedLog.date;
|
||||
const lastSeenId = entry.lastSkippedLog.id;
|
||||
try {
|
||||
loadingMore.value = true;
|
||||
const { logs, signal } = await loadBetween(container, params, from, to, { lastSeenId });
|
||||
if (logs && signal.aborted === false) {
|
||||
messages.value = messages.value.slice(logs.length).flatMap((log) => (log === entry ? logs : [log]));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,12 +236,49 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
|
||||
|
||||
return {
|
||||
messages,
|
||||
loadOlderLogs,
|
||||
isLoadingMore,
|
||||
hasComplexLogs,
|
||||
opened,
|
||||
error,
|
||||
loading,
|
||||
eventSourceURL: urlWithParams,
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadBetween(
|
||||
container: Ref<Container>,
|
||||
params: Ref<URLSearchParams>,
|
||||
from: Date,
|
||||
to: Date,
|
||||
{ lastSeenId, min, maxStart }: { lastSeenId?: number; min?: number; maxStart?: number } = {},
|
||||
) {
|
||||
const url = computed(() => `/api/hosts/${container.value.host}/containers/${container.value.id}/logs`);
|
||||
const abortController = new AbortController();
|
||||
const signal = abortController.signal;
|
||||
|
||||
const urlWithMoreParams = computed(() => {
|
||||
const loadMoreParams = new URLSearchParams(params.value);
|
||||
loadMoreParams.append("from", from.toISOString());
|
||||
loadMoreParams.append("to", to.toISOString());
|
||||
if (min) {
|
||||
loadMoreParams.append("min", String(min));
|
||||
}
|
||||
if (maxStart) {
|
||||
loadMoreParams.append("maxStart", String(maxStart));
|
||||
}
|
||||
if (lastSeenId) {
|
||||
loadMoreParams.append("lastSeenId", String(lastSeenId));
|
||||
}
|
||||
return withBase(`${url.value}?${loadMoreParams.toString()}`);
|
||||
});
|
||||
const stopWatcher = watchOnce(urlWithMoreParams, () => abortController.abort("stream changed"));
|
||||
const logs = await (await fetch(urlWithMoreParams.value, { signal })).text();
|
||||
stopWatcher();
|
||||
|
||||
if (!logs) return { logs: [], signal };
|
||||
|
||||
return {
|
||||
logs: logs
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((line) => parseMessage(line)),
|
||||
signal,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { HistoricalContainer } from "@/models/Container";
|
||||
import { JSONObject, LoadMoreLogEntry, LogEntry } from "@/models/LogEntry";
|
||||
import { ShallowRef } from "vue";
|
||||
import { loadBetween } from "@/composable/eventStreams";
|
||||
|
||||
export function useHistoricalContainerLog(historicalContainer: Ref<HistoricalContainer>): LogStreamSource {
|
||||
const messages: ShallowRef<LogEntry<string | JSONObject>[]> = shallowRef([]);
|
||||
const opened = ref(false);
|
||||
const loading = ref(true);
|
||||
const error = ref(false);
|
||||
const container = toRef(() => historicalContainer.value.container);
|
||||
|
||||
const { streamConfig, levels, loadingMore } = useLoggingContext();
|
||||
const { isSearching, debouncedSearchFilter } = useSearchFilter();
|
||||
|
||||
const params = computed(() => {
|
||||
const params = new URLSearchParams();
|
||||
if (streamConfig.value.stdout) params.append("stdout", "1");
|
||||
if (streamConfig.value.stderr) params.append("stderr", "1");
|
||||
if (isSearching.value) params.append("filter", debouncedSearchFilter.value);
|
||||
for (const level of levels.value) {
|
||||
params.append("levels", level);
|
||||
}
|
||||
return params;
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
async function loadLogs() {
|
||||
loadingMore.value = true;
|
||||
try {
|
||||
const lastSeenId = route.query.logId ? +route.query.logId : undefined;
|
||||
const [{ logs: before }, { logs: after }] = await Promise.all([
|
||||
loadBetween(
|
||||
container,
|
||||
params,
|
||||
new Date(historicalContainer.value.date.getTime() - 1000 * 60 * 5),
|
||||
new Date(historicalContainer.value.date.getTime() + 1000),
|
||||
{
|
||||
min: 50,
|
||||
lastSeenId,
|
||||
},
|
||||
),
|
||||
loadBetween(container, params, historicalContainer.value.date, new Date(), {
|
||||
maxStart: 50,
|
||||
}),
|
||||
]);
|
||||
const loaderOlder = new LoadMoreLogEntry(new Date(), loadOlderLogs);
|
||||
const loadNewer = new LoadMoreLogEntry(new Date(), loadNewerLogs, false);
|
||||
messages.value = [loaderOlder, ...before, ...after, loadNewer];
|
||||
loading.value = false;
|
||||
opened.value = true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watchArray([params, container], loadLogs, { immediate: true });
|
||||
|
||||
async function loadOlderLogs(entry: LoadMoreLogEntry) {
|
||||
loadingMore.value = true;
|
||||
try {
|
||||
const item = messages.value[1];
|
||||
const { logs, signal } = await loadBetween(
|
||||
container,
|
||||
params,
|
||||
new Date(item.date.getTime() - 1000 * 60 * 5),
|
||||
item.date,
|
||||
{
|
||||
min: 200,
|
||||
lastSeenId: item.id,
|
||||
},
|
||||
);
|
||||
|
||||
if (signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!logs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [loader, ...rest] = messages.value;
|
||||
messages.value = [loader, ...logs, ...rest];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNewerLogs(entry: LoadMoreLogEntry) {
|
||||
loadingMore.value = true;
|
||||
try {
|
||||
const item = messages.value.at(-2)!;
|
||||
const { logs, signal } = await loadBetween(container, params, item.date, new Date(), {
|
||||
maxStart: 100,
|
||||
});
|
||||
|
||||
if (signal.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!logs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loader = messages.value.at(-1)!;
|
||||
const rest = messages.value.slice(0, -1);
|
||||
messages.value = [...rest, ...logs, loader];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
loadingMore.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
messages,
|
||||
opened,
|
||||
error,
|
||||
loading,
|
||||
};
|
||||
}
|
||||
@@ -9,6 +9,7 @@ type LogContext = {
|
||||
levels: Set<Level>;
|
||||
showContainerName: boolean;
|
||||
showHostname: boolean;
|
||||
historical: boolean;
|
||||
};
|
||||
|
||||
export const allLevels: Level[] = ["info", "debug", "warn", "error", "fatal", "trace", "unknown"];
|
||||
@@ -21,7 +22,7 @@ const stderr = searchParams.has("stderr") ? searchParams.get("stderr") === "true
|
||||
|
||||
export const provideLoggingContext = (
|
||||
containers: Ref<Container[]>,
|
||||
{ showContainerName = false, showHostname = false } = {},
|
||||
{ showContainerName = false, showHostname = false, historical = false } = {},
|
||||
) => {
|
||||
provide(
|
||||
loggingContextKey,
|
||||
@@ -33,6 +34,7 @@ export const provideLoggingContext = (
|
||||
levels: new Set<Level>(allLevels),
|
||||
showContainerName,
|
||||
showHostname,
|
||||
historical,
|
||||
}),
|
||||
);
|
||||
};
|
||||
@@ -48,6 +50,7 @@ export const useLoggingContext = () => {
|
||||
levels: new Set<Level>(allLevels),
|
||||
showContainerName: false,
|
||||
showHostname: false,
|
||||
historical: false,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
type ScrollContext = {
|
||||
loading: boolean;
|
||||
paused: boolean;
|
||||
progress: number;
|
||||
currentDate: Date;
|
||||
@@ -9,20 +8,18 @@ type ScrollContext = {
|
||||
export const scrollContextKey = Symbol("scrollContext") as InjectionKey<ScrollContext>;
|
||||
|
||||
export const provideScrollContext = () => {
|
||||
const context = defauleValue();
|
||||
const context = defaultValue();
|
||||
provide(scrollContextKey, context);
|
||||
return context;
|
||||
};
|
||||
|
||||
export const useScrollContext = () => {
|
||||
const defaultValue = defauleValue();
|
||||
const context = inject(scrollContextKey, defaultValue);
|
||||
const context = inject(scrollContextKey, defaultValue());
|
||||
return toRefs(context);
|
||||
};
|
||||
|
||||
function defauleValue() {
|
||||
function defaultValue() {
|
||||
return reactive({
|
||||
loading: false,
|
||||
paused: false,
|
||||
progress: 1,
|
||||
currentDate: new Date(),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@theme {
|
||||
--color-green: oklch(69% 0.119722 188.479048);
|
||||
--color-green: oklch(0.62 0.119722 158.82);
|
||||
--color-red: oklch(64% 0.218 28.85);
|
||||
--color-purple: oklch(51.49% 0.215 321.03);
|
||||
--color-blue: oklch(65% 0.171 249.5);
|
||||
@@ -143,3 +143,7 @@ body {
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
|
||||
[class*="shadow-"] {
|
||||
@apply shadow-base-content/8;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,13 @@ export class GroupedContainers {
|
||||
) {}
|
||||
}
|
||||
|
||||
export class HistoricalContainer {
|
||||
constructor(
|
||||
public readonly container: Container,
|
||||
public readonly date: Date,
|
||||
) {}
|
||||
}
|
||||
|
||||
export class Container {
|
||||
private _stat: Ref<Stat>;
|
||||
private _name: string;
|
||||
@@ -73,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() {
|
||||
|
||||
@@ -4,6 +4,7 @@ import ComplexLogItem from "@/components/LogViewer/ComplexLogItem.vue";
|
||||
import SimpleLogItem from "@/components/LogViewer/SimpleLogItem.vue";
|
||||
import ContainerEventLogItem from "@/components/LogViewer/ContainerEventLogItem.vue";
|
||||
import SkippedEntriesLogItem from "@/components/LogViewer/SkippedEntriesLogItem.vue";
|
||||
import LoadMoreLogItem from "@/components/LogViewer/LoadMoreLogItem.vue";
|
||||
|
||||
export type JSONValue = string | number | boolean | JSONObject | Array<JSONValue>;
|
||||
export type JSONObject = { [x: string]: JSONValue };
|
||||
@@ -28,6 +29,7 @@ export interface LogEvent {
|
||||
readonly p: Position;
|
||||
readonly s: "stdout" | "stderr" | "unknown";
|
||||
readonly c: string;
|
||||
readonly rm: string;
|
||||
}
|
||||
|
||||
export abstract class LogEntry<T extends string | JSONObject> {
|
||||
@@ -38,6 +40,7 @@ export abstract class LogEntry<T extends string | JSONObject> {
|
||||
public readonly id: number,
|
||||
public readonly date: Date,
|
||||
public readonly std: Std,
|
||||
public readonly rawMessage: string,
|
||||
public readonly level?: Level,
|
||||
) {
|
||||
this._message = message;
|
||||
@@ -59,8 +62,9 @@ export class SimpleLogEntry extends LogEntry<string> {
|
||||
public readonly level: Level,
|
||||
public readonly position: Position,
|
||||
public readonly std: Std,
|
||||
public readonly rawMessage: string,
|
||||
) {
|
||||
super(message, containerID, id, date, std, level);
|
||||
super(message, containerID, id, date, std, rawMessage, level);
|
||||
}
|
||||
getComponent(): Component {
|
||||
return SimpleLogItem;
|
||||
@@ -77,9 +81,10 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
|
||||
date: Date,
|
||||
public readonly level: Level,
|
||||
public readonly std: Std,
|
||||
public readonly rawMessage: string,
|
||||
visibleKeys?: Ref<Map<string[], boolean>>,
|
||||
) {
|
||||
super(message, containerID, id, date, std, level);
|
||||
super(message, containerID, id, date, std, rawMessage, level);
|
||||
if (visibleKeys) {
|
||||
this.filteredMessage = computed(() => {
|
||||
if (visibleKeys.value.size === 0) {
|
||||
@@ -123,6 +128,7 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
|
||||
event.date,
|
||||
event.level,
|
||||
event.std,
|
||||
event.rawMessage,
|
||||
visibleKeys,
|
||||
);
|
||||
}
|
||||
@@ -143,7 +149,7 @@ export class ContainerEventLogEntry extends LogEntry<string> {
|
||||
}
|
||||
|
||||
export class SkippedLogsEntry extends LogEntry<string> {
|
||||
private _totalSkipped = 0;
|
||||
private _totalSkipped = ref(0);
|
||||
private lastSkipped: LogEntry<string | JSONObject>;
|
||||
|
||||
constructor(
|
||||
@@ -151,9 +157,10 @@ export class SkippedLogsEntry extends LogEntry<string> {
|
||||
totalSkipped: number,
|
||||
public readonly firstSkipped: LogEntry<string | JSONObject>,
|
||||
lastSkipped: LogEntry<string | JSONObject>,
|
||||
private readonly loader: (i: SkippedLogsEntry) => Promise<void>,
|
||||
) {
|
||||
super("", "", date.getTime(), date, "stderr", "info");
|
||||
this._totalSkipped = totalSkipped;
|
||||
this._totalSkipped.value = totalSkipped;
|
||||
this.lastSkipped = lastSkipped;
|
||||
}
|
||||
getComponent(): Component {
|
||||
@@ -161,20 +168,42 @@ export class SkippedLogsEntry extends LogEntry<string> {
|
||||
}
|
||||
|
||||
public get message(): string {
|
||||
return `Skipped ${this.totalSkipped} entries`;
|
||||
return `Skipped ${this._totalSkipped.value} entries`;
|
||||
}
|
||||
|
||||
public addSkippedEntries(totalSkipped: number, lastItem: LogEntry<string | JSONObject>) {
|
||||
this._totalSkipped += totalSkipped;
|
||||
this._totalSkipped.value += totalSkipped;
|
||||
this.lastSkipped = lastItem;
|
||||
}
|
||||
|
||||
public get totalSkipped(): number {
|
||||
return this._totalSkipped;
|
||||
public get lastSkippedLog(): LogEntry<string | JSONObject> {
|
||||
return this.lastSkipped;
|
||||
}
|
||||
|
||||
public get lastSkippedItem(): LogEntry<string | JSONObject> {
|
||||
return this.lastSkipped;
|
||||
public async loadSkippedEntries(): Promise<void> {
|
||||
await this.loader(this);
|
||||
}
|
||||
|
||||
public get totalSkipped(): number {
|
||||
return unref(this._totalSkipped);
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadMoreLogEntry extends LogEntry<string> {
|
||||
constructor(
|
||||
date: Date,
|
||||
private readonly loader: (i: LoadMoreLogEntry) => Promise<void>,
|
||||
public readonly rememberScrollPosition: boolean = true,
|
||||
) {
|
||||
super("", "", date.getTime(), date, "stderr", "info");
|
||||
}
|
||||
|
||||
getComponent(): Component {
|
||||
return LoadMoreLogItem;
|
||||
}
|
||||
|
||||
async loadMore(): Promise<void> {
|
||||
await this.loader(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +216,7 @@ export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
|
||||
new Date(event.ts),
|
||||
event.l,
|
||||
event.s === "unknown" ? "stderr" : (event.s ?? "stderr"),
|
||||
event.rm,
|
||||
);
|
||||
} else {
|
||||
return new SimpleLogEntry(
|
||||
@@ -197,6 +227,7 @@ export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
|
||||
event.l,
|
||||
event.p,
|
||||
event.s === "unknown" ? "stderr" : (event.s ?? "stderr"),
|
||||
event.rm,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<HistoricalContainerLog :id :date show-title :scrollable="pinnedLogs.length > 0" v-if="currentContainer" />
|
||||
<div v-else-if="ready" class="hero bg-base-200 min-h-screen">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<p class="py-6 text-2xl font-bold">{{ $t("error.container-not-found") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const route = useRoute("/container/[id].time.[datetime]");
|
||||
const id = toRef(() => route.params.id);
|
||||
const date = toRef(() => new Date(route.params.datetime));
|
||||
const containerStore = useContainerStore();
|
||||
const currentContainer = containerStore.currentContainer(id);
|
||||
const { ready } = storeToRefs(containerStore);
|
||||
const pinnedLogsStore = usePinnedLogsStore();
|
||||
const { pinnedLogs } = storeToRefs(pinnedLogsStore);
|
||||
|
||||
watchEffect(() => {
|
||||
if (ready.value) {
|
||||
if (currentContainer.value) {
|
||||
setTitle(currentContainer.value.name);
|
||||
} else {
|
||||
setTitle("Not Found");
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
menu: host
|
||||
</route>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Search />
|
||||
<ContainerLog :id="id" :show-title="true" :scrollable="pinnedLogs.length > 0" v-if="currentContainer" />
|
||||
<ContainerLog :id show-title :scrollable="pinnedLogs.length > 0" v-if="currentContainer" />
|
||||
<div v-else-if="ready" class="hero bg-base-200 min-h-screen">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
@@ -39,6 +39,7 @@ watch(currentContainer, () => (redirectTrigger.value = false));
|
||||
|
||||
watchEffect(() => {
|
||||
if (redirectTrigger.value) return;
|
||||
if (automaticRedirect.value === "none") return;
|
||||
if (!currentContainer.value) return;
|
||||
if (currentContainer.value.state === "running") return;
|
||||
if (Date.now() - +currentContainer.value.finishedAt > 5 * 60 * 1000) return;
|
||||
@@ -49,7 +50,7 @@ watchEffect(() => {
|
||||
|
||||
if (!nextContainer) return;
|
||||
|
||||
if (automaticRedirect.value) {
|
||||
if (automaticRedirect.value === "delayed") {
|
||||
redirectTrigger.value = true;
|
||||
showToast(
|
||||
{
|
||||
@@ -73,6 +74,16 @@ watchEffect(() => {
|
||||
},
|
||||
{ timed: 4000 },
|
||||
);
|
||||
} else {
|
||||
router.push({ name: "/container/[id]", params: { id: nextContainer.id } });
|
||||
showToast(
|
||||
{
|
||||
title: t("alert.redirected.title"),
|
||||
message: t("alert.redirected.message", { containerId: nextContainer.id }),
|
||||
type: "info",
|
||||
},
|
||||
{ expire: 3000 },
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -5,12 +5,35 @@
|
||||
<h2>{{ $t("settings.about") }}</h2>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex flex-row gap-2">
|
||||
<span v-html="$t('settings.using-version', { version: config.version })"></span>
|
||||
<div
|
||||
<span
|
||||
v-if="hasRelease"
|
||||
v-html="$t('settings.update-available', { nextVersion: latestRelease?.name, href: latestRelease?.htmlUrl })"
|
||||
></div>
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ $t("settings.help-support") }}
|
||||
|
||||
<ul class="mt-6 flex gap-2">
|
||||
<li>
|
||||
<a href="https://github.com/amir20/dozzle" target="_blank" rel="noopener noreferrer" class="btn">
|
||||
<mdi:github /> amir20/dozzle
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://buymeacoffee.com/amirraminfar"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
<mdi:beer />
|
||||
Buy me a beer
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -119,13 +142,28 @@
|
||||
<div class="has-underline">
|
||||
<h2>{{ $t("settings.options") }}</h2>
|
||||
</div>
|
||||
|
||||
<LabeledInput>
|
||||
<template #label>
|
||||
{{ $t("settings.automatic-redirect") }}
|
||||
</template>
|
||||
<template #input>
|
||||
<DropdownMenu
|
||||
v-model="automaticRedirect"
|
||||
:options="[
|
||||
{ label: 'Instant', value: 'instant' },
|
||||
{ label: 'Delayed', value: 'delayed' },
|
||||
{ label: 'None', value: 'none' },
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</LabeledInput>
|
||||
|
||||
<Toggle v-model="search">
|
||||
{{ $t("settings.search") }} <key-shortcut char="f" class="align-top"></key-shortcut>
|
||||
</Toggle>
|
||||
|
||||
<Toggle v-model="showAllContainers">{{ $t("settings.show-stopped-containers") }}</Toggle>
|
||||
|
||||
<Toggle v-model="automaticRedirect">{{ $t("settings.automatic-redirect") }}</Toggle>
|
||||
</section>
|
||||
</PageWithLinks>
|
||||
</template>
|
||||
@@ -166,8 +204,8 @@ const hoursAgo = (hours: number) => {
|
||||
const fakeMessages = computedWithControl(
|
||||
() => i18n.global.locale.value,
|
||||
() => [
|
||||
new SimpleLogEntry(t("settings.log.preview"), "123", 1, hoursAgo(16), "info", undefined, "stdout"),
|
||||
new SimpleLogEntry(t("settings.log.warning"), "123", 2, hoursAgo(12), "warn", undefined, "stdout"),
|
||||
new SimpleLogEntry(t("settings.log.preview"), "123", 1, hoursAgo(16), "info", undefined, "stdout", ""),
|
||||
new SimpleLogEntry(t("settings.log.warning"), "123", 2, hoursAgo(12), "warn", undefined, "stdout", ""),
|
||||
new SimpleLogEntry(
|
||||
t("settings.log.multi-line-error.start-line"),
|
||||
"123",
|
||||
@@ -176,6 +214,7 @@ const fakeMessages = computedWithControl(
|
||||
"error",
|
||||
"start",
|
||||
"stderr",
|
||||
"",
|
||||
),
|
||||
new SimpleLogEntry(
|
||||
t("settings.log.multi-line-error.middle-line"),
|
||||
@@ -185,8 +224,9 @@ const fakeMessages = computedWithControl(
|
||||
"error",
|
||||
"middle",
|
||||
"stderr",
|
||||
"",
|
||||
),
|
||||
new SimpleLogEntry(t("settings.log.multi-line-error.end-line"), "123", 5, new Date(), "error", "end", "stderr"),
|
||||
new SimpleLogEntry(t("settings.log.multi-line-error.end-line"), "123", 5, new Date(), "error", "end", "stderr", ""),
|
||||
new ComplexLogEntry(
|
||||
{
|
||||
message: t("settings.log.complex"),
|
||||
@@ -200,8 +240,9 @@ const fakeMessages = computedWithControl(
|
||||
new Date(),
|
||||
"info",
|
||||
"stdout",
|
||||
"",
|
||||
),
|
||||
new SimpleLogEntry(t("settings.log.simple"), "123", 7, new Date(), "debug", undefined, "stderr"),
|
||||
new SimpleLogEntry(t("settings.log.simple"), "123", 7, new Date(), "debug", undefined, "stderr", ""),
|
||||
],
|
||||
);
|
||||
</script>
|
||||
@@ -209,14 +250,14 @@ const fakeMessages = computedWithControl(
|
||||
@reference "@/main.css";
|
||||
|
||||
.has-underline {
|
||||
@apply border-base-content/50 mb-4 border-b py-4;
|
||||
@apply border-base-content/50 mb-4 border-b py-2;
|
||||
|
||||
h2 {
|
||||
@apply text-2xl;
|
||||
@apply text-3xl;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(a:not(.menu a)) {
|
||||
:deep(a:not(.menu a):not(.btn)) {
|
||||
@apply text-primary underline-offset-4 hover:underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -14,7 +14,7 @@ export type Settings = {
|
||||
dateLocale: "auto" | "en-US" | "en-GB" | "de-DE" | "en-CA";
|
||||
softWrap: boolean;
|
||||
collapseNav: boolean;
|
||||
automaticRedirect: boolean;
|
||||
automaticRedirect: "instant" | "delayed" | "none";
|
||||
locale: string;
|
||||
};
|
||||
export const DEFAULT_SETTINGS: Settings = {
|
||||
@@ -31,12 +31,20 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
dateLocale: "auto",
|
||||
softWrap: true,
|
||||
collapseNav: false,
|
||||
automaticRedirect: true,
|
||||
automaticRedirect: "delayed",
|
||||
locale: "",
|
||||
};
|
||||
|
||||
export const settings = useProfileStorage("settings", DEFAULT_SETTINGS);
|
||||
|
||||
// @ts-ignore: automaticRedirect is now a string enum, but might be a boolean in older data
|
||||
if (settings.value.automaticRedirect === true) {
|
||||
settings.value.automaticRedirect = "delayed";
|
||||
// @ts-ignore: automaticRedirect is now a string enum, but might be a boolean in older data
|
||||
} else if (settings.value.automaticRedirect === false) {
|
||||
settings.value.automaticRedirect = "none";
|
||||
}
|
||||
|
||||
export const {
|
||||
collapseNav,
|
||||
compact,
|
||||
|
||||
@@ -21,6 +21,7 @@ declare module 'vue-router/auto-routes' {
|
||||
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
||||
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
|
||||
'/container/[id]': RouteRecordInfo<'/container/[id]', '/container/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
||||
'/container/[id].time.[datetime]': RouteRecordInfo<'/container/[id].time.[datetime]', '/container/:id/time/:datetime', { id: ParamValue<true>, datetime: ParamValue<true> }, { id: ParamValue<false>, datetime: ParamValue<false> }>,
|
||||
'/group/[name]': RouteRecordInfo<'/group/[name]', '/group/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
|
||||
'/host/[id]': RouteRecordInfo<'/host/[id]', '/host/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
|
||||
'/login': RouteRecordInfo<'/login', '/login', Record<never, never>, Record<never, never>>,
|
||||
@@ -30,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
|
||||
}
|
||||
|
||||
@@ -109,3 +109,27 @@ export function hashCode(str: string) {
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
const units: [Intl.RelativeTimeFormatUnit, number][] = [
|
||||
["year", 31536000],
|
||||
["month", 2592000],
|
||||
["week", 604800],
|
||||
["day", 86400],
|
||||
["hour", 3600],
|
||||
["minute", 60],
|
||||
["second", 1],
|
||||
];
|
||||
|
||||
export function toRelativeTime(date: Date, locale: string | undefined): string {
|
||||
const diffInSeconds = (date.getTime() - new Date().getTime()) / 1000;
|
||||
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
||||
|
||||
for (const [unit, seconds] of units) {
|
||||
const value = Math.round(diffInSeconds / seconds);
|
||||
if (Math.abs(value) >= 1) {
|
||||
return rtf.format(value, unit);
|
||||
}
|
||||
}
|
||||
|
||||
return rtf.format(0, "second");
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
|
||||
playwright:
|
||||
container_name: playwright
|
||||
image: mcr.microsoft.com/playwright:v1.52.0-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.56.0-jammy
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
|
||||
@@ -57,6 +57,7 @@ export default defineConfig({
|
||||
items: [
|
||||
{ text: "What is Dozzle?", link: "/guide/what-is-dozzle" },
|
||||
{ text: "Getting Started", link: "/guide/getting-started" },
|
||||
{ text: "Introducing dtop 🚀", link: "/guide/dtop" },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -32,6 +32,10 @@ services:
|
||||
|
||||
:::
|
||||
|
||||
> [!NOTE] Docker Socket Proxy users
|
||||
> If you are using a remote agent you **CANNOT** add a socket proxy on top of the agent. Dozzle agents **REPLACE** using a proxy, see [Remote Hosts](/guide/remote-hosts.md) for more info and how to use a socket proxy instead of an agent.
|
||||
|
||||
|
||||
The agent will start and listen on port `7007`. You can connect to the agent using the Dozzle UI by providing the agent's IP address and port. The agent will only show the containers that are available on the host where the agent is running.
|
||||
|
||||
> [!TIP]
|
||||
|
||||
@@ -25,9 +25,10 @@ users:
|
||||
admin:
|
||||
email: me@email.net
|
||||
name: Admin
|
||||
# Generate with docker run run -it --rm amir20/dozzle generate --name Admin --email me@email.net --password secret admin
|
||||
# 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:
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Introducing dtop
|
||||
---
|
||||
|
||||
# What is dtop?
|
||||
|
||||
`dtop` is a command-line tool that provides a real-time view of the Docker containers running on your system. It is a lightweight alternative to the `docker ps` command, and it is designed to be used in a terminal or command prompt. `dtop` supports connecting to multiple hosts via `ssh`, `tcp` or `unix socket`. It also integrates with Dozzle by providing a quick way to open logs quickly.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
Full installation instructions can be found at [https://github.com/amir20/dtop](https://github.com/amir20/dtop?tab=readme-ov-file#installation). You can also install using brew:
|
||||
|
||||
```bash
|
||||
brew install amir20/homebrew-dtop/dtop
|
||||
```
|
||||
|
||||
## Project Status
|
||||
|
||||
`dtop` is a new project and not feature rich as Dozzle. However, I am actively working on adding more features. I use it personally to monitor all my containers across multiple hosts on the command line. If you have suggestions then please open issues at [https://github.com/amir20/dtop/issues](https://github.com/amir20/dtop/issues).
|
||||
@@ -28,11 +28,22 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
# Uncomment to enable container actions (stop, start, restart). See https://dozzle.dev/guide/actions
|
||||
# - DOZZLE_ENABLE_ACTIONS=true
|
||||
#
|
||||
# Uncomment to allow access to container shells. See https://dozzle.dev/guide/shell
|
||||
# - DOZZLE_ENABLE_SHELL=true
|
||||
#
|
||||
# Uncomment to enable authentication. See https://dozzle.dev/guide/authentication
|
||||
# - DOZZLE_AUTH_PROVIDER=simple
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Dozzle also supports connecting to remote agents to monitor multiple Docker hosts. See [agent](/guide/agent) to learn more.
|
||||
> [!TIP]
|
||||
> Dozzle supports actions, such as stopping, starting, and restarting containers, or attaching to container shells. But they are disabled by default for security reasons. To enable them, uncomment the corresponding environment variables.
|
||||
> Dozzle also supports connecting to remote agents to monitor multiple Docker hosts. See [agent](/guide/agent) to learn more.
|
||||
|
||||
## Docker Swarm
|
||||
|
||||
@@ -70,6 +81,9 @@ See [swarm mode](/guide/swarm-mode) for more information.
|
||||
|
||||
Dozzle supports running in Kubernetes. It only needs to be deployed on one node within the cluster.
|
||||
|
||||
<details>
|
||||
<summary>Kubernetes Configuration</summary>
|
||||
|
||||
```yaml [k8s-dozzle.yml]
|
||||
# rbac.yaml
|
||||
apiVersion: v1
|
||||
@@ -130,9 +144,11 @@ spec:
|
||||
value: "k8s"
|
||||
```
|
||||
|
||||
Then apply the configuration using the following command:
|
||||
</details>
|
||||
|
||||
```bash
|
||||
Apply the configuration using the following command:
|
||||
|
||||
```sh
|
||||
kubectl apply -f k8s-dozzle.yml
|
||||
```
|
||||
|
||||
|
||||
@@ -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,6 +8,8 @@ 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 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.
|
||||
|
||||
@@ -42,7 +44,7 @@ services:
|
||||
|
||||
## Connecting with a Socket Proxy
|
||||
|
||||
If you are in a private network, then you can use [Docker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy) which exposes `docker.sock` file without the need for TLS. Dozzle will never try to write to Docker but it will need access to list APIs. The following command will start a proxy with minimal access:
|
||||
If you are in a private network, then you can use [Docker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy) which exposes `docker.sock` file without the need for TLS. This will remove the need for a Dozzle agent and Dozzle will connect directly to the Socket Proxy instead. Dozzle will never try to write to Docker but it will need access to list APIs. The following command will start a proxy with minimal access:
|
||||
|
||||
```sh
|
||||
$ docker container run --privileged -e CONTAINERS=1 -e INFO=1 -v /var/run/docker.sock:/var/run/docker.sock -p 2375:2375 tecnativa/docker-socket-proxy
|
||||
|
||||
@@ -6,44 +6,51 @@ 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 tcp://167.99.1.1:7007 --remote-agent tcp://167.99.1.2:7007` or comma-separated `DOZZLE_REMOTE_AGENT=tcp://167.99.1.1:7007,tcp://167.99.1.2:7007`.
|
||||
> 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`.
|
||||
|
||||
## Generate users.yml
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ networks:
|
||||
|
||||
Note that the `DOZZLE_MODE` environment variable is set to `swarm`. This tells Dozzle to automatically discover other Dozzle instances in the swarm. The `overlay` network is used to create the mesh network between the different Dozzle instances.
|
||||
|
||||
> [!NOTE]
|
||||
> Due to implementation details, <strike>the name for the service must be exactly `dozzle`</strike>. This is no longer required starting with version `v8.2`. You can name the service anything you want. The service name is automatically detected by Dozzle using `com.docker.swarm.service.name` label.
|
||||
> [!WARNING]
|
||||
> Socket-proxy cannot be used in Docker Swarm mode. This limitation stems from Docker itself, not Dozzle. In Swarm mode, services can only communicate with other services, but Dozzle requires direct connections to individual proxy instances—which isn't supported. If you have a solution for using socket-proxy in Swarm mode, we'd love to hear from you!
|
||||
|
||||
## Setting Up Simple Authentication in Swarm Mode
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"dozzle": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/playfair-display": "^5.2.5"
|
||||
"@fontsource-variable/playfair-display": "^5.2.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,16 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
esbuild@<=0.24.2: '>=0.25.0'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@fontsource-variable/playfair-display':
|
||||
specifier: ^5.2.5
|
||||
version: 5.2.5
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.8
|
||||
devDependencies:
|
||||
dozzle:
|
||||
specifier: workspace:*
|
||||
@@ -18,9 +21,9 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
'@fontsource-variable/playfair-display@5.2.5':
|
||||
resolution: {integrity: sha512-nzSCC5pbWHjkJBqjPe81cPIC7fqJ6U5DwJ6tec7vF1bQ2DJnaKRBZluRQJuseFpACZLTN8LeN/1gBQEA7PnKOw==}
|
||||
'@fontsource-variable/playfair-display@5.2.8':
|
||||
resolution: {integrity: sha512-ZzVIXPOrL85yyOvZYoBzUszIJM+xKkHqni4IYn2CVLaGQQdJR8sBeC8yFNgjxSJ7ludTwta8qpULeOFuk5X75A==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@fontsource-variable/playfair-display@5.2.5': {}
|
||||
'@fontsource-variable/playfair-display@5.2.8': {}
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -2,9 +2,9 @@ module github.com/amir20/dozzle
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/alexflint/go-arg v1.5.1
|
||||
github.com/alexflint/go-arg v1.6.0
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/docker/docker v28.1.1+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.39.0 // indirect
|
||||
golang.org/x/sys v0.32.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.1.1
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/andybalholm/brotli v1.2.0
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||
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.0.0
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/samber/lo v1.50.0
|
||||
github.com/samber/lo v1.52.0
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/yuin/goldmark v1.7.11
|
||||
golang.org/x/crypto v0.37.0
|
||||
golang.org/x/sync v0.13.0
|
||||
google.golang.org/grpc v1.72.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
k8s.io/api v0.33.0
|
||||
k8s.io/apimachinery v0.33.0
|
||||
k8s.io/client-go v0.33.0
|
||||
k8s.io/metrics v0.33.0
|
||||
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 (
|
||||
@@ -50,21 +50,23 @@ require (
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
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
|
||||
@@ -83,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
|
||||
@@ -93,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.34.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.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.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-20250219182151-9fdb1cabc7b2 // 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.2
|
||||
go 1.25.2
|
||||
|
||||
@@ -6,10 +6,14 @@ github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiU
|
||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
|
||||
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||
github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRSo=
|
||||
github.com/alexflint/go-arg v1.6.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
@@ -22,6 +26,10 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
@@ -36,6 +44,24 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
|
||||
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.2.1+incompatible h1:aTSWVTDStpHbnRu0xBcGoJEjRf5EQKt6nik6Vif8sWw=
|
||||
github.com/docker/docker v28.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
||||
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.0+incompatible h1:ffS62aKWupCWdvcee7nBU9fhnmknOqDPaJAMtfK0ImQ=
|
||||
github.com/docker/docker v28.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.1+incompatible h1:20+BmuA9FXlCX4ByQ0vYJcUEnOmRM6XljDnFWR+jCyY=
|
||||
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=
|
||||
@@ -44,21 +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=
|
||||
@@ -78,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=
|
||||
@@ -147,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=
|
||||
@@ -169,6 +213,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.0.0 h1:F1za+MBXzDQtQq+OVgFsojSX4w66rsNDmQNebPFAncA=
|
||||
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=
|
||||
@@ -178,6 +226,10 @@ github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
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=
|
||||
@@ -186,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=
|
||||
@@ -199,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=
|
||||
@@ -212,26 +270,52 @@ github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
|
||||
github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
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=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
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=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
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=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
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=
|
||||
@@ -242,6 +326,18 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
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=
|
||||
@@ -264,10 +360,22 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.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=
|
||||
@@ -279,6 +387,14 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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=
|
||||
@@ -297,6 +413,16 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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=
|
||||
@@ -308,6 +434,16 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
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=
|
||||
@@ -319,6 +455,18 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
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=
|
||||
@@ -340,12 +488,42 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M=
|
||||
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=
|
||||
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
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=
|
||||
@@ -366,26 +544,78 @@ k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
|
||||
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
|
||||
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
|
||||
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
|
||||
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=
|
||||
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
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=
|
||||
k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg=
|
||||
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=
|
||||
k8s.io/metrics v0.33.0/go.mod h1:XewckTFXmE2AJiP7PT3EXaY7hi7bler3t2ZLyOdQYzU=
|
||||
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=
|
||||
@@ -395,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)
|
||||
@@ -158,6 +162,7 @@ func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *container.
|
||||
Position: container.LogPosition(resp.Event.Position),
|
||||
Level: resp.Event.Level,
|
||||
Stream: resp.Event.Stream,
|
||||
RawMessage: resp.Event.RawMessage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.5
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// source: rpc.proto
|
||||
|
||||
@@ -1048,194 +1048,81 @@ func (x *ContainerExecResponse) GetStdout() []byte {
|
||||
|
||||
var File_rpc_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_rpc_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x09, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x1a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x01, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x43, 0x0a,
|
||||
0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e,
|
||||
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46,
|
||||
0x69, 0x6c, 0x74, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74,
|
||||
0x65, 0x72, 0x1a, 0x53, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72,
|
||||
0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
||||
0x6b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x52, 0x65,
|
||||
0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x28, 0x0a, 0x0e, 0x52, 0x65, 0x70, 0x65, 0x61,
|
||||
0x74, 0x65, 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x73, 0x22, 0x4d, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
||||
0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0a, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73,
|
||||
0x22, 0xd1, 0x01, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
||||
0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x42, 0x0a, 0x06, 0x66,
|
||||
0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x6c, 0x74,
|
||||
0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a,
|
||||
0x53, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
|
||||
0x12, 0x2e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x18, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x52, 0x65, 0x70, 0x65, 0x61,
|
||||
0x74, 0x65, 0x64, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
|
||||
0x3a, 0x02, 0x38, 0x01, 0x22, 0x4a, 0x0a, 0x15, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a,
|
||||
0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74,
|
||||
0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||
0x22, 0x89, 0x01, 0x0a, 0x11, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69,
|
||||
0x6e, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x74,
|
||||
0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
|
||||
0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0x3e, 0x0a, 0x12,
|
||||
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x6f, 0x67,
|
||||
0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0xc1, 0x01, 0x0a,
|
||||
0x17, 0x4c, 0x6f, 0x67, 0x73, 0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x44, 0x61, 0x74, 0x65,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63,
|
||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x73, 0x69,
|
||||
0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x30, 0x0a, 0x05,
|
||||
0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x12, 0x20,
|
||||
0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x73, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x73,
|
||||
0x22, 0xbf, 0x01, 0x0a, 0x15, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x61, 0x77, 0x42, 0x79,
|
||||
0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x05,
|
||||
0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x30,
|
||||
0x0a, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x05, 0x75, 0x6e, 0x74, 0x69, 0x6c,
|
||||
0x12, 0x20, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x73, 0x18,
|
||||
0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70,
|
||||
0x65, 0x73, 0x22, 0x2c, 0x0a, 0x16, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x61, 0x77, 0x42,
|
||||
0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04,
|
||||
0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61,
|
||||
0x22, 0x15, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x46, 0x0a, 0x14, 0x53, 0x74, 0x72, 0x65, 0x61,
|
||||
0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x2e, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69,
|
||||
0x6e, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22,
|
||||
0x14, 0x0a, 0x12, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x42, 0x0a, 0x13, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53,
|
||||
0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x04,
|
||||
0x73, 0x74, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53,
|
||||
0x74, 0x61, 0x74, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x22, 0x11, 0x0a, 0x0f, 0x48, 0x6f, 0x73,
|
||||
0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x36, 0x0a, 0x10,
|
||||
0x48, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x22, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x52, 0x04,
|
||||
0x68, 0x6f, 0x73, 0x74, 0x22, 0x1f, 0x0a, 0x1d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f,
|
||||
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x53, 0x0a, 0x1e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43,
|
||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x31, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52,
|
||||
0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, 0x6d, 0x0a, 0x16, 0x43, 0x6f,
|
||||
0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||
0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x6f, 0x6e,
|
||||
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x68, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||
0x72, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18,
|
||||
0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
|
||||
0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x64, 0x69,
|
||||
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x64, 0x69, 0x6e, 0x22, 0x2f,
|
||||
0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x45, 0x78, 0x65, 0x63, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75,
|
||||
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x32,
|
||||
0xc3, 0x07, 0x0a, 0x0c, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x12, 0x55, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||
0x72, 0x73, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69,
|
||||
0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c,
|
||||
0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x0d, 0x46, 0x69, 0x6e, 0x64, 0x43,
|
||||
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||
0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||
0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0a, 0x53,
|
||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x57, 0x0a, 0x10, 0x4c, 0x6f, 0x67, 0x73,
|
||||
0x42, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x42, 0x65, 0x74, 0x77,
|
||||
0x65, 0x65, 0x6e, 0x44, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61,
|
||||
0x6d, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30,
|
||||
0x01, 0x12, 0x57, 0x0a, 0x0e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x61, 0x77, 0x42, 0x79,
|
||||
0x74, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53,
|
||||
0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x61, 0x77, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x61, 0x77, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x51, 0x0a, 0x0c, 0x53, 0x74,
|
||||
0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1d, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e,
|
||||
0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74,
|
||||
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x4e, 0x0a,
|
||||
0x0b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1c, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74,
|
||||
0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74,
|
||||
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x6f, 0x0a,
|
||||
0x16, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||
0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x27, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
||||
0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x28, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x65,
|
||||
0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74,
|
||||
0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x43,
|
||||
0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x48, 0x6f, 0x73, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||
0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
||||
0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x56, 0x0a,
|
||||
0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x45, 0x78, 0x65, 0x63, 0x12, 0x1e,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69,
|
||||
0x6e, 0x65, 0x72, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69,
|
||||
0x6e, 0x65, 0x72, 0x45, 0x78, 0x65, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x13, 0x5a, 0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
|
||||
0x6c, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
})
|
||||
const file_rpc_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\trpc.proto\x12\bprotobuf\x1a\vtypes.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb1\x01\n" +
|
||||
"\x15ListContainersRequest\x12C\n" +
|
||||
"\x06filter\x18\x01 \x03(\v2+.protobuf.ListContainersRequest.FilterEntryR\x06filter\x1aS\n" +
|
||||
"\vFilterEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12.\n" +
|
||||
"\x05value\x18\x02 \x01(\v2\x18.protobuf.RepeatedStringR\x05value:\x028\x01\"(\n" +
|
||||
"\x0eRepeatedString\x12\x16\n" +
|
||||
"\x06values\x18\x01 \x03(\tR\x06values\"M\n" +
|
||||
"\x16ListContainersResponse\x123\n" +
|
||||
"\n" +
|
||||
"containers\x18\x01 \x03(\v2\x13.protobuf.ContainerR\n" +
|
||||
"containers\"\xd1\x01\n" +
|
||||
"\x14FindContainerRequest\x12 \n" +
|
||||
"\vcontainerId\x18\x01 \x01(\tR\vcontainerId\x12B\n" +
|
||||
"\x06filter\x18\x02 \x03(\v2*.protobuf.FindContainerRequest.FilterEntryR\x06filter\x1aS\n" +
|
||||
"\vFilterEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12.\n" +
|
||||
"\x05value\x18\x02 \x01(\v2\x18.protobuf.RepeatedStringR\x05value:\x028\x01\"J\n" +
|
||||
"\x15FindContainerResponse\x121\n" +
|
||||
"\tcontainer\x18\x01 \x01(\v2\x13.protobuf.ContainerR\tcontainer\"\x89\x01\n" +
|
||||
"\x11StreamLogsRequest\x12 \n" +
|
||||
"\vcontainerId\x18\x01 \x01(\tR\vcontainerId\x120\n" +
|
||||
"\x05since\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05since\x12 \n" +
|
||||
"\vstreamTypes\x18\x03 \x01(\x05R\vstreamTypes\">\n" +
|
||||
"\x12StreamLogsResponse\x12(\n" +
|
||||
"\x05event\x18\x01 \x01(\v2\x12.protobuf.LogEventR\x05event\"\xc1\x01\n" +
|
||||
"\x17LogsBetweenDatesRequest\x12 \n" +
|
||||
"\vcontainerId\x18\x01 \x01(\tR\vcontainerId\x120\n" +
|
||||
"\x05since\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05since\x120\n" +
|
||||
"\x05until\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x05until\x12 \n" +
|
||||
"\vstreamTypes\x18\x04 \x01(\x05R\vstreamTypes\"\xbf\x01\n" +
|
||||
"\x15StreamRawBytesRequest\x12 \n" +
|
||||
"\vcontainerId\x18\x01 \x01(\tR\vcontainerId\x120\n" +
|
||||
"\x05since\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\x05since\x120\n" +
|
||||
"\x05until\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x05until\x12 \n" +
|
||||
"\vstreamTypes\x18\x04 \x01(\x05R\vstreamTypes\",\n" +
|
||||
"\x16StreamRawBytesResponse\x12\x12\n" +
|
||||
"\x04data\x18\x01 \x01(\fR\x04data\"\x15\n" +
|
||||
"\x13StreamEventsRequest\"F\n" +
|
||||
"\x14StreamEventsResponse\x12.\n" +
|
||||
"\x05event\x18\x01 \x01(\v2\x18.protobuf.ContainerEventR\x05event\"\x14\n" +
|
||||
"\x12StreamStatsRequest\"B\n" +
|
||||
"\x13StreamStatsResponse\x12+\n" +
|
||||
"\x04stat\x18\x01 \x01(\v2\x17.protobuf.ContainerStatR\x04stat\"\x11\n" +
|
||||
"\x0fHostInfoRequest\"6\n" +
|
||||
"\x10HostInfoResponse\x12\"\n" +
|
||||
"\x04host\x18\x01 \x01(\v2\x0e.protobuf.HostR\x04host\"\x1f\n" +
|
||||
"\x1dStreamContainerStartedRequest\"S\n" +
|
||||
"\x1eStreamContainerStartedResponse\x121\n" +
|
||||
"\tcontainer\x18\x01 \x01(\v2\x13.protobuf.ContainerR\tcontainer\"m\n" +
|
||||
"\x16ContainerActionRequest\x12 \n" +
|
||||
"\vcontainerId\x18\x01 \x01(\tR\vcontainerId\x121\n" +
|
||||
"\x06action\x18\x02 \x01(\x0e2\x19.protobuf.ContainerActionR\x06action\"\x19\n" +
|
||||
"\x17ContainerActionResponse\"h\n" +
|
||||
"\x14ContainerExecRequest\x12 \n" +
|
||||
"\vcontainerId\x18\x01 \x01(\tR\vcontainerId\x12\x18\n" +
|
||||
"\acommand\x18\x02 \x03(\tR\acommand\x12\x14\n" +
|
||||
"\x05stdin\x18\x03 \x01(\fR\x05stdin\"/\n" +
|
||||
"\x15ContainerExecResponse\x12\x16\n" +
|
||||
"\x06stdout\x18\x01 \x01(\fR\x06stdout2\xc3\a\n" +
|
||||
"\fAgentService\x12U\n" +
|
||||
"\x0eListContainers\x12\x1f.protobuf.ListContainersRequest\x1a .protobuf.ListContainersResponse\"\x00\x12R\n" +
|
||||
"\rFindContainer\x12\x1e.protobuf.FindContainerRequest\x1a\x1f.protobuf.FindContainerResponse\"\x00\x12K\n" +
|
||||
"\n" +
|
||||
"StreamLogs\x12\x1b.protobuf.StreamLogsRequest\x1a\x1c.protobuf.StreamLogsResponse\"\x000\x01\x12W\n" +
|
||||
"\x10LogsBetweenDates\x12!.protobuf.LogsBetweenDatesRequest\x1a\x1c.protobuf.StreamLogsResponse\"\x000\x01\x12W\n" +
|
||||
"\x0eStreamRawBytes\x12\x1f.protobuf.StreamRawBytesRequest\x1a .protobuf.StreamRawBytesResponse\"\x000\x01\x12Q\n" +
|
||||
"\fStreamEvents\x12\x1d.protobuf.StreamEventsRequest\x1a\x1e.protobuf.StreamEventsResponse\"\x000\x01\x12N\n" +
|
||||
"\vStreamStats\x12\x1c.protobuf.StreamStatsRequest\x1a\x1d.protobuf.StreamStatsResponse\"\x000\x01\x12o\n" +
|
||||
"\x16StreamContainerStarted\x12'.protobuf.StreamContainerStartedRequest\x1a(.protobuf.StreamContainerStartedResponse\"\x000\x01\x12C\n" +
|
||||
"\bHostInfo\x12\x19.protobuf.HostInfoRequest\x1a\x1a.protobuf.HostInfoResponse\"\x00\x12X\n" +
|
||||
"\x0fContainerAction\x12 .protobuf.ContainerActionRequest\x1a!.protobuf.ContainerActionResponse\"\x00\x12V\n" +
|
||||
"\rContainerExec\x12\x1e.protobuf.ContainerExecRequest\x1a\x1f.protobuf.ContainerExecResponse\"\x00(\x010\x01B\x13Z\x11internal/agent/pbb\x06proto3"
|
||||
|
||||
var (
|
||||
file_rpc_proto_rawDescOnce sync.Once
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.5
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// source: types.proto
|
||||
|
||||
@@ -337,6 +337,7 @@ type LogEvent struct {
|
||||
Level string `protobuf:"bytes,5,opt,name=level,proto3" json:"level,omitempty"`
|
||||
Stream string `protobuf:"bytes,6,opt,name=stream,proto3" json:"stream,omitempty"`
|
||||
Position string `protobuf:"bytes,7,opt,name=position,proto3" json:"position,omitempty"`
|
||||
RawMessage string `protobuf:"bytes,8,opt,name=rawMessage,proto3" json:"rawMessage,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -420,6 +421,13 @@ func (x *LogEvent) GetPosition() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *LogEvent) GetRawMessage() string {
|
||||
if x != nil {
|
||||
return x.RawMessage
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SimpleMessage struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
@@ -710,126 +718,81 @@ func (x *Host) GetDockerVersion() string {
|
||||
|
||||
var File_types_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_types_proto_rawDesc = string([]byte{
|
||||
0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
||||
0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0xa2, 0x05, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
|
||||
0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
|
||||
0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x03,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x49, 0x6d, 0x61,
|
||||
0x67, 0x65, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x49, 0x6d, 0x61, 0x67,
|
||||
0x65, 0x49, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x07,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x73, 0x74, 0x61,
|
||||
0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
|
||||
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12,
|
||||
0x16, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x06, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18,
|
||||
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74,
|
||||
0x74, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x74, 0x74, 0x79, 0x12, 0x37, 0x0a,
|
||||
0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e,
|
||||
0x65, 0x72, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06,
|
||||
0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18,
|
||||
0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x52, 0x05,
|
||||
0x73, 0x74, 0x61, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x0e,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x63,
|
||||
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f,
|
||||
0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x36, 0x0a, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65,
|
||||
0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x52, 0x08, 0x66, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x20, 0x0a,
|
||||
0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x11, 0x20, 0x01,
|
||||
0x28, 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12,
|
||||
0x1a, 0x0a, 0x08, 0x63, 0x70, 0x75, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28,
|
||||
0x01, 0x52, 0x08, 0x63, 0x70, 0x75, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x66,
|
||||
0x75, 0x6c, 0x6c, 0x79, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x18, 0x13, 0x20, 0x01, 0x28, 0x08,
|
||||
0x52, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x1a, 0x39, 0x0a,
|
||||
0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
|
||||
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x87, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6e,
|
||||
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x70,
|
||||
0x75, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a,
|
||||
0x63, 0x70, 0x75, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x6d, 0x65,
|
||||
0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52,
|
||||
0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, 0x24, 0x0a, 0x0d,
|
||||
0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x01, 0x52, 0x0d, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x50, 0x65, 0x72, 0x63, 0x65,
|
||||
0x6e, 0x74, 0x22, 0xf0, 0x01, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12,
|
||||
0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49,
|
||||
0x64, 0x12, 0x2e, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x6c,
|
||||
0x65, 0x76, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65,
|
||||
0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x06, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6f, 0x73,
|
||||
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x73,
|
||||
0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x29, 0x0a, 0x0d, 0x53, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x4d,
|
||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
|
||||
0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
|
||||
0x22, 0x24, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x78, 0x4d, 0x65, 0x73, 0x73, 0x61,
|
||||
0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
|
||||
0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x8c, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74,
|
||||
0x6f, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x6f,
|
||||
0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18,
|
||||
0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74,
|
||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
|
||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65,
|
||||
0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xaf, 0x03, 0x0a, 0x04, 0x48, 0x6f, 0x73, 0x74, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12,
|
||||
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
|
||||
0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||
0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65, 0x41, 0x64, 0x64,
|
||||
0x72, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, 0x61, 0x72, 0x6d, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x77, 0x61, 0x72, 0x6d, 0x12, 0x32, 0x0a, 0x06, 0x6c, 0x61,
|
||||
0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c,
|
||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x28,
|
||||
0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65,
|
||||
0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
|
||||
0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x73, 0x56, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x73, 0x56,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x73, 0x54, 0x79, 0x70, 0x65,
|
||||
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x63, 0x70, 0x75, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d,
|
||||
0x52, 0x08, 0x63, 0x70, 0x75, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65,
|
||||
0x6d, 0x6f, 0x72, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x65, 0x6d, 0x6f,
|
||||
0x72, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72,
|
||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64,
|
||||
0x6f, 0x63, 0x6b, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0x39, 0x0a, 0x0b,
|
||||
0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
|
||||
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x33, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x74,
|
||||
0x61, 0x72, 0x74, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x10, 0x01, 0x12,
|
||||
0x0b, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x10, 0x02, 0x42, 0x13, 0x5a, 0x11,
|
||||
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70,
|
||||
0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
})
|
||||
const file_types_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\vtypes.proto\x12\bprotobuf\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x19google/protobuf/any.proto\"\xa2\x05\n" +
|
||||
"\tContainer\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" +
|
||||
"\x05image\x18\x03 \x01(\tR\x05image\x12\x16\n" +
|
||||
"\x06status\x18\x04 \x01(\tR\x06status\x12\x14\n" +
|
||||
"\x05state\x18\x05 \x01(\tR\x05state\x12\x18\n" +
|
||||
"\aImageId\x18\x06 \x01(\tR\aImageId\x124\n" +
|
||||
"\acreated\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\acreated\x124\n" +
|
||||
"\astarted\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\astarted\x12\x16\n" +
|
||||
"\x06health\x18\t \x01(\tR\x06health\x12\x12\n" +
|
||||
"\x04host\x18\n" +
|
||||
" \x01(\tR\x04host\x12\x10\n" +
|
||||
"\x03tty\x18\v \x01(\bR\x03tty\x127\n" +
|
||||
"\x06labels\x18\f \x03(\v2\x1f.protobuf.Container.LabelsEntryR\x06labels\x12-\n" +
|
||||
"\x05stats\x18\r \x03(\v2\x17.protobuf.ContainerStatR\x05stats\x12\x14\n" +
|
||||
"\x05group\x18\x0e \x01(\tR\x05group\x12\x18\n" +
|
||||
"\acommand\x18\x0f \x01(\tR\acommand\x126\n" +
|
||||
"\bfinished\x18\x10 \x01(\v2\x1a.google.protobuf.TimestampR\bfinished\x12 \n" +
|
||||
"\vmemoryLimit\x18\x11 \x01(\x04R\vmemoryLimit\x12\x1a\n" +
|
||||
"\bcpuLimit\x18\x12 \x01(\x01R\bcpuLimit\x12 \n" +
|
||||
"\vfullyLoaded\x18\x13 \x01(\bR\vfullyLoaded\x1a9\n" +
|
||||
"\vLabelsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x87\x01\n" +
|
||||
"\rContainerStat\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x1e\n" +
|
||||
"\n" +
|
||||
"cpuPercent\x18\x02 \x01(\x01R\n" +
|
||||
"cpuPercent\x12 \n" +
|
||||
"\vmemoryUsage\x18\x03 \x01(\x01R\vmemoryUsage\x12$\n" +
|
||||
"\rmemoryPercent\x18\x04 \x01(\x01R\rmemoryPercent\"\x90\x02\n" +
|
||||
"\bLogEvent\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\rR\x02id\x12 \n" +
|
||||
"\vcontainerId\x18\x02 \x01(\tR\vcontainerId\x12.\n" +
|
||||
"\amessage\x18\x03 \x01(\v2\x14.google.protobuf.AnyR\amessage\x128\n" +
|
||||
"\ttimestamp\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12\x14\n" +
|
||||
"\x05level\x18\x05 \x01(\tR\x05level\x12\x16\n" +
|
||||
"\x06stream\x18\x06 \x01(\tR\x06stream\x12\x1a\n" +
|
||||
"\bposition\x18\a \x01(\tR\bposition\x12\x1e\n" +
|
||||
"\n" +
|
||||
"rawMessage\x18\b \x01(\tR\n" +
|
||||
"rawMessage\")\n" +
|
||||
"\rSimpleMessage\x12\x18\n" +
|
||||
"\amessage\x18\x01 \x01(\tR\amessage\"$\n" +
|
||||
"\x0eComplexMessage\x12\x12\n" +
|
||||
"\x04data\x18\x01 \x01(\fR\x04data\"\x8c\x01\n" +
|
||||
"\x0eContainerEvent\x12\x18\n" +
|
||||
"\aactorId\x18\x01 \x01(\tR\aactorId\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
|
||||
"\x04host\x18\x03 \x01(\tR\x04host\x128\n" +
|
||||
"\ttimestamp\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\"\xaf\x03\n" +
|
||||
"\x04Host\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12 \n" +
|
||||
"\vnodeAddress\x18\x03 \x01(\tR\vnodeAddress\x12\x14\n" +
|
||||
"\x05swarm\x18\x04 \x01(\bR\x05swarm\x122\n" +
|
||||
"\x06labels\x18\x05 \x03(\v2\x1a.protobuf.Host.LabelsEntryR\x06labels\x12(\n" +
|
||||
"\x0foperatingSystem\x18\x06 \x01(\tR\x0foperatingSystem\x12\x1c\n" +
|
||||
"\tosVersion\x18\a \x01(\tR\tosVersion\x12\x16\n" +
|
||||
"\x06osType\x18\b \x01(\tR\x06osType\x12\x1a\n" +
|
||||
"\bcpuCores\x18\t \x01(\rR\bcpuCores\x12\x16\n" +
|
||||
"\x06memory\x18\n" +
|
||||
" \x01(\x04R\x06memory\x12\"\n" +
|
||||
"\fagentVersion\x18\v \x01(\tR\fagentVersion\x12$\n" +
|
||||
"\rdockerVersion\x18\f \x01(\tR\rdockerVersion\x1a9\n" +
|
||||
"\vLabelsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*3\n" +
|
||||
"\x0fContainerAction\x12\t\n" +
|
||||
"\x05Start\x10\x00\x12\b\n" +
|
||||
"\x04Stop\x10\x01\x12\v\n" +
|
||||
"\aRestart\x10\x02B\x13Z\x11internal/agent/pbb\x06proto3"
|
||||
|
||||
var (
|
||||
file_types_proto_rawDescOnce sync.Once
|
||||
|
||||
@@ -60,10 +60,11 @@ func (s *server) StreamLogs(in *pb.StreamLogsRequest, out pb.AgentService_Stream
|
||||
return err
|
||||
}
|
||||
|
||||
events := make(chan *container.LogEvent)
|
||||
dockerReader := docker.NewLogReader(reader, c.Tty)
|
||||
g := container.NewEventGenerator(out.Context(), dockerReader, c)
|
||||
g := container.NewEventGenerator(out.Context(), dockerReader, c, events)
|
||||
|
||||
for event := range g.Events {
|
||||
for event := range events {
|
||||
out.Send(&pb.StreamLogsResponse{
|
||||
Event: logEventToPb(event),
|
||||
})
|
||||
@@ -88,12 +89,13 @@ func (s *server) LogsBetweenDates(in *pb.LogsBetweenDatesRequest, out pb.AgentSe
|
||||
return err
|
||||
}
|
||||
|
||||
events := make(chan *container.LogEvent)
|
||||
dockerReader := docker.NewLogReader(reader, c.Tty)
|
||||
g := container.NewEventGenerator(out.Context(), dockerReader, c)
|
||||
g := container.NewEventGenerator(out.Context(), dockerReader, c, events)
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-g.Events:
|
||||
case event := <-events:
|
||||
out.Send(&pb.StreamLogsResponse{
|
||||
Event: logEventToPb(event),
|
||||
})
|
||||
@@ -290,10 +292,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 +306,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 +321,7 @@ func (s *server) ContainerExec(stream pb.AgentService_ContainerExecServer) error
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@@ -387,6 +386,7 @@ func logEventToPb(event *container.LogEvent) *pb.LogEvent {
|
||||
Level: event.Level,
|
||||
Stream: event.Stream,
|
||||
Position: string(event.Position),
|
||||
RawMessage: string(event.RawMessage),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
type EventGenerator struct {
|
||||
Events chan *LogEvent
|
||||
events chan<- *LogEvent
|
||||
Errors chan error
|
||||
reader LogReader
|
||||
next *LogEvent
|
||||
@@ -33,12 +33,12 @@ type LogReader interface {
|
||||
Read() (string, StdType, error)
|
||||
}
|
||||
|
||||
func NewEventGenerator(ctx context.Context, reader LogReader, container Container) *EventGenerator {
|
||||
func NewEventGenerator(ctx context.Context, reader LogReader, container Container, events chan<- *LogEvent) *EventGenerator {
|
||||
generator := &EventGenerator{
|
||||
reader: reader,
|
||||
buffer: make(chan *LogEvent, 100),
|
||||
Errors: make(chan error, 1),
|
||||
Events: make(chan *LogEvent),
|
||||
events: events,
|
||||
containerID: container.ID,
|
||||
ctx: ctx,
|
||||
}
|
||||
@@ -69,14 +69,12 @@ loop:
|
||||
checkPosition(current, next)
|
||||
|
||||
select {
|
||||
case g.Events <- current:
|
||||
case g.events <- current:
|
||||
case <-g.ctx.Done():
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
close(g.Events)
|
||||
|
||||
g.wg.Done()
|
||||
}
|
||||
|
||||
@@ -114,6 +112,10 @@ func (g *EventGenerator) peek() *LogEvent {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *EventGenerator) Wait() {
|
||||
g.wg.Wait()
|
||||
}
|
||||
|
||||
func createEvent(message string, streamType StdType) *LogEvent {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(message))
|
||||
@@ -124,6 +126,7 @@ func createEvent(message string, streamType StdType) *LogEvent {
|
||||
logEvent.Timestamp = timestamp.UnixMilli()
|
||||
message = strings.TrimSuffix(message[index+1:], "\n")
|
||||
logEvent.Message = message
|
||||
logEvent.RawMessage = message
|
||||
if message == "" {
|
||||
logEvent.Message = "" // empty message so do nothing
|
||||
} else if json.Valid([]byte(message)) {
|
||||
@@ -144,6 +147,11 @@ func createEvent(message string, streamType StdType) *LogEvent {
|
||||
}
|
||||
} else if data, err := ParseLogFmt(message); err == nil {
|
||||
logEvent.Message = data
|
||||
data, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to marshal json")
|
||||
}
|
||||
logEvent.RawMessage = string(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@ import (
|
||||
func TestEventGenerator_Events_tty(t *testing.T) {
|
||||
input := "example input"
|
||||
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: true})
|
||||
event := <-g.Events
|
||||
events := make(chan *LogEvent, 1)
|
||||
NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: true}, events)
|
||||
event := <-events
|
||||
|
||||
require.NotNil(t, event, "Expected event to not be nil, but got nil")
|
||||
assert.Equal(t, input, event.Message)
|
||||
@@ -26,8 +27,9 @@ func TestEventGenerator_Events_tty(t *testing.T) {
|
||||
func TestEventGenerator_Events_non_tty(t *testing.T) {
|
||||
input := "example input"
|
||||
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||
event := <-g.Events
|
||||
events := make(chan *LogEvent, 1)
|
||||
NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false}, events)
|
||||
event := <-events
|
||||
|
||||
require.NotNil(t, event, "Expected event to not be nil, but got nil")
|
||||
assert.Equal(t, input, event.Message)
|
||||
@@ -36,9 +38,12 @@ func TestEventGenerator_Events_non_tty(t *testing.T) {
|
||||
func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
|
||||
input := "example input"
|
||||
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||
<-g.Events
|
||||
_, ok := <-g.Events
|
||||
events := make(chan *LogEvent, 1)
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false}, events)
|
||||
<-events
|
||||
g.wg.Wait() // Wait for goroutines to finish
|
||||
close(events) // Close the channel manually since generator no longer owns it
|
||||
_, ok := <-events
|
||||
|
||||
assert.False(t, ok, "Expected channel to be closed")
|
||||
}
|
||||
@@ -46,8 +51,9 @@ func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
|
||||
func TestEventGenerator_Events_routines_done(t *testing.T) {
|
||||
input := "example input"
|
||||
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false})
|
||||
<-g.Events
|
||||
events := make(chan *LogEvent, 1)
|
||||
g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: false}, events)
|
||||
<-events
|
||||
assert.False(t, waitTimeout(&g.wg, 1*time.Second), "Expected routines to be done")
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ var SupportedLogLevels map[string]struct{}
|
||||
// Changing this also needs to change the logContext.ts file
|
||||
var logLevels = [][]string{
|
||||
{"error", "err"},
|
||||
{"warn", "warning"},
|
||||
{"warn", "warning", "wrn"},
|
||||
{"info", "inf"},
|
||||
{"debug", "dbg"},
|
||||
{"trace"},
|
||||
|
||||
@@ -40,6 +40,7 @@ func TestGuessLogLevel(t *testing.T) {
|
||||
{"DBG with more error=msg", "debug"},
|
||||
{"inf Something went wrong", "info"},
|
||||
{"crit: Something went wrong", "fatal"},
|
||||
{"[21:01:45] [WRN] this is a test", "warn"},
|
||||
{orderedmap.New[string, string](
|
||||
orderedmap.WithInitialData(
|
||||
orderedmap.Pair[string, string]{Key: "key", Value: "value"},
|
||||
|
||||