Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48d9958e0c | |||
| ea2132efc9 | |||
| 7a6914c6cf | |||
| da66540952 | |||
| f863fc1bd3 | |||
| 91b9c26c3a | |||
| ae483639bb | |||
| 38383e8924 | |||
| c66bbc520c | |||
| 7c2b7a5dcb | |||
| 0c96821f15 | |||
| d1a225adc1 | |||
| 3a7efe9ada | |||
| 3e3bb02b5f | |||
| c3ee2da33c | |||
| a488e2e7ee | |||
| 46fdb88b81 | |||
| c88429c854 | |||
| 9e2d053ec8 | |||
| 97ece4c5ac | |||
| 4da50bbbd9 | |||
| ababa789c3 | |||
| 60100dfdbb | |||
| 0370759f69 | |||
| 1b01710aa8 | |||
| 0ffdd53568 | |||
| d9c9d20fff | |||
| e0797d7675 | |||
| 25d8ba413a | |||
| 80bbb317aa | |||
| c1c61c0ce6 | |||
| 792a9f1187 | |||
| a3a6c03f11 | |||
| 46b4fc0994 | |||
| 4878144592 | |||
| 99f203a23e | |||
| 211a442147 | |||
| 02aafac2a9 | |||
| 3b260bef27 | |||
| fe0902e62a |
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
go-version: 1.24.1
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -92,12 +92,12 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -15,12 +15,12 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.23"
|
||||
go-version: "1.24.1"
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
go-version: "1.24.1"
|
||||
check-latest: true
|
||||
- name: Generate dependencies
|
||||
run: make fake_assets shared_key.pem shared_cert.pem
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build assets
|
||||
FROM --platform=$BUILDPLATFORM node:23.9.0-alpine AS node
|
||||
FROM --platform=$BUILDPLATFORM node:23.10.0-alpine AS node
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ watchEffect(() => {
|
||||
if (theme === "auto") {
|
||||
theme = mode.value;
|
||||
}
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute("content", theme == "dark" ? "#121212" : "#3FA68F");
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -31,6 +31,7 @@ declare global {
|
||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||
const createPinia: typeof import('pinia')['createPinia']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createRef: typeof import('@vueuse/core')['createRef']
|
||||
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
|
||||
@@ -421,6 +422,7 @@ declare module 'vue' {
|
||||
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
|
||||
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
||||
readonly createRef: UnwrapRef<typeof import('@vueuse/core')['createRef']>
|
||||
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
|
||||
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
|
||||
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
|
||||
|
||||
@@ -62,6 +62,7 @@ declare module 'vue' {
|
||||
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
|
||||
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
|
||||
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
|
||||
'MaterialSymbols:terminal': typeof import('~icons/material-symbols/terminal')['default']
|
||||
'Mdi:account': typeof import('~icons/mdi/account')['default']
|
||||
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
|
||||
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
|
||||
@@ -103,6 +104,7 @@ declare module 'vue' {
|
||||
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
|
||||
Popup: typeof import('./components/Popup.vue')['default']
|
||||
RandomColorTag: typeof import('./components/LogViewer/RandomColorTag.vue')['default']
|
||||
'Ri:terminalWindowFill': typeof import('~icons/ri/terminal-window-fill')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
|
||||
@@ -121,6 +123,7 @@ declare module 'vue' {
|
||||
StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default']
|
||||
SwarmMenu: typeof import('./components/SwarmMenu.vue')['default']
|
||||
Tag: typeof import('./components/common/Tag.vue')['default']
|
||||
Terminal: typeof import('./components/Terminal.vue')['default']
|
||||
TimedButton: typeof import('./components/common/TimedButton.vue')['default']
|
||||
ToastModal: typeof import('./components/common/ToastModal.vue')['default']
|
||||
Toggle: typeof import('./components/common/Toggle.vue')['default']
|
||||
|
||||
@@ -134,6 +134,20 @@
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<li class="line"></li>
|
||||
<li>
|
||||
<a @click.prevent="showDrawer(Terminal, { container, action: 'attach' }, 'lg')">
|
||||
<ri:terminal-window-fill /> Attach
|
||||
<KeyShortcut char="a" :modifiers="['shift', 'meta']" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a @click.prevent="showDrawer(Terminal, { container, action: 'exec' }, 'lg')">
|
||||
<material-symbols:terminal /> Shell
|
||||
<KeyShortcut char="e" :modifiers="['shift', 'meta']" />
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -142,6 +156,7 @@
|
||||
import { Container } from "@/models/Container";
|
||||
import { allLevels } from "@/composable/logContext";
|
||||
import LogAnalytics from "../LogViewer/LogAnalytics.vue";
|
||||
import Terminal from "@/components/Terminal.vue";
|
||||
|
||||
const { showSearch } = useSearchFilter();
|
||||
const { enableActions } = config;
|
||||
@@ -161,6 +176,20 @@ onKeyStroke("f", (e) => {
|
||||
}
|
||||
});
|
||||
|
||||
onKeyStroke("a", (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
showDrawer(Terminal, { container, action: "attach" }, "lg");
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
onKeyStroke("e", (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
|
||||
showDrawer(Terminal, { container, action: "exec" }, "lg");
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
const downloadParams = computed(() =>
|
||||
Object.entries(toValue(streamConfig))
|
||||
.filter(([, value]) => value)
|
||||
|
||||
@@ -62,9 +62,9 @@ const store = useContainerStore();
|
||||
const { containers: allContainers } = storeToRefs(store);
|
||||
|
||||
const otherContainers = computed(() =>
|
||||
[...allContainers.value.filter((c) => c.name === container.name && c.id !== container.id)].sort(
|
||||
(a, b) => +b.created - +a.created,
|
||||
),
|
||||
allContainers.value
|
||||
.filter((c) => c.name === container.name && c.id !== container.id)
|
||||
.sort((a, b) => +b.created - +a.created),
|
||||
);
|
||||
const wrapper = useTemplateRef("wrapper");
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComplexLogEntry } from "@/models/LogEntry";
|
||||
import { useSortable } from "@vueuse/integrations/useSortable";
|
||||
import { UseClipboard } from "@vueuse/components";
|
||||
|
||||
const { entry } = defineProps<{ entry: ComplexLogEntry }>();
|
||||
@@ -82,6 +81,8 @@ const container = currentContainer(toRef(() => entry.containerID));
|
||||
const visibleKeys = persistentVisibleKeysForContainer(container);
|
||||
const { hosts } = useHosts();
|
||||
|
||||
const { useSortable } = await import("@vueuse/integrations/useSortable");
|
||||
|
||||
function toggleField(key: string[]) {
|
||||
if (visibleKeys.value.size === 0) {
|
||||
visibleKeys.value = new Map<string[], boolean>(fields.value.map(({ key }) => [key, true]));
|
||||
@@ -146,8 +147,8 @@ function syntaxHighlight(json: any) {
|
||||
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|\b\d+\b)/g,
|
||||
function (match: string) {
|
||||
var cls = "json-number";
|
||||
if (/^"/.test(match)) {
|
||||
if (/:$/.test(match)) {
|
||||
if (match.startsWith('"')) {
|
||||
if (match.endsWith(":")) {
|
||||
cls = "json-key";
|
||||
} else {
|
||||
cls = "json-string";
|
||||
|
||||
@@ -29,7 +29,7 @@ defineProps<{
|
||||
|
||||
const colorize = (value: string) => ansiConvertor.toHtml(value);
|
||||
const urlPattern =
|
||||
/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_+.~#?&\/=]*/g;
|
||||
/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_+.~#?&/=]*/g;
|
||||
const linkify = (text: string) =>
|
||||
text.replace(urlPattern, (url) => `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`);
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<aside>
|
||||
<header class="flex items-center gap-4">
|
||||
<material-symbols:terminal class="size-8" />
|
||||
<h1 class="text-2xl max-md:hidden">{{ container.name }}</h1>
|
||||
<h2 class="text-sm">Started <DistanceTime :date="container.created" /></h2>
|
||||
</header>
|
||||
|
||||
<div class="mt-8 flex flex-col gap-2">
|
||||
<section>
|
||||
<div ref="host" class="shell"></div>
|
||||
</section>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Container } from "@/models/Container";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
const { container, action } = defineProps<{ container: Container; action: "attach" | "exec" }>();
|
||||
|
||||
const { Terminal } = await import("@xterm/xterm");
|
||||
const { WebLinksAddon } = await import("@xterm/addon-web-links");
|
||||
|
||||
const host = useTemplateRef<HTMLDivElement>("host");
|
||||
const terminal = new Terminal({
|
||||
cursorBlink: true,
|
||||
cursorStyle: "block",
|
||||
});
|
||||
terminal.loadAddon(new WebLinksAddon());
|
||||
|
||||
let ws: WebSocket | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
terminal.open(host.value!);
|
||||
terminal.resize(100, 40);
|
||||
ws = new WebSocket(withBase(`/api/hosts/${container.host}/containers/${container.id}/${action}`));
|
||||
ws.onopen = () => {
|
||||
terminal.writeln(`Attached to ${container.name} 🚀`);
|
||||
if (action === "attach") {
|
||||
ws?.send("\r");
|
||||
}
|
||||
terminal.onData((data) => {
|
||||
ws?.send(data);
|
||||
});
|
||||
terminal.focus();
|
||||
};
|
||||
ws.onmessage = (event) => terminal.write(event.data);
|
||||
ws.addEventListener("close", () => {
|
||||
terminal.writeln("⚠️ Connection closed");
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log("Closing WebSocket");
|
||||
terminal.dispose();
|
||||
ws?.close();
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
@import "@/main.css" reference;
|
||||
|
||||
.shell {
|
||||
& :deep(.terminal) {
|
||||
@apply overflow-hidden rounded border-1 p-2;
|
||||
&:is(.focus) {
|
||||
@apply border-primary;
|
||||
}
|
||||
}
|
||||
|
||||
& :deep(.xterm-viewport) {
|
||||
@apply bg-base-200!;
|
||||
}
|
||||
|
||||
& :deep(.xterm-rows) {
|
||||
@apply text-base-content;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<dialog ref="panel" class="modal-right modal items-start outline-hidden backdrop:bg-none">
|
||||
<div class="modal-box" :width="width">
|
||||
<form method="dialog">
|
||||
<button class="swap swap-rotate hover:swap-active absolute top-4 right-4 outline-hidden">
|
||||
<button class="swap hover:swap-active absolute top-4 right-4 outline-hidden">
|
||||
<mdi:keyboard-esc class="swap-off" />
|
||||
<mdi:close class="swap-on" />
|
||||
</button>
|
||||
|
||||
@@ -29,7 +29,7 @@ export function useProfileStorage<K extends keyof Profile>(
|
||||
if (transformer) {
|
||||
storage.value = transformer.from(config.profile[key]);
|
||||
} else if (storage.value instanceof Set && config.profile[key] instanceof Array) {
|
||||
storage.value = new Set([...(config.profile[key] as Iterable<any>)]) as unknown as NonNullable<Profile[K]>;
|
||||
storage.value = new Set(config.profile[key] as Iterable<any>) as unknown as NonNullable<Profile[K]>;
|
||||
} else if (config.profile[key] instanceof Array) {
|
||||
storage.value = config.profile[key] as NonNullable<Profile[K]>;
|
||||
} else if (config.profile[key] instanceof Object) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<Pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav && !forceMenuHidden">
|
||||
<SidePanel @search="showFuzzySearch" />
|
||||
</Pane>
|
||||
<Pane min-size="10">
|
||||
<Pane min-size="10" :size="100 - menuWidth">
|
||||
<Splitpanes>
|
||||
<Pane class="router-view min-h-screen">
|
||||
<router-view></router-view>
|
||||
@@ -91,9 +91,9 @@ function showFuzzySearch() {
|
||||
open.value = true;
|
||||
}
|
||||
|
||||
function onResized(e: any) {
|
||||
if (e.length == 2) {
|
||||
menuWidth.value = e[0].size;
|
||||
function onResized({ panes }: { panes: { size: number }[] }) {
|
||||
if (panes.length == 2) {
|
||||
menuWidth.value = Math.min(panes[0].size, 50);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -102,7 +102,7 @@ function onResized(e: any) {
|
||||
@import "@/main.css" reference;
|
||||
|
||||
:deep(.splitpanes--vertical > .splitpanes__splitter) {
|
||||
@apply bg-base-100 hover:bg-secondary min-w-[3px];
|
||||
@apply bg-base-100 hover:bg-secondary min-w-[5px];
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
|
||||
@@ -126,3 +126,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
|
||||
playwright:
|
||||
container_name: playwright
|
||||
image: mcr.microsoft.com/playwright:v1.50.1-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.51.1-jammy
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 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.0.1+incompatible
|
||||
github.com/docker/docker v28.0.4+incompatible
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
@@ -15,31 +15,31 @@ require (
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/spf13/afero v1.12.0
|
||||
github.com/spf13/afero v1.14.0
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.10.2
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/yuin/goldmark v1.7.8
|
||||
golang.org/x/crypto v0.35.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/sync v0.12.0
|
||||
google.golang.org/grpc v1.71.0
|
||||
google.golang.org/protobuf v1.36.5
|
||||
k8s.io/api v0.32.2
|
||||
k8s.io/apimachinery v0.32.2
|
||||
k8s.io/client-go v0.32.2
|
||||
k8s.io/metrics v0.32.2
|
||||
google.golang.org/protobuf v1.36.6
|
||||
k8s.io/api v0.32.3
|
||||
k8s.io/apimachinery v0.32.3
|
||||
k8s.io/client-go v0.32.3
|
||||
k8s.io/metrics v0.32.3
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -66,6 +66,7 @@ require (
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
@@ -93,8 +94,8 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.26.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/term v0.30.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
|
||||
@@ -34,6 +34,10 @@ github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR1
|
||||
github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
|
||||
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.0.2+incompatible h1:9BILleFwug5FSSqWBgVevgL3ewDJfWWWyZVqlDMttE8=
|
||||
github.com/docker/docker v28.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=
|
||||
github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -50,6 +54,8 @@ github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3 h1:50Uzmacu35/ZP9ER2Ht6SazwPsnLQ9LRJy6zTZJpHEo=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -84,6 +90,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
@@ -154,8 +162,11 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPK
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
@@ -166,6 +177,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
@@ -222,6 +235,8 @@ golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
|
||||
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -277,6 +292,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -288,6 +305,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@@ -299,6 +318,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -327,6 +348,8 @@ google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@@ -345,16 +368,24 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw=
|
||||
k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y=
|
||||
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
|
||||
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
|
||||
k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ=
|
||||
k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
|
||||
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA=
|
||||
k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94=
|
||||
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
|
||||
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg=
|
||||
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas=
|
||||
k8s.io/metrics v0.32.2 h1:7t/rZzTHFrGa9f94XcgLlm3ToAuJtdlHANcJEHlYl9g=
|
||||
k8s.io/metrics v0.32.2/go.mod h1:VL3nJpzcgB6L5nSljkkzoE0nilZhVgcjCfNRgoylaIQ=
|
||||
k8s.io/metrics v0.32.3 h1:2vsBvw0v8rIIlczZ/lZ8Kcqk9tR6Fks9h+dtFNbc2a4=
|
||||
k8s.io/metrics v0.32.3/go.mod h1:9R1Wk5cb+qJpCQon9h52mgkVCcFeYxcY+YkumfwHVCU=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
|
||||
@@ -395,6 +395,14 @@ func (c *Client) ContainerAction(ctx context.Context, containerId string, action
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) ContainerAttach(ctx context.Context, containerId string) (io.WriteCloser, io.Reader, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Client) ContainerExec(ctx context.Context, containerId string, cmd []string) (io.WriteCloser, io.Reader, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
@@ -38,4 +38,6 @@ type Client interface {
|
||||
Ping(context.Context) error
|
||||
Host() Host
|
||||
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
|
||||
ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error)
|
||||
ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ type DockerCLI interface {
|
||||
ContainerStart(ctx context.Context, containerID string, options docker.StartOptions) error
|
||||
ContainerStop(ctx context.Context, containerID string, options docker.StopOptions) error
|
||||
ContainerRestart(ctx context.Context, containerID string, options docker.StopOptions) error
|
||||
ContainerAttach(ctx context.Context, containerID string, options docker.AttachOptions) (types.HijackedResponse, error)
|
||||
ContainerExecCreate(ctx context.Context, containerID string, options docker.ExecOptions) (docker.ExecCreateResponse, error)
|
||||
ContainerExecAttach(ctx context.Context, execID string, config docker.ExecAttachOptions) (types.HijackedResponse, error)
|
||||
ContainerExecResize(ctx context.Context, execID string, options docker.ResizeOptions) error
|
||||
Info(ctx context.Context) (system.Info, error)
|
||||
}
|
||||
|
||||
@@ -297,6 +301,54 @@ func (d *DockerClient) Host() container.Host {
|
||||
return d.host
|
||||
}
|
||||
|
||||
func (d *DockerClient) ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error) {
|
||||
log.Debug().Str("id", id).Str("host", d.host.Name).Msg("Attaching to container")
|
||||
options := docker.AttachOptions{
|
||||
Stream: true,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
}
|
||||
|
||||
waiter, err := d.cli.ContainerAttach(ctx, id, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return waiter.Conn, waiter.Reader, nil
|
||||
}
|
||||
|
||||
func (d *DockerClient) ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error) {
|
||||
log.Debug().Str("id", id).Str("host", d.host.Name).Msg("Executing command in container")
|
||||
options := docker.ExecOptions{
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
AttachStdin: true,
|
||||
Cmd: cmd,
|
||||
Tty: true,
|
||||
}
|
||||
|
||||
execID, err := d.cli.ContainerExecCreate(ctx, id, options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
waiter, err := d.cli.ContainerExecAttach(ctx, execID.ID, docker.ExecAttachOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err = d.cli.ContainerExecResize(ctx, execID.ID, docker.ResizeOptions{
|
||||
Width: 100,
|
||||
Height: 40,
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return waiter.Conn, waiter.Reader, nil
|
||||
}
|
||||
|
||||
func newContainer(c docker.Summary, host string) container.Container {
|
||||
name := "no name"
|
||||
if c.Labels["dev.dozzle.name"] != "" {
|
||||
|
||||
@@ -241,8 +241,15 @@ func (k *K8sClient) Host() container.Host {
|
||||
}
|
||||
|
||||
func (k *K8sClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
|
||||
// Implementation for container actions (start, stop, restart, etc.)
|
||||
return nil
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (k *K8sClient) ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (k *K8sClient) ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Helper function to parse pod and container names from container ID
|
||||
|
||||
@@ -70,3 +70,11 @@ func (d *agentService) SubscribeContainersStarted(ctx context.Context, container
|
||||
func (a *agentService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
|
||||
return a.client.ContainerAction(ctx, container.ID, action)
|
||||
}
|
||||
|
||||
func (a *agentService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (a *agentService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
@@ -16,13 +16,17 @@ type ClientService interface {
|
||||
Host(ctx context.Context) (container.Host, error)
|
||||
ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error
|
||||
LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error)
|
||||
RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error)
|
||||
RawLogs(context.Context, container.Container, time.Time, time.Time, container.StdType) (io.ReadCloser, error)
|
||||
|
||||
// Subscriptions
|
||||
SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat)
|
||||
SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent)
|
||||
SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container)
|
||||
SubscribeStats(context.Context, chan<- container.ContainerStat)
|
||||
SubscribeEvents(context.Context, chan<- container.ContainerEvent)
|
||||
SubscribeContainersStarted(context.Context, chan<- container.Container)
|
||||
|
||||
// Blocking streaming functions that should be used in a goroutine
|
||||
StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error
|
||||
StreamLogs(context.Context, container.Container, time.Time, container.StdType, chan<- *container.LogEvent) error
|
||||
|
||||
// Terminal
|
||||
Attach(context.Context, container.Container, io.Reader, io.Writer) error
|
||||
Exec(context.Context, container.Container, []string, io.Reader, io.Writer) error
|
||||
}
|
||||
|
||||
@@ -35,3 +35,11 @@ func (c *ContainerService) StreamLogs(ctx context.Context, from time.Time, stdTy
|
||||
func (c *ContainerService) Action(ctx context.Context, action container.ContainerAction) error {
|
||||
return c.clientService.ContainerAction(ctx, c.Container, action)
|
||||
}
|
||||
|
||||
func (c *ContainerService) Attach(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
|
||||
return c.clientService.Attach(ctx, c.Container, stdin, stdout)
|
||||
}
|
||||
|
||||
func (c *ContainerService) Exec(ctx context.Context, cmd []string, stdin io.Reader, stdout io.Writer) error {
|
||||
return c.clientService.Exec(ctx, c.Container, cmd, stdin, stdout)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package docker_support
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
@@ -109,3 +110,75 @@ func (d *DockerClientService) SubscribeEvents(ctx context.Context, events chan<-
|
||||
func (d *DockerClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
|
||||
d.store.SubscribeNewContainers(ctx, containers)
|
||||
}
|
||||
|
||||
func (d *DockerClientService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
|
||||
cancelCtx, cancel := context.WithCancel(ctx)
|
||||
containerWriter, containerReader, err := d.client.ContainerAttach(cancelCtx, container.ID)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, err := io.Copy(containerWriter, stdin); err != nil {
|
||||
log.Error().Err(err).Msg("error while reading from ws")
|
||||
}
|
||||
cancel()
|
||||
containerWriter.Close()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if container.Tty {
|
||||
if _, err := io.Copy(stdout, containerReader); err != nil {
|
||||
log.Error().Err(err).Msg("error while writing to ws")
|
||||
}
|
||||
} else {
|
||||
if _, err := stdcopy.StdCopy(stdout, stdout, containerReader); err != nil {
|
||||
log.Error().Err(err).Msg("error while writing to ws")
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerClientService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
|
||||
cancelCtx, cancel := context.WithCancel(ctx)
|
||||
containerWriter, containerReader, err := d.client.ContainerExec(cancelCtx, container.ID, cmd)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, err := io.Copy(containerWriter, stdin); err != nil {
|
||||
log.Error().Err(err).Msg("error while reading from ws")
|
||||
}
|
||||
cancel()
|
||||
containerWriter.Close()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if _, err := stdcopy.StdCopy(stdout, stdout, containerReader); err != nil {
|
||||
log.Error().Err(err).Msg("error while writing to ws")
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -90,3 +90,11 @@ func (k *K8sClientService) SubscribeEvents(ctx context.Context, events chan<- co
|
||||
func (k *K8sClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
|
||||
k.store.SubscribeNewContainers(ctx, containers)
|
||||
}
|
||||
|
||||
func (k *K8sClientService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (k *K8sClientService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
@@ -84,6 +84,10 @@ Content-Type: text/html
|
||||
{"m":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout","c":"123456"}
|
||||
{"m":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr","c":"123456"}
|
||||
|
||||
/* snapshot: Test_handler_between_dates_with_everything_complex */
|
||||
{"m":{"msg":"a complex log message"},"ts":1589396197772,"id":62280847,"l":"unknown","s":"stdout","c":"123456"}
|
||||
|
||||
|
||||
/* snapshot: Test_handler_between_dates_with_fill */
|
||||
{"m":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout","c":"123456"}
|
||||
{"m":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr","c":"123456"}
|
||||
|
||||
@@ -129,34 +129,30 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
for event := range events {
|
||||
if everything {
|
||||
if _, ok := event.Message.(string); onlyComplex && ok {
|
||||
continue
|
||||
}
|
||||
if err := encoder.Encode(event); err != nil {
|
||||
log.Error().Err(err).Msg("error encoding log event")
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if regex != nil {
|
||||
if !search.Search(regex, event) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if onlyComplex {
|
||||
if _, ok := event.Message.(string); ok {
|
||||
if _, ok := levels[event.Level]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if regex != nil {
|
||||
if !search.Search(regex, event) {
|
||||
continue
|
||||
if lastSeenId != 0 && event.Id == lastSeenId {
|
||||
log.Debug().Uint32("lastSeenId", lastSeenId).Msg("found last seen id")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := levels[event.Level]; !ok {
|
||||
continue
|
||||
buffer.Push(event)
|
||||
}
|
||||
|
||||
if lastSeenId != 0 && event.Id == lastSeenId {
|
||||
log.Debug().Uint32("lastSeenId", lastSeenId).Msg("found last seen id")
|
||||
break
|
||||
}
|
||||
|
||||
buffer.Push(event)
|
||||
}
|
||||
|
||||
if everything || from.Before(containerService.Container.Created) {
|
||||
|
||||
@@ -300,6 +300,46 @@ func Test_handler_between_dates_with_fill(t *testing.T) {
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_handler_between_dates_with_everything_complex(t *testing.T) {
|
||||
id := "123456"
|
||||
req, err := http.NewRequest("GET", "/api/hosts/localhost/containers/"+id+"/logs", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("jsonOnly", "true")
|
||||
q.Add("stdout", "true")
|
||||
q.Add("stderr", "true")
|
||||
q.Add("everything", "true")
|
||||
q.Add("levels", "info")
|
||||
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
|
||||
first := makeMessage("2020-05-13T18:55:37.772853839Z INFO Testing stdout logs...\n", container.STDOUT)
|
||||
second := makeMessage("2020-05-13T18:56:37.772853839Z {\"msg\":\"a complex log message\"}\n", container.STDOUT)
|
||||
data := append(first, second...)
|
||||
|
||||
mockedClient.On("ContainerLogsBetweenDates", mock.Anything, id, mock.Anything, mock.Anything, container.STDALL).
|
||||
Return(io.NopCloser(bytes.NewReader(data)), nil).
|
||||
Once()
|
||||
mockedClient.On("FindContainer", mock.Anything, id).Return(container.Container{ID: id}, nil)
|
||||
mockedClient.On("Host").Return(container.Host{
|
||||
ID: "localhost",
|
||||
})
|
||||
mockedClient.On("ListContainers", mock.Anything, mock.Anything).Return([]container.Container{
|
||||
{ID: id, Name: "test", Host: "localhost", State: "running"},
|
||||
}, nil)
|
||||
mockedClient.On("ContainerEvents", mock.Anything, mock.AnythingOfType("chan<- container.ContainerEvent")).Return(nil)
|
||||
|
||||
handler := createDefaultHandler(mockedClient)
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
reader := strings.NewReader(regexp.MustCompile(`"time":"[^"]*"`).ReplaceAllString(rr.Body.String(), `"time":"<removed>"`))
|
||||
abide.AssertReader(t, t.Name(), reader)
|
||||
mockedClient.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func makeMessage(message string, stream container.StdType) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(data[4:], uint32(len(message)))
|
||||
|
||||
@@ -107,6 +107,8 @@ func createRouter(h *handler) *chi.Mux {
|
||||
r.Get("/hosts/{host}/containers/{id}/logs/stream", h.streamContainerLogs)
|
||||
r.Get("/hosts/{host}/logs/stream", h.streamHostLogs)
|
||||
r.Get("/hosts/{host}/containers/{id}/logs", h.fetchLogsBetweenDates)
|
||||
r.Get("/hosts/{host}/containers/{id}/attach", h.attach)
|
||||
r.Get("/hosts/{host}/containers/{id}/exec", h.exec)
|
||||
r.Get("/hosts/{host}/logs/mergedStream/{ids}", h.streamLogsMerged)
|
||||
r.Get("/containers/{hostIds}/download", h.downloadLogs) // formatted as host:container,host:container
|
||||
r.Get("/stacks/{stack}/logs/stream", h.streamStackLogs)
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/amir20/dozzle/internal/auth"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func (h *handler) attach(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to upgrade connection")
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
id := chi.URLParam(r, "id")
|
||||
userLabels := h.config.Labels
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
}
|
||||
|
||||
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to find container")
|
||||
return
|
||||
}
|
||||
|
||||
wsReader := &webSocketReader{conn: conn}
|
||||
wsWriter := &webSocketWriter{conn: conn}
|
||||
if err = containerService.Attach(r.Context(), wsReader, wsWriter); err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to attach to container")
|
||||
conn.WriteMessage(websocket.TextMessage, []byte("🚨 Error while trying to attach to container\r\n"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) exec(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to upgrade connection")
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
id := chi.URLParam(r, "id")
|
||||
userLabels := h.config.Labels
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
user := auth.UserFromContext(r.Context())
|
||||
if user.ContainerLabels.Exists() {
|
||||
userLabels = user.ContainerLabels
|
||||
}
|
||||
}
|
||||
|
||||
containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to find container")
|
||||
return
|
||||
}
|
||||
|
||||
wsReader := &webSocketReader{conn: conn}
|
||||
wsWriter := &webSocketWriter{conn: conn}
|
||||
if err = containerService.Exec(r.Context(), []string{"sh", "-c", "command -v bash >/dev/null 2>&1 && exec bash || exec sh"}, wsReader, wsWriter); err != nil {
|
||||
log.Error().Err(err).Msg("error while trying to attach to container")
|
||||
conn.WriteMessage(websocket.TextMessage, []byte("🚨 Error while trying to attach to container\r\n"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type webSocketWriter struct {
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
func (w *webSocketWriter) Write(p []byte) (int, error) {
|
||||
err := w.conn.WriteMessage(websocket.TextMessage, p)
|
||||
return len(p), err
|
||||
}
|
||||
|
||||
type webSocketReader struct {
|
||||
conn *websocket.Conn
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func (r *webSocketReader) Read(p []byte) (n int, err error) {
|
||||
if len(r.buffer) > 0 {
|
||||
n = copy(p, r.buffer)
|
||||
r.buffer = r.buffer[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Otherwise, read a new message
|
||||
_, message, err := r.conn.ReadMessage()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n = copy(p, message)
|
||||
|
||||
// If we couldn't copy the entire message, store the rest in our buffer
|
||||
if n < len(message) {
|
||||
r.buffer = message[n:]
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "dozzle",
|
||||
"version": "8.11.7",
|
||||
"version": "8.12.0",
|
||||
"description": "Realtime log viewer for docker containers.",
|
||||
"homepage": "https://github.com/amir20/dozzle#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/amir20/dozzle/issues"
|
||||
},
|
||||
"packageManager": "pnpm@10.5.2",
|
||||
"packageManager": "pnpm@10.7.0",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,19 +33,21 @@
|
||||
"@iconify-json/carbon": "^1.2.8",
|
||||
"@iconify-json/cil": "^1.2.2",
|
||||
"@iconify-json/ic": "^1.2.2",
|
||||
"@iconify-json/material-symbols": "^1.2.15",
|
||||
"@iconify-json/material-symbols": "^1.2.17",
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@iconify-json/mdi-light": "^1.2.2",
|
||||
"@iconify-json/octicon": "^1.2.5",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.3",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.5",
|
||||
"@oddbird/css-anchor-positioning": "^0.4.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tailwindcss/vite": "4.0.9",
|
||||
"@vueuse/components": "^12.8.2",
|
||||
"@vueuse/core": "^12.8.2",
|
||||
"@vueuse/integrations": "^12.8.2",
|
||||
"@vueuse/router": "^12.8.2",
|
||||
"@tailwindcss/vite": "4.0.17",
|
||||
"@vueuse/components": "^13.0.0",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"@vueuse/integrations": "^13.0.0",
|
||||
"@vueuse/router": "^13.0.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"d3-array": "^3.2.4",
|
||||
"d3-ease": "^3.0.1",
|
||||
@@ -53,34 +55,36 @@
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-shape": "^3.2.0",
|
||||
"d3-transition": "^3.0.1",
|
||||
"daisyui": "5.0.0",
|
||||
"daisyui": "5.0.9",
|
||||
"date-fns": "^4.1.0",
|
||||
"entities": "^6.0.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"pinia": "^3.0.1",
|
||||
"sortablejs": "^1.15.6",
|
||||
"splitpanes": "^3.1.8",
|
||||
"splitpanes": "^4.0.3",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"tailwindcss": "4.0.9",
|
||||
"unplugin-auto-import": "^19.1.1",
|
||||
"tailwindcss": "4.0.17",
|
||||
"unplugin-auto-import": "^19.1.2",
|
||||
"unplugin-icons": "^22.1.0",
|
||||
"unplugin-vue-components": "^28.4.1",
|
||||
"unplugin-vue-macros": "^2.14.5",
|
||||
"unplugin-vue-router": "^0.12.0",
|
||||
"vite": "6.2.0",
|
||||
"vite": "6.2.3",
|
||||
"vite-plugin-compression2": "^1.3.3",
|
||||
"vite-plugin-vue-layouts": "^0.11.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vitepress": "1.6.3",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^11.1.1",
|
||||
"vue-i18n": "^11.1.2",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apache-arrow/esnext-esm": "^19.0.1",
|
||||
"@iconify-json/material-symbols-light": "^1.2.17",
|
||||
"@iconify-json/ri": "^1.2.5",
|
||||
"@pinia/testing": "^1.0.0",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@types/d3-array": "^3.2.1",
|
||||
"@types/d3-ease": "^3.0.2",
|
||||
"@types/d3-scale": "^4.0.9",
|
||||
@@ -88,24 +92,24 @@
|
||||
"@types/d3-shape": "^3.1.7",
|
||||
"@types/d3-transition": "^3.0.9",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/node": "^22.13.9",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@types/node": "^22.13.14",
|
||||
"@vitejs/plugin-vue": "5.2.3",
|
||||
"@vue/compiler-sfc": "^3.5.13",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"bumpp": "^10.0.3",
|
||||
"bumpp": "^10.1.0",
|
||||
"c8": "^10.1.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"lint-staged": "^15.4.3",
|
||||
"lint-staged": "^15.5.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"simple-git-hooks": "^2.12.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.2",
|
||||
"vitest": "^3.0.7",
|
||||
"vue-component-type-helpers": "^2.2.8",
|
||||
"vue-tsc": "^2.2.8"
|
||||
"vitest": "^3.0.9",
|
||||
"vue-component-type-helpers": "3.0.0-alpha.2",
|
||||
"vue-tsc": "3.0.0-alpha.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,vue,css,ts,html,md}": [
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<html class="bg-base-200 text-base-content">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="theme-color" content="" />
|
||||
<title>Dozzle</title>
|
||||
<script type="application/json" id="config__json">
|
||||
{{ marshal .Config }}
|
||||
@@ -16,9 +19,10 @@
|
||||
<link rel="stylesheet" href="{{ .Base }}/{{ index $css 0}}" />
|
||||
<script type="module" src="{{ .Base }}/{{ $js }}"></script>
|
||||
{{- end }}
|
||||
<meta name="description" content="Dozzle is a log viewer for Docker" />
|
||||
<meta name="description" content="A log viewer for containers" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<link rel="apple-touch-icon" href="{{ .Base }}/apple-touch-icon.png" sizes="512x512" />
|
||||
<link rel="manifest" href="{{ .Base }}/manifest.webmanifest" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Dozzle",
|
||||
"short_name": "Dozzle",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"lang": "en",
|
||||
"scope": "/",
|
||||
"description": "A log viewer for containers",
|
||||
"icons": [{ "src": "/apple-touch-icon.png", "sizes": "512x512", "type": "image/png" }]
|
||||
}
|
||||
@@ -68,7 +68,6 @@ export default defineConfig(() => ({
|
||||
}),
|
||||
VueI18nPlugin({
|
||||
runtimeOnly: true,
|
||||
compositionOnly: true,
|
||||
strictMessage: false,
|
||||
include: [path.resolve(__dirname, "locales/**")],
|
||||
}),
|
||||
|
||||