1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 13:23:07 +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"
>
<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>
<ContainerHealth :health="item.health" />
<span

View File

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

View File

@@ -1,33 +1,30 @@
<template>
<div class="relative flex w-full items-start gap-x-2">
<ContainerName class="flex-none" :id="logEntry.containerID" v-if="showContainerName" />
<LogDate :date="logEntry.date" v-if="showTimestamp" />
<LogLevel class="flex" />
<div class="whitespace-pre-wrap" :data-event="logEntry.event" v-html="logEntry.message"></div>
</div>
<div
class="alert alert-info mt-8 w-auto text-[1rem] md:mx-auto md:w-1/2"
v-if="nextContainer && logEntry.event === 'container-stopped'"
>
<carbon:information class="size-6 shrink-0 stroke-current" />
<div>
<h3 class="text-lg font-bold">{{ $t("alert.similar-container-found.title") }}</h3>
{{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }}
<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" />
<LogDate :date="logEntry.date" v-if="showTimestamp" />
<LogLevel class="flex" />
<div class="whitespace-pre-wrap" :data-event="logEntry.event" v-html="logEntry.message"></div>
</div>
<div>
<TimedButton
v-if="automaticRedirect && containers.length == 1"
class="btn-primary btn-sm"
@finished="redirectNow()"
>Cancel</TimedButton
>
<router-link
:to="{ name: 'container-id', params: { id: nextContainer.id } }"
class="btn btn-primary btn-sm"
v-else
>
Redirect
</router-link>
<div
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'"
>
<carbon:information class="size-6 shrink-0 stroke-current" />
<div>
<h3 class="text-lg font-bold">{{ $t("alert.similar-container-found.title") }}</h3>
{{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }}
</div>
<div>
<TimedButton v-if="automaticRedirect" class="btn-primary btn-sm" @finished="redirectNow()">Cancel</TimedButton>
<router-link
:to="{ name: 'container-id', params: { id: nextContainer.id } }"
class="btn btn-primary btn-sm"
v-else
>
Redirect
</router-link>
</div>
</div>
</div>
</template>
@@ -44,29 +41,34 @@ const { logEntry } = defineProps<{
const { containers } = useLoggingContext();
const nextContainer = computed(
() =>
[
...containers.value.filter(
(c) =>
c.host === containers.value[0].host &&
c.created > logEntry.date &&
c.name === containers.value[0].name &&
c.state === "running",
),
].sort((a, b) => +a.created - +b.created)[0],
const store = useContainerStore();
const { containers: allContainers } = storeToRefs(store);
const nextContainer = computed(() =>
containers.value.length === 1 && logEntry.event === "container-stopped"
? [
...allContainers.value.filter(
(c) =>
c.host === containers.value[0].host &&
c.created > logEntry.date &&
c.name === containers.value[0].name &&
c.state === "running",
),
].sort((a, b) => +a.created - +b.created)[0]
: null,
);
function redirectNow() {
showToast(
{
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",
},
{ expire: 5000 },
);
router.push({ name: "container-id", params: { id: nextContainer.value.id } });
router.push({ name: "container-id", params: { id: nextContainer.value?.id } });
}
</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">;
const SWARM_ID_REGEX = /(\.[a-z0-9]{25})+$/i;
const hosts = computed(() =>
config.hosts.reduce(
(acc, item) => {
@@ -26,15 +24,14 @@ export class GroupedContainers {
export class Container {
private _stat: 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 _name: string;
constructor(
public readonly id: string,
public readonly created: Date,
public readonly image: string,
public readonly name: string,
name: string,
public readonly command: string,
public readonly host: 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.movingAverageStat = useExponentialMovingAverage(this._stat, 0.2);
const match = name.match(SWARM_ID_REGEX);
if (match) {
this.swarmId = match[0];
this.name = name.replace(`${this.swarmId}`, "");
this.isSwarm = true;
}
this._name = name;
}
get statsHistory() {
@@ -84,6 +76,20 @@ export class Container {
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) {
if (isRef(this._stat)) {
this._stat.value = stat;