normalized array of containers for the table view * inspect GET (&id=...) -> raw inspect JSON, for a detail dialog * start POST {"id": "..."} * stop POST {"id": "...", "timeout": 10} * restart POST {"id": "...", "timeout": 10} * remove POST {"id": "...", "force": false} * logs GET (&id=...&tail=200) -> plain text */ declare(strict_types=1); require __DIR__ . '/../include/bootstrap.php'; $action = $_GET['action'] ?? ''; switch ($action) { case 'list': podman_json_response(containers_list($client)); break; case 'inspect': $id = (string) ($_GET['id'] ?? ''); if ($id === '') { podman_json_error('Missing id', 400); } podman_json_response($client->inspectContainer($id)); break; case 'logs': $id = (string) ($_GET['id'] ?? ''); if ($id === '') { podman_json_error('Missing id', 400); } $tail = (int) ($_GET['tail'] ?? 200); podman_json_response(['text' => $client->containerLogs($id, $tail)]); break; case 'start': $body = podman_read_json_body(); $client->startContainer(require_id($body)); podman_json_response(['status' => 'started']); break; case 'stop': $body = podman_read_json_body(); $client->stopContainer(require_id($body), (int) ($body['timeout'] ?? $podmanConfig->stopTimeoutSeconds)); podman_json_response(['status' => 'stopped']); break; case 'restart': $body = podman_read_json_body(); $client->restartContainer(require_id($body), (int) ($body['timeout'] ?? $podmanConfig->stopTimeoutSeconds)); podman_json_response(['status' => 'restarted']); break; case 'remove': $body = podman_read_json_body(); $client->removeContainer(require_id($body), (bool) ($body['force'] ?? false)); podman_json_response(['status' => 'removed']); break; default: podman_json_error("Unknown action '{$action}'", 400); } /** @param array $body */ function require_id(array $body): string { $id = (string) ($body['id'] ?? ''); if ($id === '') { podman_json_error('Missing id in request body', 400); } return $id; } /** * Normalizes libpod's /containers/json entries into exactly what the * Containers table (javascript/containers.js) renders — keeping this * shaping logic server-side means the frontend never has to know libpod's * raw field names/quirks (e.g. Names is an array, State vs Status, etc). * * @return array> */ function containers_list(PodmanClient $client): array { $raw = $client->listContainers(true); $out = []; foreach ($raw as $c) { $names = $c['Names'] ?? []; $name = is_array($names) && count($names) > 0 ? ltrim((string) $names[0], '/') : podman_short_id((string) ($c['Id'] ?? '')); $ports = []; foreach (($c['Ports'] ?? []) as $p) { if (isset($p['host_port'], $p['container_port'])) { $ports[] = "{$p['host_port']}:{$p['container_port']}/" . ($p['protocol'] ?? 'tcp'); } elseif (isset($p['container_port'])) { $ports[] = "{$p['container_port']}/" . ($p['protocol'] ?? 'tcp'); } } $startedAt = podman_parse_time($c['StartedAt'] ?? null); $state = strtolower((string) ($c['State'] ?? 'unknown')); $out[] = [ 'id' => (string) ($c['Id'] ?? ''), 'shortId' => podman_short_id((string) ($c['Id'] ?? '')), 'name' => $name, 'image' => (string) ($c['Image'] ?? ''), 'state' => $state, 'status' => (string) ($c['Status'] ?? ''), 'health' => $c['Health']['Status'] ?? null, 'ports' => $ports, 'pod' => $c['Pod'] ?? null, 'podName' => $c['PodName'] ?? null, 'uptimeSeconds' => ($state === 'running' && $startedAt !== null) ? (time() - $startedAt) : null, 'createdAt' => podman_parse_time($c['Created'] ?? null), ]; } usort($out, static fn($a, $b) => strcmp($a['name'], $b['name'])); return $out; }