From 099298d6955764384d871aed82e25356572bcf72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:39:34 +0000 Subject: [PATCH] Rebase onto base branch, use startStreamingResponse from base Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com> --- php/src/Controller/DockerController.php | 177 ++++++++++++++++-------- php/templates/containers.twig | 37 ++--- 2 files changed, 141 insertions(+), 73 deletions(-) diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php index 42a3ae2e..be182c28 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -87,43 +87,64 @@ readonly class DockerController { } public function StartBackupContainerBackup(Request $request, Response $response, array $args) : Response { + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); + $forceStopNextcloud = true; - $this->startBackup($forceStopNextcloud); - return $response->withStatus(201)->withHeader('Location', '.'); + $this->startBackup($forceStopNextcloud, $addToStreamingResponseBody); + + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; } - public function startBackup(bool $forceStopNextcloud = false) : void { + public function startBackup(bool $forceStopNextcloud = false, ?\Closure $addToStreamingResponseBody = null) : void { $this->configurationManager->backupMode = 'backup'; $id = self::TOP_CONTAINER; - $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); + $this->PerformRecursiveContainerStop($id, $forceStopNextcloud, $addToStreamingResponseBody); $id = 'nextcloud-aio-borgbackup'; - $this->PerformRecursiveContainerStart($id); + $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); } public function StartBackupContainerCheck(Request $request, Response $response, array $args) : Response { - $this->checkBackup(); - return $response->withStatus(201)->withHeader('Location', '.'); + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); + + $this->checkBackup($addToStreamingResponseBody); + + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; } public function StartBackupContainerList(Request $request, Response $response, array $args) : Response { - $this->listBackup(); - return $response->withStatus(201)->withHeader('Location', '.'); + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); + + $this->listBackup($addToStreamingResponseBody); + + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; } - public function checkBackup() : void { + public function checkBackup(?\Closure $addToStreamingResponseBody = null) : void { $this->configurationManager->backupMode = 'check'; $id = 'nextcloud-aio-borgbackup'; - $this->PerformRecursiveContainerStart($id); + $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); } - private function listBackup() : void { + private function listBackup(?\Closure $addToStreamingResponseBody = null) : void { $this->configurationManager->backupMode = 'list'; $id = 'nextcloud-aio-borgbackup'; - $this->PerformRecursiveContainerStart($id); + $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); } public function StartBackupContainerRestore(Request $request, Response $response, array $args) : Response { @@ -133,26 +154,38 @@ readonly class DockerController { $this->configurationManager->restoreExcludePreviews = isset($request->getParsedBody()['restore-exclude-previews']); $this->configurationManager->commitTransaction(); + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); + $id = self::TOP_CONTAINER; $forceStopNextcloud = true; - $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); + $this->PerformRecursiveContainerStop($id, $forceStopNextcloud, $addToStreamingResponseBody); $id = 'nextcloud-aio-borgbackup'; - $this->PerformRecursiveContainerStart($id); + $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); - return $response->withStatus(201)->withHeader('Location', '.'); + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; } public function StartBackupContainerCheckRepair(Request $request, Response $response, array $args) : Response { $this->configurationManager->backupMode = 'check-repair'; + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); + $id = 'nextcloud-aio-borgbackup'; - $this->PerformRecursiveContainerStart($id); + $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); // Restore to backup check which is needed to make the UI logic work correctly $this->configurationManager->backupMode = 'check'; - return $response->withStatus(201)->withHeader('Location', '.'); + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; } public function StartBackupContainerTest(Request $request, Response $response, array $args) : Response { @@ -161,13 +194,19 @@ readonly class DockerController { $this->configurationManager->instanceRestoreAttempt = false; $this->configurationManager->commitTransaction(); + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); + $id = self::TOP_CONTAINER; - $this->PerformRecursiveContainerStop($id); + $this->PerformRecursiveContainerStop($id, true, $addToStreamingResponseBody); $id = 'nextcloud-aio-borgbackup'; - $this->PerformRecursiveContainerStart($id); + $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); - return $response->withStatus(201)->withHeader('Location', '.'); + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; } public function StartContainer(Request $request, Response $response, array $args) : Response @@ -202,14 +241,9 @@ readonly class DockerController { error_log('WARNING: Not pulling container images. Instead, using local ones.'); } + // Get streaming response start and closure $nonbufResp = $this->startStreamingResponse($response); - - // Create a closure to pass around to the code, which should to the logging (because it e.g. decides - // if it'll actually pull an image), but which should not need to know anything about the - // wanted markup or formatting. - $addToStreamingResponseBody = function (Container $container, string $message) use ($nonbufResp) : void { - $nonbufResp->getBody()->write("
{$container->displayName}: {$message}
"); - }; + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); // Start container $this->startTopContainer($pullImage, $addToStreamingResponseBody); @@ -218,6 +252,7 @@ readonly class DockerController { // Temporarily disabled as it leads much faster to docker rate limits // apcu_clear_cache(); + // End streaming response $this->finalizeStreamingResponse($nonbufResp); return $nonbufResp; } @@ -234,17 +269,24 @@ readonly class DockerController { } public function StartWatchtowerContainer(Request $request, Response $response, array $args) : Response { - $this->startWatchtower(); - return $response->withStatus(201)->withHeader('Location', '.'); + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); + + $this->startWatchtower($addToStreamingResponseBody); + + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; } - public function startWatchtower() : void { + public function startWatchtower(?\Closure $addToStreamingResponseBody = null) : void { $id = 'nextcloud-aio-watchtower'; - $this->PerformRecursiveContainerStart($id); + $this->PerformRecursiveContainerStart($id, true, $addToStreamingResponseBody); } - private function PerformRecursiveContainerStop(string $id, bool $forceStopNextcloud = false) : void + private function PerformRecursiveContainerStop(string $id, bool $forceStopNextcloud = false, ?\Closure $addToStreamingResponseBody = null) : void { $container = $this->containerDefinitionFetcher->GetContainerById($id); @@ -252,7 +294,11 @@ readonly class DockerController { // Stop Collabora first to make sure it force-saves // See https://github.com/nextcloud/richdocuments/issues/3799 if ($id === self::TOP_CONTAINER && $this->configurationManager->isCollaboraEnabled) { - $this->PerformRecursiveContainerStop('nextcloud-aio-collabora'); + $this->PerformRecursiveContainerStop('nextcloud-aio-collabora', false, $addToStreamingResponseBody); + } + + if ($addToStreamingResponseBody !== null) { + $addToStreamingResponseBody($container, "Stopping container"); } // Stop itself first and then all the dependencies @@ -263,17 +309,23 @@ readonly class DockerController { $this->dockerActionManager->StopContainer($container, $forceStopNextcloud); } foreach($container->dependsOn as $dependency) { - $this->PerformRecursiveContainerStop($dependency, $forceStopNextcloud); + $this->PerformRecursiveContainerStop($dependency, $forceStopNextcloud, $addToStreamingResponseBody); } } public function StopContainer(Request $request, Response $response, array $args) : Response { + // Get streaming response start and closure + $nonbufResp = $this->startStreamingResponse($response); + $addToStreamingResponseBody = $this->getAddToStreamingResponseBody($nonbufResp); + $id = self::TOP_CONTAINER; $forceStopNextcloud = true; - $this->PerformRecursiveContainerStop($id, $forceStopNextcloud); + $this->PerformRecursiveContainerStop($id, $forceStopNextcloud, $addToStreamingResponseBody); - return $response->withStatus(201)->withHeader('Location', '.'); + // End streaming response + $this->finalizeStreamingResponse($nonbufResp); + return $nonbufResp; } public function SystemPrune(Request $request, Response $response, array $args) : Response { @@ -336,24 +388,6 @@ readonly class DockerController { $this->PerformRecursiveContainerStop($id); } - private function startStreamingResponse(Response $response) : Response { - $nonbufResp = $response - ->withBody(new NonBufferedBody()) - ->withHeader('Content-Type', 'text/html; charset=utf-8') - ->withHeader('X-Accel-Buffering', 'no') - ->withHeader('Cache-Control', 'no-cache'); - - // Text written into this body is immediately sent to the client, without waiting for later content. - $streamingResponseBody = $nonbufResp->getBody(); - $streamingResponseBody->write($this->getStreamingResponseHtmlStart()); - - return $nonbufResp; - } - - private function finalizeStreamingResponse(Response $nonbufResp) : void { - $nonbufResp->getBody()->write($this->getStreamingResponseHtmlEnd()); - } - private function getStreamingResponseHtmlStart() : string { return << @@ -376,7 +410,38 @@ readonly class DockerController { END; } - + + private function startStreamingResponse(Response $response) : Response { + $nonbufResp = $response + ->withBody(new NonBufferedBody()) + ->withHeader('Content-Type', 'text/html; charset=utf-8') + ->withHeader('X-Accel-Buffering', 'no') + ->withHeader('Content-Length', '-1') + ->withHeader('Cache-Control', 'no-cache'); + + // Text written into this body is immediately sent to the client, without waiting for later content. + $streamingResponseBody = $nonbufResp->getBody(); + + $streamingResponseBody->write($this->getStreamingResponseHtmlStart()); + + return $nonbufResp; + } + + private function getAddToStreamingResponseBody(Response $nonbufResp) : ?\Closure { + // Create a closure to pass around to the code, which should to the logging (because it e.g. decides + // if it'll actually pull an image), but which should not need to know anything about the + // wanted markup or formatting. + $addToStreamingResponseBody = function (Container $container, string $message) use ($nonbufResp) : void { + $nonbufResp->getBody()->write("
{$container->displayName}: {$message}
"); + }; + + return $addToStreamingResponseBody; + } + + private function finalizeStreamingResponse(Response $nonbufResp) : void { + $nonbufResp->getBody()->write($this->getStreamingResponseHtmlEnd()); + } + private function getStreamingResponseHtmlEnd() : string { return "\n \n"; } diff --git a/php/templates/containers.twig b/php/templates/containers.twig index d14eda20..83b1740c 100644 --- a/php/templates/containers.twig +++ b/php/templates/containers.twig @@ -47,10 +47,10 @@ {% endif %} {% for container in containers %} - {% if container.displayName != '' and container.GetRunningState().value == 'running' %} + {% if container.hideFromList != true and container.GetRunningState().value == 'running' %} {% set isAnyRunning = true %} {% endif %} - {% if container.displayName != '' and container.GetRestartingState().value == 'restarting' %} + {% if container.hideFromList != true and container.GetRestartingState().value == 'restarting' %} {% set isAnyRestarting = true %} {% endif %} {% if container.identifier == 'nextcloud-aio-watchtower' and container.GetRunningState().value == 'running' %} @@ -90,7 +90,7 @@ {% elseif is_mastercontainer_update_available == true %}

Mastercontainer update

⚠️ A mastercontainer update is available. Please click on the button below to update it. Afterwards, you will be able to proceed with the setup.

-
+ @@ -150,7 +150,7 @@
Reveal repair option

Below is the option to repair the integrity of your backup. Please note: Please only use this after you have read the documentation above! (It will run the command 'borg check --repair' for you.)

- + @@ -161,7 +161,7 @@

Last {{ borg_backup_mode }} successful! (Logs)

{% if borg_backup_mode == 'test' %}

Feel free to check the integrity of the backup archive below before starting the restore process in order to make ensure that the restore will work. This can take a long time though depending on the size of the backup archive and is thus not required.

- + @@ -169,7 +169,7 @@ {% endif %}

Choose the backup that you want to restore and click on the button below to restore the selected backup. This will restore the whole AIO instance. Please note that the current AIO passphrase will be kept and the previous AIO passphrase will not be restored from backup!

Important: If the backup that you want to restore contained any community container, you need to restore the same backup a second time after this attempt so that the community container data is also correctly restored.

- + @@ -264,7 +264,7 @@ {% else %}

It seems at least one container was not able to start correctly and is currently restarting.

To break this endless loop, you can stop the containers below and investigate the issue in the container logs before starting the containers again.

- + @@ -282,7 +282,7 @@
    {# @var containers \AIO\Container\Container[] #} {% for container in containers %} - {% if container.displayName != '' %} + {% if container.hideFromList != true %} {% include 'components/container-state.twig' with {'c': container} only %} {% endif %} {% endfor %} @@ -317,7 +317,7 @@

    You can find all changes here

    {% endif %} {% endif %} - + @@ -337,7 +337,7 @@ {% endif %} {% if is_mastercontainer_update_available == true %}

    ⚠️ A mastercontainer update is available. Please click on the button below to update it.

    - + @@ -358,6 +358,9 @@ + {% if bypass_container_update == true %} + + {% endif %} {% else %} @@ -366,7 +369,7 @@ {% if bypass_container_update == true %} - + {% endif %} @@ -412,7 +415,7 @@
    Reveal repair option

    Below is the option to repair the integrity of your backup. Please note: Please only use this after you have read the documentation above! (It will run the command 'borg check --repair' for you.)

    -
    + @@ -477,7 +480,7 @@ {% if isApacheStarting != true %}

    Backup creation

    Clicking on the button below will create a backup.

    - + @@ -489,7 +492,7 @@

    Backup check

    Click on the button below to perform a backup integrity check. This is an option that verifies that your backup is intact. It shouldn't be needed in most situations.

    - + @@ -497,7 +500,7 @@

    Backup restore

    Choose the backup that you want to restore and click on the button below to restore the selected backup. This will overwrite all your files with the chosen backup so you should consider creating a backup first. You can run an integrity check before restoring your files but this shouldn't be needed in most situations. Please note that this will not restore additionally chosen backup directories! The restore process should be pretty fast as rsync, which only transfers changed files, is used to restore the chosen backup.

    - +