diff --git a/app/Http/Controllers/V5/HomeController.php b/app/Http/Controllers/V5/HomeController.php index 79a54c8ca..051224386 100644 --- a/app/Http/Controllers/V5/HomeController.php +++ b/app/Http/Controllers/V5/HomeController.php @@ -20,33 +20,11 @@ class HomeController extends Controller { public function __invoke(Request $request, FluxHealth $fluxHealth): Response { - /** @var User $user */ - $user = $request->user(); $currentTeam = $request->attributes->get('v5.currentTeam'); return Inertia::render('Home', [ 'flux' => $fluxHealth->check(), 'clusters' => $this->clusters($currentTeam), - 'cooldServers' => $this->cooldServers($currentTeam), - 'privateKeys' => $this->privateKeys($currentTeam), - '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, - ]), ]); } @@ -124,27 +102,6 @@ class HomeController extends Controller ]); } - /** - * @return array - */ - private function privateKeys(mixed $currentTeam): array - { - if (! $currentTeam instanceof Team) { - return []; - } - - return PrivateKey::query() - ->where('team_id', $currentTeam->id) - ->select('uuid', 'name') - ->orderBy('name') - ->get() - ->map(fn (PrivateKey $privateKey) => [ - 'uuid' => $privateKey->uuid, - 'name' => $privateKey->name, - ]) - ->all(); - } - /** * @return array}>}> */ @@ -175,31 +132,4 @@ class HomeController extends Controller ]) ->all(); } - - /** - * @return array, builderEnabled: bool, builderCapacity: int, lastBootstrappedAt: string|null}> - */ - private function cooldServers(mixed $currentTeam): array - { - if (! $currentTeam instanceof Team) { - return []; - } - - return V5Server::query() - ->where('team_id', $currentTeam->id) - ->orderBy('name') - ->get() - ->map(fn (V5Server $server) => [ - 'id' => (string) $server->id, - 'host' => $server->host, - 'sshUser' => $server->ssh_user, - 'sshPort' => $server->ssh_port, - 'status' => $server->status, - 'capabilities' => $server->capabilities ?? [], - 'builderEnabled' => $server->builder_enabled, - 'builderCapacity' => $server->builder_capacity, - 'lastBootstrappedAt' => $server->last_bootstrapped_at?->toISOString(), - ]) - ->all(); - } } diff --git a/app/Models/V5/Project.php b/app/Models/V5/Project.php deleted file mode 100644 index a04a381f9..000000000 --- a/app/Models/V5/Project.php +++ /dev/null @@ -1,29 +0,0 @@ -belongsTo(Team::class); - } - - public function creator(): BelongsTo - { - return $this->belongsTo(User::class, 'created_by_user_id'); - } -} 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 deleted file mode 100644 index 8148c6cce..000000000 --- a/database/migrations/2026_06_04_050157_create_v5_projects_table.php +++ /dev/null @@ -1,33 +0,0 @@ -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/migrations/2026_06_16_130649_create_v5_clusters_table.php b/database/migrations/2026_06_16_130649_v5_create_clusters_table.php similarity index 100% rename from database/migrations/2026_06_16_130649_create_v5_clusters_table.php rename to database/migrations/2026_06_16_130649_v5_create_clusters_table.php diff --git a/database/migrations/2026_06_16_130650_create_v5_servers_table.php b/database/migrations/2026_06_16_130650_v5_create_servers_table.php similarity index 90% rename from database/migrations/2026_06_16_130650_create_v5_servers_table.php rename to database/migrations/2026_06_16_130650_v5_create_servers_table.php index d7511c961..29fbaf91d 100644 --- a/database/migrations/2026_06_16_130650_create_v5_servers_table.php +++ b/database/migrations/2026_06_16_130650_v5_create_servers_table.php @@ -14,6 +14,7 @@ return new class extends Migration Schema::create('v5_servers', function (Blueprint $table) { $table->id(); $table->foreignId('team_id')->constrained('teams')->cascadeOnDelete(); + $table->foreignId('cluster_id')->nullable()->constrained('v5_clusters')->nullOnDelete(); $table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete(); $table->foreignId('private_key_id')->nullable()->constrained('private_keys')->nullOnDelete(); $table->string('name'); @@ -28,6 +29,7 @@ return new class extends Migration $table->timestamps(); $table->unique(['team_id', 'host', 'ssh_port']); + $table->index(['team_id', 'cluster_id']); $table->index(['team_id', 'status']); }); } diff --git a/database/migrations/2026_06_16_131229_add_cluster_id_to_v5_servers_table.php b/database/migrations/2026_06_16_131229_add_cluster_id_to_v5_servers_table.php deleted file mode 100644 index 7637a42e4..000000000 --- a/database/migrations/2026_06_16_131229_add_cluster_id_to_v5_servers_table.php +++ /dev/null @@ -1,35 +0,0 @@ -foreignId('cluster_id') - ->nullable() - ->after('team_id') - ->constrained('v5_clusters') - ->nullOnDelete(); - - $table->index(['team_id', 'cluster_id']); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('v5_servers', function (Blueprint $table) { - $table->dropIndex(['team_id', 'cluster_id']); - $table->dropConstrainedForeignId('cluster_id'); - }); - } -}; diff --git a/database/migrations/2026_06_16_132000_make_v5_server_private_key_nullable.php b/database/migrations/2026_06_16_132000_make_v5_server_private_key_nullable.php deleted file mode 100644 index cbd9dbd59..000000000 --- a/database/migrations/2026_06_16_132000_make_v5_server_private_key_nullable.php +++ /dev/null @@ -1,28 +0,0 @@ -foreignId('private_key_id')->nullable()->change(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('v5_servers', function (Blueprint $table) { - $table->foreignId('private_key_id')->nullable(false)->change(); - }); - } -}; diff --git a/database/schema/testing-schema.sql b/database/schema/testing-schema.sql index 1b27d8b98..289b4df2a 100644 --- a/database/schema/testing-schema.sql +++ b/database/schema/testing-schema.sql @@ -1350,17 +1350,6 @@ CREATE TABLE IF NOT EXISTS "v5_servers" ( "updated_at" TEXT ); -CREATE TABLE IF NOT EXISTS "v5_projects" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "team_id" INTEGER NOT NULL, - "cluster_id" INTEGER, - "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, @@ -1792,8 +1781,5 @@ 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); -INSERT INTO "migrations" ("id", "migration", "batch") VALUES (316, '2026_06_16_130650_create_v5_servers_table', 316); -INSERT INTO "migrations" ("id", "migration", "batch") VALUES (317, '2026_06_16_130649_create_v5_clusters_table', 317); -INSERT INTO "migrations" ("id", "migration", "batch") VALUES (318, '2026_06_16_131229_add_cluster_id_to_v5_servers_table', 318); -INSERT INTO "migrations" ("id", "migration", "batch") VALUES (319, '2026_06_16_132000_make_v5_server_private_key_nullable', 319); +INSERT INTO "migrations" ("id", "migration", "batch") VALUES (316, '2026_06_16_130650_v5_create_servers_table', 316); +INSERT INTO "migrations" ("id", "migration", "batch") VALUES (317, '2026_06_16_130649_v5_create_clusters_table', 317); diff --git a/resources/js/v5/Pages/Home.jsx b/resources/js/v5/Pages/Home.jsx index 350036599..35f5ca9c4 100644 --- a/resources/js/v5/Pages/Home.jsx +++ b/resources/js/v5/Pages/Home.jsx @@ -1,75 +1,6 @@ import { Head } from '@inertiajs/react'; -import { useState } from 'react'; - -export default function Home({ currentTeam, teams, flux, clusters, cooldServers, privateKeys }) { - const firstPrivateKey = privateKeys[0]?.uuid || ''; - const [bootstrapResult, setBootstrapResult] = useState(null); - const [bootstrapping, setBootstrapping] = useState(false); - const [bootstrapForm, setBootstrapForm] = useState({ - host: '', - ssh_user: 'root', - ssh_port: '22', - private_key_uuid: firstPrivateKey, - wg_listen_port: '', - wg_endpoint: '', - enable_builder: true, - builder_capacity: '2', - }); - - function csrfToken() { - return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''; - } - - function updateBootstrapForm(field, value) { - setBootstrapForm((current) => ({ - ...current, - [field]: value, - })); - } - - async function bootstrapCoolifyMesh(event) { - event.preventDefault(); - setBootstrapping(true); - setBootstrapResult(null); - - const payload = { - host: bootstrapForm.host, - ssh_user: bootstrapForm.ssh_user, - ssh_port: Number(bootstrapForm.ssh_port), - private_key_uuid: bootstrapForm.private_key_uuid, - enable_builder: bootstrapForm.enable_builder, - builder_capacity: bootstrapForm.builder_capacity === '' ? null : Number(bootstrapForm.builder_capacity), - wg_listen_port: bootstrapForm.wg_listen_port === '' ? null : Number(bootstrapForm.wg_listen_port), - wg_endpoint: bootstrapForm.wg_endpoint || null, - }; - - try { - const response = await fetch('/v5/coolify/bootstrap', { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': csrfToken(), - }, - body: JSON.stringify(payload), - }); - const result = await response.json(); - - setBootstrapResult(result); - } catch (error) { - setBootstrapResult({ - successful: false, - label: 'Bootstrap failed', - message: 'Could not start the coolify bootstrap command.', - output: null, - errorOutput: null, - exitCode: null, - }); - } finally { - setBootstrapping(false); - } - } +export default function Home({ flux, clusters }) { return ( <> @@ -110,177 +41,6 @@ export default function Home({ currentTeam, teams, flux, clusters, cooldServers, )} - -
-

coold servers

- - {cooldServers.length === 0 ? ( -

No coold serverss have been added yet.

- ) : ( -
    - {cooldServers.map((host) => ( -
  • - {host.host} ({host.status}) — SSH {host.sshUser}@{host.host}:{host.sshPort};{' '} - {host.capabilities.join(', ')}; builder{' '} - {host.builderEnabled - ? `enabled, capacity ${host.builderCapacity}` - : 'disabled'} -
  • - ))} -
- )} -
- -
-

coolify

- -
-

Bootstrap server

- - - - - - - - - -
- Advanced mesh options - - - - - - - - -
- - - - {privateKeys.length === 0 ? ( -

Add a private key before bootstrapping a server.

- ) : null} -
- - {bootstrapResult ? ( -
-

- {bootstrapResult.label} -

-

{bootstrapResult.message}

- {bootstrapResult.exitCode !== null ? ( -

Exit code: {bootstrapResult.exitCode}

- ) : null} - {bootstrapResult.output ?
{bootstrapResult.output}
: null} - {bootstrapResult.errorOutput ?
{bootstrapResult.errorOutput}
: null} -
- ) : null} -
- -

Current team

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

No team selected.

- )} - -

Your teams

- -
    - {teams.map((team) => ( -
  • - {team.name} ({team.role}) -
  • - ))} -
); diff --git a/tests/Feature/V5/HomeTest.php b/tests/Feature/V5/HomeTest.php index e05b06564..e914f6cef 100644 --- a/tests/Feature/V5/HomeTest.php +++ b/tests/Feature/V5/HomeTest.php @@ -24,7 +24,6 @@ beforeEach(function () { Schema::dropIfExists('v5_servers'); Schema::dropIfExists('v5_clusters'); - Schema::dropIfExists('v5_projects'); Schema::dropIfExists('private_keys'); Schema::dropIfExists('team_user'); Schema::dropIfExists('teams'); @@ -52,22 +51,9 @@ it('uses separated v5 middleware groups', function () { ->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('reuses existing projects instead of creating v5 projects', function () { + expect(file_exists(database_path('migrations/2026_06_04_050157_v5_create_projects_table.php')))->toBeFalse() + ->and(file_exists(app_path('Models/V5/Project.php')))->toBeFalse(); }); it('creates v5 cluster tables and lets each server belong to one cluster', function () { @@ -75,7 +61,7 @@ it('creates v5 cluster tables and lets each server belong to one cluster', funct Schema::dropIfExists('v5_servers'); Schema::dropIfExists('v5_clusters'); - $clusterMigration = include database_path('migrations/2026_06_16_130649_create_v5_clusters_table.php'); + $clusterMigration = include database_path('migrations/2026_06_16_130649_v5_create_clusters_table.php'); $clusterMigration->up(); expect(Schema::hasTable('v5_clusters'))->toBeTrue() @@ -89,14 +75,9 @@ it('creates v5 cluster tables and lets each server belong to one cluster', funct 'updated_at', ]))->toBeTrue(); - Schema::dropIfExists('v5_servers'); - - $serverMigration = include database_path('migrations/2026_06_16_130650_create_v5_servers_table.php'); + $serverMigration = include database_path('migrations/2026_06_16_130650_v5_create_servers_table.php'); $serverMigration->up(); - $serverClusterMigration = include database_path('migrations/2026_06_16_131229_add_cluster_id_to_v5_servers_table.php'); - $serverClusterMigration->up(); - expect(Schema::hasColumn('v5_servers', 'cluster_id'))->toBeTrue(); }); @@ -106,15 +87,12 @@ it('creates v5 server tables in the shared database', function () { Schema::dropIfExists('v5_servers'); Schema::dropIfExists('v5_clusters'); - $clusterMigration = include database_path('migrations/2026_06_16_130649_create_v5_clusters_table.php'); + $clusterMigration = include database_path('migrations/2026_06_16_130649_v5_create_clusters_table.php'); $clusterMigration->up(); - $migration = include database_path('migrations/2026_06_16_130650_create_v5_servers_table.php'); + $migration = include database_path('migrations/2026_06_16_130650_v5_create_servers_table.php'); $migration->up(); - $serverClusterMigration = include database_path('migrations/2026_06_16_131229_add_cluster_id_to_v5_servers_table.php'); - $serverClusterMigration->up(); - expect(Schema::hasTable('v5_servers'))->toBeTrue() ->and(Schema::hasColumns('v5_servers', [ 'id', @@ -136,20 +114,21 @@ it('creates v5 server tables in the shared database', function () { ]))->toBeTrue(); }); -it('includes v5 project tables in the dev testing schema', function () { +it('includes v5 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') + expect($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') + ->and($schema)->not->toContain('CREATE TABLE IF NOT EXISTS "v5_projects"') + ->and($schema)->not->toContain('2026_06_04_050157_v5_create_projects_table') ->and($schema)->toContain('CREATE TABLE IF NOT EXISTS "v5_servers"') ->and($schema)->toContain('"cluster_id" INTEGER') ->and($schema)->toContain('CREATE TABLE IF NOT EXISTS "v5_clusters"') ->and($schema)->toContain('"private_key_id" INTEGER') - ->and($schema)->toContain('2026_06_16_130650_create_v5_servers_table') - ->and($schema)->toContain('2026_06_16_130649_create_v5_clusters_table') - ->and($schema)->toContain('2026_06_16_131229_add_cluster_id_to_v5_servers_table') + ->and($schema)->toContain('2026_06_16_130650_v5_create_servers_table') + ->and($schema)->toContain('2026_06_16_130649_v5_create_clusters_table') + ->and($schema)->not->toContain('2026_06_16_131229_add_cluster_id_to_v5_servers_table') + ->and($schema)->not->toContain('2026_06_16_132000_make_v5_server_private_key_nullable') ->and($schema)->not->toContain('v5_hosts'); }); @@ -186,16 +165,20 @@ it('serves the v5 inertia shell', function () { ->assertSee('Home', false) ->assertDontSee('v5-ready', false) ->assertDontSee('This page is served from Laravel through Inertia and React') + ->assertDontSee('Bootstrap server') + ->assertDontSee('privateKeys', false) ->assertSee('Running') ->assertSee('Flux is running.') ->assertSee('"clusters":[]', false) - ->assertSee('"cooldServers":[]', false) + ->assertDontSee('cooldServers', false) ->assertDontSee('coold-dev') ->assertDontSee('100.64.0.1') - ->assertSee('V5 Shared Team') - ->assertSee('Shared team details') - ->assertSee('owner') - ->assertSee($user->email); + ->assertDontSee('Current team') + ->assertDontSee('Your teams') + ->assertDontSee('currentTeam', false) + ->assertDontSee('teams', false) + ->assertDontSee('V5 Shared Team') + ->assertDontSee('Shared team details'); }); it('shows v5 clusters with their servers on the inertia shell', function () { @@ -263,8 +246,8 @@ it('selects a shared team when the session has no current team', function () { ->get('/v5') ->assertSuccessful() ->assertSessionHas('currentTeam') - ->assertSee('Auto Selected Team') - ->assertSee('admin'); + ->assertDontSee('Auto Selected Team') + ->assertDontSee('admin'); }); it('shows when flux is unavailable', function () { @@ -430,9 +413,10 @@ it('runs coolify bootstrap from dynamic UI input and a selected team private key ->withSession(['currentTeam' => $team]) ->get('/v5') ->assertSuccessful() - ->assertSee('"host":"192.0.2.10"', false) - ->assertSee('"status":"installed"', false) - ->assertSee('"capabilities":["coold","builder"]', false); + ->assertDontSee('"host":"192.0.2.10"', false) + ->assertDontSee('"capabilities":["coold","builder"]', false); + + expect(V5Server::query()->where('host', '192.0.2.10')->where('status', 'installed')->exists())->toBeTrue(); $sshKeyPath = null;