1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 21:33:18 +01:00

fix: bug when a new container in service mode is created (#2982)

This commit is contained in:
Amir Raminfar
2024-05-24 13:04:50 -07:00
committed by GitHub
parent a089b394e0
commit c268fa4e8b
5 changed files with 62 additions and 97 deletions

View File

@@ -52,7 +52,7 @@
:title="item.name" :title="item.name"
> >
<div class="truncate"> <div class="truncate">
{{ item.name }}<span class="font-light opacity-70" v-if="item.isSwarm">{{ item.swarmId }}</span> {{ item.name }}<span class="font-light opacity-70" v-if="item.isSwarm">.{{ item.swarmId }}</span>
</div> </div>
<ContainerHealth :health="item.health" /> <ContainerHealth :health="item.health" />
<span <span

View File

@@ -12,8 +12,6 @@ const { id } = defineProps<{
id: string; id: string;
}>(); }>();
const { containers } = useLoggingContext();
const colors = [ const colors = [
"#4B0082", "#4B0082",
"#FF00FF", "#FF00FF",
@@ -32,10 +30,7 @@ const colors = [
"#FF4040", "#FF4040",
] as const; ] as const;
const color = computed(() => { const color = computed(() => colors[Math.abs(hashCode(id)) % colors.length]);
const index = containers.value.findIndex((container) => container.id === id);
return colors[index % colors.length];
});
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped>

View File

@@ -1,12 +1,13 @@
<template> <template>
<div class="relative flex w-full items-start gap-x-2"> <div class="flex w-full flex-col">
<div class="relative flex items-start gap-x-2">
<ContainerName class="flex-none" :id="logEntry.containerID" v-if="showContainerName" /> <ContainerName class="flex-none" :id="logEntry.containerID" v-if="showContainerName" />
<LogDate :date="logEntry.date" v-if="showTimestamp" /> <LogDate :date="logEntry.date" v-if="showTimestamp" />
<LogLevel class="flex" /> <LogLevel class="flex" />
<div class="whitespace-pre-wrap" :data-event="logEntry.event" v-html="logEntry.message"></div> <div class="whitespace-pre-wrap" :data-event="logEntry.event" v-html="logEntry.message"></div>
</div> </div>
<div <div
class="alert alert-info mt-8 w-auto text-[1rem] md:mx-auto md:w-1/2" class="alert alert-info mt-8 w-auto flex-none font-sans text-[1rem] md:mx-auto md:w-1/2"
v-if="nextContainer && logEntry.event === 'container-stopped'" v-if="nextContainer && logEntry.event === 'container-stopped'"
> >
<carbon:information class="size-6 shrink-0 stroke-current" /> <carbon:information class="size-6 shrink-0 stroke-current" />
@@ -15,12 +16,7 @@
{{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }} {{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }}
</div> </div>
<div> <div>
<TimedButton <TimedButton v-if="automaticRedirect" class="btn-primary btn-sm" @finished="redirectNow()">Cancel</TimedButton>
v-if="automaticRedirect && containers.length == 1"
class="btn-primary btn-sm"
@finished="redirectNow()"
>Cancel</TimedButton
>
<router-link <router-link
:to="{ name: 'container-id', params: { id: nextContainer.id } }" :to="{ name: 'container-id', params: { id: nextContainer.id } }"
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
@@ -30,6 +26,7 @@
</router-link> </router-link>
</div> </div>
</div> </div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { DockerEventLogEntry } from "@/models/LogEntry"; import { DockerEventLogEntry } from "@/models/LogEntry";
@@ -44,29 +41,34 @@ const { logEntry } = defineProps<{
const { containers } = useLoggingContext(); const { containers } = useLoggingContext();
const nextContainer = computed( const store = useContainerStore();
() =>
[ const { containers: allContainers } = storeToRefs(store);
...containers.value.filter(
const nextContainer = computed(() =>
containers.value.length === 1 && logEntry.event === "container-stopped"
? [
...allContainers.value.filter(
(c) => (c) =>
c.host === containers.value[0].host && c.host === containers.value[0].host &&
c.created > logEntry.date && c.created > logEntry.date &&
c.name === containers.value[0].name && c.name === containers.value[0].name &&
c.state === "running", c.state === "running",
), ),
].sort((a, b) => +a.created - +b.created)[0], ].sort((a, b) => +a.created - +b.created)[0]
: null,
); );
function redirectNow() { function redirectNow() {
showToast( showToast(
{ {
title: t("alert.redirected.title"), title: t("alert.redirected.title"),
message: t("alert.redirected.message", { containerId: nextContainer.value.id }), message: t("alert.redirected.message", { containerId: nextContainer.value?.id }),
type: "info", type: "info",
}, },
{ expire: 5000 }, { expire: 5000 },
); );
router.push({ name: "container-id", params: { id: nextContainer.value.id } }); router.push({ name: "container-id", params: { id: nextContainer.value?.id } });
} }
</script> </script>

View File

@@ -1,38 +0,0 @@
import { describe, expect, test, vi } from "vitest";
import { Container } from "./Container";
vi.mock("@/stores/config", () => ({
__esModule: true,
default: { base: "", hosts: [{ name: "localhost", id: "localhost" }] },
}));
describe("Container", () => {
const names = [
[
"foo.gb1cto7gaq68fp4refnsr5hep.byqr1prci82zyfoos6gx1yhz0",
"foo",
".gb1cto7gaq68fp4refnsr5hep.byqr1prci82zyfoos6gx1yhz0",
],
["bar.gb1cto7gaq68fp4refnsr5hep", "bar", ".gb1cto7gaq68fp4refnsr5hep"],
["baz", "baz", null],
];
test.each(names)("name %s should be %s and %s", (name, expectedName, expectedSwarmId) => {
const c = new Container(
"id",
new Date(),
"image",
name!,
"command",
"host",
{},
"status",
"created",
[],
"group",
"healthy",
);
expect(c.name).toBe(expectedName);
expect(c.swarmId).toBe(expectedSwarmId);
});
});

View File

@@ -4,8 +4,6 @@ import { Ref } from "vue";
export type Stat = Omit<ContainerStat, "id">; export type Stat = Omit<ContainerStat, "id">;
const SWARM_ID_REGEX = /(\.[a-z0-9]{25})+$/i;
const hosts = computed(() => const hosts = computed(() =>
config.hosts.reduce( config.hosts.reduce(
(acc, item) => { (acc, item) => {
@@ -26,15 +24,14 @@ export class GroupedContainers {
export class Container { export class Container {
private _stat: Ref<Stat>; private _stat: Ref<Stat>;
private readonly _statsHistory: Ref<Stat[]>; private readonly _statsHistory: Ref<Stat[]>;
public readonly swarmId: string | null = null;
public readonly isSwarm: boolean = false;
private readonly movingAverageStat: Ref<Stat>; private readonly movingAverageStat: Ref<Stat>;
private readonly _name: string;
constructor( constructor(
public readonly id: string, public readonly id: string,
public readonly created: Date, public readonly created: Date,
public readonly image: string, public readonly image: string,
public readonly name: string, name: string,
public readonly command: string, public readonly command: string,
public readonly host: string, public readonly host: string,
public readonly labels = {} as Record<string, string>, public readonly labels = {} as Record<string, string>,
@@ -48,12 +45,7 @@ export class Container {
this._statsHistory = useSimpleRefHistory(this._stat, { capacity: 300, deep: true, initial: stats }); this._statsHistory = useSimpleRefHistory(this._stat, { capacity: 300, deep: true, initial: stats });
this.movingAverageStat = useExponentialMovingAverage(this._stat, 0.2); this.movingAverageStat = useExponentialMovingAverage(this._stat, 0.2);
const match = name.match(SWARM_ID_REGEX); this._name = name;
if (match) {
this.swarmId = match[0];
this.name = name.replace(`${this.swarmId}`, "");
this.isSwarm = true;
}
} }
get statsHistory() { get statsHistory() {
@@ -84,6 +76,20 @@ export class Container {
return this.group; return this.group;
} }
get name() {
return this.isSwarm
? this.labels["com.docker.swarm.task.name"].replace(`.${this.labels["com.docker.swarm.task.id"]}`, "")
: this._name;
}
get swarmId() {
return this.labels["com.docker.swarm.service.id"];
}
get isSwarm() {
return Boolean(this.labels["com.docker.swarm.service.id"]);
}
public updateStat(stat: Stat) { public updateStat(stat: Stat) {
if (isRef(this._stat)) { if (isRef(this._stat)) {
this._stat.value = stat; this._stat.value = stat;