diff --git a/Containers/mastercontainer/mastercontainer.conf b/Containers/mastercontainer/mastercontainer.conf index 7d294694..73a13e6b 100644 --- a/Containers/mastercontainer/mastercontainer.conf +++ b/Containers/mastercontainer/mastercontainer.conf @@ -21,6 +21,11 @@ Listen 8080 https SetHandler "proxy:fcgi://127.0.0.1:9000" + + # Disable output buffering to enable streaming responses. + + + # Master dir DocumentRoot /var/www/docker-aio/php/public/ diff --git a/php/composer.json b/php/composer.json index 892bdd5d..de8be9d8 100644 --- a/php/composer.json +++ b/php/composer.json @@ -16,7 +16,8 @@ "http-interop/http-factory-guzzle": "^1.2", "slim/twig-view": "^3.3", "slim/csrf": "^1.3", - "ext-apcu": "*" + "ext-apcu": "*", + "slim/psr7": "^1.8" }, "require-dev": { "sserbin/twig-linter": "@dev", diff --git a/php/composer.lock b/php/composer.lock index 77403624..28a95f19 100644 --- a/php/composer.lock +++ b/php/composer.lock @@ -4,8 +4,64 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "19598625395cc28e64f15d2719f8f98f", + "content-hash": "a47e950885b06f1a4b631a1eea56c57e", "packages": [ + { + "name": "fig/http-message-util", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "time": "2020-11-24T22:02:12+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "7.10.0", @@ -1146,6 +1202,85 @@ }, "time": "2025-11-02T14:58:28+00:00" }, + { + "name": "slim/psr7", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Psr7.git", + "reference": "76e7e3b1cdfd583e9035c4c966c08e01e45ce959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/76e7e3b1cdfd583e9035c4c966c08e01e45ce959", + "reference": "76e7e3b1cdfd583e9035c4c966c08e01e45ce959", + "shasum": "" + }, + "require": { + "fig/http-message-util": "^1.1.5", + "php": "^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.0 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.0 || ^2.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.5|| ^2.0", + "ext-json": "*", + "http-interop/http-factory-tests": "^1.0 || ^2.0", + "php-http/psr7-integration-tests": "^1.5", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.6 || ^10", + "squizlabs/php_codesniffer": "^3.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Psr7\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "https://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "https://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "https://www.lgse.com" + } + ], + "description": "Strict PSR-7 implementation", + "homepage": "https://www.slimframework.com", + "keywords": [ + "http", + "psr-7", + "psr7" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Psr7/issues", + "source": "https://github.com/slimphp/Slim-Psr7/tree/1.8.0" + }, + "time": "2025-11-02T17:51:19+00:00" + }, { "name": "slim/slim", "version": "4.15.1", @@ -4813,5 +4948,5 @@ "ext-apcu": "*" }, "platform-dev": {}, - "plugin-api-version": "2.9.0" + "plugin-api-version": "2.6.0" } diff --git a/php/public/forms.js b/php/public/forms.js index af72d801..a7f6425d 100644 --- a/php/public/forms.js +++ b/php/public/forms.js @@ -80,6 +80,17 @@ function showPassword(id) { for (const form of forms) { initForm(form); } + const overlayLogForms = document.querySelectorAll('form[target="overlay-log"]') + for (const form of overlayLogForms) { + form.onsubmit = function() { + enableSpinner(); + document.getElementById('overlay-log')?.classList.add('visible'); + // Reload the page after the response was fully loaded into the iframe. + document.querySelector('iframe[name="overlay-log"]').addEventListener('load', () => { + location.reload(); + }); + }; + } } if (document.readyState === 'loading') { diff --git a/php/public/style.css b/php/public/style.css index 09fdb131..b134ac25 100644 --- a/php/public/style.css +++ b/php/public/style.css @@ -480,7 +480,7 @@ input[type="checkbox"]:disabled:not(:checked) + label { #overlay #overlay-log.visible { visibility: visible; opacity: 1; - transition: opacity 500ms ease-in; + transition: opacity 1s ease-in; } #overlay #overlay-log { @@ -489,11 +489,12 @@ input[type="checkbox"]:disabled:not(:checked) + label { position: absolute; top: calc(50% + 120px); width: 20%; - margin: 0 40%; - color: white; - background-color: rgba(0, 0, 0, 0.5); - padding: 2rem; - border-radius: 5px; + height: 5rem; + margin: 0 39%; + padding: 1rem 1.5rem; + border-radius: 10px; + border: solid thin rgb(192, 192, 192); + background-color: rgba(128, 128, 128); } #overlay #overlay-log div { diff --git a/php/src/Controller/DockerController.php b/php/src/Controller/DockerController.php index 818f71ce..577ffea2 100644 --- a/php/src/Controller/DockerController.php +++ b/php/src/Controller/DockerController.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; use AIO\Data\ConfigurationManager; use AIO\Data\DataConst; +use Slim\Psr7\NonBufferedBody; readonly class DockerController { private const string TOP_CONTAINER = 'nextcloud-aio-apache'; @@ -20,12 +21,12 @@ readonly class DockerController { ) { } - private function PerformRecursiveContainerStart(string $id, bool $pullImage = true) : void { + private function PerformRecursiveContainerStart(string $id, bool $pullImage = true, ?NonBufferedBody $body = null) : void { $container = $this->containerDefinitionFetcher->GetContainerById($id); // Start all dependencies first and then itself foreach($container->dependsOn as $dependency) { - $this->PerformRecursiveContainerStart($dependency, $pullImage); + $this->PerformRecursiveContainerStart($dependency, $pullImage, $body); } // Don't start if container is already running @@ -35,22 +36,18 @@ readonly class DockerController { return; } - // Emit a container-start event for frontend clients (one JSON line per event) - try { - $this->pruneEventsFileIfTooLarge(); - $this->writeEventsToFile(['event' => 'Starting container', 'name' => $id, 'time' => time()]); - } catch (\Throwable $e) { - // non-fatal, just log - error_log('Could not write container-start event: ' . $e->getMessage()); - } - $this->dockerActionManager->DeleteContainer($container); $this->dockerActionManager->CreateVolumes($container); + if ($body) { + $body->write("
{$container->displayName}: Pulling image
"); + } $this->dockerActionManager->PullImage($container, $pullImage); + if ($body) { + $body->write("
{$container->displayName}: Starting container
"); + } $this->dockerActionManager->CreateContainer($container); $this->dockerActionManager->StartContainer($container); $this->dockerActionManager->ConnectContainerToNetwork($container); - $container->logEvent('Container is running'); } private function PerformRecursiveImagePull(string $id) : void { @@ -208,17 +205,29 @@ readonly class DockerController { if ($pullImage === false) { error_log('WARNING: Not pulling container images. Instead, using local ones.'); } + + $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. + $body = $nonbufResp->getBody(); + + $body->write(""); + // Start container - $this->startTopContainer($pullImage); + $this->startTopContainer($pullImage, $body); // Clear apcu cache in order to check if container updates are available // Temporarily disabled as it leads much faster to docker rate limits // apcu_clear_cache(); - return $response->withStatus(201)->withHeader('Location', '.'); + $body->write(""); + return $nonbufResp; } - public function startTopContainer(bool $pullImage) : void { + public function startTopContainer(bool $pullImage, ?NonBufferedBody $body = null) : void { $this->configurationManager->aioToken = bin2hex(random_bytes(24)); // Stop domaincheck since apache would not be able to start otherwise @@ -226,7 +235,7 @@ readonly class DockerController { $id = self::TOP_CONTAINER; - $this->PerformRecursiveContainerStart($id, $pullImage); + $this->PerformRecursiveContainerStart($id, $pullImage, $body); } public function StartWatchtowerContainer(Request $request, Response $response, array $args) : Response { diff --git a/php/templates/containers.twig b/php/templates/containers.twig index 8e437bc2..9bab741b 100644 --- a/php/templates/containers.twig +++ b/php/templates/containers.twig @@ -338,7 +338,7 @@ {% else %} {% if was_start_button_clicked == false %} -
+ @@ -355,7 +355,7 @@
{% else %} -
+ diff --git a/php/templates/layout.twig b/php/templates/layout.twig index 4be63c76..e3984bd6 100644 --- a/php/templates/layout.twig +++ b/php/templates/layout.twig @@ -14,7 +14,7 @@
-
+