From dcd325dc44f20806d983c92d4fa60fb28ad961d7 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:09:49 +0200 Subject: [PATCH] feat(v5): add Inertia app shell with Flux health Adds v5 routing, middleware, home page rendering, team context sharing, project model/table support, and Flux health reporting. Installs Flux in container builds with role-aware s6 services and documents runtime roles. --- README.md | 25 + app/Http/Controllers/V5/HomeController.php | 44 + app/Http/Kernel.php | 18 + app/Http/Middleware/V5/EnsureCurrentTeam.php | 54 ++ .../Middleware/V5/HandleInertiaRequests.php | 25 + app/Models/V5/Project.php | 29 + app/Models/V5/V5Model.php | 10 + app/Providers/RouteServiceProvider.php | 5 + app/Services/Flux/FluxHealth.php | 66 ++ composer.json | 1 + composer.lock | 111 ++- config/flux.php | 6 + ..._06_04_050157_create_v5_projects_table.php | 33 + database/schema/testing-schema.sql | 11 + docker-compose.dev.yml | 2 + docker-compose.prod.yml | 4 + docker/development/Dockerfile | 27 + .../s6-rc.d/flux/dependencies.d/init-setup | 0 .../etc/s6-overlay/s6-rc.d/flux/run | 49 ++ .../etc/s6-overlay/s6-rc.d/flux/type | 1 + .../etc/s6-overlay/s6-rc.d/horizon/run | 14 + .../s6-overlay/s6-rc.d/nightwatch-agent/run | 14 + .../s6-overlay/s6-rc.d/scheduler-worker/run | 14 + .../s6-overlay/s6-rc.d/user/contents.d/flux | 0 docker/production/Dockerfile | 27 + .../s6-rc.d/flux/dependencies.d/init-script | 0 .../etc/s6-overlay/s6-rc.d/flux/run | 49 ++ .../etc/s6-overlay/s6-rc.d/flux/type | 1 + .../etc/s6-overlay/s6-rc.d/horizon/run | 14 + .../s6-overlay/s6-rc.d/nightwatch-agent/run | 14 + .../s6-overlay/s6-rc.d/scheduler-worker/run | 14 + .../s6-overlay/s6-rc.d/user/contents.d/flux | 0 package-lock.json | 793 ++++++++++++++++-- package.json | 7 +- resources/css/v5/app.css | 11 + resources/js/v5/Pages/Home.jsx | 59 ++ resources/js/v5/app.jsx | 11 + resources/views/v5/app.blade.php | 18 + routes/v5.php | 8 + tests/Feature/V5/HomeTest.php | 214 +++++ vite.config.js | 10 +- 41 files changed, 1705 insertions(+), 108 deletions(-) create mode 100644 app/Http/Controllers/V5/HomeController.php create mode 100644 app/Http/Middleware/V5/EnsureCurrentTeam.php create mode 100644 app/Http/Middleware/V5/HandleInertiaRequests.php create mode 100644 app/Models/V5/Project.php create mode 100644 app/Models/V5/V5Model.php create mode 100644 app/Services/Flux/FluxHealth.php create mode 100644 config/flux.php create mode 100644 database/migrations/2026_06_04_050157_create_v5_projects_table.php create mode 100644 docker/development/etc/s6-overlay/s6-rc.d/flux/dependencies.d/init-setup create mode 100755 docker/development/etc/s6-overlay/s6-rc.d/flux/run create mode 100644 docker/development/etc/s6-overlay/s6-rc.d/flux/type create mode 100644 docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/flux create mode 100644 docker/production/etc/s6-overlay/s6-rc.d/flux/dependencies.d/init-script create mode 100755 docker/production/etc/s6-overlay/s6-rc.d/flux/run create mode 100644 docker/production/etc/s6-overlay/s6-rc.d/flux/type create mode 100644 docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/flux create mode 100644 resources/css/v5/app.css create mode 100644 resources/js/v5/Pages/Home.jsx create mode 100644 resources/js/v5/app.jsx create mode 100644 resources/views/v5/app.blade.php create mode 100644 routes/v5.php create mode 100644 tests/Feature/V5/HomeTest.php diff --git a/README.md b/README.md index b387d87e8..f4e289bcd 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,31 @@ You can find the installation script source [here](./scripts/install.sh). > [!NOTE] > Please refer to the [docs](https://coolify.io/docs/installation) for more information about the installation. + +## Container roles and Flux + +The Coolify image can run different process roles with `COOLIFY_CONTAINER_ROLE`: + +- `all` (default): self-hosted mode; runs the web process, worker services, and Flux when configured. +- `web`: web/API process only; s6 worker services and Flux sleep. +- `worker`: Horizon, Laravel scheduler worker, and optional Nightwatch agent. +- `flux`: Flux only; used by Cloud/HA deployments that scale coold connection routers separately. + +Flux is installed from the coold nightly release into `/usr/local/bin/flux`. Containers running the `all` or `flux` role expose Flux on port `6443` and use these runtime variables: + +```env +COOLIFY_FLUX_GRPC_BIND=0.0.0.0:6443 +COOLIFY_FLUX_UNIX_SOCKET_PATH=/run/coolify/flux.sock +COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH=/var/www/html/storage/app/flux/jwt.priv +COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH=/var/www/html/storage/app/flux/jwt.pub +COOLIFY_FLUX_ALLOW_PUBLIC_BIND=1 +COOLIFY_FLUX_ID=flux-eu-1 +COOLIFY_FLUX_PUBLIC_URL=grpcs://flux-eu-1.example.com:6443 +COOLIFY_FLUX_INTERNAL_URL=http://coolify-flux-eu-1.internal:6443 +``` + +If `COOLIFY_FLUX_ENABLED=false` is set, the Flux s6 service sleeps even for `all` and `flux` roles. The Flux s6 service creates a persistent JWT keypair on first boot when the key files are missing. The current Coolify image only installs the nightly Flux artifact during the Docker build; the s6 service verifies the binary at runtime and sleeps with a clear error if the published artifact is not compatible with the Alpine base image. + ## Support Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact). diff --git a/app/Http/Controllers/V5/HomeController.php b/app/Http/Controllers/V5/HomeController.php new file mode 100644 index 000000000..b8f9fa6db --- /dev/null +++ b/app/Http/Controllers/V5/HomeController.php @@ -0,0 +1,44 @@ +user(); + $currentTeam = $request->attributes->get('v5.currentTeam'); + + return Inertia::render('Home', [ + 'status' => 'v5-ready', + 'flux' => $fluxHealth->check(), + 'currentTeam' => $currentTeam instanceof Team ? [ + 'id' => $currentTeam->id, + 'name' => $currentTeam->name, + 'description' => $currentTeam->description, + 'role' => $currentTeam->pivot?->role ?? $user->roleInTeam($currentTeam->id), + 'personal' => $currentTeam->personal_team, + ] : null, + 'teams' => $user->teams() + ->select('teams.id', 'teams.name', 'teams.description', 'teams.personal_team') + ->orderBy('teams.name') + ->get() + ->map(fn (Team $team) => [ + 'id' => $team->id, + 'name' => $team->name, + 'description' => $team->description, + 'role' => $team->pivot->role, + 'personal' => $team->personal_team, + ]), + ]); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 02a49aaa8..dfd66284a 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -18,6 +18,8 @@ use App\Http\Middleware\RedirectIfAuthenticated; use App\Http\Middleware\TrimStrings; use App\Http\Middleware\TrustHosts; use App\Http\Middleware\TrustProxies; +use App\Http\Middleware\V5\EnsureCurrentTeam as V5EnsureCurrentTeam; +use App\Http\Middleware\V5\HandleInertiaRequests as V5HandleInertiaRequests; use App\Http\Middleware\ValidateSignature; use App\Http\Middleware\VerifyCsrfToken; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; @@ -76,6 +78,22 @@ class Kernel extends HttpKernel ], + 'v5.web' => [ + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + V5HandleInertiaRequests::class, + ], + + 'v5.authenticated' => [ + 'auth', + 'verified', + V5EnsureCurrentTeam::class, + ], + 'api' => [ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, ThrottleRequests::class.':api', diff --git a/app/Http/Middleware/V5/EnsureCurrentTeam.php b/app/Http/Middleware/V5/EnsureCurrentTeam.php new file mode 100644 index 000000000..f8aa78f92 --- /dev/null +++ b/app/Http/Middleware/V5/EnsureCurrentTeam.php @@ -0,0 +1,54 @@ +user(); + + if (! $user) { + return $next($request); + } + + $currentTeam = $this->resolveCurrentTeam($user); + + if (! $currentTeam) { + abort(403, 'No team available for this user.'); + } + + session(['currentTeam' => $currentTeam]); + $request->attributes->set('v5.currentTeam', $currentTeam); + + return $next($request); + } + + private function resolveCurrentTeam(User $user): ?Team + { + $sessionTeamId = data_get(session('currentTeam'), 'id'); + + if ($sessionTeamId) { + $sessionTeam = $user->teams() + ->select('teams.id', 'teams.name', 'teams.description', 'teams.personal_team') + ->whereKey($sessionTeamId) + ->first(); + + if ($sessionTeam) { + return $sessionTeam; + } + } + + return $user->teams() + ->select('teams.id', 'teams.name', 'teams.description', 'teams.personal_team') + ->orderBy('teams.id') + ->first(); + } +} diff --git a/app/Http/Middleware/V5/HandleInertiaRequests.php b/app/Http/Middleware/V5/HandleInertiaRequests.php new file mode 100644 index 000000000..2772c9c2e --- /dev/null +++ b/app/Http/Middleware/V5/HandleInertiaRequests.php @@ -0,0 +1,25 @@ + [ + 'user' => $request->user() ? [ + 'id' => $request->user()->id, + 'name' => $request->user()->name, + 'email' => $request->user()->email, + ] : null, + ], + ]; + } +} diff --git a/app/Models/V5/Project.php b/app/Models/V5/Project.php new file mode 100644 index 000000000..a04a381f9 --- /dev/null +++ b/app/Models/V5/Project.php @@ -0,0 +1,29 @@ +belongsTo(Team::class); + } + + public function creator(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by_user_id'); + } +} diff --git a/app/Models/V5/V5Model.php b/app/Models/V5/V5Model.php new file mode 100644 index 000000000..9907e827d --- /dev/null +++ b/app/Models/V5/V5Model.php @@ -0,0 +1,10 @@ +group(base_path('routes/webhooks.php')); + Route::middleware('v5.web') + ->prefix('v5') + ->as('v5.') + ->group(base_path('routes/v5.php')); + Route::middleware('web') ->group(base_path('routes/web.php')); }); diff --git a/app/Services/Flux/FluxHealth.php b/app/Services/Flux/FluxHealth.php new file mode 100644 index 000000000..85d49d948 --- /dev/null +++ b/app/Services/Flux/FluxHealth.php @@ -0,0 +1,66 @@ +unavailable(null, 'Flux socket is not configured.'); + } + + if (! file_exists($socketPath)) { + return $this->unavailable($socketPath, 'Flux socket was not found.'); + } + + $timeout = (float) config('flux.health_timeout_seconds', 1.0); + $stream = @stream_socket_client("unix://{$socketPath}", $errorCode, $errorMessage, $timeout); + + if ($stream === false) { + return $this->unavailable($socketPath, $errorMessage ?: "Could not connect to Flux socket ({$errorCode})."); + } + + stream_set_timeout($stream, (int) ceil($timeout)); + + fwrite($stream, "GET /v1/health HTTP/1.1\r\nHost: flux\r\nAccept: application/json\r\nConnection: close\r\n\r\n"); + $response = stream_get_contents($stream) ?: ''; + fclose($stream); + + if (! str_starts_with($response, 'HTTP/1.1 200') && ! str_starts_with($response, 'HTTP/1.0 200')) { + return $this->unavailable($socketPath, 'Flux health endpoint did not return HTTP 200.'); + } + + $body = str_contains($response, "\r\n\r\n") ? substr($response, strpos($response, "\r\n\r\n") + 4) : ''; + $payload = json_decode($body, true); + + if (! is_array($payload) || ($payload['ok'] ?? false) !== true) { + return $this->unavailable($socketPath, 'Flux health endpoint returned an invalid response.'); + } + + return [ + 'available' => true, + 'label' => 'Running', + 'message' => 'Flux is running.', + 'socket' => $socketPath, + ]; + } + + /** + * @return array{available: false, label: string, message: string, socket: string|null} + */ + private function unavailable(?string $socketPath, string $message): array + { + return [ + 'available' => false, + 'label' => 'Unavailable', + 'message' => $message, + 'socket' => $socketPath, + ]; + } +} diff --git a/composer.json b/composer.json index 9415aa624..1d94dca00 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "danharrin/livewire-rate-limiting": "^2.1.0", "doctrine/dbal": "^4.4.1", "guzzlehttp/guzzle": "^7.10.0", + "inertiajs/inertia-laravel": "^3.1", "laravel/fortify": "^1.34.0", "laravel/framework": "^12.49.0", "laravel/horizon": "^5.43.0", diff --git a/composer.lock b/composer.lock index 7d958a9cc..ded6d4b6c 100644 --- a/composer.lock +++ b/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": "64b77285a7140ce68e83db2659e9a21d", + "content-hash": "d7e1f3eef5ae01d358f9482007814245", "packages": [ { "name": "aws/aws-crt-php", @@ -1643,6 +1643,79 @@ ], "time": "2025-08-22T14:27:06+00:00" }, + { + "name": "inertiajs/inertia-laravel", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/inertiajs/inertia-laravel.git", + "reference": "f588ce4a5beb25d166f4e372bac0c7f46a4f52ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/f588ce4a5beb25d166f4e372bac0c7f46a4f52ac", + "reference": "f588ce4a5beb25d166f4e372bac0c7f46a4f52ac", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel/framework": "^11.0|^12.0|^13.0", + "php": "^8.2.0", + "symfony/console": "^7.0|^8.0" + }, + "conflict": { + "laravel/boost": "<2.2.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.2", + "larastan/larastan": "^3.0", + "laravel/pint": "^1.16", + "mockery/mockery": "^1.3.3", + "orchestra/testbench": "^9.2|^10.0|^11.0", + "phpunit/phpunit": "^11.5|^12.0", + "roave/security-advisories": "dev-master" + }, + "suggest": { + "ext-pcntl": "Recommended when running the Inertia SSR server via the `inertia:start-ssr` artisan command." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Inertia\\ServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "./helpers.php" + ], + "psr-4": { + "Inertia\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Reinink", + "email": "jonathan@reinink.ca", + "homepage": "https://reinink.ca" + } + ], + "description": "The Laravel adapter for Inertia.js.", + "keywords": [ + "inertia", + "laravel" + ], + "support": { + "issues": "https://github.com/inertiajs/inertia-laravel/issues", + "source": "https://github.com/inertiajs/inertia-laravel/tree/v3.1.0" + }, + "time": "2026-04-30T15:30:29+00:00" + }, { "name": "jean85/pretty-package-versions", "version": "2.1.1", @@ -8973,16 +9046,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.4.8", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "9381209597ec66c25be154cbf2289076e64d1eab" + "reference": "bc354f47c62301e990b7874fa662326368508e2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9381209597ec66c25be154cbf2289076e64d1eab", - "reference": "9381209597ec66c25be154cbf2289076e64d1eab", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/bc354f47c62301e990b7874fa662326368508e2c", + "reference": "bc354f47c62301e990b7874fa662326368508e2c", "shasum": "" }, "require": { @@ -9031,7 +9104,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.8" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.13" }, "funding": [ { @@ -9051,7 +9124,7 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-05-24T11:20:33+00:00" }, { "name": "symfony/http-kernel", @@ -10008,16 +10081,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + "reference": "8339098cae28673c15cce00d80734af0453054e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", + "reference": "8339098cae28673c15cce00d80734af0453054e2", "shasum": "" }, "require": { @@ -10064,7 +10137,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1" }, "funding": [ { @@ -10084,7 +10157,7 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/polyfill-php84", @@ -10650,16 +10723,16 @@ }, { "name": "symfony/routing", - "version": "v7.4.12", + "version": "v7.4.13", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204" + "reference": "3a162171bb008e5e0f15dce6581373a4c0e8390d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", - "reference": "3b04a5ec4887a8135a12ebf0f4cbc5b8fc8ee204", + "url": "https://api.github.com/repos/symfony/routing/zipball/3a162171bb008e5e0f15dce6581373a4c0e8390d", + "reference": "3a162171bb008e5e0f15dce6581373a4c0e8390d", "shasum": "" }, "require": { @@ -10711,7 +10784,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.12" + "source": "https://github.com/symfony/routing/tree/v7.4.13" }, "funding": [ { @@ -10731,7 +10804,7 @@ "type": "tidelift" } ], - "time": "2026-05-20T07:20:23+00:00" + "time": "2026-05-24T11:20:33+00:00" }, { "name": "symfony/serializer", diff --git a/config/flux.php b/config/flux.php new file mode 100644 index 000000000..e82914de0 --- /dev/null +++ b/config/flux.php @@ -0,0 +1,6 @@ + env('COOLIFY_FLUX_UNIX_SOCKET_PATH', '/run/coolify/flux.sock'), + 'health_timeout_seconds' => (float) env('COOLIFY_FLUX_HEALTH_TIMEOUT_SECONDS', 1.0), +]; diff --git a/database/migrations/2026_06_04_050157_create_v5_projects_table.php b/database/migrations/2026_06_04_050157_create_v5_projects_table.php new file mode 100644 index 000000000..8148c6cce --- /dev/null +++ b/database/migrations/2026_06_04_050157_create_v5_projects_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('team_id')->constrained('teams')->cascadeOnDelete(); + $table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete(); + $table->string('name'); + $table->text('description')->nullable(); + $table->timestamps(); + + $table->index(['team_id', 'name']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('v5_projects'); + } +}; diff --git a/database/schema/testing-schema.sql b/database/schema/testing-schema.sql index edbc35db4..7555922ce 100644 --- a/database/schema/testing-schema.sql +++ b/database/schema/testing-schema.sql @@ -1321,6 +1321,16 @@ CREATE TABLE IF NOT EXISTS "users" ( "email_change_code_expires_at" TEXT ); +CREATE TABLE IF NOT EXISTS "v5_projects" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + "team_id" INTEGER NOT NULL, + "created_by_user_id" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "created_at" TEXT, + "updated_at" TEXT +); + CREATE TABLE IF NOT EXISTS "webhook_notification_settings" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "team_id" INTEGER NOT NULL, @@ -1752,3 +1762,4 @@ INSERT INTO "migrations" ("id", "migration", "batch") VALUES (311, '2025_12_10_1 INSERT INTO "migrations" ("id", "migration", "batch") VALUES (312, '2025_12_15_143052_trim_s3_storage_credentials', 312); INSERT INTO "migrations" ("id", "migration", "batch") VALUES (313, '2025_12_17_000001_add_is_wire_navigate_enabled_to_instance_settings_table', 313); INSERT INTO "migrations" ("id", "migration", "batch") VALUES (314, '2025_12_17_000002_add_restart_tracking_to_standalone_databases', 314); +INSERT INTO "migrations" ("id", "migration", "batch") VALUES (315, '2026_06_04_050157_create_v5_projects_table', 315); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9c93678af..bad72a22d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -10,8 +10,10 @@ services: - GROUP_ID=${GROUPID:-1000} ports: - "${APP_PORT:-8000}:8080" + - "${FORWARD_FLUX_PORT:-6443}:6443" environment: AUTORUN_ENABLED: false + COOLIFY_CONTAINER_ROLE: "${COOLIFY_CONTAINER_ROLE:-all}" PUSHER_HOST: "${PUSHER_HOST}" PUSHER_PORT: "${PUSHER_PORT}" PUSHER_SCHEME: "${PUSHER_SCHEME:-http}" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8907a30b9..fd4aabeef 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -11,6 +11,7 @@ services: - /data/coolify/databases:/var/www/html/storage/app/databases - /data/coolify/services:/var/www/html/storage/app/services - /data/coolify/backups:/var/www/html/storage/app/backups + - /data/coolify/flux:/var/www/html/storage/app/flux environment: - APP_ENV=${APP_ENV:-production} - PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT:-256M} @@ -18,12 +19,15 @@ services: - PHP_FPM_PM_START_SERVERS=${PHP_FPM_PM_START_SERVERS:-1} - PHP_FPM_PM_MIN_SPARE_SERVERS=${PHP_FPM_PM_MIN_SPARE_SERVERS:-1} - PHP_FPM_PM_MAX_SPARE_SERVERS=${PHP_FPM_PM_MAX_SPARE_SERVERS:-10} + - COOLIFY_CONTAINER_ROLE=${COOLIFY_CONTAINER_ROLE:-all} env_file: - /data/coolify/source/.env ports: - "${APP_PORT:-8000}:8080" + - "${COOLIFY_FLUX_PORT:-6443}:6443" expose: - "${APP_PORT:-8000}" + - "${COOLIFY_FLUX_PORT:-6443}" healthcheck: test: curl --fail http://127.0.0.1:8080/api/health || exit 1 interval: 5s diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile index 8fc46e32d..08450e817 100644 --- a/docker/development/Dockerfile +++ b/docker/development/Dockerfile @@ -5,6 +5,8 @@ ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine ARG MINIO_VERSION=RELEASE.2025-05-21T01-59-54Z # https://github.com/cloudflare/cloudflared/releases ARG CLOUDFLARED_VERSION=2025.7.0 +# https://github.com/coollabsio/coold/releases/tag/nightly +ARG COOLIFY_FLUX_VERSION=nightly # https://www.postgresql.org/support/versioning/ # Note: We are using version 18 of the postgres client (while still using postgres 15 for the postgres server) as version 15 has been removed from Alpine 3.23+ https://pkgs.alpinelinux.org/packages?name=postgresql*-client&branch=v3.23&repo=&arch=x86_64&origin=&flagged=&maintainer= ARG POSTGRES_VERSION=18 @@ -24,8 +26,10 @@ FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} ARG USER_ID ARG GROUP_ID ARG TARGETPLATFORM +ARG TARGETARCH ARG POSTGRES_VERSION ARG CLOUDFLARED_VERSION +ARG COOLIFY_FLUX_VERSION ARG NGINX_VERSION WORKDIR /var/www/html @@ -58,6 +62,7 @@ RUN apk upgrade --no-cache && \ RUN apk add --no-cache \ postgresql${POSTGRES_VERSION}-client \ openssh-client \ + openssl \ git \ git-lfs \ jq \ @@ -81,6 +86,28 @@ RUN mkdir -p /usr/local/bin && \ fi && \ chmod +x /usr/local/bin/cloudflared +# Install Flux from coold nightly release based on architecture +RUN set -eux; \ + mkdir -p /usr/local/bin /run/coolify /etc/coolify; \ + chown -R www-data:www-data /run/coolify /etc/coolify; \ + case "${TARGETARCH:-}" in \ + amd64|arm64) FLUX_ARCH="${TARGETARCH}" ;; \ + "") \ + case "$(uname -m)" in \ + x86_64) FLUX_ARCH="amd64" ;; \ + aarch64) FLUX_ARCH="arm64" ;; \ + *) echo "unsupported Flux arch: $(uname -m)" >&2; exit 1 ;; \ + esac ;; \ + *) echo "unsupported Flux TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + curl -fsSL --retry 3 --max-time 120 \ + -o /tmp/flux.tar.gz \ + "https://github.com/coollabsio/coold/releases/download/${COOLIFY_FLUX_VERSION}/flux-linux-musl-${FLUX_ARCH}.tar.gz"; \ + tar -xzf /tmp/flux.tar.gz -C /tmp; \ + test -f /tmp/flux; \ + install -m 0755 /tmp/flux /usr/local/bin/flux; \ + rm -f /tmp/flux /tmp/flux.tar.gz + # Configure PHP COPY docker/development/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini ENV PHP_OPCACHE_ENABLE=0 diff --git a/docker/development/etc/s6-overlay/s6-rc.d/flux/dependencies.d/init-setup b/docker/development/etc/s6-overlay/s6-rc.d/flux/dependencies.d/init-setup new file mode 100644 index 000000000..e69de29bb diff --git a/docker/development/etc/s6-overlay/s6-rc.d/flux/run b/docker/development/etc/s6-overlay/s6-rc.d/flux/run new file mode 100755 index 000000000..95f648f8e --- /dev/null +++ b/docker/development/etc/s6-overlay/s6-rc.d/flux/run @@ -0,0 +1,49 @@ +#!/bin/sh + +cd /var/www/html + +role="${COOLIFY_CONTAINER_ROLE:-}" +if [ -z "$role" ]; then + role="$(grep -E '^COOLIFY_CONTAINER_ROLE=' .env 2>/dev/null | tail -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" +fi +role="${role:-all}" + +case "$role" in + all|flux) ;; + *) + echo " INFO Flux is disabled for role '$role', sleeping." + exec sleep infinity + ;; +esac + +if grep -qE '^COOLIFY_FLUX_ENABLED=false' .env 2>/dev/null || [ "${COOLIFY_FLUX_ENABLED:-}" = "false" ]; then + echo " INFO Flux is disabled, sleeping." + exec sleep infinity +fi + +export COOLIFY_FLUX_GRPC_BIND="${COOLIFY_FLUX_GRPC_BIND:-0.0.0.0:6443}" +export COOLIFY_FLUX_UNIX_SOCKET_PATH="${COOLIFY_FLUX_UNIX_SOCKET_PATH:-/run/coolify/flux.sock}" +export COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH="${COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH:-/var/www/html/storage/app/flux/jwt.priv}" +export COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH="${COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH:-/var/www/html/storage/app/flux/jwt.pub}" +export COOLIFY_FLUX_ALLOW_PUBLIC_BIND="${COOLIFY_FLUX_ALLOW_PUBLIC_BIND:-1}" + +if [ ! -r "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH" ]; then + echo " INFO Flux JWT public key not found at $COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH, generating keypair..." + mkdir -p "$(dirname "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH")" "$(dirname "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH")" + openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH.tmp" + chmod 0600 "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH.tmp" + mv "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH.tmp" "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH" + openssl pkey -in "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH" -pubout -out "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH.tmp" + chmod 0644 "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH.tmp" + mv "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH.tmp" "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH" +fi + +if ! /usr/local/bin/flux --version >/dev/null 2>&1; then + echo " ERROR Flux binary cannot run in this container. Check that the installed coold nightly Flux artifact is compatible with Alpine." + exec sleep infinity +fi + +mkdir -p "$(dirname "$COOLIFY_FLUX_UNIX_SOCKET_PATH")" + +echo " INFO Flux is enabled for role '$role', starting..." +exec /usr/local/bin/flux diff --git a/docker/development/etc/s6-overlay/s6-rc.d/flux/type b/docker/development/etc/s6-overlay/s6-rc.d/flux/type new file mode 100644 index 000000000..1780f9f44 --- /dev/null +++ b/docker/development/etc/s6-overlay/s6-rc.d/flux/type @@ -0,0 +1 @@ +longrun \ No newline at end of file diff --git a/docker/development/etc/s6-overlay/s6-rc.d/horizon/run b/docker/development/etc/s6-overlay/s6-rc.d/horizon/run index dbc472d06..9a8d96d2a 100644 --- a/docker/development/etc/s6-overlay/s6-rc.d/horizon/run +++ b/docker/development/etc/s6-overlay/s6-rc.d/horizon/run @@ -2,6 +2,20 @@ cd /var/www/html +role="${COOLIFY_CONTAINER_ROLE:-}" +if [ -z "$role" ]; then + role="$(grep -E '^COOLIFY_CONTAINER_ROLE=' .env 2>/dev/null | tail -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" +fi +role="${role:-all}" + +case "$role" in + all|worker) ;; + *) + echo " INFO Horizon is disabled for role '$role', sleeping." + exec sleep infinity + ;; +esac + if grep -qE '^HORIZON_ENABLED=false' .env 2>/dev/null; then echo " INFO Horizon is disabled, sleeping." exec sleep infinity diff --git a/docker/development/etc/s6-overlay/s6-rc.d/nightwatch-agent/run b/docker/development/etc/s6-overlay/s6-rc.d/nightwatch-agent/run index ee46dba7e..7242b3f79 100644 --- a/docker/development/etc/s6-overlay/s6-rc.d/nightwatch-agent/run +++ b/docker/development/etc/s6-overlay/s6-rc.d/nightwatch-agent/run @@ -2,6 +2,20 @@ cd /var/www/html +role="${COOLIFY_CONTAINER_ROLE:-}" +if [ -z "$role" ]; then + role="$(grep -E '^COOLIFY_CONTAINER_ROLE=' .env 2>/dev/null | tail -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" +fi +role="${role:-all}" + +case "$role" in + all|worker) ;; + *) + echo " INFO Nightwatch is disabled for role '$role', sleeping." + exec sleep infinity + ;; +esac + if grep -qE '^NIGHTWATCH_ENABLED=true' .env 2>/dev/null; then echo " INFO Nightwatch is enabled, starting..." exec php artisan nightwatch:agent diff --git a/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/run index bfa44c7e3..0dbc2e716 100644 --- a/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/run +++ b/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/run @@ -2,6 +2,20 @@ cd /var/www/html +role="${COOLIFY_CONTAINER_ROLE:-}" +if [ -z "$role" ]; then + role="$(grep -E '^COOLIFY_CONTAINER_ROLE=' .env 2>/dev/null | tail -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" +fi +role="${role:-all}" + +case "$role" in + all|worker) ;; + *) + echo " INFO Scheduler worker is disabled for role '$role', sleeping." + exec sleep infinity + ;; +esac + if grep -qE '^SCHEDULER_ENABLED=false' .env 2>/dev/null; then echo " INFO Scheduler is disabled, sleeping." exec sleep infinity diff --git a/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/flux b/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/flux new file mode 100644 index 000000000..e69de29bb diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 0f849785e..b6dd5fac0 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -5,6 +5,8 @@ ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine ARG MINIO_VERSION=RELEASE.2025-05-21T01-59-54Z # https://github.com/cloudflare/cloudflared/releases ARG CLOUDFLARED_VERSION=2025.7.0 +# https://github.com/coollabsio/coold/releases/tag/nightly +ARG COOLIFY_FLUX_VERSION=nightly # https://www.postgresql.org/support/versioning/ # Note: We are using version 18 of the postgres client (while still using postgres 15 for the postgres server) as version 15 has been removed from Alpine 3.23+ https://pkgs.alpinelinux.org/packages?name=postgresql*-client&branch=v3.23&repo=&arch=x86_64&origin=&flagged=&maintainer= ARG POSTGRES_VERSION=18 @@ -73,8 +75,10 @@ FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} ARG USER_ID ARG GROUP_ID ARG TARGETPLATFORM +ARG TARGETARCH ARG POSTGRES_VERSION ARG CLOUDFLARED_VERSION +ARG COOLIFY_FLUX_VERSION ARG NGINX_VERSION ARG CI=true @@ -108,6 +112,7 @@ RUN --mount=type=cache,target=/var/cache/apk \ apk add --no-cache \ postgresql${POSTGRES_VERSION}-client \ openssh-client \ + openssl \ git \ git-lfs \ jq \ @@ -128,6 +133,28 @@ RUN mkdir -p /usr/local/bin && \ fi && \ chmod +x /usr/local/bin/cloudflared +# Install Flux from coold nightly release based on architecture +RUN set -eux; \ + mkdir -p /usr/local/bin /run/coolify /etc/coolify; \ + chown -R www-data:www-data /run/coolify /etc/coolify; \ + case "${TARGETARCH:-}" in \ + amd64|arm64) FLUX_ARCH="${TARGETARCH}" ;; \ + "") \ + case "$(uname -m)" in \ + x86_64) FLUX_ARCH="amd64" ;; \ + aarch64) FLUX_ARCH="arm64" ;; \ + *) echo "unsupported Flux arch: $(uname -m)" >&2; exit 1 ;; \ + esac ;; \ + *) echo "unsupported Flux TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + curl -fsSL --retry 3 --max-time 120 \ + -o /tmp/flux.tar.gz \ + "https://github.com/coollabsio/coold/releases/download/${COOLIFY_FLUX_VERSION}/flux-linux-musl-${FLUX_ARCH}.tar.gz"; \ + tar -xzf /tmp/flux.tar.gz -C /tmp; \ + test -f /tmp/flux; \ + install -m 0755 /tmp/flux /usr/local/bin/flux; \ + rm -f /tmp/flux /tmp/flux.tar.gz + # Configure PHP COPY docker/production/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini ENV PHP_OPCACHE_ENABLE=1 diff --git a/docker/production/etc/s6-overlay/s6-rc.d/flux/dependencies.d/init-script b/docker/production/etc/s6-overlay/s6-rc.d/flux/dependencies.d/init-script new file mode 100644 index 000000000..e69de29bb diff --git a/docker/production/etc/s6-overlay/s6-rc.d/flux/run b/docker/production/etc/s6-overlay/s6-rc.d/flux/run new file mode 100755 index 000000000..95f648f8e --- /dev/null +++ b/docker/production/etc/s6-overlay/s6-rc.d/flux/run @@ -0,0 +1,49 @@ +#!/bin/sh + +cd /var/www/html + +role="${COOLIFY_CONTAINER_ROLE:-}" +if [ -z "$role" ]; then + role="$(grep -E '^COOLIFY_CONTAINER_ROLE=' .env 2>/dev/null | tail -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" +fi +role="${role:-all}" + +case "$role" in + all|flux) ;; + *) + echo " INFO Flux is disabled for role '$role', sleeping." + exec sleep infinity + ;; +esac + +if grep -qE '^COOLIFY_FLUX_ENABLED=false' .env 2>/dev/null || [ "${COOLIFY_FLUX_ENABLED:-}" = "false" ]; then + echo " INFO Flux is disabled, sleeping." + exec sleep infinity +fi + +export COOLIFY_FLUX_GRPC_BIND="${COOLIFY_FLUX_GRPC_BIND:-0.0.0.0:6443}" +export COOLIFY_FLUX_UNIX_SOCKET_PATH="${COOLIFY_FLUX_UNIX_SOCKET_PATH:-/run/coolify/flux.sock}" +export COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH="${COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH:-/var/www/html/storage/app/flux/jwt.priv}" +export COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH="${COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH:-/var/www/html/storage/app/flux/jwt.pub}" +export COOLIFY_FLUX_ALLOW_PUBLIC_BIND="${COOLIFY_FLUX_ALLOW_PUBLIC_BIND:-1}" + +if [ ! -r "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH" ]; then + echo " INFO Flux JWT public key not found at $COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH, generating keypair..." + mkdir -p "$(dirname "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH")" "$(dirname "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH")" + openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH.tmp" + chmod 0600 "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH.tmp" + mv "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH.tmp" "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH" + openssl pkey -in "$COOLIFY_FLUX_JWT_PRIVATE_KEY_PATH" -pubout -out "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH.tmp" + chmod 0644 "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH.tmp" + mv "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH.tmp" "$COOLIFY_FLUX_JWT_PUBLIC_KEY_PATH" +fi + +if ! /usr/local/bin/flux --version >/dev/null 2>&1; then + echo " ERROR Flux binary cannot run in this container. Check that the installed coold nightly Flux artifact is compatible with Alpine." + exec sleep infinity +fi + +mkdir -p "$(dirname "$COOLIFY_FLUX_UNIX_SOCKET_PATH")" + +echo " INFO Flux is enabled for role '$role', starting..." +exec /usr/local/bin/flux diff --git a/docker/production/etc/s6-overlay/s6-rc.d/flux/type b/docker/production/etc/s6-overlay/s6-rc.d/flux/type new file mode 100644 index 000000000..1780f9f44 --- /dev/null +++ b/docker/production/etc/s6-overlay/s6-rc.d/flux/type @@ -0,0 +1 @@ +longrun \ No newline at end of file diff --git a/docker/production/etc/s6-overlay/s6-rc.d/horizon/run b/docker/production/etc/s6-overlay/s6-rc.d/horizon/run index dbc472d06..9a8d96d2a 100644 --- a/docker/production/etc/s6-overlay/s6-rc.d/horizon/run +++ b/docker/production/etc/s6-overlay/s6-rc.d/horizon/run @@ -2,6 +2,20 @@ cd /var/www/html +role="${COOLIFY_CONTAINER_ROLE:-}" +if [ -z "$role" ]; then + role="$(grep -E '^COOLIFY_CONTAINER_ROLE=' .env 2>/dev/null | tail -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" +fi +role="${role:-all}" + +case "$role" in + all|worker) ;; + *) + echo " INFO Horizon is disabled for role '$role', sleeping." + exec sleep infinity + ;; +esac + if grep -qE '^HORIZON_ENABLED=false' .env 2>/dev/null; then echo " INFO Horizon is disabled, sleeping." exec sleep infinity diff --git a/docker/production/etc/s6-overlay/s6-rc.d/nightwatch-agent/run b/docker/production/etc/s6-overlay/s6-rc.d/nightwatch-agent/run index ee46dba7e..7242b3f79 100644 --- a/docker/production/etc/s6-overlay/s6-rc.d/nightwatch-agent/run +++ b/docker/production/etc/s6-overlay/s6-rc.d/nightwatch-agent/run @@ -2,6 +2,20 @@ cd /var/www/html +role="${COOLIFY_CONTAINER_ROLE:-}" +if [ -z "$role" ]; then + role="$(grep -E '^COOLIFY_CONTAINER_ROLE=' .env 2>/dev/null | tail -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" +fi +role="${role:-all}" + +case "$role" in + all|worker) ;; + *) + echo " INFO Nightwatch is disabled for role '$role', sleeping." + exec sleep infinity + ;; +esac + if grep -qE '^NIGHTWATCH_ENABLED=true' .env 2>/dev/null; then echo " INFO Nightwatch is enabled, starting..." exec php artisan nightwatch:agent diff --git a/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/run index bfa44c7e3..0dbc2e716 100644 --- a/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/run +++ b/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/run @@ -2,6 +2,20 @@ cd /var/www/html +role="${COOLIFY_CONTAINER_ROLE:-}" +if [ -z "$role" ]; then + role="$(grep -E '^COOLIFY_CONTAINER_ROLE=' .env 2>/dev/null | tail -n1 | cut -d= -f2- | tr -d '"' | tr -d "'")" +fi +role="${role:-all}" + +case "$role" in + all|worker) ;; + *) + echo " INFO Scheduler worker is disabled for role '$role', sleeping." + exec sleep infinity + ;; +esac + if grep -qE '^SCHEDULER_ENABLED=false' .env 2>/dev/null; then echo " INFO Scheduler is disabled, sleeping." exec sleep infinity diff --git a/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/flux b/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/flux new file mode 100644 index 000000000..e69de29bb diff --git a/package-lock.json b/package-lock.json index 9d495c412..288474a88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,14 +6,19 @@ "": { "name": "coolify", "dependencies": { + "@inertiajs/react": "^3.3.0", + "@inertiajs/vite": "^3.3.0", "@tailwindcss/forms": "0.5.10", "@tailwindcss/typography": "0.5.16", "@xterm/addon-fit": "0.10.0", "@xterm/xterm": "5.5.0", - "playwright": "^1.58.2" + "playwright": "^1.58.2", + "react": "^19.2.7", + "react-dom": "^19.2.7" }, "devDependencies": { "@tailwindcss/postcss": "4.1.18", + "@vitejs/plugin-react": "^5.2.0", "laravel-vite-plugin": "2.0.1", "postcss": "8.5.15", "tailwind-scrollbar": "4.0.2", @@ -34,6 +39,289 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -41,7 +329,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -58,7 +345,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -75,7 +361,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -92,7 +377,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -109,7 +393,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -126,7 +409,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -143,7 +425,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -160,7 +441,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -177,7 +457,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -194,7 +473,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -211,7 +489,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -228,7 +505,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -245,7 +521,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -262,7 +537,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -279,7 +553,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -296,7 +569,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -313,7 +585,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -330,7 +601,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -347,7 +617,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -364,7 +633,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -381,7 +649,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -398,7 +665,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -415,7 +681,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -432,7 +697,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -449,7 +713,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -466,7 +729,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -476,6 +738,53 @@ "node": ">=18" } }, + "node_modules/@inertiajs/core": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-3.3.0.tgz", + "integrity": "sha512-6tEe5vtjwXJj68h0nkz84WkeZ8XA0phJ8ep1l/V+im62KTzlZTw9sxlmf8uNKu4seleXVw+idw48svxUCWla/Q==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "es-toolkit": "^1.33.0", + "laravel-precognition": "^2.0.0" + }, + "peerDependencies": { + "axios": "^1.15.2" + }, + "peerDependenciesMeta": { + "axios": { + "optional": true + } + } + }, + "node_modules/@inertiajs/react": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@inertiajs/react/-/react-3.3.0.tgz", + "integrity": "sha512-8Es6i1FOyP5sHWjLlE3yFi+dOuJp3IBkl1ccoxb101Jr4/vTcNsdXvpl+U8IZmvnmyDS4dikeAa29p7xFUwgyQ==", + "license": "MIT", + "dependencies": { + "@inertiajs/core": "3.3.0", + "es-toolkit": "^1.33.0", + "laravel-precognition": "^2.0.0" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, + "node_modules/@inertiajs/vite": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@inertiajs/vite/-/vite-3.3.0.tgz", + "integrity": "sha512-UWm+I5OpxGnm1LBjgaPOI6/M8gurloxXRnzeNMd2UC6f6ObQo6RZvyLMMWInwS0fBief7lfkpj5sPZy+680CRA==", + "license": "MIT", + "dependencies": { + "@inertiajs/core": "3.3.0", + "tinyglobby": "^0.2.15" + }, + "peerDependencies": { + "vite": "^7.0.0 || ^8.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -502,7 +811,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -512,20 +820,25 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", @@ -533,7 +846,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -547,7 +859,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -561,7 +872,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -575,7 +885,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -589,7 +898,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -603,7 +911,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -617,7 +924,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -631,7 +937,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -645,7 +950,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -659,7 +963,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -673,7 +976,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -687,7 +989,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -701,7 +1002,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -715,7 +1015,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -729,7 +1028,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -743,7 +1041,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -757,7 +1054,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -771,7 +1067,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -785,7 +1080,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -799,7 +1093,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -813,7 +1106,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -827,7 +1119,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -841,7 +1132,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -855,7 +1145,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -869,7 +1158,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1234,11 +1522,55 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/prismjs": { @@ -1248,6 +1580,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/@xterm/addon-fit": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", @@ -1261,7 +1614,77 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" }, "node_modules/clsx": { "version": "2.1.1", @@ -1273,6 +1696,13 @@ "node": ">=6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1285,16 +1715,41 @@ "node": ">=4" } }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.366", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.366.tgz", + "integrity": "sha512-OlRuhb688YTCzzU3gXPLn6nGyd+F+53INE1qaKKlu6kETErE8FYsyDh0XqXEU+uBRn0MpCzz2vfNwORhkap8qg==", + "dev": true, + "license": "ISC" + }, "node_modules/enhanced-resolve": { "version": "5.19.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", @@ -1309,11 +1764,20 @@ "node": ">=10.13.0" } }, + "node_modules/es-toolkit": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz", + "integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -1351,11 +1815,20 @@ "@esbuild/win32-x64": "0.27.2" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -1373,7 +1846,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1384,6 +1856,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1395,12 +1877,62 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/laravel-precognition": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/laravel-precognition/-/laravel-precognition-2.0.0.tgz", + "integrity": "sha512-dmA4HGc9m+TsVNsJs9/XQBI8u6j7coilN+qKkBuhuXQzH3HypwS/c5dFQ4UqUGjBbcxIM7zdk91kM/SRZwIvWQ==", + "license": "MIT", + "dependencies": { + "es-toolkit": "^1.32.0" + }, + "peerDependencies": { + "axios": "^1.4.0" + }, + "peerDependenciesMeta": { + "axios": { + "optional": true + } + } + }, "node_modules/laravel-vite-plugin": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", @@ -1425,7 +1957,7 @@ "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "dev": true, + "devOptional": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -1458,7 +1990,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1479,7 +2010,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1500,7 +2030,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1521,7 +2050,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1542,7 +2070,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1563,7 +2090,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1584,7 +2110,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1605,7 +2130,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1626,7 +2150,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1647,7 +2170,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1668,7 +2190,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -1700,6 +2221,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1719,11 +2250,17 @@ "mini-svg-data-uri": "cli.js" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, "funding": [ { "type": "github", @@ -1738,19 +2275,28 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -1806,7 +2352,6 @@ "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1859,21 +2404,42 @@ } }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "dev": true, + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -1914,11 +2480,26 @@ "fsevents": "~2.3.2" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -1944,7 +2525,8 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -1964,7 +2546,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -1977,6 +2558,37 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1987,8 +2599,8 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", - "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -2081,6 +2693,13 @@ "funding": { "url": "https://github.com/sponsors/jonschlinkert" } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" } } } diff --git a/package.json b/package.json index c3fb1bc5f..43c4c738a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "devDependencies": { "@tailwindcss/postcss": "4.1.18", + "@vitejs/plugin-react": "^5.2.0", "laravel-vite-plugin": "2.0.1", "postcss": "8.5.15", "tailwind-scrollbar": "4.0.2", @@ -15,10 +16,14 @@ "vite": "7.3.2" }, "dependencies": { + "@inertiajs/react": "^3.3.0", + "@inertiajs/vite": "^3.3.0", "@tailwindcss/forms": "0.5.10", "@tailwindcss/typography": "0.5.16", "@xterm/addon-fit": "0.10.0", "@xterm/xterm": "5.5.0", - "playwright": "^1.58.2" + "playwright": "^1.58.2", + "react": "^19.2.7", + "react-dom": "^19.2.7" } } diff --git a/resources/css/v5/app.css b/resources/css/v5/app.css new file mode 100644 index 000000000..a363687ee --- /dev/null +++ b/resources/css/v5/app.css @@ -0,0 +1,11 @@ +@font-face { + font-display: swap; + font-family: 'Geist Sans'; + font-style: normal; + font-weight: 100 900; + src: url('../../fonts/geist-sans-variable.woff2') format('woff2'); +} + +body { + font-family: 'Geist Sans', sans-serif; +} diff --git a/resources/js/v5/Pages/Home.jsx b/resources/js/v5/Pages/Home.jsx new file mode 100644 index 000000000..0fa9a231b --- /dev/null +++ b/resources/js/v5/Pages/Home.jsx @@ -0,0 +1,59 @@ +import { Head } from '@inertiajs/react'; + +export default function Home({ status, currentTeam, teams, flux }) { + return ( + <> + + +
+

{status}

+ +

Coolify v5

+ +

+ This page is served from Laravel through Inertia and React, with routes, + assets, and future v5 tables isolated from the current v4 Livewire app. +

+ +
+

Flux status

+ +

+ {flux.label} +

+ +

{flux.message}

+ + {flux.socket ?

Socket: {flux.socket}

: null} +
+ +

Current team

+ + {currentTeam ? ( +
+
Name
+
{currentTeam.name}
+ +
Description
+
{currentTeam.description || 'No description'}
+ +
Your role
+
{currentTeam.role}
+
+ ) : ( +

No team selected.

+ )} + +

Your teams

+ + +
+ + ); +} diff --git a/resources/js/v5/app.jsx b/resources/js/v5/app.jsx new file mode 100644 index 000000000..fa391dab8 --- /dev/null +++ b/resources/js/v5/app.jsx @@ -0,0 +1,11 @@ +import { createInertiaApp } from '@inertiajs/react'; +import '../../css/v5/app.css'; + +createInertiaApp({ + id: 'v5-app', + pages: { + path: './Pages', + extension: '.jsx', + }, + strictMode: true, +}); diff --git a/resources/views/v5/app.blade.php b/resources/views/v5/app.blade.php new file mode 100644 index 000000000..7f1a5fd70 --- /dev/null +++ b/resources/views/v5/app.blade.php @@ -0,0 +1,18 @@ + + + + + + + + {{ config('app.name') }} v5 + @viteReactRefresh + @vite('resources/js/v5/app.jsx') + + + + + + + + diff --git a/routes/v5.php b/routes/v5.php new file mode 100644 index 000000000..16525bf73 --- /dev/null +++ b/routes/v5.php @@ -0,0 +1,8 @@ +group(function () { + Route::get('/', HomeController::class)->name('home'); +}); diff --git a/tests/Feature/V5/HomeTest.php b/tests/Feature/V5/HomeTest.php new file mode 100644 index 000000000..c37b47496 --- /dev/null +++ b/tests/Feature/V5/HomeTest.php @@ -0,0 +1,214 @@ +toBeTrue(); +}); + +it('uses separated v5 middleware groups', function () { + $kernel = app(Kernel::class); + $reflection = new ReflectionClass($kernel); + $property = $reflection->getProperty('middlewareGroups'); + $property->setAccessible(true); + $groups = $property->getValue($kernel); + + expect($groups)->toHaveKey('v5.web') + ->and($groups)->toHaveKey('v5.authenticated') + ->and($groups['v5.web'])->toContain(HandleInertiaRequests::class) + ->and($groups['v5.web'])->not->toContain(CheckForcePasswordReset::class) + ->and($groups['v5.web'])->not->toContain(DecideWhatToDoWithUser::class) + ->and($groups['v5.authenticated'])->toContain('auth') + ->and($groups['v5.authenticated'])->toContain('verified') + ->and($groups['v5.authenticated'])->toContain(EnsureCurrentTeam::class); +}); + +it('creates v5 project tables in the shared database', function () { + createSharedUserAndTeamTables(); + + $migration = include database_path('migrations/2026_06_04_050157_create_v5_projects_table.php'); + $migration->up(); + + expect(Schema::hasTable('v5_projects'))->toBeTrue() + ->and(Schema::hasColumns('v5_projects', [ + 'id', + 'team_id', + 'created_by_user_id', + 'name', + 'description', + 'created_at', + 'updated_at', + ]))->toBeTrue(); +}); + +it('includes v5 project tables in the dev testing schema', function () { + $schema = file_get_contents(database_path('schema/testing-schema.sql')); + + expect($schema)->toContain('CREATE TABLE IF NOT EXISTS "v5_projects"') + ->and($schema)->toContain('"team_id" INTEGER NOT NULL') + ->and($schema)->toContain('"created_by_user_id" INTEGER NOT NULL') + ->and($schema)->toContain('2026_06_04_050157_create_v5_projects_table'); +}); + +it('redirects guests to the shared login', function () { + $this->get('/v5') + ->assertRedirect('/login'); +}); + +it('serves the v5 inertia shell', function () { + $this->withoutVite(); + fakeFluxHealth(); + createSharedUserAndTeamTables(); + + $user = User::withoutEvents(fn () => User::query()->create([ + 'name' => 'Ada Lovelace', + 'email' => 'ada@example.com', + 'email_verified_at' => now(), + 'password' => 'password', + ])); + $team = Team::withoutEvents(fn () => Team::query()->create([ + 'name' => 'V5 Shared Team', + 'description' => 'Shared team details', + 'personal_team' => true, + 'show_boarding' => false, + ])); + $user->teams()->attach($team, ['role' => 'owner']); + + $this + ->actingAs($user) + ->withSession(['currentTeam' => $team]) + ->get('/v5') + ->assertSuccessful() + ->assertSee('v5-app', false) + ->assertSee('Home', false) + ->assertSee('v5-ready', false) + ->assertSee('Running') + ->assertSee('Flux is running.') + ->assertSee('V5 Shared Team') + ->assertSee('Shared team details') + ->assertSee('owner') + ->assertSee($user->email); +}); + +it('selects a shared team when the session has no current team', function () { + $this->withoutVite(); + fakeFluxHealth(); + createSharedUserAndTeamTables(); + + $user = User::withoutEvents(fn () => User::query()->create([ + 'name' => 'Grace Hopper', + 'email' => 'grace@example.com', + 'email_verified_at' => now(), + 'password' => 'password', + ])); + $team = Team::withoutEvents(fn () => Team::query()->create([ + 'name' => 'Auto Selected Team', + 'description' => null, + 'personal_team' => false, + 'show_boarding' => false, + ])); + $user->teams()->attach($team, ['role' => 'admin']); + + $this + ->actingAs($user) + ->get('/v5') + ->assertSuccessful() + ->assertSessionHas('currentTeam') + ->assertSee('Auto Selected Team') + ->assertSee('admin'); +}); + +it('shows when flux is unavailable', function () { + $this->withoutVite(); + fakeFluxHealth(false, 'Flux socket was not found.'); + createSharedUserAndTeamTables(); + + $user = User::withoutEvents(fn () => User::query()->create([ + 'name' => 'Katherine Johnson', + 'email' => 'katherine@example.com', + 'email_verified_at' => now(), + 'password' => 'password', + ])); + $team = Team::withoutEvents(fn () => Team::query()->create([ + 'name' => 'Flux Test Team', + 'description' => null, + 'personal_team' => false, + 'show_boarding' => false, + ])); + $user->teams()->attach($team, ['role' => 'owner']); + + $this + ->actingAs($user) + ->withSession(['currentTeam' => $team]) + ->get('/v5') + ->assertSuccessful() + ->assertSee('Unavailable') + ->assertSee('Flux socket was not found.'); +}); + +function fakeFluxHealth(bool $available = true, string $message = 'Flux is running.'): void +{ + app()->instance(FluxHealth::class, Mockery::mock(FluxHealth::class, function (MockInterface $mock) use ($available, $message) { + $mock->shouldReceive('check') + ->once() + ->andReturn([ + 'available' => $available, + 'label' => $available ? 'Running' : 'Unavailable', + 'message' => $message, + 'socket' => '/run/coolify/flux.sock', + ]); + })); +} + +function createSharedUserAndTeamTables(): void +{ + Schema::create('users', function ($table) { + $table->id(); + $table->string('name')->default('Anonymous'); + $table->string('email'); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password')->nullable(); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('teams', function ($table) { + $table->id(); + $table->string('name'); + $table->string('description')->nullable(); + $table->boolean('personal_team')->default(false); + $table->boolean('show_boarding')->default(false); + $table->timestamps(); + }); + + Schema::create('team_user', function ($table) { + $table->id(); + $table->foreignId('team_id'); + $table->foreignId('user_id'); + $table->string('role')->default('member'); + $table->timestamps(); + + $table->unique(['team_id', 'user_id']); + }); +} diff --git a/vite.config.js b/vite.config.js index 36069fb28..3c36862f8 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,5 +1,7 @@ import { defineConfig, loadEnv } from "vite"; import laravel from "laravel-vite-plugin"; +import react from "@vitejs/plugin-react"; +import inertia from "@inertiajs/vite"; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') @@ -32,9 +34,15 @@ export default defineConfig(({ mode }) => { }, plugins: [ laravel({ - input: ["resources/css/app.css", "resources/js/app.js"], + input: [ + "resources/css/app.css", + "resources/js/app.js", + "resources/js/v5/app.jsx", + ], refresh: true, }), + inertia({ ssr: false }), + react(), ], } });