feat: detect podman vs docker and show runtime icon (#4717)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Amir Raminfar
2026-05-20 05:47:08 -07:00
committed by GitHub
parent 118a7deeab
commit 727b33c8cc
11 changed files with 64 additions and 20 deletions
+1
View File
@@ -184,6 +184,7 @@ declare module 'vue' {
SideDrawer: typeof import('./components/common/SideDrawer.vue')['default'] SideDrawer: typeof import('./components/common/SideDrawer.vue')['default']
SideMenu: typeof import('./components/SideMenu.vue')['default'] SideMenu: typeof import('./components/SideMenu.vue')['default']
SidePanel: typeof import('./components/SidePanel.vue')['default'] SidePanel: typeof import('./components/SidePanel.vue')['default']
'SimpleIcons:podman': typeof import('~icons/simple-icons/podman')['default']
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default'] SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default'] SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default']
SlideTransition: typeof import('./components/common/SlideTransition.vue')['default'] SlideTransition: typeof import('./components/common/SlideTransition.vue')['default']
+22 -15
View File
@@ -1,19 +1,17 @@
<template> <template>
<div class="card bg-base-100"> <div class="card bg-base-100">
<div class="card-body flex gap-2 max-md:p-4"> <div class="card-body flex gap-3 max-md:p-4">
<div class="flex flex-row gap-2 overflow-hidden"> <div class="flex flex-row items-center gap-3 overflow-hidden">
<div class="flex items-center gap-1 truncate text-xl font-semibold"> <div class="flex min-w-0 items-center gap-2 text-lg font-semibold tracking-tight md:text-xl">
<HostIcon :type="host.type" class="flex-none" /> <HostIcon :type="host.type" class="text-base-content/80 flex-none" />
<div class="truncate"> <div class="truncate">{{ host.name }}</div>
{{ host.name }}
</div>
<span class="badge badge-error badge-xs gap-2 p-2" v-if="!host.available"> <span class="badge badge-error badge-xs gap-1 p-2 font-normal" v-if="!host.available">
<carbon:warning /> <carbon:warning />
offline offline
</span> </span>
<span <span
class="badge badge-success badge-xs gap-2 p-2" class="badge badge-success badge-xs gap-1 p-2 font-normal"
:class="{ 'badge-warning': config.version != host.agentVersion }" :class="{ 'badge-warning': config.version != host.agentVersion }"
v-else-if="host.type == 'agent'" v-else-if="host.type == 'agent'"
title="Dozzle Agent" title="Dozzle Agent"
@@ -21,12 +19,19 @@
{{ host.agentVersion }} {{ host.agentVersion }}
</span> </span>
</div> </div>
<ul class="ml-auto flex flex-row flex-wrap gap-x-2 text-sm max-md:text-xs md:gap-3">
<li class="flex items-center gap-1"> <ul
<octicon:container-24 class="inline-block" /> class="text-base-content/60 ml-auto flex shrink-0 flex-row flex-wrap items-center gap-x-3 text-xs tabular-nums md:text-sm"
>
<li class="flex items-center gap-1.5">
<octicon:container-24 class="size-3.5" />
{{ $t("label.container", hostContainers.length) }} {{ $t("label.container", hostContainers.length) }}
</li> </li>
<li class="flex items-center gap-1"><mdi:docker class="inline-block" /> {{ host.dockerVersion }}</li> <li class="flex items-center gap-1.5" :title="runtimeLabel">
<simple-icons:podman v-if="host.runtime === 'podman'" class="size-3.5" />
<mdi:docker v-else class="size-3.5" />
{{ host.dockerVersion }}
</li>
</ul> </ul>
</div> </div>
@@ -35,7 +40,7 @@
:icon="PhCpu" :icon="PhCpu"
:value="stats.weighted.movingAverage.totalCPU" :value="stats.weighted.movingAverage.totalCPU"
:chartData="cpuHistory" :chartData="cpuHistory"
container-class="border-primary/40 bg-primary/20" container-class="bg-primary/10"
text-class="text-primary" text-class="text-primary"
bar-class="bg-primary" bar-class="bg-primary"
:formatValue="(value) => `${value.toFixed(1)}%`" :formatValue="(value) => `${value.toFixed(1)}%`"
@@ -46,7 +51,7 @@
:icon="PhMemory" :icon="PhMemory"
:value="stats.weighted.movingAverage.totalMemUsage" :value="stats.weighted.movingAverage.totalMemUsage"
:chartData="memHistory" :chartData="memHistory"
container-class="border-secondary/40 bg-secondary/20" container-class="bg-secondary/10"
text-class="text-secondary" text-class="text-secondary"
bar-class="bg-secondary" bar-class="bg-secondary"
:formatValue="(value) => formatBytes(value, { decimals: 1 })" :formatValue="(value) => formatBytes(value, { decimals: 1 })"
@@ -78,6 +83,8 @@ const hostContainers = computed(() =>
containers.value.filter((container) => container.host === props.host.id && container.state === "running"), containers.value.filter((container) => container.host === props.host.id && container.state === "running"),
); );
const runtimeLabel = computed(() => (props.host.runtime === "podman" ? "Podman" : "Docker"));
function toContainerCores(container: Container): number { function toContainerCores(container: Container): number {
if (container.cpuLimit && container.cpuLimit > 0) { if (container.cpuLimit && container.cpuLimit > 0) {
return container.cpuLimit; return container.cpuLimit;
@@ -15,7 +15,7 @@
> >
<template #value="{ hoveredValue }"> <template #value="{ hoveredValue }">
<span class="tabular-nums"> <span class="tabular-nums">
<span class="font-semibold"> {{ Math.max(0, hoveredValue ?? totalStat.cpu).toFixed(2) }}% </span> <span class="font-semibold"> {{ Math.max(0, hoveredValue ?? totalStat.cpu).toFixed(1) }}% </span>
<span class="text-base-content/60 max-md:hidden"> / {{ roundCPU(limits.cpu) }} CPU</span> <span class="text-base-content/60 max-md:hidden"> / {{ roundCPU(limits.cpu) }} CPU</span>
</span> </span>
</template> </template>
@@ -34,7 +34,9 @@
> >
<template #value="{ hoveredValue }"> <template #value="{ hoveredValue }">
<span class="tabular-nums"> <span class="tabular-nums">
<span class="font-semibold">{{ formatBytes(hoveredValue ?? totalStat.memoryUsage) }}</span> <span class="font-semibold">{{
formatBytes(hoveredValue ?? totalStat.memoryUsage, { short: true, decimals: 1 })
}}</span>
<span class="text-base-content/60 max-md:hidden"> <span class="text-base-content/60 max-md:hidden">
/ {{ formatBytes(limits.memory, { short: true, decimals: 1 }) }}</span / {{ formatBytes(limits.memory, { short: true, decimals: 1 }) }}</span
> >
+1 -1
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="rounded-lg border p-2 md:p-3" :class="containerClass"> <div class="rounded-lg p-2 md:p-3" :class="containerClass">
<div class="mb-2 flex items-center gap-1.5 text-sm font-medium" :class="textClass"> <div class="mb-2 flex items-center gap-1.5 text-sm font-medium" :class="textClass">
<component :is="icon" class="text-lg" /> <component :is="icon" class="text-lg" />
<span>{{ label }}</span> <span>{{ label }}</span>
+1
View File
@@ -7,6 +7,7 @@ export type Host = {
endpoint: string; endpoint: string;
available: boolean; available: boolean;
dockerVersion: string; dockerVersion: string;
runtime?: "docker" | "podman";
agentVersion: string; agentVersion: string;
group?: string; group?: string;
}; };
+1
View File
@@ -388,6 +388,7 @@ func (c *Client) Host(ctx context.Context) (container.Host, error) {
Endpoint: c.endpoint, Endpoint: c.endpoint,
Type: "agent", Type: "agent",
DockerVersion: info.Host.DockerVersion, DockerVersion: info.Host.DockerVersion,
Runtime: info.Host.Runtime,
AgentVersion: info.Host.AgentVersion, AgentVersion: info.Host.AgentVersion,
Group: c.group, Group: c.group,
} }
+11 -2
View File
@@ -937,6 +937,7 @@ type Host struct {
Memory uint64 `protobuf:"varint,10,opt,name=memory,proto3" json:"memory,omitempty"` Memory uint64 `protobuf:"varint,10,opt,name=memory,proto3" json:"memory,omitempty"`
AgentVersion string `protobuf:"bytes,11,opt,name=agentVersion,proto3" json:"agentVersion,omitempty"` AgentVersion string `protobuf:"bytes,11,opt,name=agentVersion,proto3" json:"agentVersion,omitempty"`
DockerVersion string `protobuf:"bytes,12,opt,name=dockerVersion,proto3" json:"dockerVersion,omitempty"` DockerVersion string `protobuf:"bytes,12,opt,name=dockerVersion,proto3" json:"dockerVersion,omitempty"`
Runtime string `protobuf:"bytes,13,opt,name=runtime,proto3" json:"runtime,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -1055,6 +1056,13 @@ func (x *Host) GetDockerVersion() string {
return "" return ""
} }
func (x *Host) GetRuntime() string {
if x != nil {
return x.Runtime
}
return ""
}
type NotificationSubscription struct { type NotificationSubscription struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
@@ -1471,7 +1479,7 @@ const file_types_proto_rawDesc = "" +
"\tcontainer\x18\x06 \x01(\v2\x13.protobuf.ContainerR\tcontainer\x1aB\n" + "\tcontainer\x18\x06 \x01(\v2\x13.protobuf.ContainerR\tcontainer\x1aB\n" +
"\x14ActorAttributesEntry\x12\x10\n" + "\x14ActorAttributesEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xaf\x03\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc9\x03\n" +
"\x04Host\x12\x0e\n" + "\x04Host\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12 \n" + "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" +
@@ -1485,7 +1493,8 @@ const file_types_proto_rawDesc = "" +
"\x06memory\x18\n" + "\x06memory\x18\n" +
" \x01(\x04R\x06memory\x12\"\n" + " \x01(\x04R\x06memory\x12\"\n" +
"\fagentVersion\x18\v \x01(\tR\fagentVersion\x12$\n" + "\fagentVersion\x18\v \x01(\tR\fagentVersion\x12$\n" +
"\rdockerVersion\x18\f \x01(\tR\rdockerVersion\x1a9\n" + "\rdockerVersion\x18\f \x01(\tR\rdockerVersion\x12\x18\n" +
"\aruntime\x18\r \x01(\tR\aruntime\x1a9\n" +
"\vLabelsEntry\x12\x10\n" + "\vLabelsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xea\x02\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xea\x02\n" +
+1
View File
@@ -270,6 +270,7 @@ func (s *server) HostInfo(ctx context.Context, in *pb.HostInfoRequest) (*pb.Host
CpuCores: uint32(host.NCPU), CpuCores: uint32(host.NCPU),
Memory: uint64(host.MemTotal), Memory: uint64(host.MemTotal),
DockerVersion: host.DockerVersion, DockerVersion: host.DockerVersion,
Runtime: host.Runtime,
AgentVersion: s.version, AgentVersion: s.version,
}, },
}, nil }, nil
+1
View File
@@ -22,6 +22,7 @@ type Host struct {
MemTotal int64 `json:"memTotal"` MemTotal int64 `json:"memTotal"`
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
DockerVersion string `json:"dockerVersion"` DockerVersion string `json:"dockerVersion"`
Runtime string `json:"runtime,omitempty"`
AgentVersion string `json:"agentVersion,omitempty"` AgentVersion string `json:"agentVersion,omitempty"`
Type string `json:"type"` Type string `json:"type"`
Available bool `json:"available"` Available bool `json:"available"`
+20
View File
@@ -37,6 +37,7 @@ type DockerCLI interface {
ExecAttach(ctx context.Context, execID string, config client.ExecAttachOptions) (client.ExecAttachResult, error) ExecAttach(ctx context.Context, execID string, config client.ExecAttachOptions) (client.ExecAttachResult, error)
ExecResize(ctx context.Context, execID string, options client.ExecResizeOptions) (client.ExecResizeResult, error) ExecResize(ctx context.Context, execID string, options client.ExecResizeOptions) (client.ExecResizeResult, error)
Info(ctx context.Context, options client.InfoOptions) (client.SystemInfoResult, error) Info(ctx context.Context, options client.InfoOptions) (client.SystemInfoResult, error)
ServerVersion(ctx context.Context, options client.ServerVersionOptions) (client.ServerVersionResult, error)
ImagePull(ctx context.Context, refStr string, options client.ImagePullOptions) (client.ImagePullResponse, error) ImagePull(ctx context.Context, refStr string, options client.ImagePullOptions) (client.ImagePullResponse, error)
ContainerRemove(ctx context.Context, containerID string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error) ContainerRemove(ctx context.Context, containerID string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, error)
ContainerCreate(ctx context.Context, options client.ContainerCreateOptions) (client.ContainerCreateResult, error) ContainerCreate(ctx context.Context, options client.ContainerCreateOptions) (client.ContainerCreateResult, error)
@@ -66,6 +67,7 @@ func NewClient(cli DockerCLI, host container.Host) *DockerClient {
host.NCPU = info.NCPU host.NCPU = info.NCPU
host.MemTotal = info.MemTotal host.MemTotal = info.MemTotal
host.DockerVersion = info.ServerVersion host.DockerVersion = info.ServerVersion
host.Runtime = detectRuntime(cli, info)
host.Swarm = info.Swarm.NodeID != "" host.Swarm = info.Swarm.NodeID != ""
return &DockerClient{ return &DockerClient{
@@ -139,6 +141,24 @@ func NewRemoteClient(host container.Host) (*DockerClient, error) {
return NewClient(cli, host), nil return NewClient(cli, host), nil
} }
func detectRuntime(cli DockerCLI, info system.Info) string {
version, err := cli.ServerVersion(context.Background(), client.ServerVersionOptions{})
if err == nil {
for _, c := range version.Components {
if strings.Contains(strings.ToLower(c.Name), "podman") {
return "podman"
}
}
if strings.Contains(strings.ToLower(version.Platform.Name), "podman") {
return "podman"
}
}
if strings.Contains(strings.ToLower(info.OperatingSystem), "podman") {
return "podman"
}
return "docker"
}
// Finds a container by id, skipping the filters // Finds a container by id, skipping the filters
func (d *DockerClient) FindContainer(ctx context.Context, id string) (container.Container, error) { func (d *DockerClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
log.Debug().Str("id", id).Msg("Finding container") log.Debug().Str("id", id).Msg("Finding container")
+1
View File
@@ -115,6 +115,7 @@ message Host {
uint64 memory = 10; uint64 memory = 10;
string agentVersion = 11; string agentVersion = 11;
string dockerVersion = 12; string dockerVersion = 12;
string runtime = 13;
} }
enum ContainerAction { enum ContainerAction {