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"
|
: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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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">;
|
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user