From 5287b0024d728e091be148eadf187d056abc0d1e Mon Sep 17 00:00:00 2001 From: Boban Blaskovic Date: Sun, 31 May 2026 22:18:51 +0200 Subject: [PATCH] Foundation + Login Glass page from template - Tailwind theme tokens rewritten to match template light-glass design (color-fg / muted / accent / glass / hairline + radii + animations). - Component classes: clu-glass, clu-brand-mark, clu-status-chip, clu-sso-btn, clu-input, clu-btn-primary, clu-alt-btn, clu-checkbox, clu-stat, clu-term-*, clu-live-tag, clu-pill. - layouts/login.blade.php: clu-bg-aurora body, @stack('scripts') for page JS. - pages/auth/login: full re-port of templates/akutell/CluPilot Login Glass.html: two-col grid, SSO buttons, e-mail+password with eye toggle (Alpine), remember checkbox, magic-link/passkey alt buttons, footer with TLS info, right-side live cluster panel with stat strip, animated terminal, live tags. - Pest installed (pest, pest-plugin-laravel, pest-plugin-livewire). - tests/Pest.php with RefreshDatabase binding. - tests/Feature/Auth/LoginPageTest.php: 5 tests, all green (renders chrome, mounts Volt, rejects empty, authenticates, rejects invalid). - 12 design templates committed to templates/akutell/. --- app/composer.json | 3 + app/composer.lock | 1241 +++++++++++++++-- app/resources/css/app.css | 641 +++++---- app/resources/views/layouts/login.blade.php | 5 +- .../views/livewire/pages/auth/login.blade.php | 473 ++++--- app/tests/Feature/Auth/LoginPageTest.php | 62 + app/tests/Pest.php | 6 + templates/akutell/Backups.html | 177 +++ .../akutell/CluPilot Dashboard Glass.html | 645 +++++++++ templates/akutell/CluPilot Login Glass.html | 405 ++++++ templates/akutell/CluPilot Signup Glass.html | 474 +++++++ templates/akutell/CluPilot Signup Light.html | 477 +++++++ templates/akutell/Cluster anlegen.html | 338 +++++ templates/akutell/Einstellungen.html | 139 ++ templates/akutell/Server.html | 204 +++ templates/akutell/Sicherheit.html | 179 +++ templates/akutell/Site Details.html | 391 ++++++ templates/akutell/Sites.html | 187 +++ templates/akutell/Team.html | 176 +++ templates/akutell/sitedetails.js | 430 ++++++ 20 files changed, 6047 insertions(+), 606 deletions(-) create mode 100644 app/tests/Feature/Auth/LoginPageTest.php create mode 100644 app/tests/Pest.php create mode 100644 templates/akutell/Backups.html create mode 100644 templates/akutell/CluPilot Dashboard Glass.html create mode 100644 templates/akutell/CluPilot Login Glass.html create mode 100644 templates/akutell/CluPilot Signup Glass.html create mode 100644 templates/akutell/CluPilot Signup Light.html create mode 100644 templates/akutell/Cluster anlegen.html create mode 100644 templates/akutell/Einstellungen.html create mode 100644 templates/akutell/Server.html create mode 100644 templates/akutell/Sicherheit.html create mode 100644 templates/akutell/Site Details.html create mode 100644 templates/akutell/Sites.html create mode 100644 templates/akutell/Team.html create mode 100644 templates/akutell/sitedetails.js diff --git a/app/composer.json b/app/composer.json index 5f1ad77..ad7c3fc 100644 --- a/app/composer.json +++ b/app/composer.json @@ -22,6 +22,9 @@ "laravel/sail": "^1.41", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.6", + "pestphp/pest": "^3.8", + "pestphp/pest-plugin-laravel": "^3.2", + "pestphp/pest-plugin-livewire": "^3.0", "phpunit/phpunit": "^11.5.50" }, "autoload": { diff --git a/app/composer.lock b/app/composer.lock index d0b1f01..c957576 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ad566b31127e615cf2a5d04b89f2ef07", + "content-hash": "52c1c32c2e88358d76189ceb15d4a072", "packages": [ { "name": "brick/math", @@ -1030,16 +1030,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.10.3", + "version": "2.10.4", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130" + "reference": "d2a1a094e396da8957e797489fddaf860c340cfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7c1472269227dc6f18930bd903d7a88fe6c52130", - "reference": "7c1472269227dc6f18930bd903d7a88fe6c52130", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/d2a1a094e396da8957e797489fddaf860c340cfc", + "reference": "d2a1a094e396da8957e797489fddaf860c340cfc", "shasum": "" }, "require": { @@ -1127,7 +1127,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.10.3" + "source": "https://github.com/guzzle/psr7/tree/2.10.4" }, "funding": [ { @@ -1143,7 +1143,7 @@ "type": "tidelift" } ], - "time": "2026-05-27T11:48:20+00:00" + "time": "2026-05-29T12:59:07+00:00" }, { "name": "guzzlehttp/uri-template", @@ -4411,22 +4411,21 @@ }, { "name": "symfony/clock", - "version": "v7.4.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111" + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/674fa3b98e21531dd040e613479f5f6fa8f32111", - "reference": "674fa3b98e21531dd040e613479f5f6fa8f32111", + "url": "https://api.github.com/repos/symfony/clock/zipball/701ef4de9705d6c32292ebee5e8044094a09fbf6", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/clock": "^1.0", - "symfony/polyfill-php83": "^1.28" + "php": ">=8.4.1", + "psr/clock": "^1.0" }, "provide": { "psr/clock-implementation": "1.0" @@ -4465,7 +4464,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.4.8" + "source": "https://github.com/symfony/clock/tree/v8.1.0" }, "funding": [ { @@ -4485,7 +4484,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/console", @@ -4587,20 +4586,20 @@ }, { "name": "symfony/css-selector", - "version": "v7.4.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "b75663ed96cf4756e28e3105476f220f92886cc4" + "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/b75663ed96cf4756e28e3105476f220f92886cc4", - "reference": "b75663ed96cf4756e28e3105476f220f92886cc4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/dc0e2be45c9b5588c82414f02ac574b4b986abcd", + "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -4632,7 +4631,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.9" + "source": "https://github.com/symfony/css-selector/tree/v8.1.0" }, "funding": [ { @@ -4652,7 +4651,7 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:18:21+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4809,24 +4808,25 @@ }, { "name": "symfony/event-dispatcher", - "version": "v7.4.9", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101" + "reference": "f249ae3f680958b6f1f9dd76e5747cf0695b4102" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e4a2e29753c7801f7a8340e066cfa788f3bc8101", - "reference": "e4a2e29753c7801f7a8340e066cfa788f3bc8101", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f249ae3f680958b6f1f9dd76e5747cf0695b4102", + "reference": "f249ae3f680958b6f1f9dd76e5747cf0695b4102", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<6.4", + "symfony/security-http": "<7.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -4835,14 +4835,14 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/error-handler": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0|^8.0" + "symfony/stopwatch": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -4870,7 +4870,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.0" }, "funding": [ { @@ -4890,7 +4890,7 @@ "type": "tidelift" } ], - "time": "2026-04-18T13:18:21+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6482,35 +6482,34 @@ }, { "name": "symfony/string", - "version": "v7.4.13", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde" + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", - "reference": "961683010db3b27ec6ebcd7308e6e1ee8fa7ffde", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "symfony/var-exporter": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -6549,7 +6548,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.13" + "source": "https://github.com/symfony/string/tree/v8.1.0" }, "funding": [ { @@ -6569,38 +6568,31 @@ "type": "tidelift" } ], - "time": "2026-05-23T15:23:29+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/translation", - "version": "v7.4.10", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde" + "reference": "b2bd012ca28c4acae830ee1206a5b6e35dd99693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/ada7578c30dd5feaa8259cff3e885069ea81ddde", - "reference": "ada7578c30dd5feaa8259cff3e885069ea81ddde", + "url": "https://api.github.com/repos/symfony/translation/zipball/b2bd012ca28c4acae830ee1206a5b6e35dd99693", + "reference": "b2bd012ca28c4acae830ee1206a5b6e35dd99693", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5.3|^3.3" + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" }, "conflict": { "nikic/php-parser": "<5.0", - "symfony/config": "<6.4", - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<6.4", - "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<6.4", - "symfony/yaml": "<6.4" + "symfony/service-contracts": "<2.5" }, "provide": { "symfony/translation-implementation": "2.3|3.0" @@ -6608,17 +6600,17 @@ "require-dev": { "nikic/php-parser": "^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/routing": "^7.4|^8.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/yaml": "^7.4|^8.0" }, "type": "library", "autoload": { @@ -6649,7 +6641,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.10" + "source": "https://github.com/symfony/translation/tree/v8.1.0" }, "funding": [ { @@ -6669,7 +6661,7 @@ "type": "tidelift" } ], - "time": "2026-05-06T11:19:24+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/translation-contracts", @@ -7197,6 +7189,147 @@ } ], "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.8.5", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.3.0", + "jean85/pretty-package-versions": "^2.1.1", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-timer": "^7.0.1", + "phpunit/phpunit": "^11.5.46", + "sebastian/environment": "^7.2.1", + "symfony/console": "^6.4.22 || ^7.3.4 || ^8.0.3", + "symfony/process": "^6.4.20 || ^7.3.4 || ^8.0.3" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0.0", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^2.1.33", + "phpstan/phpstan-deprecation-rules": "^2.0.3", + "phpstan/phpstan-phpunit": "^2.0.11", + "phpstan/phpstan-strict-rules": "^2.0.7", + "squizlabs/php_codesniffer": "^3.13.5", + "symfony/filesystem": "^6.4.13 || ^7.3.2 || ^8.0.1" + }, + "bin": [ + "bin/paratest", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.8.5" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2026-01-08T08:02:38+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+00:00" + }, { "name": "fakerphp/faker", "version": "v1.24.1", @@ -7260,6 +7393,67 @@ }, "time": "2024-11-21T13:46:39+00:00" }, + { + "name": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+00:00" + }, { "name": "filp/whoops", "version": "2.18.4", @@ -7382,6 +7576,66 @@ }, "time": "2025-04-30T06:54:44+00:00" }, + { + "name": "jean85/pretty-package-versions", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + }, + "time": "2025-03-19T14:43:43+00:00" + }, { "name": "laravel/breeze", "version": "v2.4.2", @@ -7893,6 +8147,470 @@ ], "time": "2026-04-21T14:04:20+00:00" }, + { + "name": "pestphp/pest", + "version": "v3.8.6", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "8871a6f5ef1de8e7c8dee2a270991449a7b6af73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/8871a6f5ef1de8e7c8dee2a270991449a7b6af73", + "reference": "8871a6f5ef1de8e7c8dee2a270991449a7b6af73", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.8.5", + "nunomaduro/collision": "^8.9.1", + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest-plugin": "^3.0.0", + "pestphp/pest-plugin-arch": "^3.1.1", + "pestphp/pest-plugin-mutate": "^3.0.5", + "php": "^8.2.0", + "phpunit/phpunit": "^11.5.50" + }, + "conflict": { + "filp/whoops": "<2.16.0", + "phpunit/phpunit": ">11.5.50", + "sebastian/exporter": "<6.0.0", + "webmozart/assert": "<1.11.0" + }, + "require-dev": { + "pestphp/pest-dev-tools": "^3.4.0", + "pestphp/pest-plugin-type-coverage": "^3.6.1", + "symfony/process": "^7.4.5" + }, + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Mutate\\Plugins\\Mutate", + "Pest\\Plugins\\Configuration", + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v3.8.6" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2026-03-10T21:04:33+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e79b26c65bc11c41093b10150c1341cc5cdbea83", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.2" + }, + "conflict": { + "pestphp/pest": "<3.0.0" + }, + "require-dev": { + "composer/composer": "^2.7.9", + "pestphp/pest": "^3.0.0", + "pestphp/pest-dev-tools": "^3.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-09-08T23:21:41+00:00" + }, + { + "name": "pestphp/pest-plugin-arch", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "db7bd9cb1612b223e16618d85475c6f63b9c8daa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/db7bd9cb1612b223e16618d85475c6f63b9c8daa", + "reference": "db7bd9cb1612b223e16618d85475c6f63b9c8daa", + "shasum": "" + }, + "require": { + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "ta-tikoma/phpunit-architecture-test": "^0.8.4" + }, + "require-dev": { + "pestphp/pest": "^3.8.1", + "pestphp/pest-dev-tools": "^3.4.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Arch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-04-16T22:59:48+00:00" + }, + { + "name": "pestphp/pest-plugin-laravel", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-laravel.git", + "reference": "6801be82fd92b96e82dd72e563e5674b1ce365fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/6801be82fd92b96e82dd72e563e5674b1ce365fc", + "reference": "6801be82fd92b96e82dd72e563e5674b1ce365fc", + "shasum": "" + }, + "require": { + "laravel/framework": "^11.39.1|^12.9.2", + "pestphp/pest": "^3.8.2", + "php": "^8.2.0" + }, + "require-dev": { + "laravel/dusk": "^8.2.13|dev-develop", + "orchestra/testbench": "^9.9.0|^10.2.1", + "pestphp/pest-dev-tools": "^3.4.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Laravel\\Plugin" + ] + }, + "laravel": { + "providers": [ + "Pest\\Laravel\\PestServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest Laravel Plugin", + "keywords": [ + "framework", + "laravel", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-laravel/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-04-21T07:40:53+00:00" + }, + { + "name": "pestphp/pest-plugin-livewire", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-livewire.git", + "reference": "e2f2edb0a7d414d6837d87908a0e148256d3bf89" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-livewire/zipball/e2f2edb0a7d414d6837d87908a0e148256d3bf89", + "reference": "e2f2edb0a7d414d6837d87908a0e148256d3bf89", + "shasum": "" + }, + "require": { + "livewire/livewire": "^3.5.6", + "pestphp/pest": "^3.0.0", + "php": "^8.1" + }, + "require-dev": { + "orchestra/testbench": "^9.4.0", + "pestphp/pest-dev-tools": "^3.0.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Livewire\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest Livewire Plugin", + "keywords": [ + "framework", + "livewire", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-livewire/tree/v3.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2024-09-09T00:05:59+00:00" + }, + { + "name": "pestphp/pest-plugin-mutate", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-mutate.git", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.2.0", + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "psr/simple-cache": "^3.0.0" + }, + "require-dev": { + "pestphp/pest": "^3.0.8", + "pestphp/pest-dev-tools": "^3.0.0", + "pestphp/pest-plugin-type-coverage": "^3.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Pest\\Mutate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "Mutates your code to find untested cases", + "keywords": [ + "framework", + "mutate", + "mutation", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v3.0.5" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/gehrisandro", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-09-22T07:54:40+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.4", @@ -8011,6 +8729,229 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" + }, + "time": "2026-03-18T20:49:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" + }, + "time": "2026-01-06T21:53:42+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "11.0.12", @@ -8360,16 +9301,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.55", + "version": "11.5.50", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", "shasum": "" }, "require": { @@ -8384,7 +9325,7 @@ "phar-io/version": "^3.2.1", "php": ">=8.2", "phpunit/php-code-coverage": "^11.0.12", - "phpunit/php-file-iterator": "^5.1.1", + "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", @@ -8396,7 +9337,6 @@ "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/recursion-context": "^6.0.3", "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" @@ -8442,7 +9382,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" }, "funding": [ { @@ -8466,7 +9406,7 @@ "type": "tidelift" } ], - "time": "2026-02-18T12:37:06+00:00" + "time": "2026-01-27T05:59:18+00:00" }, { "name": "sebastian/cli-parser", @@ -8580,7 +9520,6 @@ "type": "github" } ], - "abandoned": true, "time": "2025-03-19T07:56:08+00:00" }, { @@ -8637,7 +9576,6 @@ "type": "github" } ], - "abandoned": true, "time": "2024-07-03T04:45:54+00:00" }, { @@ -9584,6 +10522,65 @@ ], "time": "2026-05-25T06:06:12+00:00" }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.7", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/1248f3f506ca9641d4f68cebcd538fa489754db8", + "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18.0 || ^5.0.0", + "php": "^8.1.0", + "phpdocumentor/reflection-docblock": "^5.3.0 || ^6.0.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "symfony/finder": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "require-dev": { + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\Architecture\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ni Shi", + "email": "futik0ma011@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Methods for testing application architecture", + "keywords": [ + "architecture", + "phpunit", + "stucture", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.7" + }, + "time": "2026-02-17T17:25:14+00:00" + }, { "name": "theseer/tokenizer", "version": "1.3.1", @@ -9633,6 +10630,72 @@ } ], "time": "2025-11-17T20:03:58+00:00" + }, + { + "name": "webmozart/assert", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9007ea6f45ecf352a9422b36644e4bfc039b9155", + "reference": "9007ea6f45ecf352a9422b36644e4bfc039b9155", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.4.0" + }, + "time": "2026-05-20T13:07:01+00:00" } ], "aliases": [], diff --git a/app/resources/css/app.css b/app/resources/css/app.css index 6cbb6ff..24856b6 100644 --- a/app/resources/css/app.css +++ b/app/resources/css/app.css @@ -1,331 +1,406 @@ @import "tailwindcss"; -@theme { - --color-bg: #0a0a0f; - --color-s0: #0c0c12; - --color-s1: #111117; - --color-s2: #16161e; - --color-s3: #1c1c26; - --color-hover: #1a1a24; - --color-surface: #101019; - --color-surface2: #1d1d28; - --color-b1: rgba(255,255,255,0.06); - --color-b2: rgba(255,255,255,0.10); - --color-t1: #f0f0f5; - --color-t2: #8888a0; - --color-t3: #44445a; - --color-dim: #5c5c70; - --color-akzent: #4f8ef7; - --color-akzent2: #7c5cff; - --color-success: #3ecf8e; - --color-warning: #f5a623; - --color-danger: #f05252; - --color-purple: #c084fc; - --font-sans: "Inter", system-ui, sans-serif; - --font-mono: "JetBrains Mono", monospace; +/* ──────────────────────────────────────────────────────────────────────── + CluPilot design tokens — sourced from templates/akutell/* (light glass). + ──────────────────────────────────────────────────────────────────────── */ +@theme { + --color-fg: #1a1a22; + --color-muted: #5c5c6b; + --color-muted-2: #9596a6; + --color-hairline: rgba(30,35,60,0.08); + + --color-accent: #3b6ff2; + --color-accent-2: #7c5cff; + --color-success: #15a06a; + --color-warning: #c2820f; + --color-danger: #db3b3b; + --color-purple: #c084fc; + + --color-accent-soft: rgba(59,111,242,0.12); + --color-success-soft: rgba(21,160,106,0.12); + --color-warning-soft: rgba(194,130,15,0.12); + --color-danger-soft: rgba(219,59,59,0.12); + + --color-glass: rgba(255,255,255,0.55); + --color-glass-strong: rgba(255,255,255,0.72); + --color-glass-soft: rgba(255,255,255,0.40); + --color-glass-border: rgba(255,255,255,0.75); + + --color-term-bg: rgba(14,15,24,0.92); + --color-term-border: rgba(255,255,255,0.12); + + --radius-xs: 11px; + --radius-sm: 13px; + --radius: 20px; + + --font-sans: "Inter", system-ui, -apple-system, sans-serif; + --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace; + + --shadow-glass: 0 10px 34px -10px rgba(40,52,92,0.20), 0 2px 8px -2px rgba(40,52,92,0.08); + --shadow-glass-inset: inset 0 1px 0 rgba(255,255,255,0.95), inset 0 -1px 0 rgba(255,255,255,0.25); +} + +/* ───────── Base ───────── */ +@layer base { + *, *::before, *::after { box-sizing: border-box; } + html, body { + margin: 0; + color: var(--color-fg); + font-family: var(--font-sans); + font-size: 13.5px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + min-height: 100vh; + } + a { color: var(--color-accent); text-decoration: none; } + a:hover { text-decoration: underline; } + button { font-family: inherit; color: inherit; background: none; border: 0; cursor: pointer; } + + body.clu-bg-aurora { + background: + radial-gradient(60% 55% at 12% 8%, rgba(99,140,255,0.30), transparent 60%), + radial-gradient(55% 50% at 88% 0%, rgba(140,108,255,0.26), transparent 58%), + radial-gradient(50% 60% at 78% 92%, rgba(64,196,210,0.22), transparent 60%), + radial-gradient(45% 50% at 25% 100%, rgba(120,170,255,0.20), transparent 60%), + linear-gradient(160deg,#eef1fb 0%,#f4f3fb 50%,#eaf2f8 100%); + background-attachment: fixed; + } + body.clu-bg-app { + background: + radial-gradient(80% 60% at 0% 0%, rgba(99,140,255,0.16), transparent 60%), + radial-gradient(60% 50% at 100% 0%, rgba(140,108,255,0.14), transparent 60%), + linear-gradient(180deg,#f3f4fa 0%,#eef0f6 100%); + background-attachment: fixed; + } } -/* ── Keyframes ── */ @keyframes clu-pulse { 0%{transform:scale(.6);opacity:.5} 100%{transform:scale(2.4);opacity:0} } @keyframes clu-type-in { to{opacity:1;transform:translateY(0)} } @keyframes clu-blink { 50%{opacity:0} } @keyframes clu-spin { to{transform:rotate(360deg)} } @keyframes clu-dot-pulse { 0%,100%{opacity:1} 50%{opacity:.3} } +@keyframes clu-fade { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:none} } @layer utilities { - .dot-pulse { animation: clu-dot-pulse 2s infinite; } - .clu-reset { all: revert; } - .clu-spin { animation: clu-spin .8s linear infinite; } + .clu-spin { animation: clu-spin .8s linear infinite; } + .clu-fade-in { animation: clu-fade .25s ease-out both; } + .dot-pulse { animation: clu-dot-pulse 2s infinite; } } @layer components { - /* Login ambient background */ - .clu-login-body::before { - content:"";position:fixed;inset:0;z-index:0;pointer-events:none; - background: - radial-gradient(60% 50% at 12% 10%,rgba(79,142,247,.10),transparent 65%), - radial-gradient(50% 50% at 88% 100%,rgba(124,92,255,.08),transparent 65%); + /* Glass panel */ + .clu-glass { + border-radius: var(--radius); + background: var(--color-glass-strong); + backdrop-filter: blur(28px) saturate(175%); + -webkit-backdrop-filter: blur(28px) saturate(175%); + border: 1px solid var(--color-glass-border); + box-shadow: var(--shadow-glass), var(--shadow-glass-inset); } - .clu-login-body::after { - content:"";position:fixed;inset:0;z-index:0;pointer-events:none; - background-image: - linear-gradient(rgba(255,255,255,.018) 1px,transparent 1px), - linear-gradient(90deg,rgba(255,255,255,.018) 1px,transparent 1px); - background-size:32px 32px; - mask-image:radial-gradient(ellipse at center,#000 30%,transparent 75%); + .clu-glass-sm { + border-radius: var(--radius-sm); + background: var(--color-glass); + backdrop-filter: blur(14px) saturate(160%); + -webkit-backdrop-filter: blur(14px) saturate(160%); + border: 1px solid var(--color-glass-border); + box-shadow: inset 0 1px 0 rgba(255,255,255,0.8); } - /* Brand marks */ + /* Brand mark */ .clu-brand-mark { - background:linear-gradient(135deg,#4f8ef7 0%,#7c5cff 100%); - box-shadow:0 0 0 1px rgba(255,255,255,.06) inset,0 6px 16px rgba(79,142,247,.30); - display:grid;place-items:center; + width: 30px; height: 30px; + border-radius: 9px; + background: linear-gradient(135deg, #5a8bff 0%, #8a63ff 100%); + display: grid; place-items: center; + box-shadow: 0 0 0 1px rgba(255,255,255,0.4) inset, 0 6px 16px -4px rgba(90,139,255,0.6); + flex: none; } .clu-brand-mark::before { - content:"";width:11px;height:11px;border-radius:50%; - background:#0a0a0f;box-shadow:inset 0 0 0 2px #fff; - } - .clu-brand-mark-sm { - background:linear-gradient(135deg,#4f8ef7 0%,#7c5cff 100%); - box-shadow:0 0 0 1px rgba(255,255,255,.06) inset,0 4px 12px rgba(79,142,247,.25); - display:grid;place-items:center; - } - .clu-brand-mark-sm::before { - content:"";width:10px;height:10px;border-radius:50%; - background:#0c0c12;box-shadow:inset 0 0 0 2px #fff; + content: ""; + width: 11px; height: 11px; + border-radius: 50%; + background: rgba(255,255,255,0.96); + box-shadow: inset 0 0 0 2px #5a8bff; } - /* Pulse dot */ - .clu-pulse-dot { position:relative;flex:none; } + /* Status chip */ + .clu-status-chip { + display: inline-flex; align-items: center; gap: 7px; + padding: 4px 11px 4px 9px; + border: 1px solid rgba(21,160,106,0.28); + background: var(--color-success-soft); + color: var(--color-success); + border-radius: 999px; + font-size: 11px; font-weight: 600; + font-family: var(--font-mono); + } + .clu-pulse-dot { + width: 6px; height: 6px; + border-radius: 50%; + background: currentColor; + position: relative; flex: none; + } .clu-pulse-dot::after { - content:"";position:absolute;inset:-3px;border-radius:50%; - background:currentColor;opacity:.35;animation:clu-pulse 1.8s ease-out infinite; - } - - /* Input with :focus-within */ - .clu-input { - display:flex;align-items:center;gap:10px;padding:0 12px; - background:#101019;border:1px solid rgba(255,255,255,.06);border-radius:8px; - transition:border-color .15s,background .15s,box-shadow .15s;height:40px; - } - .clu-input:hover { border-color:rgba(255,255,255,.10); } - .clu-input:focus-within { - border-color:rgba(79,142,247,.50);background:#12121c; - box-shadow:0 0 0 3px rgba(79,142,247,.10); - } - .clu-input input { - flex:1;background:none;border:0;outline:0; - color:#f0f0f5;font-size:13.5px;font-family:inherit;min-width:0;height:100%; - } - .clu-input input::placeholder { color:#5c5c70; } - - /* Divider */ - .clu-divider { - display:flex;align-items:center;gap:12px;color:#5c5c70; - font-size:11px;font-family:var(--font-mono);text-transform:uppercase;letter-spacing:.08em; - } - .clu-divider::before,.clu-divider::after { - content:"";flex:1;height:1px;background:rgba(255,255,255,.06); + content: ""; + position: absolute; inset: -3px; + border-radius: 50%; + background: currentColor; + opacity: 0.35; + animation: clu-pulse 1.8s ease-out infinite; } /* SSO button */ .clu-sso-btn { - display:flex;align-items:center;justify-content:center;gap:8px;padding:9px 12px; - background:#101019;border:1px solid rgba(255,255,255,.06);border-radius:8px; - color:#f0f0f5;font-size:12.5px;font-weight:500;transition:all .15s;cursor:pointer;font-family:inherit; + display: flex; align-items: center; justify-content: center; gap: 8px; + padding: 10px 12px; + background: rgba(255,255,255,0.55); + border: 1px solid var(--color-hairline); + border-radius: var(--radius-xs); + color: var(--color-fg); + font-size: 12.5px; font-weight: 600; + transition: background .15s, border-color .15s; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.8); } - .clu-sso-btn:hover { background:#1d1d28;border-color:rgba(255,255,255,.10); } + .clu-sso-btn:hover { background: rgba(255,255,255,0.9); border-color: var(--color-glass-border); } + .clu-sso-btn svg { flex: none; } + + /* Divider */ + .clu-divider { + display: flex; align-items: center; gap: 12px; + color: var(--color-muted-2); + font-size: 11px; + font-family: var(--font-mono); + text-transform: uppercase; + letter-spacing: 0.08em; + } + .clu-divider::before, .clu-divider::after { + content: ""; + flex: 1; height: 1px; + background: var(--color-hairline); + } + + /* Input wrapper */ + .clu-input { + display: flex; align-items: center; gap: 10px; + padding: 0 13px; + background: rgba(255,255,255,0.5); + border: 1px solid var(--color-hairline); + border-radius: var(--radius-xs); + transition: border-color .15s, background .15s, box-shadow .15s; + height: 42px; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.7); + } + .clu-input:hover { border-color: var(--color-glass-border); background: rgba(255,255,255,0.65); } + .clu-input:focus-within { + border-color: rgba(59,111,242,0.5); + background: rgba(255,255,255,0.85); + box-shadow: 0 0 0 3px rgba(59,111,242,0.12); + } + .clu-input svg { color: var(--color-muted-2); flex: none; } + .clu-input input { + flex: 1; + background: none; border: 0; outline: 0; + color: var(--color-fg); + font-size: 13.5px; + font-family: inherit; + min-width: 0; height: 100%; + } + .clu-input input::placeholder { color: var(--color-muted-2); } + .clu-input .clu-eye { + color: var(--color-muted); + padding: 4px; + border-radius: 6px; + display: grid; place-items: center; + transition: color .15s, background .15s; + } + .clu-input .clu-eye:hover { color: var(--color-fg); background: rgba(255,255,255,0.7); } /* Primary button */ .clu-btn-primary { - display:inline-flex;align-items:center;justify-content:center;gap:8px; - padding:10px 16px;background:#4f8ef7;color:#fff;border-radius:8px; - font-weight:500;font-size:13.5px;height:40px;width:100%; - transition:background .15s,transform .05s;border:none;cursor:pointer;font-family:inherit; - box-shadow:0 1px 0 rgba(255,255,255,.08) inset,0 1px 3px rgba(79,142,247,.35); + display: inline-flex; align-items: center; justify-content: center; gap: 8px; + padding: 11px 16px; + background: linear-gradient(180deg, #4f7dff, #3b6ff2); + color: #fff; + border-radius: var(--radius-xs); + font-weight: 600; font-size: 13.5px; + height: 44px; + transition: filter .15s, transform .05s; + box-shadow: 0 1px 0 rgba(255,255,255,0.3) inset, 0 6px 16px -5px rgba(59,111,242,0.65); } - .clu-btn-primary:hover { background:#5e98f7; } - .clu-btn-primary:active { transform:translateY(1px); } - .clu-btn-primary:disabled { opacity:.7;cursor:not-allowed; } - .clu-btn-primary svg.arrow { transition:transform .15s; } - .clu-btn-primary:hover:not(:disabled) svg.arrow { transform:translateX(2px); } + .clu-btn-primary:hover { filter: brightness(1.06); } + .clu-btn-primary:active { transform: translateY(1px); } + .clu-btn-primary:disabled { opacity: 0.7; cursor: progress; } + .clu-btn-primary .arrow { transition: transform .15s; } + .clu-btn-primary:hover .arrow { transform: translateX(2px); } - /* Alt button */ + /* Alt / ghost button */ .clu-alt-btn { - display:flex;align-items:center;gap:10px;padding:9px 12px; - background:transparent;border:1px solid rgba(255,255,255,.06);border-radius:8px; - font-size:12.5px;color:#f0f0f5;font-weight:500;transition:all .15s; - width:100%;cursor:pointer;font-family:inherit; + display: flex; align-items: center; gap: 11px; + padding: 10px 13px; + background: rgba(255,255,255,0.4); + border: 1px solid var(--color-hairline); + border-radius: var(--radius-xs); + font-size: 12.5px; color: var(--color-fg); font-weight: 600; + transition: background .15s, border-color .15s; } - .clu-alt-btn:hover { border-color:rgba(255,255,255,.10);background:#101019; } + .clu-alt-btn:hover { background: rgba(255,255,255,0.7); border-color: var(--color-glass-border); } - /* Live side glow */ - .clu-live-side::before { - content:"";position:absolute;inset:0;pointer-events:none; - background:radial-gradient(80% 50% at 100% 0%,rgba(79,142,247,.12),transparent 65%); + /* Checkbox */ + .clu-checkbox { + display: inline-flex; align-items: center; gap: 9px; + font-size: 12.5px; color: var(--color-muted); + cursor: pointer; user-select: none; } + .clu-checkbox input { display: none; } + .clu-checkbox .box { + width: 16px; height: 16px; + border-radius: 5px; + border: 1px solid rgba(30,35,60,0.2); + background: rgba(255,255,255,0.7); + display: grid; place-items: center; + transition: background .15s, border-color .15s; + flex: none; + } + .clu-checkbox input:checked + .box { background: var(--color-accent); border-color: var(--color-accent); } + .clu-checkbox .box svg { opacity: 0; transform: scale(.6); transition: opacity .15s, transform .15s; color: #fff; } + .clu-checkbox input:checked + .box svg { opacity: 1; transform: scale(1); } + .clu-checkbox:hover { color: var(--color-fg); } - /* Terminal */ + /* Stat tile */ + .clu-stat { + background: var(--color-glass); + border: 1px solid var(--color-glass-border); + border-radius: var(--radius-sm); + padding: 13px; + backdrop-filter: blur(14px) saturate(160%); + -webkit-backdrop-filter: blur(14px) saturate(160%); + box-shadow: inset 0 1px 0 rgba(255,255,255,0.8); + } + .clu-stat-label { + font-size: 10.5px; + color: var(--color-muted); + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 600; + } + .clu-stat-value { + font-family: var(--font-mono); + font-size: 19px; font-weight: 600; + letter-spacing: -0.01em; + margin-top: 5px; + } + .clu-stat-value .unit { + font-size: 12px; color: var(--color-muted-2); font-weight: 500; margin-left: 2px; + } + .clu-stat-trend { + display: inline-flex; align-items: center; gap: 4px; + font-family: var(--font-mono); font-size: 10.5px; + color: var(--color-success); + margin-top: 5px; + } + .clu-stat-trend.warn { color: var(--color-warning); } + .clu-stat-trend.bad { color: var(--color-danger); } + + /* Terminal card */ + .clu-term-card { + background: var(--color-term-bg); + backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--color-term-border); + border-radius: var(--radius-sm); + overflow: hidden; + display: flex; flex-direction: column; + min-height: 280px; + box-shadow: 0 14px 40px -12px rgba(20,24,50,0.45), inset 0 1px 0 rgba(255,255,255,0.08); + } + .clu-term-head { + display: flex; align-items: center; gap: 10px; + padding: 10px 14px; + border-bottom: 1px solid rgba(255,255,255,0.07); + background: rgba(10,10,16,0.6); + } + .clu-term-dots { display: flex; gap: 5px; } + .clu-term-dots span { width: 9px; height: 9px; border-radius: 50%; background: #2a2a36; } + .clu-term-dots span:nth-child(1) { background: #f05252; } + .clu-term-dots span:nth-child(2) { background: #f5a623; } + .clu-term-dots span:nth-child(3) { background: #3ecf8e; } + .clu-term-title { font-family: var(--font-mono); font-size: 11.5px; color: #8888a0; flex: 1; text-align: center; } + .clu-term-meta { font-family: var(--font-mono); font-size: 10.5px; color: #5c5c70; } + .clu-term-body { + padding: 14px 16px; + font-family: var(--font-mono); + font-size: 11.5px; line-height: 1.6; + flex: 1; overflow: hidden; + color: #cccddd; + } .clu-term-line { - display:flex;gap:10px;align-items:flex-start; - opacity:0;transform:translateY(4px);animation:clu-type-in .35s ease forwards; + display: flex; gap: 10px; align-items: flex-start; + opacity: 0; transform: translateY(4px); + animation: clu-type-in .35s ease forwards; } - .clu-term-ok { color:#3ecf8e; } - .clu-term-warn { color:#f5a623; } - .clu-term-err { color:#f05252; } - .clu-term-acc { color:#4f8ef7; } - .clu-term-dim { color:#5c5c70; } - .clu-term-pkg { color:#c084fc; } - - /* Cursor */ + .clu-term-time { color: #5c5c70; flex: none; width: 60px; font-size: 10.5px; padding-top: 1px; } + .clu-term-text { flex: 1; min-width: 0; word-break: break-word; } + .clu-term-text .ok { color: #3ecf8e; } + .clu-term-text .warn { color: #f5a623; } + .clu-term-text .err { color: #f05252; } + .clu-term-text .acc { color: #6f9bff; } + .clu-term-text .dim { color: #8888a0; } + .clu-term-text .pkg { color: #c084fc; } + .clu-term-prompt { display: inline-flex; align-items: center; gap: 7px; color: #3ecf8e; } + .clu-term-prompt::before { content: "$"; color: #6f9bff; font-weight: 600; } .clu-cursor { - display:inline-block;width:7px;height:13px;background:#4f8ef7; - vertical-align:-2px;animation:clu-blink 1s steps(1) infinite;margin-left:2px; + display: inline-block; + width: 7px; height: 13px; + background: #6f9bff; + vertical-align: -2px; + animation: clu-blink 1s steps(1) infinite; + margin-left: 2px; } - /* Dashboard topbar */ - .clu-topbar { - backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px); - background:rgba(10,10,15,.85); + /* Live tag */ + .clu-live-tag { + display: inline-flex; align-items: center; gap: 5px; + padding: 4px 9px; + font-size: 10.5px; + font-family: var(--font-mono); + color: var(--color-muted); + background: var(--color-glass); + border: 1px solid var(--color-glass-border); + border-radius: 7px; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.7); + } + .clu-live-tag .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--color-success); } + .clu-live-tag.warn .dot { background: var(--color-warning); } + .clu-live-tag.bad .dot { background: var(--color-danger); } + + /* Auth two-column grid */ + .clu-auth-grid { + position: relative; + z-index: 1; + min-height: 100vh; + display: grid; + grid-template-columns: minmax(420px, 1fr) 1.05fr; + gap: 14px; + padding: 14px; + } + @media (max-width: 1080px) { + .clu-auth-grid { grid-template-columns: 1fr; padding: 12px; } + .clu-auth-grid > .clu-auth-side-right { display: none; } } - /* Search */ - .clu-search { - display:flex;align-items:center;gap:8px;padding:6px 10px; - background:#101019;border:1px solid rgba(255,255,255,.06);border-radius:7px; - color:#8888a0;transition:border-color .15s;min-width:280px; - } - .clu-search:hover { border-color:rgba(255,255,255,.10); } - .clu-search:focus-within { border-color:rgba(79,142,247,.5);background:#12121c; } - .clu-search input { - flex:1;background:none;border:0;outline:0; - color:#f0f0f5;font-size:12.5px;font-family:inherit;min-width:0; - } - .clu-search input::placeholder { color:#8888a0; } - - /* Dashboard button */ - .clu-btn-dash { - display:inline-flex;align-items:center;gap:6px;padding:7px 12px; - background:#4f8ef7;color:#fff;border-radius:7px;font-weight:500;font-size:12.5px; - transition:background .15s;border:none;cursor:pointer;font-family:inherit; - box-shadow:0 1px 0 rgba(255,255,255,.08) inset,0 1px 3px rgba(79,142,247,.35); - } - .clu-btn-dash:hover { background:#5e98f7; } - - /* Metric top stripe */ - .clu-metric { position:relative;overflow:hidden; } - .clu-metric::before { content:"";position:absolute;top:0;left:0;right:0;height:2px;background:#4f8ef7; } - .clu-metric-success::before { background:#3ecf8e; } - .clu-metric-warning::before { background:#f5a623; } - .clu-metric-danger::before { background:#f05252; } - .clu-metric-neutral::before { background:#5c5c70; } - - /* Nav active bar */ - .clu-nav-active { position:relative; } - .clu-nav-active::before { - content:"";position:absolute;left:-12px;top:6px;bottom:6px; - width:2px;border-radius:2px;background:#4f8ef7; - } - - /* Row actions */ - .clu-row-actions { display:flex;gap:4px;justify-content:flex-end;opacity:0;transition:opacity .15s; } - tr:hover .clu-row-actions { opacity:1; } - - /* Plan box overlay */ - .clu-plan-box { position:relative;overflow:hidden; } - .clu-plan-box::before { - content:"";position:absolute;inset:0;pointer-events:none; - background:radial-gradient(120% 80% at 0% 0%,rgba(79,142,247,.10),transparent 60%); - } - - /* Chart axis */ - .clu-chart-axis { font-family:var(--font-mono);font-size:9.5px;fill:#5c5c70; } - - /* ── Stepper ── */ - .clu-stepper { display:flex;align-items:center;gap:10px;margin-top:28px;font-family:var(--font-mono);font-size:11px;color:#44445a; } - .clu-step { display:flex;align-items:center;gap:7px; } - .clu-step-num { width:20px;height:20px;border-radius:50%;border:1px solid rgba(255,255,255,.06);display:grid;place-items:center;font-size:10.5px;color:#8888a0;background:#101019; } - .clu-step.active .clu-step-num { border-color:#4f8ef7;color:#4f8ef7;background:rgba(79,142,247,.12); } - .clu-step.done .clu-step-num { border-color:#3ecf8e;color:#0a0a0f;background:#3ecf8e; } - .clu-step-label { font-size:11px;color:#8888a0;text-transform:uppercase;letter-spacing:.06em;font-weight:500; } - .clu-step.active .clu-step-label { color:#f0f0f5; } - .clu-step-line { flex:1;height:1px;background:rgba(255,255,255,.06);min-width:14px;max-width:28px; } - - /* ── Two-column field row ── */ - .clu-field-row { display:grid;grid-template-columns:1fr 1fr;gap:10px; } - - /* ── Input suffix ── */ - .clu-input.with-suffix { padding-right:0; } - .clu-input-suffix { height:100%;display:flex;align-items:center;padding:0 12px;border-left:1px solid rgba(255,255,255,.06);color:#8888a0;font-family:var(--font-mono);font-size:11.5px;background:rgba(255,255,255,.015);border-top-right-radius:8px;border-bottom-right-radius:8px; } - - /* ── Input valid checkmark ── */ - .clu-input-check { color:#3ecf8e;display:none;flex:none; } - .clu-input.valid .clu-input-check { display:inline-flex; } - - /* ── Slug preview ── */ - .clu-slug-preview { font-family:var(--font-mono);font-size:11px;color:#8888a0;margin-top:4px; } - .clu-slug-preview b { color:#4f8ef7;font-weight:500; } - - /* ── Password strength meter ── */ - .clu-pw-meter { display:flex;gap:4px;margin-top:6px; } - .clu-pw-meter span { flex:1;height:3px;border-radius:2px;background:#16161e;transition:background .2s; } - .clu-pw-meter.s1 span:nth-child(-n+1) { background:#f05252; } - .clu-pw-meter.s2 span:nth-child(-n+2) { background:#f5a623; } - .clu-pw-meter.s3 span:nth-child(-n+3) { background:#4f8ef7; } - .clu-pw-meter.s4 span { background:#3ecf8e; } - .clu-pw-rules { display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:6px;font-family:var(--font-mono);font-size:10.5px;color:#5c5c70; } - .clu-pw-rule { display:inline-flex;align-items:center;gap:5px; } - .clu-pw-rule .clu-pw-bullet { width:5px;height:5px;border-radius:50%;background:#5c5c70;transition:background .15s; } - .clu-pw-rule.ok { color:#3ecf8e; } - .clu-pw-rule.ok .clu-pw-bullet { background:#3ecf8e; } - - /* ── Checkbox ── */ - .clu-checkbox { display:flex;align-items:flex-start;gap:10px;font-size:12.5px;color:#8888a0;cursor:pointer;user-select:none;line-height:1.45; } - .clu-checkbox .clu-check-box { width:15px;height:15px;border-radius:4px;border:1px solid rgba(255,255,255,.10);background:#101019;display:grid;place-items:center;transition:all .15s;flex:none;margin-top:1px; } - .clu-checkbox input:checked ~ .clu-check-box { background:#4f8ef7;border-color:#4f8ef7; } - .clu-checkbox input:checked ~ .clu-check-box svg { opacity:1;transform:scale(1); } - .clu-checkbox .clu-check-box svg { opacity:0;transform:scale(.6);transition:all .15s;color:#fff; } - .clu-checkbox:hover { color:#f0f0f5; } - - /* ── Trial card (signup right panel) ── */ - .clu-trial-card { background:#16161e;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:18px 20px;position:relative;overflow:hidden; } - .clu-trial-card::before { content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,#4f8ef7,#7c5cff); } - .clu-trial-row { display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px; } - .clu-trial-tag { font-family:var(--font-mono);font-size:10.5px;color:#4f8ef7;background:rgba(79,142,247,.12);padding:3px 8px;border-radius:5px;border:1px solid rgba(79,142,247,.25);font-weight:500;text-transform:uppercase;letter-spacing:.05em; } - .clu-trial-count { font-family:var(--font-mono);font-size:11px;color:#8888a0;display:inline-flex;align-items:center;gap:6px; } - .clu-trial-title { font-size:22px;font-weight:600;letter-spacing:-.015em;line-height:1.25;margin-top:12px; } - .clu-trial-title em { font-style:normal;background:linear-gradient(90deg,#4f8ef7,#a78bfa);-webkit-background-clip:text;background-clip:text;color:transparent;font-weight:700; } - .clu-trial-sub { color:#8888a0;font-size:13px;margin-top:6px; } - - /* ── Benefit list ── */ - .clu-benefit-list { list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:14px; } - .clu-benefit { display:flex;gap:12px;align-items:flex-start; } - .clu-benefit-ico { width:30px;height:30px;border-radius:8px;background:rgba(79,142,247,.12);border:1px solid rgba(79,142,247,.22);color:#4f8ef7;display:grid;place-items:center;flex:none; } - .clu-benefit-ico.success { background:rgba(62,207,142,.12);border-color:rgba(62,207,142,.22);color:#3ecf8e; } - .clu-benefit-ico.purple { background:rgba(124,92,255,.12);border-color:rgba(124,92,255,.25);color:#a78bfa; } - .clu-benefit-ico.warn { background:rgba(245,166,35,.10);border-color:rgba(245,166,35,.22);color:#f5a623; } - .clu-benefit-title { font-size:13.5px;font-weight:500;color:#f0f0f5; } - .clu-benefit-desc { font-size:12.5px;color:#8888a0;margin-top:2px;line-height:1.5; } - - /* ── Quote card ── */ - .clu-quote-card { background:#16161e;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:18px 20px;margin-top:auto; } - .clu-quote-text { font-size:13.5px;color:#dcdce8;line-height:1.55; } - .clu-quote-text::before { content:"\201E";color:#4f8ef7;font-size:24px;line-height:0;margin-right:4px;vertical-align:-6px;font-weight:600; } - .clu-quote-text::after { content:"\201D";color:#4f8ef7;font-weight:600; } - .clu-quote-author { display:flex;align-items:center;gap:10px;margin-top:12px; } - .clu-avatar { width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#3ecf8e,#2a9d6f);display:grid;place-items:center;color:#0a0a0f;font-weight:600;font-size:12px;font-family:var(--font-mono); } - .clu-author-name { font-size:12px;color:#f0f0f5;font-weight:500; } - .clu-author-role { font-size:11px;color:#8888a0;font-family:var(--font-mono); } - - /* ── Logo strip ── */ - .clu-logos { display:flex;align-items:center;gap:20px;flex-wrap:wrap;opacity:.55;font-family:var(--font-mono);font-size:11px;color:#8888a0;text-transform:uppercase;letter-spacing:.08em; } - .clu-logo-pill { padding:5px 10px;border:1px solid rgba(255,255,255,.06);border-radius:5px;background:#101019; } - - /* Login page responsive grid */ - .clu-login-grid { - display:grid; - grid-template-columns:minmax(420px,1fr) 1fr; - min-height:100vh; - min-height:100dvh; - position:relative; - z-index:10; - } - .clu-form-side { padding:32px 48px; } - .clu-sso-row { display:grid;grid-template-columns:1fr 1fr;gap:8px; } - @media(max-width:1080px){ - .clu-login-grid { grid-template-columns:1fr; } - .clu-live-side { display:none !important; } - .clu-form-side { padding:28px 36px; } - } - @media(max-width:640px){ - .clu-form-side { padding:20px; } - .clu-sso-row { grid-template-columns:1fr; } - .clu-login-status { display:none !important; } - .clu-field-row { grid-template-columns:1fr; } - .clu-step-label { display:none; } - } - @media(max-width:390px){ - .clu-form-side { padding:16px; } + /* Pills */ + .clu-pill { + display: inline-flex; align-items: center; gap: 5px; + padding: 2px 8px; + font-size: 10.5px; + font-family: var(--font-mono); + font-weight: 600; + border-radius: 999px; + border: 1px solid currentColor; } + .clu-pill.success { color: var(--color-success); background: var(--color-success-soft); border-color: rgba(21,160,106,0.28); } + .clu-pill.warning { color: var(--color-warning); background: var(--color-warning-soft); border-color: rgba(194,130,15,0.28); } + .clu-pill.danger { color: var(--color-danger); background: var(--color-danger-soft); border-color: rgba(219,59,59,0.28); } + .clu-pill.accent { color: var(--color-accent); background: var(--color-accent-soft); border-color: rgba(59,111,242,0.28); } + .clu-pill.muted { color: var(--color-muted); background: rgba(0,0,0,0.04); border-color: var(--color-hairline); } } diff --git a/app/resources/views/layouts/login.blade.php b/app/resources/views/layouts/login.blade.php index d5c9c94..3e3a106 100644 --- a/app/resources/views/layouts/login.blade.php +++ b/app/resources/views/layouts/login.blade.php @@ -4,15 +4,16 @@ - Anmelden · {{ config('app.name', 'CluPilot') }} + {{ $title ?? 'Anmelden' }} · {{ config('app.name', 'CluPilot') }} @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles - + {{ $slot }} @livewireScripts + @stack('scripts') diff --git a/app/resources/views/livewire/pages/auth/login.blade.php b/app/resources/views/livewire/pages/auth/login.blade.php index afe6250..df1426a 100644 --- a/app/resources/views/livewire/pages/auth/login.blade.php +++ b/app/resources/views/livewire/pages/auth/login.blade.php @@ -5,8 +5,7 @@ use Illuminate\Support\Facades\Session; use Livewire\Attributes\Layout; use Livewire\Volt\Component; -new #[Layout('layouts.login')] class extends Component -{ +new #[Layout('layouts.login')] class extends Component { public LoginForm $form; public function login(): void @@ -18,249 +17,259 @@ new #[Layout('layouts.login')] class extends Component } }; ?> -
+
- {{-- ===== LEFT: FORM ===== --}} -
+ {{-- ============ LEFT: FORM ============ --}} +
- {{-- Top row --}} -
-
- -
- CluPilotv2.4 -
-
- -
- - {{-- Form wrap --}} -
- -
-

Willkommen zurück

-

- Bei CluPilot anmelden oder - @if(Route::has('register')) - neues Konto erstellen - @endif -

-
- - {{-- SSO --}} -
- - -
- -
oder mit E-Mail
- - {{-- Static dev hint --}} -
- - Testinstanz — admin@clupilot.test / clupilot -
- - {{-- Session status --}} - - - {{-- Form --}} -
- - {{-- Email --}} -
- -
- - -
- + {{-- Top row: brand + status chip --}} +
+
+ +
+ CluPilotv2.4 +
+
+ Alle Systeme aktiv
- {{-- Password --}} -
- -
- - - -
- + {{-- Form wrap --}} +
+ +
+

Willkommen zurück

+

+ Bei CluPilot anmelden oder + @if (Route::has('register')) + neues Konto erstellen + @endif +

+
+ + {{-- SSO --}} +
+ + +
+ +
oder mit E-Mail
+ + {{-- Session status (Laravel default partial) --}} + + + {{-- Form --}} + + + {{-- Email --}} +
+ +
+ + +
+ +
+ + {{-- Password --}} +
+ +
+ + + +
+ +
+ + {{-- Remember --}} +
+ +
+ + {{-- Submit --}} + + + {{-- Alt options --}} +
+ + +
+
- {{-- Remember (CSS peer toggle) --}} -
- + {{-- Footer --}} + +
+ + {{-- ============ RIGHT: LIVE PANEL ============ --}} +
- - {{-- ===== RIGHT: LIVE PANEL ===== --}} - + + {{-- Terminal type-in script --}} + @push('scripts') + + @endpush
diff --git a/app/tests/Feature/Auth/LoginPageTest.php b/app/tests/Feature/Auth/LoginPageTest.php new file mode 100644 index 0000000..8e89c1b --- /dev/null +++ b/app/tests/Feature/Auth/LoginPageTest.php @@ -0,0 +1,62 @@ +assertStatus(200) + ->assertSeeText('Willkommen zurück') + ->assertSeeText('Anmelden') + ->assertSeeText('Magic-Link per E-Mail senden') + ->assertSeeText('Mit Passkey anmelden') + ->assertSeeText('Alle Systeme aktiv') + ->assertSeeText('Verwalte') + ->assertSeeText('WordPress-Sites') + ->assertSee('clu-glass', false) + ->assertSee('clu-brand-mark', false) + ->assertSee('clu-term-card', false) + ->assertSee('clu-stat', false); +}); + +it('mounts the Volt component without errors', function () { + livewire('pages.auth.login') + ->assertSet('form.email', '') + ->assertSet('form.password', '') + ->assertSet('form.remember', false); +}); + +it('rejects empty credentials', function () { + livewire('pages.auth.login') + ->set('form.email', '') + ->set('form.password', '') + ->call('login') + ->assertHasErrors(['form.email', 'form.password']); +}); + +it('authenticates a user with valid credentials', function () { + $user = User::factory()->create(['password' => bcrypt('hunter2-secret')]); + + livewire('pages.auth.login') + ->set('form.email', $user->email) + ->set('form.password', 'hunter2-secret') + ->call('login') + ->assertHasNoErrors() + ->assertRedirect(route('dashboard', absolute: false)); + + expect(auth()->check())->toBeTrue(); +}); + +it('rejects an invalid password', function () { + $user = User::factory()->create(['password' => bcrypt('correct-password')]); + + livewire('pages.auth.login') + ->set('form.email', $user->email) + ->set('form.password', 'wrong-password') + ->call('login') + ->assertHasErrors('form.email'); + + expect(auth()->check())->toBeFalse(); +}); diff --git a/app/tests/Pest.php b/app/tests/Pest.php new file mode 100644 index 0000000..4e4a744 --- /dev/null +++ b/app/tests/Pest.php @@ -0,0 +1,6 @@ +in('Feature'); +uses(Tests\TestCase::class)->in('Unit'); diff --git a/templates/akutell/Backups.html b/templates/akutell/Backups.html new file mode 100644 index 0000000..48a05fc --- /dev/null +++ b/templates/akutell/Backups.html @@ -0,0 +1,177 @@ + + + + + +CluPilot — Backups + + + + + + +
+ + +
+
+
+
Backups
+
automatisch · tägl. 03:00 utc
+
+ alle gesichert +
+ + +
+ +
+
+
+
Letztes Backup
+
12min
+
erfolgreich · 87/87 sites
+
+
+
Speicher belegt
+
1,84/ 4 TB
+
46% object storage
+
+
+
Wiederherstellungspunkte
+
2.310
+
30 tage aufbewahrung
+
+
+
Fehlgeschlagen
+
1
+
staging.opencrate · retry geplant
+
+
+ +
+ +
+
+

Letzte Backups

+
+
+
+ + + +
SiteTypGrößeZeitpunktStatus
+
+
+ + +
+
+

Zeitplan

+
+
+
Tägliche Backups
jeden Tag um 03:00 UTC
+
+
+
+
Stündliche Snapshots
nur Shop- & High-Traffic-Sites
+
+
+
+
Off-site Replikation
zweites Rechenzentrum (Helsinki)
+
+
+
+
Vor Updates sichern
automatischer Snapshot vor jedem Update
+
+
+
+
+ +
+

Speicher

eu-central · s3
+
+
+
1,84 TB belegt4 TB inkl.
+
+
Voller Backup-Satz1,21 TB
+
Inkrementell (30 t)0,63 TB
+
Komprimierungzstd · 3,1×
+
+
+
+
+
+ +
CluPilot · backups verschlüsselt · aes-256-gcm
+
+
+
+ + + + diff --git a/templates/akutell/CluPilot Dashboard Glass.html b/templates/akutell/CluPilot Dashboard Glass.html new file mode 100644 index 0000000..ceb1729 --- /dev/null +++ b/templates/akutell/CluPilot Dashboard Glass.html @@ -0,0 +1,645 @@ + + + + + +CluPilot — Dashboard + + + + + + +
+ + + + +
+ +
+ Dashboard + Alle Systeme aktiv +
+ + + + + Cluster anlegen + +
+ +
+ + +
+
+
+
Sites Online
+
+ +
+
+
87/ 87
+
100% Verfügbarkeit · 24 h
+
+ +
+
+
Ø Response
+
+ +
+
+
142ms
+
↓ 8,4% vs. letzte Woche
+
+ +
+
+
Updates offen
+
+ +
+
+
24
+
7 kritisch · 17 minor
+
+ +
+
+
Security Events
+
+ +
+
+
128
+
↑ 23% Angriffe blockiert
+
+
+ + +
+ +
+
+

+ + Traffic + live +

+
+ + + + +
+
+
+
+
+
14,2kreq/min
+
Peak vor 12 min · 16,8k req/min
+
+
+ Requests + Cache hit +
+
+ + + + + + + + + + + + + + + + + 20k + 15k + 10k + + + + + + -60m + -30m + jetzt + +
+
+ + +
+
+

+ + Aktivität + live +

+ 30 min +
+
+
+
+ + +
+
+

+ + WordPress-Sites + 8 von 87 +

+
+ + +
+
+
+ + + + + + + + + + + + +
SiteServerWordPressBesucher / 24hStatus
+
+
+ +
CluPilot · region eu-central-1 · uptime 99,984%
+ +
+
+
+ + + + diff --git a/templates/akutell/CluPilot Login Glass.html b/templates/akutell/CluPilot Login Glass.html new file mode 100644 index 0000000..c417a7a --- /dev/null +++ b/templates/akutell/CluPilot Login Glass.html @@ -0,0 +1,405 @@ + + + + + +Anmelden · CluPilot + + + + + + +
+ + +
+
+
+ +
CluPilotv2.4
+
+ Alle Systeme aktiv +
+ +
+
+

Willkommen zurück

+

Bei CluPilot anmelden oder neues Konto erstellen

+
+ +
+ + +
+ +
oder mit E-Mail
+ +
+
+ +
+ + +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + + +
+ + +
+
+
+ + +
+ + + +
+ + + + diff --git a/templates/akutell/CluPilot Signup Glass.html b/templates/akutell/CluPilot Signup Glass.html new file mode 100644 index 0000000..45d49e2 --- /dev/null +++ b/templates/akutell/CluPilot Signup Glass.html @@ -0,0 +1,474 @@ + + + + + +Konto erstellen · CluPilot + + + + + + +
+ + +
+
+
+ +
CluPilotv2.4
+
+ +
+ +
+
+
1
+
Konto
+
+
+
+
2
+
Workspace
+
+
+
+
3
+
Erster Server
+
+
+ +
+
+

14 Tage kostenlos testen

+

Keine Kreditkarte. Volle Funktionen. Jederzeit kündbar.

+
+ +
+ + +
+ +
oder mit E-Mail
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + + .clupilot.io +
+
Erreichbar unter dein-team.clupilot.io
+
+ +
+ +
+ + + +
+
+
+ 10+ Zeichen + Großbuchstabe + Zahl + Sonderzeichen +
+
+ + + + +
+
+ + +
+ + + +
+ + + + diff --git a/templates/akutell/CluPilot Signup Light.html b/templates/akutell/CluPilot Signup Light.html new file mode 100644 index 0000000..a23baf0 --- /dev/null +++ b/templates/akutell/CluPilot Signup Light.html @@ -0,0 +1,477 @@ + + + + + +Konto erstellen · CluPilot + + + + + + +
+ + +
+
+
+ +
CluPilotv2.4
+
+ +
+ +
+
+
1
+
Konto
+
+
+
+
2
+
Workspace
+
+
+
+
3
+
Erster Server
+
+
+ +
+
+

14 Tage kostenlos testen

+

Keine Kreditkarte. Volle Funktionen. Jederzeit kündbar.

+
+ +
+ + +
+ +
oder mit E-Mail
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + + .clupilot.io +
+
Erreichbar unter dein-team.clupilot.io
+
+ +
+ +
+ + + +
+
+
+ 10+ Zeichen + Großbuchstabe + Zahl + Sonderzeichen +
+
+ + + + +
+
+ + +
+ + + +
+ + + + diff --git a/templates/akutell/Cluster anlegen.html b/templates/akutell/Cluster anlegen.html new file mode 100644 index 0000000..cc4c618 --- /dev/null +++ b/templates/akutell/Cluster anlegen.html @@ -0,0 +1,338 @@ + + + + + +CluPilot — Cluster anlegen + + + + + + + +
+ + +
+
+ + + +
+
Cluster anlegen
+
neuer server-cluster · acme
+
+
+
+
1Basis
+ +
2Region & Hardware
+ +
3WordPress
+ +
4Überprüfen
+
+
+ +
+
+ +
+ +
+
+

Basis-Konfiguration

schritt 1 / 4
+
+
+ + + Wird in Listen, SSH und im Audit-Log angezeigt. +
+
+ +
.clupilot.net
+
+
+ +
+
Produktion
live · monitoring
+
Staging
test · isoliert
+
Entwicklung
sandbox
+
+
+
+ + +
+
+
+
+ + +
+
+

Region

schritt 2 / 4
+
+
+
🇩🇪 Frankfurt
de-fra · 4 ms
+
🇳🇱 Amsterdam
nl-ams · 11 ms
+
🇫🇮 Helsinki
fi-hel · 24 ms
+
🇺🇸 Ashburn
us-east · 92 ms
+
+
+
+
+

Server-Größe

+
+
+
Starter
2 vCPU · 8 GB · 160 GB NVMe · ~15 sites
29 €/mo
+
Standard beliebt
4 vCPU · 16 GB · 320 GB NVMe · ~40 sites
59 €/mo
+
Performance
8 vCPU · 32 GB · 640 GB NVMe · ~90 sites
119 €/mo
+
Dedicated
16 vCPU · 64 GB · 1,2 TB NVMe · ~200 sites
249 €/mo
+
+
+ +
+ + 1 +
+ Ab 2 Nodes wird automatisch ein Load-Balancer & Failover eingerichtet. +
+
+
+
+ + +
+
+

WordPress-Voreinstellungen

schritt 3 / 4
+
+
+
+
+
8.1
+
8.2
+
8.3
+
+
+
+
+
+
Automatische Updates
core- & sichere plugin-updates
+
SSL automatisch (Let's Encrypt)
zertifikate ausstellen & erneuern
+
Edge-CDN & Cache
globaler cache für statische inhalte
+
Tägliche Backups
03:00 utc · 30 tage aufbewahrung
+
WAF & Brute-Force-Schutz
managed firewall-regelsatz
+
+
+
+
+ + +
+
+

Überprüfen & anlegen

schritt 4 / 4
+
+
Cluster-Nameeu-prod-04
+
Hostnameeu-prod-04.clupilot.net
+
UmgebungProduktion
+
RegionFrankfurt · de-fra
+
Server-GrößeStandard
+
Nodes1
+
PHP / WordPress8.2 · WP 6.5.2
+
Funktionen5 aktiv
+
+
+
+
Die Bereitstellung dauert i. d. R. 2–4 Minuten. Du wirst benachrichtigt, sobald der Cluster bereit ist.
+
+
+
+ + +
+ + +
+
+ + +
+

Zusammenfassung

+
+
Nameeu-prod-04
+
UmgebungProduktion
+
RegionFrankfurt
+
GrößeStandard
+
Nodes
+
+
+
+
+
Geschätzt / Monat
+
59/mo
+
+ inkl. backup +
+
Abgerechnet pro Stunde · jederzeit kündbar. Preis exkl. MwSt.
+
+
+
+ +
CluPilot · bereitstellung über terraform · region eu-central-1
+
+
+
+ + + + diff --git a/templates/akutell/Einstellungen.html b/templates/akutell/Einstellungen.html new file mode 100644 index 0000000..e61f5ea --- /dev/null +++ b/templates/akutell/Einstellungen.html @@ -0,0 +1,139 @@ + + + + + +CluPilot — Einstellungen + + + + + + + +
+ + +
+
+
+
Einstellungen
+
workspace · acme cluster
+
+
+ + +
+ +
+
+ + + + +
+
+

Workspace-Details

+
+
+
+
+
clupilot.io/
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+

Standard-Verhalten

gilt für neue sites
+
+
Automatische WordPress-Core-Updates
minor-versionen automatisch einspielen
+
Plugin-Updates automatisch
nur als sicher markierte plugins
+
Staging vor Deploy erzwingen
änderungen erst auf staging testen
+
CDN für neue Sites aktivieren
global edge cache standardmäßig an
+
+
+ +
+

Abrechnung

nächste rechnung 01.06.2026
+
+
Aktueller PlanTeam · PRO
+
Sites-Kontingent87 / 140
+
Monatlich249,00 €
+
ZahlungsmethodeVISA · **** 4072
+
+
+
+ +
+

Gefahrenzone

+
+
Workspace löschen
Alle Sites, Backups und Daten werden unwiderruflich entfernt.
+ +
+
+
+
+ +
CluPilot · änderungen werden im audit-log protokolliert
+
+
+
+ + + + diff --git a/templates/akutell/Server.html b/templates/akutell/Server.html new file mode 100644 index 0000000..d66a52c --- /dev/null +++ b/templates/akutell/Server.html @@ -0,0 +1,204 @@ + + + + + +CluPilot — Server + + + + + + +
+ + + + +
+
+
+
Server
+
6 nodes · 3 regionen
+
+ 5 aktiv · 1 wartung +
+ + + + Cluster anlegen + +
+ +
+ +
+
+
+
Aktive Nodes
+
+
+
5/ 6
+
99,98% uptime · 30 d
+
+
+
+
Ø CPU-Last
+
+
+
38%
+
↓ 4,1% vs. gestern
+
+
+
+
RAM gesamt
+
+
+
112/ 192 GB
+
58% belegt
+
+
+
+
Wartung
+
+
+
1
+
eu-staging · kernel-update
+
+
+ + +
+

+ + Alle Server +

+
+
+ + + + +
+ +
+
+ + +
+ +
CluPilot · region eu-central-1 · uptime 99,984%
+
+
+
+ + + + diff --git a/templates/akutell/Sicherheit.html b/templates/akutell/Sicherheit.html new file mode 100644 index 0000000..ac950c9 --- /dev/null +++ b/templates/akutell/Sicherheit.html @@ -0,0 +1,179 @@ + + + + + +CluPilot — Sicherheit + + + + + + +
+ + +
+
+
+
Sicherheit
+
waf aktiv · 3 hinweise
+
+ 3 offene hinweise +
+ + +
+ +
+
+
+
Angriffe blockiert
+
128
+
↑ 23% letzte 24 h
+
+
+
Firewall-Regeln
+
342
+
aktiv · managed ruleset
+
+
+
SSL-Zertifikate
+
85/ 87
+
2 laufen ab in < 14 t
+
+
+
2FA-Abdeckung
+
79%
+
3 user ohne 2FA
+
+
+ + +
+
+

Offene Hinweise 3

+ +
+
+
+ +
+ +
+
+

Bedrohungs-Feed live

+ 24 h +
+
+
+ + +
+

SSL-Zertifikate

auto-renew
+
+ + + +
DomainAusstellerLäuft ab
+
+
+
+ +
CluPilot · waf managed ruleset v2026.05 · ddos-schutz aktiv
+
+
+
+ + + + diff --git a/templates/akutell/Site Details.html b/templates/akutell/Site Details.html new file mode 100644 index 0000000..5db51e4 --- /dev/null +++ b/templates/akutell/Site Details.html @@ -0,0 +1,391 @@ + + + + + +CluPilot — Site Details + + + + + + + +
+ + +
+ +
+ + + +
+
S
+
+

shop.bergmann-coffee.com

+
eu-prod-01· wp 6.4.3· php 8.1· ssl ✓
+
+
+ online +
+ + +
+ +
+ +
+ + + + + + +
+ + +
+
+
Besucher / 24h
48,7k
↑ 9,2% vs. gestern
+
Speicher
14,2/ 40 GB
36% belegt
+
PageSpeed
92/ 100
core web vitals ✓
+
Updates
3
1 core · 2 plugins
+
+
+
+

Installation

+
+
Primäre Domainshop.bergmann-coffee.com
+
Dokument-Root/var/www/bergmann/htdocs
+
WordPress6.4.3
+
PHP8.1.27 · FPM
+
DatenbankMariaDB 10.11 · 312 MB
+
SSLLet's Encrypt · 54 t
+
Aktives ThemeStorefront Child
+
+
+
+

Schnellaktionen

+
+
Wartungsmodus
site für besucher sperren
+
Objekt-Cache (Redis)
datenbankabfragen zwischenspeichern
+
Edge-CDN
statische inhalte global cachen
+
HTTPS erzwingen
alle anfragen auf ssl umleiten
+
+
+
+
+ + +
+
+

wp-config.php

/htdocs/wp-config.php
+
+
+
+
MB
+
MB
+
sek
+
+
+
+
+
WP_DEBUG
debug-modus & fehlerprotokoll
+
WP_CACHE
persistentes caching aktiv
+
DISALLOW_FILE_EDIT
datei-editor im wp-admin sperren
+
AUTOMATIC_UPDATER_DISABLED
automatische core-updates deaktiviert
+
FORCE_SSL_ADMIN
wp-admin nur über https
+
+
+
+
+ +
+
+

Datenbank

verbunden
+
+
+
+
+
anzeigen
+
+
+
+

Sicherheits-Schlüssel (Salts)

+
+

AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY & zugehörige Salts. Neu generieren meldet alle Benutzer ab.

+
Zuletzt rotiert14.03.2026
+
Schlüssel-Länge64 zeichen
+
Statusstark
+
+
+
+
+ +
+

Allgemeine WordPress-Einstellungen

+
+
+
+
+
+
+
+
+
Suchmaschinen-Sichtbarkeit
von google & co. indexieren lassen
+
Registrierung erlauben
neue benutzer können sich registrieren
+
+
+
+
+
+ + +
+
+

php.ini

php 8.1 · fpm pool: bergmann
+
+
+
M
+
sek
+
M
+
M
+
+
sek
+
+
+
+
+
display_errors
php-fehler im browser anzeigen (nur dev)
+
opcache.enable
bytecode-cache aktiv
+
log_errors
fehler in error.log schreiben
+
allow_url_fopen
externe urls als datei öffnen
+
+
+
+
+ +
+
+

OPcache

aktiv
+
+
Trefferquote99,2%
+
Speicher142 / 256 MB
+
Zwischengesp. Skripte4.218
+
opcache.revalidate_freq60 s
+
+
+
+
+

Aktive Erweiterungen

28 module
+
+ curlgdimagickintlmbstringmysqliopcacheredissoapzipbcmathexif+ 16 +
+
+
+
+ + +
+
+

Datei-Manager

+
+ + +
+
+
+
+
+ /var/www/bergmann + +
+
+
+
+
+ + Wähle eine Datei aus dem Baum + +
+ +
+ +
Klicke links auf eine Datei,
um sie hier zu bearbeiten.
+
+
+ bereit +
+ + +
+
+
+
+
+ + +
+
+

Cronjobs

+ +
+ + + + +
+

Geplante Aufgaben 5

system + wp-cron
+
+ + + +
NameZeitplanBefehlLetzter LaufStatus
+
+
+
+ + +
+
+

Logs

+
+
+ + + + +
+ + +
+
+
+

access.log

live
+
+
+
+ +
CluPilot · shop.bergmann-coffee.com · region eu-central-1
+
+
+
+ + + + diff --git a/templates/akutell/Sites.html b/templates/akutell/Sites.html new file mode 100644 index 0000000..26ec384 --- /dev/null +++ b/templates/akutell/Sites.html @@ -0,0 +1,187 @@ + + + + + +CluPilot — Sites + + + + + + +
+ + +
+
+
+
Sites
+
87 wordpress-installationen
+
+ 87 online +
+ + +
+ +
+
+
+
Sites Online
+
87/ 87
+
100% erreichbar
+
+
+
Updates offen
+
24
+
7 kritisch · 17 minor
+
+
+
Ø PageSpeed
+
94/ 100
+
↑ 3 core web vitals
+
+
+
Besucher / 24h
+
182k
+
↑ 12% vs. vorwoche
+
+
+ +
+
+
+
+ + + + +
+
+
+ + +
+
+
+ + + + + + + + + + + + + +
SiteServerWordPressPHPBesucher / 24hStatus
+
+
+ 1–15 von 87 +
+ + +
+
+
+ +
CluPilot · region eu-central-1 · uptime 99,984%
+
+
+
+ + + + diff --git a/templates/akutell/Team.html b/templates/akutell/Team.html new file mode 100644 index 0000000..8db698f --- /dev/null +++ b/templates/akutell/Team.html @@ -0,0 +1,176 @@ + + + + + +CluPilot — Team + + + + + + +
+ + +
+
+
+
Team
+
14 mitglieder · 2 einladungen offen
+
+ 6 gerade aktiv +
+ + +
+ +
+
+
+
Mitglieder
+
14/ 20
+
6 sitze frei im Team-Plan
+
+
+
Admins
+
3
+
voller zugriff
+
+
+
Einladungen offen
+
2
+
ausstehend · > 2 tage
+
+
+
2FA aktiv
+
11/ 14
+
3 ohne 2FA
+
+
+ +
+
+

Mitglieder

+
+
+
+ + + +
MitgliedRolleZugriff2FAZuletzt aktiv
+
+
+ +
+

Offene Einladungen

+
+ + + +
E-MailRolleEingeladen vonGesendet
+
+
+ +
CluPilot · rollenbasierte zugriffskontrolle · audit-log aktiv
+
+
+
+ + + + diff --git a/templates/akutell/sitedetails.js b/templates/akutell/sitedetails.js new file mode 100644 index 0000000..23f2c70 --- /dev/null +++ b/templates/akutell/sitedetails.js @@ -0,0 +1,430 @@ +/* =================================================================== + CluPilot — Site Details (Datei-Manager, Editor, Cronjobs, Logs) +=================================================================== */ +(function(){ +'use strict'; + +/* ---------- generic toggles ---------- */ +document.addEventListener('click', e=>{ + const t = e.target.closest('.toggle'); + if(t && !t.closest('[data-toggle]')) t.classList.toggle('on'); +}); + +/* ---------- detail tab switching ---------- */ +const tabs = document.querySelectorAll('#dtabs .dtab'); +const panes = { + overview:'tab-overview', wp:'tab-wp', php:'tab-php', + files:'tab-files', cron:'tab-cron', logs:'tab-logs' +}; +tabs.forEach(tab=>tab.addEventListener('click',()=>{ + tabs.forEach(t=>t.classList.remove('active')); + tab.classList.add('active'); + Object.values(panes).forEach(id=>document.getElementById(id).classList.remove('active')); + document.getElementById(panes[tab.dataset.tab]).classList.add('active'); + if(tab.dataset.tab==='logs') renderLog(currentLog); +})); + +/* ===================================================================== + FILE MANAGER + EDITOR +===================================================================== */ +const fileContents = { +'htdocs/wp-config.php': +` +RewriteEngine On +RewriteBase / +RewriteRule ^index\\.php$ - [L] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule . /index.php [L] + +# END WordPress + +# HTTPS erzwingen +RewriteCond %{HTTPS} off +RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + +# Sicherheits-Header + + Header set X-Content-Type-Options "nosniff" + Header set X-Frame-Options "SAMEORIGIN" + Header set Referrer-Policy "strict-origin-when-cross-origin" + + +# Browser-Caching + + ExpiresActive On + ExpiresByType image/webp "access plus 1 year" + ExpiresByType text/css "access plus 1 month" + +`, +'htdocs/robots.txt': +`User-agent: * +Disallow: /wp-admin/ +Allow: /wp-admin/admin-ajax.php +Disallow: /cart/ +Disallow: /checkout/ +Disallow: /my-account/ + +Sitemap: https://shop.bergmann-coffee.com/sitemap_index.xml +`, +'htdocs/index.php': +` 4 ); + +// Checkout: Telefon optional +add_filter( 'woocommerce_checkout_fields', function ( $fields ) { + $fields['billing']['billing_phone']['required'] = false; + return $fields; +}); + +// Heartbeat im Admin drosseln +add_filter( 'heartbeat_settings', function ( $s ) { $s['interval'] = 60; return $s; }); +`, +'htdocs/wp-content/themes/storefront-child/style.css': +`/* +Theme Name: Storefront Child +Template: storefront +Version: 1.4.2 +*/ +:root{ --bg-accent:#6f4e37; --bg-cream:#f5efe6; } + +.site-header{ background:var(--bg-cream); border-bottom:3px solid var(--bg-accent); } +.button.alt{ background:var(--bg-accent); } +.price{ font-family:"JetBrains Mono", monospace; font-weight:600; } +`, +'php.ini': +`; php.ini — pool: bergmann (php 8.1-fpm) +memory_limit = 256M +max_execution_time = 120 +max_input_time = 120 +max_input_vars = 3000 +upload_max_filesize = 128M +post_max_size = 128M +default_charset = "UTF-8" +date.timezone = Europe/Berlin + +display_errors = Off +log_errors = On +error_log = /var/www/bergmann/logs/php-error.log +allow_url_fopen = On + +[opcache] +opcache.enable = 1 +opcache.memory_consumption = 256 +opcache.max_accelerated_files = 10000 +opcache.revalidate_freq = 60 +`, +'logs/access.log': +`192.0.2.41 - - [31/May/2026:09:12:04] "GET /shop/ HTTP/2.0" 200 18421 +203.0.113.7 - - [31/May/2026:09:12:05] "GET /wp-json/wc/store/cart HTTP/2.0" 200 842 +192.0.2.41 - - [31/May/2026:09:12:09] "POST /?wc-ajax=add_to_cart HTTP/2.0" 200 311 +`, +}; + +const tree = [ + {name:'htdocs', type:'dir', open:true, children:[ + {name:'wp-admin', type:'dir', children:[]}, + {name:'wp-content', type:'dir', open:true, children:[ + {name:'themes', type:'dir', open:true, children:[ + {name:'storefront-child', type:'dir', open:true, children:[ + {name:'functions.php', type:'file', lang:'PHP'}, + {name:'style.css', type:'file', lang:'CSS'}, + ]}, + ]}, + {name:'plugins', type:'dir', children:[]}, + {name:'uploads', type:'dir', children:[]}, + ]}, + {name:'wp-includes', type:'dir', children:[]}, + {name:'.htaccess', type:'file', lang:'APACHE'}, + {name:'index.php', type:'file', lang:'PHP'}, + {name:'robots.txt', type:'file', lang:'TEXT'}, + {name:'wp-config.php', type:'file', lang:'PHP'}, + ]}, + {name:'php.ini', type:'file', lang:'INI'}, + {name:'logs', type:'dir', open:true, children:[ + {name:'access.log', type:'file', lang:'LOG'}, + {name:'error.log', type:'file', lang:'LOG'}, + ]}, +]; + +const ICO = { + dirClosed:'', + file:'', + chev:'', +}; + +const treeEl = document.getElementById('file-tree'); +function renderTree(nodes, parentPath, container){ + nodes.forEach(node=>{ + const path = parentPath ? parentPath+'/'+node.name : node.name; + const row = document.createElement('div'); + row.className = 'tree-item '+(node.type==='dir'?'dir':'') + (node.open?' open':''); + if(node.type==='dir'){ + row.innerHTML = ICO.chev + ICO.dirClosed + ''+node.name+''; + }else{ + row.innerHTML = '' + ICO.file + ''+node.name+''; + } + container.appendChild(row); + if(node.type==='dir'){ + const kids = document.createElement('div'); + kids.className = 'tree-children'+(node.open?'':' collapsed'); + container.appendChild(kids); + if(node.children && node.children.length){ + renderTree(node.children, path, kids); + }else{ + const empty = document.createElement('div'); + empty.className = 'tree-item'; + empty.style.cssText = 'color:var(--muted-2);font-style:italic;cursor:default'; + empty.innerHTML = 'leer'; + kids.appendChild(empty); + } + row.addEventListener('click',()=>{ + row.classList.toggle('open'); + kids.classList.toggle('collapsed'); + }); + }else{ + row.addEventListener('click',()=>openFile(path, node.lang, row)); + } + }); +} +renderTree(tree, '', treeEl); + +/* ---- editor ---- */ +const codeWrap = document.getElementById('code-wrap'); +const editorEmpty = document.getElementById('editor-empty'); +const gutter = document.getElementById('gutter'); +const codeArea = document.getElementById('code-area'); +const crumb = document.getElementById('crumb'); +const tagline = document.getElementById('editor-tagline'); +const status = document.getElementById('editor-status'); +let currentPath = null, original = ''; + +function openFile(path, lang, row){ + document.querySelectorAll('.tree-item').forEach(r=>r.classList.remove('active')); + if(row) row.classList.add('active'); + const content = fileContents[path] !== undefined ? fileContents[path] + : '# '+path+'\n# (Inhalt wird vom Server geladen…)\n'; + currentPath = path; original = content; + codeArea.value = content; + editorEmpty.style.display = 'none'; + codeWrap.style.display = 'flex'; + const parts = path.split('/'); + crumb.innerHTML = parts.map((p,i)=> i===parts.length-1 ? ''+p+'' : p).join(' / '); + tagline.textContent = lang || ''; + status.textContent = 'bereit'; + buildGutter(); +} +function buildGutter(){ + const lines = codeArea.value.split('\n').length; + let html = ''; + for(let i=1;i<=lines;i++) html += '
'+i+'
'; + gutter.innerHTML = html; + gutter.scrollTop = codeArea.scrollTop; +} +codeArea.addEventListener('input',()=>{ buildGutter(); status.textContent='● ungespeichert'; status.style.color='var(--warning)'; }); +codeArea.addEventListener('scroll',()=>{ gutter.scrollTop = codeArea.scrollTop; }); +document.getElementById('btn-save').addEventListener('click',()=>{ + if(!currentPath) return; + fileContents[currentPath] = codeArea.value; original = codeArea.value; + status.textContent='✓ gespeichert'; status.style.color='var(--success)'; +}); +document.getElementById('btn-revert').addEventListener('click',()=>{ + codeArea.value = original; buildGutter(); + status.textContent='zurückgesetzt'; status.style.color='var(--muted)'; +}); + +/* open wp-config.php by default */ +(function(){ + const rows = [...document.querySelectorAll('.tree-item')]; + const cfg = rows.find(r=>r.textContent.trim()==='wp-config.php'); + if(cfg) openFile('htdocs/wp-config.php','PHP',cfg); +})(); + +/* ===================================================================== + CRONJOBS +===================================================================== */ +const cronBody = document.getElementById('cron-body'); +const cronCount = document.getElementById('cron-count'); +let crons = [ + {name:'WP-Cron Hauptlauf', expr:'*/5 * * * *', cmd:'wp cron event run --due-now', last:'vor 2 min', st:'ok'}, + {name:'WooCommerce Lagerabgleich', expr:'0 * * * *', cmd:'wp wc tool run regenerate_stock', last:'vor 41 min', st:'ok'}, + {name:'Tägliches Backup', expr:'0 3 * * *', cmd:'clupilot backup create --site=bergmann', last:'heute 03:00', st:'ok'}, + {name:'Sitemap neu aufbauen', expr:'0 4 * * *', cmd:'wp yoast index --reindex', last:'heute 04:00', st:'ok'}, + {name:'Transients aufräumen', expr:'0 2 * * 0', cmd:'wp transient delete --expired', last:'vor 3 tagen', st:'warn'}, +]; +const moreIco = ''; +const runIco = ''; +function renderCron(){ + cronBody.innerHTML = ''; + crons.forEach(c=>{ + const st = c.st==='ok'?'ok':c.st==='bad'?'bad':'warn'; + const lbl = c.st==='ok'?'aktiv':c.st==='bad'?'fehler':'übersprungen'; + cronBody.insertAdjacentHTML('beforeend',` + +
${c.name}
+ ${c.expr} + ${c.cmd} + ${c.last} + ${lbl} +
+ `); + }); + cronCount.textContent = crons.length; + document.querySelector('.dtab[data-tab="cron"] .count').textContent = crons.length; +} +renderCron(); + +const cronForm = document.getElementById('cron-form'); +document.getElementById('btn-add-cron').addEventListener('click',()=>{ + cronForm.style.display = cronForm.style.display==='none'?'':'none'; +}); +document.getElementById('cf-cancel').addEventListener('click',()=>{ cronForm.style.display='none'; }); +document.getElementById('cf-preset').addEventListener('change',e=>{ + const v = e.target.value; + if(v!=='custom') document.getElementById('cf-expr').value = v; +}); +document.getElementById('cf-save').addEventListener('click',()=>{ + const name = document.getElementById('cf-name').value.trim() || 'Neuer Cronjob'; + const expr = document.getElementById('cf-expr').value.trim() || '0 3 * * *'; + const cmd = document.getElementById('cf-cmd').value.trim() || '—'; + crons.unshift({name, expr, cmd, last:'noch nicht gelaufen', st:'warn'}); + renderCron(); + cronForm.style.display='none'; + document.getElementById('cf-name').value=''; +}); + +/* ===================================================================== + LOGS +===================================================================== */ +const logData = { + access:[ + ['09:12:04','INFO','192.0.2.41 "GET /shop/" 200 18421 — 142ms'], + ['09:12:05','INFO','203.0.113.7 "GET /wp-json/wc/store/cart" 200 842 — 38ms'], + ['09:12:09','INFO','192.0.2.41 "POST /?wc-ajax=add_to_cart" 200 311 — 96ms'], + ['09:12:14','INFO','198.51.100.2 "GET /produkt/espresso-blend/" 200 21044 — 158ms'], + ['09:12:21','WARN','45.155.* "GET /wp-login.php" 403 — rate-limit'], + ['09:12:33','INFO','192.0.2.88 "GET /warenkorb/" 200 9920 — 71ms'], + ['09:12:40','INFO','Googlebot "GET /sitemap_index.xml" 200 4210 — 22ms'], + ['09:12:48','INFO','192.0.2.41 "POST /checkout/" 302 0 — 210ms'], + ], + error:[ + ['09:05:11','WARN','PHP Notice: Undefined index "shipping_zone" in cart.php:88'], + ['09:07:42','ERROR','Uncaught TypeError: count(): Argument must be array — class-wc-cart.php:412'], + ['09:09:03','INFO','Cache geleert: object-cache (redis) — 18421 keys'], + ['08:58:19','WARN','Slow query (2,1s): wp_bg_postmeta — SELECT meta_value …'], + ], + php:[ + ['09:00:01','OK','php-fpm pool "bergmann" reloaded — 8 worker'], + ['09:03:55','INFO','OPcache: 4218 scripts cached, 99.2% hit rate'], + ['09:08:12','WARN','Worker 3: memory peak 214M / 256M'], + ['09:11:40','INFO','request /checkout/ — 210ms, mem 41M'], + ], + wp:[ + ['09:01:00','DEBUG','WC_Cart::calculate_totals() — 12 items'], + ['09:04:22','DEBUG','do_action("woocommerce_payment_complete", 48211)'], + ['09:06:30','WARN','Plugin "Smart Coupons" deprecated hook: woocommerce_add_to_cart'], + ['09:10:05','DEBUG','wp_mail() → kunde@example.com — Bestellbestätigung #48211'], + ], +}; +const consoleEl = document.getElementById('console'); +let currentLog = 'access'; +function line(t,lvl,msg){ + return `
${t}${lvl}${msg}
`; +} +function renderLog(type){ + currentLog = type; + consoleEl.innerHTML = logData[type].map(l=>line(l[0],l[1],l[2])).join(''); + consoleEl.scrollTop = consoleEl.scrollHeight; + document.getElementById('log-title').textContent = + ({access:'access.log',error:'error.log',php:'php-fpm',wp:'wp-debug'})[type]; +} +document.querySelectorAll('#log-seg button').forEach(b=>b.addEventListener('click',()=>{ + document.querySelectorAll('#log-seg button').forEach(x=>x.classList.remove('active')); + b.classList.add('active'); + renderLog(b.dataset.log); +})); +document.getElementById('log-clear').addEventListener('click',()=>{ logData[currentLog]=[]; renderLog(currentLog); }); + +const followBtn = document.getElementById('log-follow'); +followBtn.addEventListener('click',()=>followBtn.classList.toggle('on')); +const sampleMsgs = { + access:['INFO','"GET /shop/" 200 — 130ms'], + error:['INFO','Cache hit ratio 99.3%'], + php:['INFO','request handled — 88ms'], + wp:['DEBUG','transient set: wc_products_onsale'], +}; +function tick(){ + if(followBtn.classList.contains('on') && document.getElementById('tab-logs').classList.contains('active')){ + const now = new Date(); + const t = String(now.getHours()).padStart(2,'0')+':'+String(now.getMinutes()).padStart(2,'0')+':'+String(now.getSeconds()).padStart(2,'0'); + const s = sampleMsgs[currentLog]; + const ip = currentLog==='access' ? '192.0.2.'+(10+Math.floor(Math.random()*240))+' ' : ''; + logData[currentLog].push([t, s[0], ip+s[1]]); + if(logData[currentLog].length>200) logData[currentLog].shift(); + consoleEl.insertAdjacentHTML('beforeend', line(t,s[0],ip+s[1])); + consoleEl.scrollTop = consoleEl.scrollHeight; + } +} +setInterval(tick, 2600); + +})();