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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<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" />
|
||||
<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"
|
||||
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" />
|
||||
@@ -15,12 +16,7 @@
|
||||
{{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }}
|
||||
</div>
|
||||
<div>
|
||||
<TimedButton
|
||||
v-if="automaticRedirect && containers.length == 1"
|
||||
class="btn-primary btn-sm"
|
||||
@finished="redirectNow()"
|
||||
>Cancel</TimedButton
|
||||
>
|
||||
<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"
|
||||
@@ -30,6 +26,7 @@
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DockerEventLogEntry } from "@/models/LogEntry";
|
||||
@@ -44,29 +41,34 @@ const { logEntry } = defineProps<{
|
||||
|
||||
const { containers } = useLoggingContext();
|
||||
|
||||
const nextContainer = computed(
|
||||
() =>
|
||||
[
|
||||
...containers.value.filter(
|
||||
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],
|
||||
].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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user