diff --git a/php/public/index.php b/php/public/index.php index 2bc8091b..b113d6b0 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -70,6 +70,7 @@ $app->post('/api/docker/backup-check-repair', AIO\Controller\DockerController::c $app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . ':StartBackupContainerTest'); $app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore'); $app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer'); +$app->post('/api/docker/prune', AIO\Controller\DockerController::class . ':SystemPrune'); $app->get('/api/docker/logs', AIO\Controller\DockerController::class . ':GetLogs'); $app->post('/api/auth/login', AIO\Controller\LoginController::class . ':TryLogin'); $app->get('/api/auth/getlogin', AIO\Controller\LoginController::class . ':GetTryLogin'); diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php index 5942e093..835ed6d5 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -328,6 +328,22 @@ readonly class DockerController { return $nonbufResp; } + public function SystemPrune(Request $request, Response $response, array $args) : Response { + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + + $body = $nonbufResp->getBody(); + $addToStreamingResponseBody = function (string $message) use ($body) : void { + $body->write("
$message
"); + }; + + $this->dockerActionManager->SystemPrune($addToStreamingResponseBody); + + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; + } + public function stopTopContainer() : void { $id = self::TOP_CONTAINER; $this->PerformRecursiveContainerStop($id); diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index d5afef81..0e19bcd8 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -997,4 +997,71 @@ readonly class DockerActionManager { return $this->dockerHubManager->GetLatestDigestOfTag($imageName, $tag); } } + + public function SystemPrune(?\Closure $addToStreamingResponseBody = null): void { + $endpoints = [ + // Remove stopped containers + 'containers/prune', + // Remove unused images + 'images/prune', + // Remove unused volumes + 'volumes/prune', + // Remove unused networks + 'networks/prune', + // Prune build cache + 'build/prune', + ]; + + foreach ($endpoints as $endpoint) { + // Special-case images prune to include the dangling filter as requested + if ($endpoint === 'images/prune') { + $filters = json_encode(['dangling' => ['false']]); + $url = $this->BuildApiUrl($endpoint . '?filters=' . urlencode((string) $filters)); + } else { + $url = $this->BuildApiUrl($endpoint); + } + + if ($addToStreamingResponseBody !== null) { + $addToStreamingResponseBody("Running $endpoint..."); + } + + try { + $response = $this->guzzleClient->post($url); + if ($addToStreamingResponseBody !== null) { + $data = json_decode((string)$response->getBody(), true); + $deleted = 0; + foreach (['ContainersDeleted', 'ImagesDeleted', 'VolumesDeleted', 'NetworksDeleted', 'CachesDeleted'] as $key) { + if (isset($data[$key]) && is_array($data[$key])) { + $deleted += count($data[$key]); + } + } + $reclaimed = $data['SpaceReclaimed'] ?? 0; + $parts = []; + if ($deleted > 0) { + $parts[] = "$deleted item(s) deleted"; + } + if ($reclaimed > 0) { + $i = (int)floor(log($reclaimed, 1024)); + $parts[] = 'Space reclaimed: ' . (string)round($reclaimed / (1024 ** $i), 2) . ' ' . ['B','KB','MB','GB'][$i]; + } + $addToStreamingResponseBody(!empty($parts) ? implode('. ', $parts) . '.' : 'Nothing to prune.'); + } + } catch (RequestException $e) { + error_log(sprintf('Docker prune (%s) failed: %s', $endpoint, $e->getMessage())); + if ($addToStreamingResponseBody !== null) { + $addToStreamingResponseBody('Error: ' . $e->getMessage()); + } + // continue with next prune step + } + } + + if ($addToStreamingResponseBody !== null) { + $addToStreamingResponseBody("Docker system prune completed."); + sleep(1); + + // We automatically reload after 10s so that the output can be read or copied if necessary + $addToStreamingResponseBody("Automatically reloading the page after 10s."); + sleep(10); + } + } } diff --git a/php/templates/containers.twig b/php/templates/containers.twig index 17405256..207cfceb 100644 --- a/php/templates/containers.twig +++ b/php/templates/containers.twig @@ -582,6 +582,18 @@ {% if is_backup_container_running == false %} {% if isApacheStarting == false %} + {% if isAnyRunning == true %} +

Docker System Prune

+
+ Click here to reveal a button to prune the docker system. +

By clicking the button below you can run "docker system prune -a". This will remove unused images, containers, networks, volumes and build cache. It will not delete data of running containers.

+
+ + + +
+
+ {% endif %}

AIO passphrase change

Click here to change your AIO passphrase