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']
SideMenu: typeof import('./components/SideMenu.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']
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default']
SlideTransition: typeof import('./components/common/SlideTransition.vue')['default']
+22 -15
View File
@@ -1,19 +1,17 @@
<template>
<div class="card bg-base-100">
<div class="card-body flex gap-2 max-md:p-4">
<div class="flex flex-row gap-2 overflow-hidden">
<div class="flex items-center gap-1 truncate text-xl font-semibold">
<HostIcon :type="host.type" class="flex-none" />
<div class="truncate">
{{ host.name }}
</div>
<div class="card-body flex gap-3 max-md:p-4">
<div class="flex flex-row items-center gap-3 overflow-hidden">
<div class="flex min-w-0 items-center gap-2 text-lg font-semibold tracking-tight md:text-xl">
<HostIcon :type="host.type" class="text-base-content/80 flex-none" />
<div class="truncate">{{ 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 />
offline
</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 }"
v-else-if="host.type == 'agent'"
title="Dozzle Agent"
@@ -21,12 +19,19 @@
{{ host.agentVersion }}
</span>
</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">
<octicon:container-24 class="inline-block" />
<ul
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) }}
</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>
</div>
@@ -35,7 +40,7 @@
:icon="PhCpu"
:value="stats.weighted.movingAverage.totalCPU"
:chartData="cpuHistory"
container-class="border-primary/40 bg-primary/20"
container-class="bg-primary/10"
text-class="text-primary"
bar-class="bg-primary"
:formatValue="(value) => `${value.toFixed(1)}%`"
@@ -46,7 +51,7 @@
:icon="PhMemory"
:value="stats.weighted.movingAverage.totalMemUsage"
:chartData="memHistory"
container-class="border-secondary/40 bg-secondary/20"
container-class="bg-secondary/10"
text-class="text-secondary"
bar-class="bg-secondary"
: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"),
);
const runtimeLabel = computed(() => (props.host.runtime === "podman" ? "Podman" : "Docker"));
function toContainerCores(container: Container): number {
if (container.cpuLimit && container.cpuLimit > 0) {
return container.cpuLimit;
@@ -15,7 +15,7 @@
>
<template #value="{ hoveredValue }">
<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>
</template>
@@ -34,7 +34,9 @@
>
<template #value="{ hoveredValue }">
<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">
/ {{ formatBytes(limits.memory, { short: true, decimals: 1 }) }}</span
>
+1 -1
View File
@@ -1,5 +1,5 @@
<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">
<component :is="icon" class="text-lg" />
<span>{{ label }}</span>
+1
View File
@@ -7,6 +7,7 @@ export type Host = {
endpoint: string;
available: boolean;
dockerVersion: string;
runtime?: "docker" | "podman";
agentVersion: string;
group?: string;
};
+1
View File
@@ -388,6 +388,7 @@ func (c *Client) Host(ctx context.Context) (container.Host, error) {
Endpoint: c.endpoint,
Type: "agent",
DockerVersion: info.Host.DockerVersion,
Runtime: info.Host.Runtime,
AgentVersion: info.Host.AgentVersion,
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"`
AgentVersion string `protobuf:"bytes,11,opt,name=agentVersion,proto3" json:"agentVersion,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
sizeCache protoimpl.SizeCache
}
@@ -1055,6 +1056,13 @@ func (x *Host) GetDockerVersion() string {
return ""
}
func (x *Host) GetRuntime() string {
if x != nil {
return x.Runtime
}
return ""
}
type NotificationSubscription struct {
state protoimpl.MessageState `protogen:"open.v1"`
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" +
"\x14ActorAttributesEntry\x12\x10\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" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12 \n" +
@@ -1485,7 +1493,8 @@ const file_types_proto_rawDesc = "" +
"\x06memory\x18\n" +
" \x01(\x04R\x06memory\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" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\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),
Memory: uint64(host.MemTotal),
DockerVersion: host.DockerVersion,
Runtime: host.Runtime,
AgentVersion: s.version,
},
}, nil
+1
View File
@@ -22,6 +22,7 @@ type Host struct {
MemTotal int64 `json:"memTotal"`
Endpoint string `json:"endpoint"`
DockerVersion string `json:"dockerVersion"`
Runtime string `json:"runtime,omitempty"`
AgentVersion string `json:"agentVersion,omitempty"`
Type string `json:"type"`
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)
ExecResize(ctx context.Context, execID string, options client.ExecResizeOptions) (client.ExecResizeResult, 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)
ContainerRemove(ctx context.Context, containerID string, options client.ContainerRemoveOptions) (client.ContainerRemoveResult, 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.MemTotal = info.MemTotal
host.DockerVersion = info.ServerVersion
host.Runtime = detectRuntime(cli, info)
host.Swarm = info.Swarm.NodeID != ""
return &DockerClient{
@@ -139,6 +141,24 @@ func NewRemoteClient(host container.Host) (*DockerClient, error) {
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
func (d *DockerClient) FindContainer(ctx context.Context, id string) (container.Container, error) {
log.Debug().Str("id", id).Msg("Finding container")
+1
View File
@@ -115,6 +115,7 @@ message Host {
uint64 memory = 10;
string agentVersion = 11;
string dockerVersion = 12;
string runtime = 13;
}
enum ContainerAction {