IKHSAN
ENID Baca dalam Bahasa Indonesia

homeio

Public

Forked from doctor-io/homeio

Branch
main
Latest commit
958d74616 October 2026

monitor-utils.ts

Open raw
import type { InstalledApp } from "@/lib/shared/contracts/apps";
import type { ContainerStats } from "@/lib/shared/contracts/docker";

/**
 * The installed app a container belongs to: the app's own container, or one of
 * its Compose stack's (<stack>-<service>-1 or <stack>_<service>_1). With
 * several matching stacks the longest name wins, so nextcloud-aio-… goes to
 * the nextcloud-aio stack rather than nextcloud.
 */
export function findAppForContainer(
  containerName: string,
  apps: Pick<InstalledApp, "id" | "stackName" | "containerName" | "logoUrl">[],
) {
  const name = containerName.replace(/^\//, "");
  const own = apps.find((app) => app.containerName === name);
  if (own) return own;

  let best: (typeof apps)[number] | null = null;
  for (const app of apps) {
    const stack = app.stackName;
    const matches = name === stack || name.startsWith(`${stack}-`) || name.startsWith(`${stack}_`);
    if (matches && (!best || stack.length > best.stackName.length)) best = app;
  }
  return best;
}

/**
 * Calculate total stats from Docker containers
 */
export function calculateDockerTotals(containers: ContainerStats[]) {
  if (containers.length === 0) {
    return {
      totalCpu: 0,
      totalMemoryMB: 0,
      totalNetworkRx: 0,
      totalNetworkTx: 0,
      totalDiskRead: 0,
      totalDiskWrite: 0,
      runningCount: 0,
    };
  }

  return containers.reduce(
    (acc, container) => ({
      totalCpu: acc.totalCpu + container.cpuPercent,
      totalMemoryMB: acc.totalMemoryMB + container.memoryUsed / 1024 / 1024,
      totalNetworkRx: acc.totalNetworkRx + container.networkRx,
      totalNetworkTx: acc.totalNetworkTx + container.networkTx,
      totalDiskRead: acc.totalDiskRead + container.blockRead,
      totalDiskWrite: acc.totalDiskWrite + container.blockWrite,
      runningCount:
        acc.runningCount + (container.state === "running" ? 1 : 0),
    }),
    {
      totalCpu: 0,
      totalMemoryMB: 0,
      totalNetworkRx: 0,
      totalNetworkTx: 0,
      totalDiskRead: 0,
      totalDiskWrite: 0,
      runningCount: 0,
    },
  );
}

/**
 * Convert Docker container to process-like format for table display
 */
export function containerToProcess(container: ContainerStats) {
  return {
    name: container.name,
    status: (container.state === "running"
      ? "Running"
      : container.state === "exited"
        ? "Sleeping"
        : "Background") as "Running" | "Sleeping" | "Background",
    pid: parseInt(container.id.slice(0, 8), 16) % 99999, // Pseudo PID from container ID
    cpu: container.cpuPercent,
    memory: container.memoryUsed / 1024 / 1024, // Convert to MB
    network: (container.networkRx + container.networkTx) / 1024 / 1024 / 2, // Average RX+TX in MB
    disk: (container.blockRead + container.blockWrite) / 1024 / 1024 / 2, // Average read+write in MB
  };
}

/**
 * Nudge a value randomly within min/max bounds (for animations)
 */
export function nudge(
  value: number,
  min: number,
  max: number,
  delta = 2.2,
): number {
  const next = value + (Math.random() * 2 - 1) * delta;
  return Math.min(max, Math.max(min, Number(next.toFixed(1))));
}

/**
 * Get status badge color based on container/process state
 */
export function getStatusBadgeColor(
  status: "Running" | "Sleeping" | "Background" | string,
): string {
  switch (status) {
    case "Running":
    case "running":
      return "bg-status-green/15 text-status-green";
    case "Sleeping":
    case "exited":
      return "bg-status-amber/15 text-status-amber";
    default:
      return "bg-secondary/60 text-muted-foreground";
  }
}
bytes
3,593
lines
115
Type
ts
Latest commit
16 October 2026