Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24.2
|
||||
go-version: 1.24.4
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -90,7 +90,7 @@ 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
|
||||
with:
|
||||
@@ -121,7 +121,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: .
|
||||
|
||||
@@ -13,7 +13,7 @@ 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
|
||||
with:
|
||||
@@ -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
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24.2"
|
||||
go-version: "1.24.4"
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24.2"
|
||||
go-version: "1.24.4"
|
||||
check-latest: true
|
||||
- name: Generate dependencies
|
||||
run: make fake_assets shared_key.pem shared_cert.pem
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ declare global {
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
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 +152,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 +240,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']
|
||||
@@ -460,6 +463,7 @@ declare module 'vue' {
|
||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||
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 +546,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 +634,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']>
|
||||
|
||||
@@ -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,58 @@ 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: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 +113,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']
|
||||
|
||||
@@ -22,7 +22,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 +39,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>
|
||||
|
||||
@@ -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,12 +1,16 @@
|
||||
<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>
|
||||
@@ -14,14 +18,14 @@
|
||||
<li>
|
||||
<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>
|
||||
@@ -165,7 +171,7 @@ const { enableActions, enableShell } = 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>
|
||||
@@ -30,7 +30,10 @@
|
||||
</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>
|
||||
<button @click.prevent="logout()" class="text-primary p-2">
|
||||
<material-symbols:logout />
|
||||
{{ $t("button.logout") }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
@@ -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-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-start 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,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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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,12 +1,16 @@
|
||||
<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>
|
||||
@@ -15,7 +19,7 @@
|
||||
<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>
|
||||
@@ -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,9 +1,24 @@
|
||||
// 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-start dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<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-->
|
||||
@@ -13,19 +28,31 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div 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-start dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<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-->
|
||||
@@ -35,19 +62,31 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div 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-start dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
|
||||
<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-->
|
||||
@@ -57,23 +96,22 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
|
||||
<div 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 {
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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.53.1-jammy
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
|
||||
@@ -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,7 +25,7 @@ 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:
|
||||
```
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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 uses with a Docker Socket proxy inside the Dozzle agent stack. If you wish to use a Socket Proxy on it's own without an agent see the [connecting with a socket proxy](#connecting-with-a-socket-proxy) section.
|
||||
|
||||
> [!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
|
||||
|
||||
@@ -27,7 +27,7 @@ Configurations can be done with flags or environment variables. The table below
|
||||
| `--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
|
||||
|
||||
|
||||
@@ -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.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.6
|
||||
version: 5.2.6
|
||||
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.6':
|
||||
resolution: {integrity: sha512-Uedpo4WwsWtobi1KMYGm9tlKcZJcD18EiBtPWhbbccT2u58qM7vwkOZGdrmdWVDhcEhjwNrqRUyZijwIFtsCcQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@fontsource-variable/playfair-display@5.2.5': {}
|
||||
'@fontsource-variable/playfair-display@5.2.6': {}
|
||||
|
||||
|
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 |
@@ -4,7 +4,7 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/alexflint/go-arg v1.5.1
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/docker/docker v28.1.1+incompatible
|
||||
github.com/docker/docker v28.2.2+incompatible
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
@@ -18,30 +18,30 @@ require (
|
||||
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
|
||||
golang.org/x/sys v0.33.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/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||
github.com/go-faker/faker/v4 v4.6.1
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
github.com/puzpuzpuz/xsync/v4 v4.0.0
|
||||
github.com/puzpuzpuz/xsync/v4 v4.1.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/samber/lo v1.50.0
|
||||
github.com/samber/lo v1.51.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
|
||||
github.com/yuin/goldmark v1.7.12
|
||||
golang.org/x/crypto v0.39.0
|
||||
golang.org/x/sync v0.15.0
|
||||
google.golang.org/grpc v1.73.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
|
||||
k8s.io/api v0.33.2
|
||||
k8s.io/apimachinery v0.33.2
|
||||
k8s.io/client-go v0.33.2
|
||||
k8s.io/metrics v0.33.2
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -50,6 +50,8 @@ 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
|
||||
@@ -93,15 +95,15 @@ 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.35.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.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/oauth2 v0.28.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.26.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-20250324211829-b45e905df463 // 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
|
||||
@@ -114,4 +116,4 @@ require (
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
go 1.24.2
|
||||
go 1.24.4
|
||||
|
||||
@@ -22,6 +22,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 +40,10 @@ 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/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -50,6 +58,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
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/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=
|
||||
@@ -169,6 +179,8 @@ 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/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 +190,8 @@ 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/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=
|
||||
@@ -212,24 +226,32 @@ 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=
|
||||
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/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/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/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -242,6 +264,10 @@ 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/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=
|
||||
@@ -268,6 +294,8 @@ 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/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 +307,10 @@ 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/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 +329,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.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/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 +342,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.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/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 +355,10 @@ 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/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,10 +380,18 @@ 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/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/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -366,14 +414,26 @@ 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/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/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/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=
|
||||
@@ -384,6 +444,10 @@ 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/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
|
||||
@@ -158,6 +158,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
|
||||
|
||||
@@ -387,6 +387,7 @@ func logEventToPb(event *container.LogEvent) *pb.LogEvent {
|
||||
Level: event.Level,
|
||||
Stream: event.Stream,
|
||||
Position: string(event.Position),
|
||||
RawMessage: string(event.RawMessage),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,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 +145,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -167,6 +167,7 @@ func ParseContainerAction(input string) (ContainerAction, error) {
|
||||
|
||||
type LogEvent struct {
|
||||
Message any `json:"m,omitempty"`
|
||||
RawMessage string `json:"rm,omitempty"`
|
||||
Timestamp int64 `json:"ts"`
|
||||
Id uint32 `json:"id,omitempty"`
|
||||
Level string `json:"l,omitempty"`
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
@@ -52,8 +53,16 @@ func (d *LogReader) Read() (string, container.StdType, error) {
|
||||
std = container.STDERR
|
||||
}
|
||||
|
||||
return message, std, nil
|
||||
for !strings.HasSuffix(message, "\n") {
|
||||
tail, _, err := d.readEvent()
|
||||
if err != nil {
|
||||
return "", std, err
|
||||
}
|
||||
|
||||
message += tail[32:]
|
||||
}
|
||||
|
||||
return message, std, nil
|
||||
}
|
||||
|
||||
func (d *LogReader) readEvent() (string, StdType, error) {
|
||||
|
||||
@@ -28,7 +28,7 @@ type Settings struct {
|
||||
ShowAllContainers bool `json:"showAllContainers"`
|
||||
SoftWrap bool `json:"softWrap"`
|
||||
CollapseNav bool `json:"collapseNav"`
|
||||
AutomaticRedirect bool `json:"automaticRedirect"`
|
||||
AutomaticRedirect string `json:"automaticRedirect"`
|
||||
Size string `json:"size,omitempty"`
|
||||
Compact bool `json:"compact"`
|
||||
LightTheme string `json:"lightTheme,omitempty"`
|
||||
|
||||
@@ -19,6 +19,7 @@ func StartEvent(args Args, mode string, client container.Client, subCommand stri
|
||||
RemoteClients: len(args.RemoteHost),
|
||||
SubCommand: subCommand,
|
||||
HasActions: args.EnableActions,
|
||||
HasShell: args.EnableShell,
|
||||
HasCustomAddress: args.Addr != ":8080",
|
||||
HasCustomBase: args.Base != "/",
|
||||
HasHostname: args.Hostname != "",
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
MarkerStart = "\uE000"
|
||||
MarkerEnd = "\uE001"
|
||||
)
|
||||
|
||||
func ParseRegex(search string) (*regexp.Regexp, error) {
|
||||
flags := ""
|
||||
|
||||
if search == strings.ToLower(search) {
|
||||
flags = "(?i)"
|
||||
}
|
||||
re, err := regexp.Compile(flags + search)
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Str("search", search).Msg("failed to compile regex")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return re, nil
|
||||
}
|
||||
|
||||
func Search(re *regexp.Regexp, logEvent *container.LogEvent) bool {
|
||||
switch value := logEvent.Message.(type) {
|
||||
case string:
|
||||
if re.MatchString(value) {
|
||||
logEvent.Message = re.ReplaceAllString(value, MarkerStart+"$0"+MarkerEnd)
|
||||
return true
|
||||
}
|
||||
|
||||
case *orderedmap.OrderedMap[string, any]:
|
||||
return searchMapAny(re, value)
|
||||
|
||||
case *orderedmap.OrderedMap[string, string]:
|
||||
return searchMapString(re, value)
|
||||
|
||||
case map[string]interface{}:
|
||||
panic("not implemented")
|
||||
case map[string]string:
|
||||
panic("not implemented")
|
||||
default:
|
||||
log.Debug().Type("type", value).Msg("unknown logEvent type")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func searchMapAny(re *regexp.Regexp, orderedMap *orderedmap.OrderedMap[string, any]) bool {
|
||||
found := false
|
||||
for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
|
||||
switch value := pair.Value.(type) {
|
||||
case string:
|
||||
if replaced, matched := searchString(re, value); matched {
|
||||
found = true
|
||||
orderedMap.Set(pair.Key, replaced)
|
||||
}
|
||||
|
||||
case []any:
|
||||
if searchArray(re, value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case *orderedmap.OrderedMap[string, any]:
|
||||
if searchMapAny(re, value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case *orderedmap.OrderedMap[string, string]:
|
||||
if searchMapString(re, value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
if searchMap(re, value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case int, float64, bool:
|
||||
formatted := fmt.Sprintf("%v", value)
|
||||
if re.MatchString(formatted) {
|
||||
orderedMap.Set(pair.Key, re.ReplaceAllString(formatted, MarkerStart+"$0"+MarkerEnd))
|
||||
found = true
|
||||
}
|
||||
|
||||
default:
|
||||
log.Debug().Type("type", value).Msg("unknown logEvent type inside searchMapAny")
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func searchMap(re *regexp.Regexp, data map[string]interface{}) bool {
|
||||
found := false
|
||||
for key, value := range data {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if replaced, matched := searchString(re, value); matched {
|
||||
found = true
|
||||
data[key] = replaced
|
||||
}
|
||||
case []any:
|
||||
if searchArray(re, value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
if searchMap(re, value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case int, float64, bool:
|
||||
formatted := fmt.Sprintf("%v", value)
|
||||
if re.MatchString(formatted) {
|
||||
data[key] = re.ReplaceAllString(formatted, MarkerStart+"$0"+MarkerEnd)
|
||||
found = true
|
||||
}
|
||||
default:
|
||||
log.Debug().Type("type", value).Msg("unknown logEvent type inside searchMap")
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func searchMapString(re *regexp.Regexp, orderedMap *orderedmap.OrderedMap[string, string]) bool {
|
||||
found := false
|
||||
for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
|
||||
if replaced, matched := searchString(re, pair.Value); matched {
|
||||
found = true
|
||||
orderedMap.Set(pair.Key, replaced)
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func searchArray(re *regexp.Regexp, data []any) bool {
|
||||
found := false
|
||||
for i, value := range data {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if replaced, matched := searchString(re, value); matched {
|
||||
found = true
|
||||
data[i] = replaced
|
||||
}
|
||||
case int, float64, bool:
|
||||
formatted := fmt.Sprintf("%v", value)
|
||||
if re.MatchString(formatted) {
|
||||
data[i] = re.ReplaceAllString(formatted, MarkerStart+"$0"+MarkerEnd)
|
||||
found = true
|
||||
}
|
||||
case []any:
|
||||
if searchArray(re, value) {
|
||||
found = true
|
||||
}
|
||||
case map[string]interface{}:
|
||||
if searchMap(re, value) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func searchString(re *regexp.Regexp, value string) (string, bool) {
|
||||
if re.MatchString(value) {
|
||||
replaced := re.ReplaceAllString(value, MarkerStart+"$0"+MarkerEnd)
|
||||
return replaced, true
|
||||
}
|
||||
|
||||
return value, false
|
||||
}
|
||||
@@ -2,20 +2,24 @@ package support_web
|
||||
|
||||
import (
|
||||
"html"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/support/search"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
)
|
||||
|
||||
// URL marker regex compiled once for performance
|
||||
var urlMarkerRegex = regexp.MustCompile(URLMarkerStart + "(.*?)" + URLMarkerEnd)
|
||||
|
||||
func EscapeHTMLValues(logEvent *container.LogEvent) {
|
||||
MarkURLs(logEvent)
|
||||
|
||||
switch value := logEvent.Message.(type) {
|
||||
case string:
|
||||
value = html.EscapeString(value)
|
||||
value = strings.ReplaceAll(value, search.MarkerStart, "<mark>")
|
||||
logEvent.Message = strings.ReplaceAll(value, search.MarkerEnd, "</mark>")
|
||||
logEvent.Message = escapeAndProcessMarkers(value)
|
||||
|
||||
case *orderedmap.OrderedMap[string, any]:
|
||||
escapeAnyMap(value)
|
||||
@@ -30,32 +34,82 @@ func EscapeHTMLValues(logEvent *container.LogEvent) {
|
||||
panic("not implemented")
|
||||
|
||||
default:
|
||||
log.Debug().Type("type", value).Msg("unknown logEvent type")
|
||||
log.Trace().Type("type", value).Msg("unknown logEvent type")
|
||||
}
|
||||
}
|
||||
|
||||
func escapeAndProcessMarkers(value string) string {
|
||||
value = html.EscapeString(value)
|
||||
value = strings.ReplaceAll(value, MarkerStart, "<mark>")
|
||||
value = strings.ReplaceAll(value, MarkerEnd, "</mark>")
|
||||
value = urlMarkerRegex.ReplaceAllString(value, "<a href=\"$1\" target=\"_blank\" rel=\"noopener noreferrer external\">$1</a>")
|
||||
return value
|
||||
}
|
||||
|
||||
func escapeAnyMap(orderedMap *orderedmap.OrderedMap[string, any]) {
|
||||
for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
|
||||
switch value := pair.Value.(type) {
|
||||
case string:
|
||||
value = html.EscapeString(value)
|
||||
value = strings.ReplaceAll(value, search.MarkerStart, "<mark>")
|
||||
value = strings.ReplaceAll(value, search.MarkerEnd, "</mark>")
|
||||
orderedMap.Set(pair.Key, value)
|
||||
orderedMap.Set(pair.Key, escapeAndProcessMarkers(value))
|
||||
case *orderedmap.OrderedMap[string, any]:
|
||||
escapeAnyMap(value)
|
||||
case *orderedmap.OrderedMap[string, string]:
|
||||
escapeStringMap(value)
|
||||
case map[string]interface{}:
|
||||
escapeMapStringInterface(value)
|
||||
case map[string]string:
|
||||
escapeStringMapString(value)
|
||||
case []interface{}:
|
||||
escapeSlice(value)
|
||||
orderedMap.Set(pair.Key, value)
|
||||
default:
|
||||
log.Trace().Type("type", value).Msg("unknown logEvent type")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func escapeStringMap(orderedMap *orderedmap.OrderedMap[string, string]) {
|
||||
for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
|
||||
value := html.EscapeString(pair.Value)
|
||||
value = strings.ReplaceAll(value, search.MarkerStart, "<mark>")
|
||||
value = strings.ReplaceAll(value, search.MarkerEnd, "</mark>")
|
||||
orderedMap.Set(pair.Key, value)
|
||||
orderedMap.Set(pair.Key, escapeAndProcessMarkers(pair.Value))
|
||||
}
|
||||
}
|
||||
|
||||
func escapeMapStringInterface(value map[string]interface{}) {
|
||||
for key, val := range value {
|
||||
switch val := val.(type) {
|
||||
case string:
|
||||
value[key] = escapeAndProcessMarkers(val)
|
||||
case map[string]interface{}:
|
||||
escapeMapStringInterface(val)
|
||||
case map[string]string:
|
||||
escapeStringMapString(val)
|
||||
case []interface{}:
|
||||
escapeSlice(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func escapeStringMapString(value map[string]string) {
|
||||
for key, val := range value {
|
||||
value[key] = escapeAndProcessMarkers(val)
|
||||
}
|
||||
}
|
||||
|
||||
func escapeSlice(slice []interface{}) {
|
||||
for i, val := range slice {
|
||||
switch val := val.(type) {
|
||||
case string:
|
||||
slice[i] = escapeAndProcessMarkers(val)
|
||||
case *orderedmap.OrderedMap[string, any]:
|
||||
escapeAnyMap(val)
|
||||
case *orderedmap.OrderedMap[string, string]:
|
||||
escapeStringMap(val)
|
||||
case map[string]interface{}:
|
||||
escapeMapStringInterface(val)
|
||||
case map[string]string:
|
||||
escapeStringMapString(val)
|
||||
case []interface{}:
|
||||
escapeSlice(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package support_web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
)
|
||||
|
||||
// PatternMatcher defines the interface for a regex pattern matcher
|
||||
type PatternMatcher struct {
|
||||
Regex *regexp.Regexp
|
||||
MarkerStart string
|
||||
MarkerEnd string
|
||||
}
|
||||
|
||||
// NewPatternMatcher creates a new pattern matcher with the specified regex and markers
|
||||
func NewPatternMatcher(re *regexp.Regexp, markerStart, markerEnd string) *PatternMatcher {
|
||||
return &PatternMatcher{
|
||||
Regex: re,
|
||||
MarkerStart: markerStart,
|
||||
MarkerEnd: markerEnd,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateRegex compiles a regex pattern with optional case-insensitive flag
|
||||
func CreateRegex(pattern string, caseInsensitive bool) (*regexp.Regexp, error) {
|
||||
flags := ""
|
||||
if caseInsensitive || pattern == strings.ToLower(pattern) {
|
||||
flags = "(?i)"
|
||||
}
|
||||
|
||||
re, err := regexp.Compile(flags + pattern)
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Str("pattern", pattern).Msg("failed to compile regex")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return re, nil
|
||||
}
|
||||
|
||||
// MarkInLogEvent applies the pattern matcher to the log event's message
|
||||
func (pm *PatternMatcher) MarkInLogEvent(logEvent *container.LogEvent) bool {
|
||||
switch value := logEvent.Message.(type) {
|
||||
case string:
|
||||
if pm.Regex.MatchString(value) {
|
||||
logEvent.Message = pm.Regex.ReplaceAllString(value, pm.MarkerStart+"$0"+pm.MarkerEnd)
|
||||
return true
|
||||
}
|
||||
|
||||
case *orderedmap.OrderedMap[string, any]:
|
||||
return pm.markMapAny(value)
|
||||
|
||||
case *orderedmap.OrderedMap[string, string]:
|
||||
return pm.markMapString(value)
|
||||
|
||||
case map[string]interface{}:
|
||||
return pm.markMap(value)
|
||||
|
||||
case map[string]string:
|
||||
panic("not implemented")
|
||||
|
||||
default:
|
||||
log.Debug().Type("type", value).Msg("unknown logEvent type")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (pm *PatternMatcher) markMapAny(orderedMap *orderedmap.OrderedMap[string, any]) bool {
|
||||
found := false
|
||||
for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
|
||||
switch value := pair.Value.(type) {
|
||||
case string:
|
||||
if replaced, matched := pm.markString(value); matched {
|
||||
found = true
|
||||
orderedMap.Set(pair.Key, replaced)
|
||||
}
|
||||
|
||||
case []any:
|
||||
if pm.markArray(value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case *orderedmap.OrderedMap[string, any]:
|
||||
if pm.markMapAny(value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case *orderedmap.OrderedMap[string, string]:
|
||||
if pm.markMapString(value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
if pm.markMap(value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case int, float64, bool:
|
||||
formatted := fmt.Sprintf("%v", value)
|
||||
if pm.Regex.MatchString(formatted) {
|
||||
orderedMap.Set(pair.Key, pm.Regex.ReplaceAllString(formatted, pm.MarkerStart+"$0"+pm.MarkerEnd))
|
||||
found = true
|
||||
}
|
||||
|
||||
default:
|
||||
log.Debug().Type("type", value).Msg("unknown logEvent type inside markMapAny")
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func (pm *PatternMatcher) markMap(data map[string]interface{}) bool {
|
||||
found := false
|
||||
for key, value := range data {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if replaced, matched := pm.markString(value); matched {
|
||||
found = true
|
||||
data[key] = replaced
|
||||
}
|
||||
case []any:
|
||||
if pm.markArray(value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
if pm.markMap(value) {
|
||||
found = true
|
||||
}
|
||||
|
||||
case int, float64, bool:
|
||||
formatted := fmt.Sprintf("%v", value)
|
||||
if pm.Regex.MatchString(formatted) {
|
||||
data[key] = pm.Regex.ReplaceAllString(formatted, pm.MarkerStart+"$0"+pm.MarkerEnd)
|
||||
found = true
|
||||
}
|
||||
default:
|
||||
log.Debug().Type("type", value).Msg("unknown logEvent type inside markMap")
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func (pm *PatternMatcher) markMapString(orderedMap *orderedmap.OrderedMap[string, string]) bool {
|
||||
found := false
|
||||
for pair := orderedMap.Oldest(); pair != nil; pair = pair.Next() {
|
||||
if replaced, matched := pm.markString(pair.Value); matched {
|
||||
found = true
|
||||
orderedMap.Set(pair.Key, replaced)
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func (pm *PatternMatcher) markArray(data []any) bool {
|
||||
found := false
|
||||
for i, value := range data {
|
||||
switch value := value.(type) {
|
||||
case string:
|
||||
if replaced, matched := pm.markString(value); matched {
|
||||
found = true
|
||||
data[i] = replaced
|
||||
}
|
||||
case int, float64, bool:
|
||||
formatted := fmt.Sprintf("%v", value)
|
||||
if pm.Regex.MatchString(formatted) {
|
||||
data[i] = pm.Regex.ReplaceAllString(formatted, pm.MarkerStart+"$0"+pm.MarkerEnd)
|
||||
found = true
|
||||
}
|
||||
case []any:
|
||||
if pm.markArray(value) {
|
||||
found = true
|
||||
}
|
||||
case map[string]interface{}:
|
||||
if pm.markMap(value) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
func (pm *PatternMatcher) markString(value string) (string, bool) {
|
||||
if pm.Regex.MatchString(value) {
|
||||
replaced := pm.Regex.ReplaceAllString(value, pm.MarkerStart+"$0"+pm.MarkerEnd)
|
||||
return replaced, true
|
||||
}
|
||||
|
||||
return value, false
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package support_web
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
)
|
||||
|
||||
const (
|
||||
MarkerStart = "\uE000"
|
||||
MarkerEnd = "\uE001"
|
||||
)
|
||||
|
||||
func ParseRegex(search string) (*regexp.Regexp, error) {
|
||||
return CreateRegex(search, search == strings.ToLower(search))
|
||||
}
|
||||
|
||||
func Search(re *regexp.Regexp, logEvent *container.LogEvent) bool {
|
||||
matcher := NewPatternMatcher(re, MarkerStart, MarkerEnd)
|
||||
return matcher.MarkInLogEvent(logEvent)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package support_web
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
)
|
||||
|
||||
const (
|
||||
URLMarkerStart = "\uE002"
|
||||
URLMarkerEnd = "\uE003"
|
||||
)
|
||||
|
||||
// Standard URL regex pattern to match http/https URLs
|
||||
var urlRegex = regexp.MustCompile(`(https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*))`)
|
||||
|
||||
// MarkURLs marks URLs in the logEvent message with special markers
|
||||
func MarkURLs(logEvent *container.LogEvent) bool {
|
||||
matcher := NewPatternMatcher(urlRegex, URLMarkerStart, URLMarkerEnd)
|
||||
return matcher.MarkInLogEvent(logEvent)
|
||||
}
|
||||
@@ -81,15 +81,15 @@ Content-Type: text/html
|
||||
<pre>dev</pre>
|
||||
|
||||
/* snapshot: Test_handler_between_dates */
|
||||
{"m":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout","c":"123456"}
|
||||
{"m":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr","c":"123456"}
|
||||
{"m":"INFO Testing stdout logs...","rm":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout","c":"123456"}
|
||||
{"m":"INFO Testing stderr logs...","rm":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr","c":"123456"}
|
||||
|
||||
/* snapshot: Test_handler_between_dates_with_everything_complex */
|
||||
{"m":{"msg":"a complex log message"},"ts":1589396197772,"id":62280847,"l":"unknown","s":"stdout","c":"123456"}
|
||||
{"m":{"msg":"a complex log message"},"rm":"{\"msg\":\"a complex log message\"}","ts":1589396197772,"id":62280847,"l":"unknown","s":"stdout","c":"123456"}
|
||||
|
||||
/* snapshot: Test_handler_between_dates_with_fill */
|
||||
{"m":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout","c":"123456"}
|
||||
{"m":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr","c":"123456"}
|
||||
{"m":"INFO Testing stdout logs...","rm":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout","c":"123456"}
|
||||
{"m":"INFO Testing stderr logs...","rm":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr","c":"123456"}
|
||||
|
||||
/* snapshot: Test_handler_download_logs */
|
||||
INFO Testing logs...
|
||||
@@ -164,12 +164,15 @@ stdout or stderr is required
|
||||
/* snapshot: Test_handler_streamLogs_happy */
|
||||
:ping
|
||||
|
||||
data: {"m":"INFO Testing logs...","ts":0,"id":4256192898,"l":"info","s":"stdout","c":"123456"}
|
||||
data: {"m":"INFO Testing logs...\n","ts":0,"id":3835490584,"l":"info","s":"stdout","c":"123456"}
|
||||
|
||||
|
||||
event: container-event
|
||||
data: {"name":"container-stopped","host":"localhost","actorId":"123456","time":"<removed>"}
|
||||
|
||||
|
||||
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_happy_container_stopped */
|
||||
:ping
|
||||
|
||||
@@ -179,7 +182,7 @@ data: {"name":"container-stopped","host":"localhost","actorId":"123456","time":"
|
||||
/* snapshot: Test_handler_streamLogs_happy_with_id */
|
||||
:ping
|
||||
|
||||
data: {"m":"INFO Testing logs...","ts":1589396137772,"id":1469707724,"l":"info","s":"stdout","c":"123456"}
|
||||
data: {"m":"INFO Testing logs...","rm":"INFO Testing logs...","ts":1589396137772,"id":2908612274,"l":"info","s":"stdout","c":"123456"}
|
||||
id: 1589396137772
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
@@ -20,7 +21,6 @@ import (
|
||||
"github.com/amir20/dozzle/internal/auth"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/amir20/dozzle/internal/support/search"
|
||||
support_web "github.com/amir20/dozzle/internal/support/web"
|
||||
"github.com/amir20/dozzle/internal/utils"
|
||||
"github.com/dustin/go-humanize"
|
||||
@@ -63,12 +63,11 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
buffer := utils.NewRingBuffer[*container.LogEvent](500)
|
||||
delta := max(to.Sub(from), time.Second*3)
|
||||
|
||||
var regex *regexp.Regexp
|
||||
if r.URL.Query().Has("filter") {
|
||||
regex, err = search.ParseRegex(r.URL.Query().Get("filter"))
|
||||
regex, err = support_web.ParseRegex(r.URL.Query().Get("filter"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -83,8 +82,9 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
minimum := 0
|
||||
if r.URL.Query().Has("minimum") {
|
||||
minimum, err = strconv.Atoi(r.URL.Query().Get("minimum"))
|
||||
buffer := utils.NewRingBuffer[*container.LogEvent](500)
|
||||
if r.URL.Query().Has("min") {
|
||||
minimum, err = strconv.Atoi(r.URL.Query().Get("min"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -94,6 +94,21 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
http.Error(w, errors.New("minimum must be between 0 and buffer size").Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
buffer = utils.NewRingBuffer[*container.LogEvent](minimum)
|
||||
}
|
||||
|
||||
maxStart := math.MaxInt
|
||||
if r.URL.Query().Has("maxStart") {
|
||||
maxStart, err = strconv.Atoi(r.URL.Query().Get("maxStart"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if maxStart < 1 || maxStart > buffer.Size {
|
||||
http.Error(w, errors.New("invalid maxStart").Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
levels := make(map[string]struct{})
|
||||
@@ -121,7 +136,7 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
for {
|
||||
if buffer.Len() > minimum {
|
||||
if minimum > 0 && buffer.Len() >= minimum {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -144,7 +159,7 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
} else {
|
||||
if regex != nil {
|
||||
if !search.Search(regex, event) {
|
||||
if !support_web.Search(regex, event) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -158,7 +173,11 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
break
|
||||
}
|
||||
|
||||
support_web.EscapeHTMLValues(event) // only escape when not exporting
|
||||
if buffer.Len() >= maxStart {
|
||||
break
|
||||
}
|
||||
|
||||
support_web.EscapeHTMLValues(event)
|
||||
buffer.Push(event)
|
||||
}
|
||||
}
|
||||
@@ -167,13 +186,19 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
break
|
||||
}
|
||||
|
||||
if minimum == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
from = from.Add(-delta)
|
||||
delta = delta * 2
|
||||
}
|
||||
|
||||
log.Debug().Int("buffer_size", buffer.Len()).Msg("sending logs to client")
|
||||
|
||||
for _, event := range buffer.Data() {
|
||||
data := buffer.Data()
|
||||
|
||||
for _, event := range data {
|
||||
if err := encoder.Encode(event); err != nil {
|
||||
log.Error().Err(err).Msg("error encoding log event")
|
||||
return
|
||||
@@ -280,7 +305,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
||||
|
||||
if r.URL.Query().Has("filter") {
|
||||
var err error
|
||||
regex, err = search.ParseRegex(r.URL.Query().Get("filter"))
|
||||
regex, err = support_web.ParseRegex(r.URL.Query().Get("filter"))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -317,7 +342,7 @@ func (h *handler) streamLogsForContainers(w http.ResponseWriter, r *http.Request
|
||||
if _, ok := levels[log.Level]; !ok {
|
||||
continue
|
||||
}
|
||||
if search.Search(regex, log) {
|
||||
if support_web.Search(regex, log) {
|
||||
events = append(events, log)
|
||||
}
|
||||
}
|
||||
@@ -384,7 +409,7 @@ loop:
|
||||
select {
|
||||
case logEvent := <-liveLogs:
|
||||
if regex != nil {
|
||||
if !search.Search(regex, logEvent) {
|
||||
if !support_web.Search(regex, logEvent) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func Test_handler_streamLogs_happy(t *testing.T) {
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
|
||||
data := makeMessage("INFO Testing logs...", container.STDOUT)
|
||||
data := makeMessage("INFO Testing logs...\n", container.STDOUT)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
@@ -80,7 +80,7 @@ func Test_handler_streamLogs_happy_with_id(t *testing.T) {
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
|
||||
data := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing logs...", container.STDOUT)
|
||||
data := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing logs...\n", container.STDOUT)
|
||||
|
||||
started := time.Date(2020, time.May, 13, 18, 55, 37, 772853839, time.UTC)
|
||||
|
||||
@@ -268,6 +268,7 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
|
||||
q.Add("stderr", "true")
|
||||
q.Add("fill", "true")
|
||||
q.Add("levels", "info")
|
||||
q.Add("min", "10")
|
||||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
@@ -280,16 +281,22 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
|
||||
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, from, to, container.STDALL).
|
||||
Return(io.NopCloser(bytes.NewReader([]byte{})), nil).
|
||||
Once()
|
||||
|
||||
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, time.Date(2017, time.December, 31, 14, 0, 0, 0, time.UTC), to, container.STDALL).
|
||||
Return(io.NopCloser(bytes.NewReader(data)), nil).
|
||||
Once()
|
||||
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id}, nil)
|
||||
mockedClient.On("Host").Return(container.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
|
||||
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, time.Date(2017, time.December, 30, 18, 0, 0, 0, time.UTC), to, container.STDALL).
|
||||
Return(io.NopCloser(bytes.NewReader(data)), nil).
|
||||
Once()
|
||||
|
||||
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id, Created: time.Date(2017, time.December, 31, 10, 0, 0, 0, time.UTC)}, nil)
|
||||
mockedClient.On("Host").Return(container.Host{ID: "localhost"})
|
||||
|
||||
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
|
||||
{ID: id, Name: "test", Host: "localhost", State: "running"},
|
||||
}, nil)
|
||||
|
||||
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
|
||||
|
||||
handler := createDefaultHandler(mockedClient)
|
||||
|
||||
@@ -9,6 +9,13 @@ toolbar:
|
||||
restart: Genstart
|
||||
show-hostname: Vis værtsnavn
|
||||
show-container-name: Vis containernavn
|
||||
shell: Shell
|
||||
attach: Tilknyt
|
||||
action:
|
||||
copy-log: Kopier log
|
||||
copy-link: Kopier permalink
|
||||
see-in-context: Se i kontekst
|
||||
show-details: Vis detaljer
|
||||
label:
|
||||
containers: Containere
|
||||
container: Ingen containere | 1 container | {count} containere
|
||||
@@ -30,6 +37,9 @@ label:
|
||||
host-menu: Værter og Containere
|
||||
swarm-menu: Tjenester og Stacks
|
||||
group-menu: Brugerdefinerede Grupper
|
||||
no-logs: Container har ingen logs endnu
|
||||
show-all-containers: Vis alle containere
|
||||
collapse-all: Kollaps alle
|
||||
tooltip:
|
||||
search: Søg containere (⌘ + k, ⌃k)
|
||||
pin-column: Fastgør som kolonne
|
||||
@@ -39,7 +49,7 @@ tooltip:
|
||||
error:
|
||||
page-not-found: Denne side eksisterer ikke
|
||||
invalid-auth: Brugernavn eller kodeord er ikke gyldig
|
||||
logs-skipped: Sprang over {total} indtastninger
|
||||
logs-skipped: Vis {total} skjulte indtastninger
|
||||
container-not-found: Container ikke fundet
|
||||
events-stream:
|
||||
title: Uventet fejl
|
||||
@@ -72,6 +82,8 @@ placeholder:
|
||||
search-containers: Søg containere (⌘ + k, ⌃k)
|
||||
search: Søg
|
||||
settings:
|
||||
help-support: >
|
||||
Støt venligst Dozzle ved at donere eller sponsorere os på GitHub. Dine bidrag hjælper os med at forbedre Dozzle for alle. Tak! 🙏🏼
|
||||
display: Visning
|
||||
locale: Overskriv sprog
|
||||
small-scrollbars: Brug mindre scrollbarer
|
||||
@@ -84,7 +96,7 @@ settings:
|
||||
show-stopped-containers: Vis stoppet containere
|
||||
about: Omkring
|
||||
search: Aktiver søgning med Dozzle ved brug af
|
||||
using-version: Du bruger Dozzle {version}.
|
||||
using-version: Du bruger <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
|
||||
update-available: >-
|
||||
Ny version er tilgængelig! Opdater til <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Vis stdout og stderr mærker
|
||||
|
||||
@@ -9,6 +9,13 @@ toolbar:
|
||||
restart: Neustarten
|
||||
show-hostname: Host-Name anzeigen
|
||||
show-container-name: Container-Name anzeigen
|
||||
shell: Shell
|
||||
attach: Anhängen
|
||||
action:
|
||||
copy-log: Log kopieren
|
||||
copy-link: Permalink kopieren
|
||||
see-in-context: Im Kontext anzeigen
|
||||
show-details: Details anzeigen
|
||||
label:
|
||||
containers: Container
|
||||
container: Keine Container | 1 Container | {count} Container
|
||||
@@ -30,6 +37,9 @@ label:
|
||||
host-menu: Hosts und Container
|
||||
swarm-menu: Services und Stacks
|
||||
group-menu: Benutzerdefinierte Gruppen
|
||||
no-logs: Container hat noch keine Logs
|
||||
show-all-containers: Zeige alle Container
|
||||
collapse-all: Alle einklappen
|
||||
tooltip:
|
||||
search: Suche Container (⌘ + k, ⌃k)
|
||||
pin-column: Als Spalte anheften
|
||||
@@ -39,7 +49,7 @@ tooltip:
|
||||
error:
|
||||
page-not-found: Diese Seite existiert nicht.
|
||||
invalid-auth: Benutzername und Passwort sind ungültig.
|
||||
logs-skipped: \{total} Einträge übersprungen
|
||||
logs-skipped: Zeige {total} versteckte Einträge
|
||||
container-not-found: Container nicht gefunden.
|
||||
events-stream:
|
||||
title: Unerwarteter Fehler
|
||||
@@ -72,6 +82,8 @@ placeholder:
|
||||
search-containers: Suche Container (⌘ + k, ⌃k)
|
||||
search: Suche
|
||||
settings:
|
||||
help-support: >
|
||||
Bitte unterstützen Sie Dozzle durch Spenden oder Sponsoring auf GitHub. Ihre Beiträge helfen uns, Dozzle für alle zu verbessern. Vielen Dank! 🙏🏼
|
||||
display: Anzeige
|
||||
locale: Sprache überschreiben
|
||||
small-scrollbars: Verwende kleinere Scrollbars
|
||||
@@ -84,7 +96,7 @@ settings:
|
||||
show-stopped-containers: Zeige gestoppte Container
|
||||
about: Über
|
||||
search: Aktiviere die Suche mit Dozzle mit
|
||||
using-version: Du verwendest Dozzle {version}.
|
||||
using-version: Du verwendest <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
|
||||
update-available: >-
|
||||
Eine neue Version ist verfügbar! Aktualisiere auf <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Zeige stdout und stderr Labels
|
||||
|
||||
@@ -9,6 +9,13 @@ toolbar:
|
||||
restart: Restart
|
||||
show-hostname: Show hostname
|
||||
show-container-name: Show container name
|
||||
shell: Shell
|
||||
attach: Attach
|
||||
action:
|
||||
copy-log: Copy log
|
||||
copy-link: Copy permalink
|
||||
see-in-context: See in context
|
||||
show-details: Show details
|
||||
label:
|
||||
containers: Containers
|
||||
container: No containers | 1 container | {count} containers
|
||||
@@ -30,6 +37,9 @@ label:
|
||||
host-menu: Hosts and Containers
|
||||
swarm-menu: Services and Stacks
|
||||
group-menu: Custom Groups
|
||||
no-logs: Container has no logs yet
|
||||
show-all-containers: Show all containers
|
||||
collapse-all: Collapse all
|
||||
tooltip:
|
||||
search: Search containers (⌘ + k, ⌃k)
|
||||
pin-column: Pin as column
|
||||
@@ -39,7 +49,7 @@ tooltip:
|
||||
error:
|
||||
page-not-found: This page does not exist
|
||||
invalid-auth: Username or password are not valid
|
||||
logs-skipped: Skipped {total} entries
|
||||
logs-skipped: Show {total} hidden entries
|
||||
container-not-found: Container not found
|
||||
events-stream:
|
||||
title: Unexpected Error
|
||||
@@ -76,6 +86,9 @@ placeholder:
|
||||
search-containers: Search containers (⌘ + k, ⌃k)
|
||||
search: Search
|
||||
settings:
|
||||
help-support: >
|
||||
Please support Dozzle by donating or sponsoring us on GitHub. Your contributions help us improve Dozzle for
|
||||
everyone. Thank you! 🙏🏼
|
||||
display: Display
|
||||
locale: Override language
|
||||
small-scrollbars: Use smaller scrollbars
|
||||
@@ -88,7 +101,7 @@ settings:
|
||||
show-stopped-containers: Show stopped containers
|
||||
about: About
|
||||
search: Enable searching with Dozzle using
|
||||
using-version: You are using Dozzle {version}.
|
||||
using-version: You are using <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
|
||||
update-available: >-
|
||||
New version is available! Update to <a href="{href}" target="_blank"
|
||||
rel="noreferrer noopener">{nextVersion}</a>.
|
||||
@@ -118,7 +131,7 @@ log_actions:
|
||||
toasts:
|
||||
copied:
|
||||
title: Copied
|
||||
message: Log copied to clipboard
|
||||
message: Copied to clipboard
|
||||
analytics:
|
||||
creating_table: Creating temporary table...
|
||||
downloading: Fetching container logs... ({size})
|
||||
|
||||
@@ -9,6 +9,13 @@ toolbar:
|
||||
restart: Reiniciar
|
||||
show-hostname: Mostrar nombre del host
|
||||
show-container-name: Mostrar nombre del contenedor
|
||||
shell: Shell
|
||||
attach: Conectar
|
||||
action:
|
||||
copy-log: Copiar registro
|
||||
copy-link: Copiar enlace permanente
|
||||
see-in-context: Ver en contexto
|
||||
show-details: Mostrar detalles
|
||||
label:
|
||||
containers: Contenedores
|
||||
container: No contenedores | 1 contenedor | {count} contenedores
|
||||
@@ -30,6 +37,9 @@ label:
|
||||
host-menu: Hosts y Contenedores
|
||||
swarm-menu: Servicios y Stacks
|
||||
group-menu: Grupos Personalizados
|
||||
no-logs: El contenedor aún no tiene registros
|
||||
show-all-containers: Mostrar todos los contenedores
|
||||
collapse-all: Colapsar todo
|
||||
tooltip:
|
||||
search: Buscar contenedores (⌘ + K, CTRL + K)
|
||||
pin-column: Anclar como columna
|
||||
@@ -39,7 +49,7 @@ tooltip:
|
||||
error:
|
||||
page-not-found: Esta página no existe.
|
||||
invalid-auth: El nombre de usuario y la contraseña no son válidos.
|
||||
logs-skipped: Omitidas {total} entrada/s
|
||||
logs-skipped: Mostrar {total} entradas ocultas
|
||||
container-not-found: Contenedor no encontrado.
|
||||
events-stream:
|
||||
title: Error inesperado
|
||||
@@ -72,6 +82,8 @@ placeholder:
|
||||
search-containers: Buscar contenedores (⌘ + K, CTRL + K)
|
||||
search: Buscar
|
||||
settings:
|
||||
help-support: >
|
||||
Por favor, apoya a Dozzle donando o patrocinándonos en GitHub. Tus contribuciones nos ayudan a mejorar Dozzle para todos. ¡Gracias! 🙏🏼
|
||||
display: Vista
|
||||
locale: Sobrescribir idioma
|
||||
small-scrollbars: Utilizar barras de desplazamiento más pequeñas
|
||||
@@ -84,7 +96,7 @@ settings:
|
||||
show-stopped-containers: Mostrar contenedores parados
|
||||
about: Acerca de
|
||||
search: Activar la búsqueda con Dozzle mediante
|
||||
using-version: Estás usando Dozzle {version}.
|
||||
using-version: Estás usando <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
|
||||
update-available: >-
|
||||
¡La nueva versión está disponible! Actualizar a la <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Mostrar etiquetas de salida estándar y salida de error estándar
|
||||
@@ -100,9 +112,9 @@ settings:
|
||||
middle-line: con una segunda línea
|
||||
end-line: y finalmente una tercera línea.
|
||||
releases:
|
||||
features: Características
|
||||
bugFixes: Correcciones de errores
|
||||
breaking: Cambios importantes
|
||||
features: una nueva característica | {count} características
|
||||
bugFixes: una corrección de error | {count} correcciones
|
||||
breaking: un cambio importante | {count} cambios importantes
|
||||
three_parts: "{features}, {bugFixes} y {breaking}"
|
||||
two_parts: "{features} y {bugFixes}"
|
||||
latest: Último
|
||||
|
||||
@@ -7,8 +7,15 @@ toolbar:
|
||||
stop: Stop
|
||||
start: Démarrer
|
||||
restart: Redémarrer
|
||||
show-hostname: Show hostname
|
||||
show-container-name: Show container name
|
||||
show-hostname: Afficher le nom d'hôte
|
||||
show-container-name: Afficher le nom du conteneur
|
||||
shell: Shell
|
||||
attach: Attacher
|
||||
action:
|
||||
copy-log: Copier le journal
|
||||
copy-link: Copier le permalien
|
||||
see-in-context: Voir dans le contexte
|
||||
show-details: Afficher les détails
|
||||
label:
|
||||
containers: Conteneurs
|
||||
container: Pas de conteneur | 1 conteneur | {count} conteneurs
|
||||
@@ -30,6 +37,9 @@ label:
|
||||
host-menu: Hôtes et Conteneurs
|
||||
swarm-menu: Services et Stacks
|
||||
group-menu: Groupes Personnalisés
|
||||
no-logs: Le conteneur n'a pas encore de logs
|
||||
show-all-containers: Afficher tous les conteneurs
|
||||
collapse-all: Réduire tout
|
||||
tooltip:
|
||||
search: Recherche de conteneurs (⌘ + k, ⌃k)
|
||||
pin-column: Epinglé en colonne
|
||||
@@ -39,7 +49,7 @@ tooltip:
|
||||
error:
|
||||
page-not-found: Cette page n'existe pas
|
||||
invalid-auth: Nom d'utilisateur ou mot de passe non valides
|
||||
logs-skipped: Entrées {total} ignorées
|
||||
logs-skipped: Afficher {total} entrées cachées
|
||||
container-not-found: Conteneur non trouvé
|
||||
events-stream:
|
||||
title: Erreur inattendue
|
||||
@@ -66,12 +76,14 @@ button:
|
||||
logout: Déconnexion
|
||||
login: Connexion
|
||||
settings: Paramètres
|
||||
cancel: Cancel
|
||||
redirect: Redirect
|
||||
cancel: Annuler
|
||||
redirect: Rediriger
|
||||
placeholder:
|
||||
search-containers: Recherche de conteneurs (⌘ + k, ⌃k)
|
||||
search: Recherche
|
||||
settings:
|
||||
help-support: >
|
||||
Veuillez soutenir Dozzle en faisant un don ou en nous parrainant sur GitHub. Vos contributions nous aident à améliorer Dozzle pour tout le monde. Merci ! 🙏🏼
|
||||
display: Afficher
|
||||
locale: Langue de remplacement
|
||||
small-scrollbars: Utiliser des barres de défilement plus petites
|
||||
@@ -84,7 +96,7 @@ settings:
|
||||
show-stopped-containers: Afficher les conteneurs arrêtés
|
||||
about: A propos de
|
||||
search: Activer la recherche avec Dozzle en utilisant
|
||||
using-version: Vous utilisez Dozzle {version}.
|
||||
using-version: Vous utilisez <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
|
||||
update-available: >-
|
||||
Nouvelle version disponible! Mise à jour de <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Afficher les étiquettes stdout et stderr
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
toolbar:
|
||||
clear: Bersihkan
|
||||
download: Unduh
|
||||
search: Cari
|
||||
show: Tampilkan {std}
|
||||
show-all: Tampilkan semua
|
||||
stop: Hentikan
|
||||
start: Mulai
|
||||
restart: Mulai ulang
|
||||
show-hostname: Tampilkan nama host
|
||||
show-container-name: Tampilkan nama kontainer
|
||||
shell: Shell
|
||||
attach: Lampirkan
|
||||
action:
|
||||
copy-log: Salin log
|
||||
copy-link: Salin permalink
|
||||
see-in-context: Lihat dalam konteks
|
||||
show-details: Tampilkan detail
|
||||
|
||||
label:
|
||||
containers: Kontainer
|
||||
container: Tidak ada kontainer | 1 kontainer | {count} kontainer
|
||||
service: Tidak ada layanan | 1 layanan | {count} layanan
|
||||
services: Layanan
|
||||
running-containers: Kontainer Berjalan
|
||||
all-containers: Semua Kontainer
|
||||
host: Host
|
||||
hosts: Host
|
||||
password: Kata sandi
|
||||
username: Nama pengguna
|
||||
container-name: Nama Kontainer
|
||||
status: Status
|
||||
created: Dibuat
|
||||
avg-cpu: Rata-rata CPU (%)
|
||||
avg-mem: Rata-rata MEM (%)
|
||||
pinned: Disematkan
|
||||
per-page: Baris per halaman
|
||||
host-menu: Host dan Kontainer
|
||||
swarm-menu: Layanan dan Stack
|
||||
group-menu: Grup Kustom
|
||||
no-logs: Kontainer belum memiliki log
|
||||
show-all-containers: Tampilkan semua kontainer
|
||||
collapse-all: Tutup semua
|
||||
|
||||
tooltip:
|
||||
search: Cari kontainer (⌘ + k, ⌃k)
|
||||
pin-column: Sematkan sebagai kolom
|
||||
merge-services: Gabungkan semua layanan dalam satu tampilan
|
||||
merge-containers: Gabungkan semua kontainer dalam satu tampilan
|
||||
merge-hosts: Gabungkan semua kontainer di host ini dalam satu tampilan
|
||||
|
||||
error:
|
||||
page-not-found: Halaman ini tidak ada
|
||||
invalid-auth: Nama pengguna atau kata sandi tidak valid
|
||||
logs-skipped: Tampilkan {total} entri tersembunyi
|
||||
container-not-found: Kontainer tidak ditemukan
|
||||
events-stream:
|
||||
title: Kesalahan Tak Terduga
|
||||
message: >-
|
||||
UI Dozzle tidak dapat terhubung ke API. Periksa pengaturan jaringan Anda. Jika Anda menggunakan reverse proxy, pastikan konfigurasinya sudah benar.
|
||||
events-timeout:
|
||||
title: Ada yang tidak beres
|
||||
message: >-
|
||||
UI Dozzle gagal terhubung ke API karena waktu habis. Periksa koneksi jaringan dan coba lagi.
|
||||
|
||||
alert:
|
||||
redirected:
|
||||
title: Dialihkan ke kontainer baru
|
||||
message: Dozzle secara otomatis mengalihkan Anda ke kontainer baru {containerId}.
|
||||
similar-container-found:
|
||||
title: Kontainer serupa ditemukan
|
||||
message: >-
|
||||
Dozzle menemukan kontainer serupa {containerId} yang berjalan di host yang sama dan akan otomatis beralih kecuali Anda mengklik 'Batal'.
|
||||
|
||||
title:
|
||||
page-not-found: Halaman tidak ditemukan
|
||||
login: Autentikasi Diperlukan
|
||||
dashboard: 1 kontainer | {count} kontainer
|
||||
settings: Pengaturan
|
||||
|
||||
button:
|
||||
logout: Keluar
|
||||
login: Masuk
|
||||
settings: Pengaturan
|
||||
cancel: Batal
|
||||
redirect: Alihkan
|
||||
|
||||
placeholder:
|
||||
search-containers: Cari kontainer (⌘ + k, ⌃k)
|
||||
search: Cari
|
||||
|
||||
settings:
|
||||
help-support: >
|
||||
Silakan dukung Dozzle dengan berdonasi atau mensponsori kami di GitHub. Kontribusi Anda membantu kami meningkatkan Dozzle untuk semua orang. Terima kasih! 🙏🏼
|
||||
display: Tampilan
|
||||
locale: Ganti bahasa
|
||||
small-scrollbars: Gunakan scrollbar kecil
|
||||
show-timesamps: Tampilkan cap waktu
|
||||
soft-wrap: Bungkus baris secara lunak
|
||||
datetime-format: Ganti format tanggal dan waktu
|
||||
font-size: Ukuran font untuk log
|
||||
color-scheme: Skema warna
|
||||
options: Opsi
|
||||
show-stopped-containers: Tampilkan kontainer yang dihentikan
|
||||
about: Tentang
|
||||
search: Aktifkan pencarian dengan Dozzle menggunakan
|
||||
using-version: Anda menggunakan <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
|
||||
update-available: >-
|
||||
Versi baru tersedia! Perbarui ke <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Tampilkan label stdout dan stderr
|
||||
automatic-redirect: Alihkan otomatis ke kontainer baru dengan nama yang sama
|
||||
compact: Aktifkan mode ringkas untuk log
|
||||
log:
|
||||
preview: Ini adalah pratinjau log
|
||||
warning: Log peringatan seperti ini
|
||||
complex: Ini adalah entri log kompleks dalam format json
|
||||
simple: Ini adalah pesan yang sangat panjang yang akan terbungkus secara default. Menonaktifkan pembungkusan lunak akan menonaktifkannya. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
multi-line-error:
|
||||
start-line: Ini adalah pesan kesalahan multi-baris
|
||||
middle-line: dengan baris kedua
|
||||
end-line: dan akhirnya baris ketiga.
|
||||
|
||||
releases:
|
||||
features: satu fitur baru | {count} fitur
|
||||
bugFixes: satu perbaikan bug | {count} perbaikan
|
||||
breaking: satu perubahan besar | {count} perubahan besar
|
||||
three_parts: "{first}, {second}, dan {third}"
|
||||
two_parts: "{first} dengan {second}"
|
||||
latest: Terbaru
|
||||
no_releases: Anda sudah menggunakan versi terbaru
|
||||
|
||||
log_actions:
|
||||
copy_log: Salin log
|
||||
jump_to_context: Lompat ke konteks
|
||||
|
||||
toasts:
|
||||
copied:
|
||||
title: Disalin
|
||||
message: Log disalin ke clipboard
|
||||
|
||||
analytics:
|
||||
creating_table: Membuat tabel sementara...
|
||||
downloading: Mengambil log kontainer... ({size})
|
||||
evaluating_query: Mengevaluasi kueri...
|
||||
total_records: Total {count} entri.
|
||||
showing_first: Menampilkan {count} pertama.
|
||||
@@ -9,11 +9,18 @@ toolbar:
|
||||
restart: Restart
|
||||
show-hostname: Mostra nome host
|
||||
show-container-name: Mostra nome container
|
||||
shell: Shell
|
||||
attach: Collega
|
||||
action:
|
||||
copy-log: Copia log
|
||||
copy-link: Copia permalink
|
||||
see-in-context: Vedi nel contesto
|
||||
show-details: Mostra dettagli
|
||||
label:
|
||||
containers: Container
|
||||
container: No containers | 1 container | {count} containers
|
||||
service: No services | 1 service | {count} services
|
||||
services: Services
|
||||
container: Nessun container | 1 container | {count} container
|
||||
service: Nessun servizio | 1 servizio | {count} servizi
|
||||
services: Servizi
|
||||
running-containers: Container in esecuzione
|
||||
all-containers: Tutti i Container
|
||||
host: Host
|
||||
@@ -30,6 +37,9 @@ label:
|
||||
host-menu: Host e Container
|
||||
swarm-menu: Servizi e Stack
|
||||
group-menu: Gruppi Personalizzati
|
||||
no-logs: Il container non ha ancora log
|
||||
show-all-containers: Mostra tutti i container
|
||||
collapse-all: Comprimi tutto
|
||||
tooltip:
|
||||
search: Ricerca container (⌘ + k, ⌃k)
|
||||
pin-column: Blocca come colonna
|
||||
@@ -39,7 +49,7 @@ tooltip:
|
||||
error:
|
||||
page-not-found: Questa pagina non esiste
|
||||
invalid-auth: Username o password non valide
|
||||
logs-skipped: Saltato {total} registrazioni
|
||||
logs-skipped: Mostra {total} voci nascoste
|
||||
container-not-found: Container non trovato
|
||||
events-stream:
|
||||
title: Errore inaspettato
|
||||
@@ -66,12 +76,14 @@ button:
|
||||
logout: Logout
|
||||
login: Login
|
||||
settings: Impostazioni
|
||||
cancel: Cancel
|
||||
redirect: Redirect
|
||||
cancel: Annulla
|
||||
redirect: Reindirizza
|
||||
placeholder:
|
||||
search-containers: Ricerca container (⌘ + k, ⌃k)
|
||||
search: Cerca
|
||||
settings:
|
||||
help-support: >
|
||||
Per favore sostieni Dozzle donando o sponsorizzandoci su GitHub. I tuoi contributi ci aiutano a migliorare Dozzle per tutti. Grazie! 🙏🏼
|
||||
display: Visualizza
|
||||
locale: Sovrascrivi Lingua
|
||||
small-scrollbars: Usa una scrollbars più piccola
|
||||
@@ -84,7 +96,7 @@ settings:
|
||||
show-stopped-containers: Visualizza i container arrestati
|
||||
about: Circa
|
||||
search: Abilita la ricerca attraverso Dozzle
|
||||
using-version: Stai utilizzando Dozzle {version}.
|
||||
using-version: Stai utilizzando <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
|
||||
update-available: >-
|
||||
Nuova versione disponibile! Aggiorna a <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Visualizza stdout e stderr
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
toolbar:
|
||||
clear: 지우기
|
||||
download: 다운로드
|
||||
search: 검색
|
||||
show: "{std} 보기"
|
||||
show-all: 전체 보기
|
||||
stop: 중지
|
||||
start: 시작
|
||||
restart: 다시 시작
|
||||
show-hostname: 호스트 이름 보기
|
||||
show-container-name: 컨테이너 이름 보기
|
||||
shell: 셸
|
||||
attach: 연결
|
||||
action:
|
||||
copy-log: 로그 복사
|
||||
copy-link: 영구 링크 복사
|
||||
see-in-context: 컨텍스트에서 보기
|
||||
show-details: 세부 정보 표시
|
||||
label:
|
||||
containers: 컨테이너
|
||||
container: 컨테이너가 없습니다 | 컨테이너 1개 | 컨테이너 {count}개
|
||||
service: 서비스가 없습니다 | 서비스 1개 | 서비스 {count}개
|
||||
services: 서비스
|
||||
running-containers: 실행 중인 컨테이너
|
||||
all-containers: 전체 컨테이너
|
||||
host: 호스트
|
||||
hosts: 호스트
|
||||
password: 비밀번호
|
||||
username: 사용자 이름
|
||||
container-name: 컨테이너 이름
|
||||
status: 상태
|
||||
created: 만든 날짜
|
||||
avg-cpu: 평균 CPU (%)
|
||||
avg-mem: 평균 메모리 (%)
|
||||
pinned: 고정됨
|
||||
per-page: 페이지당 행 수
|
||||
host-menu: 호스트 및 컨테이너
|
||||
swarm-menu: 서비스 및 스택
|
||||
group-menu: 사용자 그룹
|
||||
no-logs: 아직 로그가 없습니다
|
||||
show-all-containers: 전체 컨테이너 보기
|
||||
collapse-all: 전체 접기
|
||||
tooltip:
|
||||
search: 컨테이너 검색 (⌘ + k, ⌃k)
|
||||
pin-column: 열로 고정
|
||||
merge-services: 모든 서비스를 하나의 화면으로 합치기
|
||||
merge-containers: 모든 컨테이너를 하나의 화면으로 합치기
|
||||
merge-hosts: 이 호스트의 모든 컨테이너를 하나의 화면으로 합치기
|
||||
error:
|
||||
page-not-found: 페이지를 찾을 수 없습니다
|
||||
invalid-auth: 사용자 이름 또는 비밀번호가 올바르지 않습니다
|
||||
logs-skipped: 숨겨진 항목 {total}개 보기
|
||||
container-not-found: 컨테이너를 찾을 수 없습니다
|
||||
events-stream:
|
||||
title: 예상치 못한 오류
|
||||
message: >-
|
||||
Dozzle UI가 API에 연결하지 못했습니다. 네트워크 설정을 확인해 주세요.
|
||||
리버스 프록시를 사용 중이라면 설정이 올바른지 확인해 주세요.
|
||||
events-timeout:
|
||||
title: 문제가 발생했습니다
|
||||
message: >-
|
||||
Dozzle UI가 API 연결 중에 시간 초과되었습니다. 네트워크 상태를 확인하고 다시 시도해 주세요.
|
||||
alert:
|
||||
redirected:
|
||||
title: 새 컨테이너로 이동했습니다
|
||||
message: Dozzle이 자동으로 새 컨테이너 {containerId}로 이동했습니다.
|
||||
similar-container-found:
|
||||
title: 비슷한 컨테이너를 찾았습니다
|
||||
message: >-
|
||||
Dozzle이 같은 호스트에서 실행 중인 비슷한 컨테이너 {containerId}를 찾았습니다.
|
||||
'취소'를 누르지 않으면 자동으로 해당 컨테이너로 전환됩니다.
|
||||
title:
|
||||
page-not-found: 페이지를 찾을 수 없습니다
|
||||
login: 인증이 필요합니다
|
||||
dashboard: 컨테이너 1개 | 컨테이너 {count}개
|
||||
settings: 설정
|
||||
button:
|
||||
logout: 로그아웃
|
||||
login: 로그인
|
||||
settings: 설정
|
||||
cancel: 취소
|
||||
redirect: 이동
|
||||
placeholder:
|
||||
search-containers: 컨테이너 검색 (⌘ + k, ⌃k)
|
||||
search: 검색
|
||||
settings:
|
||||
help-support: >
|
||||
GitHub에서 후원하거나 기부로 Dozzle을 응원해 주세요. 여러분의 도움으로
|
||||
모두를 위한 더 나은 Dozzle을 만들 수 있습니다. 감사합니다! 🙏🏼
|
||||
display: 화면 설정
|
||||
locale: 언어 변경
|
||||
small-scrollbars: 작은 스크롤바 사용
|
||||
show-timesamps: 타임스탬프 표시
|
||||
soft-wrap: 자동 줄바꿈
|
||||
datetime-format: 날짜 및 시간 형식 변경
|
||||
font-size: 로그 글꼴 크기
|
||||
color-scheme: 색상 테마
|
||||
options: 옵션
|
||||
show-stopped-containers: 중지된 컨테이너 보기
|
||||
about: 정보
|
||||
search: Dozzle 검색 기능 사용
|
||||
using-version: <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}을 사용하고 있습니다.
|
||||
update-available: >-
|
||||
새 버전이 있습니다! <a href="{href}" target="_blank"
|
||||
rel="noreferrer noopener">{nextVersion}</a>으로 업데이트하세요.
|
||||
show-std: stdout 및 stderr 라벨 표시
|
||||
automatic-redirect: 같은 이름의 새 컨테이너로 자동 이동
|
||||
compact: 로그 간단하게 보기
|
||||
log:
|
||||
preview: 로그 미리보기입니다
|
||||
warning: 경고 로그는 이렇게 표시됩니다
|
||||
complex: JSON 형태의 복잡한 로그 항목입니다
|
||||
simple: 기본적으로 줄바꿈되는 매우 긴 메시지입니다. 자동 줄바꿈을 비활성화하면 이 기능이 꺼집니다. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
multi-line-error:
|
||||
start-line: 여러 줄로 된 오류 메시지입니다
|
||||
middle-line: 두 번째 줄과
|
||||
end-line: 마지막 세 번째 줄입니다.
|
||||
releases:
|
||||
features: 새 기능 1개 | 새 기능 {count}개
|
||||
bugFixes: 버그 수정 1개 | 버그 수정 {count}개
|
||||
breaking: 주요 변경사항 1개 | 주요 변경사항 {count}개
|
||||
three_parts: "{first}, {second} 그리고 {third}"
|
||||
two_parts: "{first} 및 {second}"
|
||||
latest: 최신
|
||||
no_releases: 최신 버전을 사용하고 있습니다
|
||||
log_actions:
|
||||
copy_log: 로그 복사
|
||||
jump_to_context: 해당 위치로 이동
|
||||
toasts:
|
||||
copied:
|
||||
title: 복사 완료
|
||||
message: 로그가 클립보드에 복사되었습니다
|
||||
analytics:
|
||||
creating_table: 임시 테이블을 생성하고 있습니다...
|
||||
downloading: 컨테이너 로그를 가져오고 있습니다... ({size})
|
||||
evaluating_query: 쿼리를 실행하고 있습니다...
|
||||
total_records: 총 {count}개 기록
|
||||
showing_first: 처음 {count}개 표시
|
||||
@@ -0,0 +1,135 @@
|
||||
toolbar:
|
||||
clear: Wissen
|
||||
download: Downloaden
|
||||
search: Zoeken
|
||||
show: Toon {std}
|
||||
show-all: Toon alles
|
||||
stop: Stoppen
|
||||
start: Starten
|
||||
restart: Herstarten
|
||||
show-hostname: Toon hostnaam
|
||||
show-container-name: Toon containernaam
|
||||
shell: Shell
|
||||
attach: Koppelen
|
||||
action:
|
||||
copy-log: Log kopiëren
|
||||
copy-link: Permalink kopiëren
|
||||
see-in-context: Bekijk in context
|
||||
show-details: Toon details
|
||||
label:
|
||||
containers: Containers
|
||||
container: Geen containers | 1 container | {count} containers
|
||||
service: Geen services | 1 service | {count} services
|
||||
services: Services
|
||||
running-containers: Actieve containers
|
||||
all-containers: Alle containers
|
||||
host: Host
|
||||
hosts: Hosts
|
||||
password: Wachtwoord
|
||||
username: Gebruikersnaam
|
||||
container-name: Containernaam
|
||||
status: Status
|
||||
created: Aangemaakt
|
||||
avg-cpu: Gem. CPU (%)
|
||||
avg-mem: Gem. geheugen (%)
|
||||
pinned: Vastgezet
|
||||
per-page: Rijen per pagina
|
||||
host-menu: Hosts en Containers
|
||||
swarm-menu: Services en Stacks
|
||||
group-menu: Aangepaste groepen
|
||||
no-logs: Container heeft nog geen logbestanden
|
||||
show-all-containers: Toon alle containers
|
||||
collapse-all: Alles inklappen
|
||||
tooltip:
|
||||
search: Zoek containers (⌘ + k, ⌃k)
|
||||
pin-column: Vastzetten als kolom
|
||||
merge-services: Voeg alle services samen in één weergave
|
||||
merge-containers: Voeg alle containers samen in één weergave
|
||||
merge-hosts: Voeg alle containers op deze host samen in één weergave
|
||||
error:
|
||||
page-not-found: Deze pagina bestaat niet
|
||||
invalid-auth: Gebruikersnaam of wachtwoord is ongeldig
|
||||
logs-skipped: Toon {total} verborgen items
|
||||
container-not-found: Container niet gevonden
|
||||
events-stream:
|
||||
title: Onverwachte fout
|
||||
message: >-
|
||||
De Dozzle-interface kon geen verbinding maken met de API. Controleer je netwerkinstellingen.
|
||||
Gebruik je een reverse proxy? Zorg er dan voor dat deze correct is geconfigureerd.
|
||||
events-timeout:
|
||||
title: Er gaat iets mis
|
||||
message: >-
|
||||
De verbinding met de API is verlopen. Controleer je netwerkverbinding en probeer het opnieuw.
|
||||
alert:
|
||||
redirected:
|
||||
title: Doorgestuurd naar nieuwe container
|
||||
message: Dozzle heeft je automatisch doorgestuurd naar de nieuwe container {containerId}.
|
||||
similar-container-found:
|
||||
title: Vergelijkbare container gevonden
|
||||
message: >-
|
||||
Dozzle heeft een vergelijkbare container gevonden ({containerId}) op dezelfde host. Je wordt hier automatisch naartoe omgeleid, tenzij je op 'Annuleren' klikt.
|
||||
title:
|
||||
page-not-found: Pagina niet gevonden
|
||||
login: Aanmelding vereist
|
||||
dashboard: 1 container | {count} containers
|
||||
settings: Instellingen
|
||||
button:
|
||||
logout: Afmelden
|
||||
login: Aanmelden
|
||||
settings: Instellingen
|
||||
cancel: Annuleren
|
||||
redirect: Doorsturen
|
||||
placeholder:
|
||||
search-containers: Zoek containers (⌘ + k, ⌃k)
|
||||
search: Zoeken
|
||||
settings:
|
||||
help-support: >
|
||||
Steun Dozzle door te doneren of door ons te sponsoren op GitHub. Jouw bijdrage helpt ons om Dozzle verder te verbeteren voor iedereen. Hartelijk dank! 🙏🏼
|
||||
display: Weergave
|
||||
locale: Taal aanpassen
|
||||
small-scrollbars: Kleinere scrollbalk gebruiken
|
||||
show-timesamps: Tijdstempel tonen
|
||||
soft-wrap: Regels zacht afbreken
|
||||
datetime-format: Datum- en tijdnotatie aanpassen
|
||||
font-size: Lettergrootte voor logs
|
||||
color-scheme: Kleurmodus
|
||||
options: Opties
|
||||
show-stopped-containers: Gestopte containers tonen
|
||||
about: Over
|
||||
search: Doorzoeken inschakelen via Dozzle
|
||||
using-version: Je gebruikt <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> {version}.
|
||||
update-available: >-
|
||||
Er is een nieuwe versie beschikbaar! Update naar <a href="{href}" target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||
show-std: Toon stdout- en stderr-labels
|
||||
automatic-redirect: Automatisch omleiden naar nieuwe containers met dezelfde naam
|
||||
compact: Compacte modus inschakelen voor logs
|
||||
log:
|
||||
preview: Voorbeeld van de logweergave
|
||||
warning: Een waarschuwingslog ziet er zo uit
|
||||
complex: Dit is een complexe logvermelding in JSON-formaat
|
||||
simple: Dit is een hele lange logregel die standaard wordt afgebroken. Als je zachte afbreking uitschakelt, gebeurt dit niet. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
multi-line-error:
|
||||
start-line: Dit is een foutmelding over meerdere regels
|
||||
middle-line: met een tweede regel
|
||||
end-line: en tenslotte een derde regel.
|
||||
releases:
|
||||
features: één nieuwe functie | {count} functies
|
||||
bugFixes: één bugfix | {count} bugfixes
|
||||
breaking: één breaking change | {count} breaking changes
|
||||
three_parts: "{first}, {second} en {third}"
|
||||
two_parts: "{first} met {second}"
|
||||
latest: Meest recent
|
||||
no_releases: Je gebruikt de nieuwste versie
|
||||
log_actions:
|
||||
copy_log: Log kopiëren
|
||||
jump_to_context: Ga naar context
|
||||
toasts:
|
||||
copied:
|
||||
title: Gekopieerd
|
||||
message: Log gekopieerd naar klembord
|
||||
analytics:
|
||||
creating_table: Tijdelijke tabel wordt aangemaakt...
|
||||
downloading: Containerlogs worden opgehaald... ({size})
|
||||
evaluating_query: Query wordt geëvalueerd...
|
||||
total_records: In totaal {count} resultaten.
|
||||
showing_first: Eerste {count} worden weergegeven.
|
||||
@@ -7,6 +7,15 @@ toolbar:
|
||||
stop: Stop
|
||||
start: Start
|
||||
restart: Restart
|
||||
show-hostname: Pokaż nazwę hosta
|
||||
show-container-name: Pokaż nazwę kontenera
|
||||
shell: Shell
|
||||
attach: Dołącz
|
||||
action:
|
||||
copy-log: Skopiuj log
|
||||
copy-link: Skopiuj permalink
|
||||
see-in-context: Zobacz w kontekście
|
||||
show-details: Pokaż szczegóły
|
||||
label:
|
||||
service: Brak usług | 1 usługa | {count} usług
|
||||
total-containers: Całkowita liczba kontenerów
|
||||
@@ -32,13 +41,19 @@ label:
|
||||
host-menu: Hosty i Kontenery
|
||||
swarm-menu: Usługi i Stosy
|
||||
group-menu: Grupy Niestandardowe
|
||||
no-logs: Kontener nie ma jeszcze logów
|
||||
show-all-containers: Pokaż wszystkie kontenery
|
||||
collapse-all: Zwiń wszystkie
|
||||
tooltip:
|
||||
search: Przeszukaj kontenery (⌘ + k, ⌃k)
|
||||
pin-column: Przypnij jako kolumna
|
||||
merge-services: Scal wszystkie usługi w jeden widok
|
||||
merge-containers: Scal wszystkie kontenery w jeden widok
|
||||
merge-hosts: Scal wszystkie kontenery na tym hoście w jeden widok
|
||||
error:
|
||||
page-not-found: Ta strona nie istnieje
|
||||
invalid-auth: Nazwa użytkownika lub hasło są niepoprawne
|
||||
logs-skipped: Pominiętych wpisów {total}
|
||||
logs-skipped: Pokaż {total} ukrytych wpisów
|
||||
container-not-found: Kontener nie został znaleziony
|
||||
events-stream:
|
||||
title: Niespodziewany błąd
|
||||
@@ -67,10 +82,14 @@ button:
|
||||
logout: Wyloguj się
|
||||
login: Zaloguj się
|
||||
settings: Ustawienia
|
||||
cancel: Anuluj
|
||||
redirect: Przekieruj
|
||||
placeholder:
|
||||
search-containers: Przeszukaj kontery (⌘ + k, ⌃k)
|
||||
search: Szukaj
|
||||
settings:
|
||||
help-support: >
|
||||
Prosimy o wsparcie Dozzle poprzez darowizny lub sponsoring na GitHub. Twoje wkłady pomagają nam ulepszać Dozzle dla wszystkich. Dziękujemy! 🙏🏼
|
||||
display: Wyświetlanie
|
||||
locale: Nadpisz język
|
||||
small-scrollbars: Użyj mniejszych suwaków
|
||||
@@ -83,7 +102,7 @@ settings:
|
||||
show-stopped-containers: Pokaż zatrzymane kontenery
|
||||
about: Informacje
|
||||
search: Włącz szukanie z dozzle
|
||||
using-version: Używasz dozzle w wersji {version}.
|
||||
using-version: Używasz <a href="https://dozzle.dev/" target="_blank" rel="noreferrer noopener">Dozzle</a> w wersji {version}.
|
||||
update-available: >-
|
||||
Nowa wersja jest dostępna! Zaktualizuj do <a href="{href}" target="_blank"
|
||||
rel="noreferrer noopener">{nextVersion}</a>.
|
||||
|
||||