mirror of
https://github.com/andwati/dokploy.git
synced 2026-10-22 13:02:20 +00:00
feat: add Images and Disk Usage tabs to docker dashboard
- Images tab: list, inspect and delete docker images, with a force-delete fallback when an image is in use. - Disk Usage tab: docker system df summary as stat cards plus a Build Cache table (docker system df -v) with a prune action.
This commit is contained in:
@@ -18,6 +18,7 @@ export * from "./services/deployment";
|
||||
export * from "./services/destination";
|
||||
export * from "./services/dns-provider";
|
||||
export * from "./services/docker";
|
||||
export * from "./services/docker-image";
|
||||
export * from "./services/docker-volume";
|
||||
export * from "./services/domain";
|
||||
export * from "./services/environment";
|
||||
@@ -104,7 +105,6 @@ export * from "./utils/docker/compose/volume";
|
||||
export * from "./utils/docker/domain";
|
||||
export * from "./utils/docker/types";
|
||||
export * from "./utils/docker/utils";
|
||||
export * from "./utils/vault";
|
||||
export * from "./utils/filesystem/directory";
|
||||
export * from "./utils/filesystem/ssh";
|
||||
export * from "./utils/git-branch-validation";
|
||||
@@ -141,6 +141,7 @@ export * from "./utils/traefik/redirect";
|
||||
export * from "./utils/traefik/security";
|
||||
export * from "./utils/traefik/types";
|
||||
export * from "./utils/traefik/web-server";
|
||||
export * from "./utils/vault";
|
||||
export * from "./utils/volume-backups/index";
|
||||
export * from "./utils/watch-paths/should-deploy";
|
||||
export * from "./verification/send-verification-email";
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
execAsync,
|
||||
execAsyncRemote,
|
||||
} from "@dokploy/server/utils/process/execAsync";
|
||||
import { quote } from "shell-quote";
|
||||
|
||||
export interface DockerImage {
|
||||
Repository: string;
|
||||
Tag: string;
|
||||
ID: string;
|
||||
Digest?: string;
|
||||
CreatedAt: string;
|
||||
CreatedSince: string;
|
||||
Size: string;
|
||||
SharedSize?: string;
|
||||
UniqueSize?: string;
|
||||
VirtualSize?: string;
|
||||
}
|
||||
|
||||
export const getImages = async (serverId?: string) => {
|
||||
try {
|
||||
const command = "docker images --format '{{json .}}'";
|
||||
const { stdout } = serverId
|
||||
? await execAsyncRemote(serverId, command)
|
||||
: await execAsync(command);
|
||||
|
||||
return stdout
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line) as DockerImage);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getImageConfig = async (imageRef: string, serverId?: string) => {
|
||||
const command = `docker image inspect ${quote([String(imageRef ?? "")])}`;
|
||||
const { stdout } = serverId
|
||||
? await execAsyncRemote(serverId, command)
|
||||
: await execAsync(command);
|
||||
|
||||
return JSON.parse(stdout.trim())[0];
|
||||
};
|
||||
|
||||
interface RemoveImageParams {
|
||||
repository: string;
|
||||
tag: string;
|
||||
id: string;
|
||||
force?: boolean;
|
||||
}
|
||||
|
||||
export const removeImage = async (
|
||||
{ repository, tag, id, force }: RemoveImageParams,
|
||||
serverId?: string,
|
||||
) => {
|
||||
const hasTaggedReference =
|
||||
repository && tag && repository !== "<none>" && tag !== "<none>";
|
||||
const reference = hasTaggedReference ? `${repository}:${tag}` : id;
|
||||
const command = `docker rmi ${force ? "-f " : ""}${quote([String(reference ?? "")])}`;
|
||||
|
||||
if (serverId) {
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync(command);
|
||||
}
|
||||
};
|
||||
@@ -291,9 +291,13 @@ const parseSizeToBytes = (size: string): number => {
|
||||
return value * (multipliers[unit] || 0);
|
||||
};
|
||||
|
||||
export const getDockerDiskUsage = async (): Promise<DockerDiskUsageItem[]> => {
|
||||
export const getDockerDiskUsage = async (
|
||||
serverId?: string,
|
||||
): Promise<DockerDiskUsageItem[]> => {
|
||||
const command = "docker system df --format '{{json .}}'";
|
||||
const { stdout } = await execAsync(command);
|
||||
const { stdout } = serverId
|
||||
? await execAsyncRemote(serverId, command)
|
||||
: await execAsync(command);
|
||||
|
||||
const lines = stdout.trim().split("\n").filter(Boolean);
|
||||
return lines.map((line) => {
|
||||
@@ -309,6 +313,49 @@ export const getDockerDiskUsage = async (): Promise<DockerDiskUsageItem[]> => {
|
||||
});
|
||||
};
|
||||
|
||||
export interface DockerBuildCacheItem {
|
||||
id: string;
|
||||
type: string;
|
||||
description: string;
|
||||
size: string;
|
||||
sizeBytes: number;
|
||||
createdSince: string;
|
||||
lastUsedSince: string;
|
||||
usageCount: number;
|
||||
shared: boolean;
|
||||
inUse: boolean;
|
||||
}
|
||||
|
||||
export const getBuildCache = async (
|
||||
serverId?: string,
|
||||
): Promise<DockerBuildCacheItem[]> => {
|
||||
try {
|
||||
const command = "docker system df -v --format '{{json .}}'";
|
||||
const { stdout } = serverId
|
||||
? await execAsyncRemote(serverId, command)
|
||||
: await execAsync(command);
|
||||
|
||||
const diskUsage = JSON.parse(stdout.trim());
|
||||
return ((diskUsage?.BuildCache ?? []) as Record<string, string>[]).map(
|
||||
(entry) => ({
|
||||
id: entry.ID ?? "",
|
||||
type: entry.CacheType ?? "",
|
||||
description: entry.Description ?? "",
|
||||
size: entry.Size ?? "",
|
||||
sizeBytes: parseSizeToBytes(entry.Size ?? ""),
|
||||
createdSince: entry.CreatedSince ?? "",
|
||||
lastUsedSince: entry.LastUsedSince ?? "",
|
||||
usageCount: Number.parseInt(entry.UsageCount ?? "0", 10) || 0,
|
||||
shared: entry.Shared === "true",
|
||||
inUse: entry.InUse === "true",
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Volume cleanup should always be performed manually by the user. The reason is that during automatic cleanup, a volume may be deleted due to a stopped container, which is a dangerous situation.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user