mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-20 08:23:25 +00:00
feat(v5): move clusters to dedicated page and remove CLI services
Extract clusters out of Home into a standalone Clusters page with its own controller action and route. Delete CoolifyCliBootstrap and CoolifyCliVersion services along with the coolifyCliVersion endpoint. Add Dialog component, warning CSS token, Inertia progress bar, and a Clusters nav link. Extend V5Server type with ssh/builder/key fields.
This commit is contained in:
@@ -4,18 +4,15 @@ namespace App\Http\Controllers\V5;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Environment;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use App\Models\V5\Cluster as V5Cluster;
|
||||
use App\Models\V5\Server as V5Server;
|
||||
use App\Services\Coold\CoolifyCliBootstrap;
|
||||
use App\Services\Coold\CoolifyCliVersion;
|
||||
use App\Services\Flux\FluxHealth;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
@@ -33,16 +30,25 @@ class HomeController extends Controller
|
||||
|
||||
return Inertia::render('Home', [
|
||||
'flux' => $fluxHealth->check(),
|
||||
'clusters' => $this->clusters($currentTeam),
|
||||
'projects' => $projects,
|
||||
'selectedProjectUuid' => $selectedProject['uuid'] ?? null,
|
||||
'selectedEnvironmentUuid' => $selectedEnvironment['uuid'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function coolifyCliVersion(CoolifyCliVersion $coolifyCliVersion): JsonResponse
|
||||
public function clustersIndex(Request $request, FluxHealth $fluxHealth): Response
|
||||
{
|
||||
return response()->json($coolifyCliVersion->check());
|
||||
$currentTeam = $request->attributes->get('v5.currentTeam');
|
||||
$projects = $this->projects($currentTeam);
|
||||
[$selectedProject, $selectedEnvironment] = $this->selectedProjectAndEnvironment($request, $projects);
|
||||
|
||||
return Inertia::render('Clusters', [
|
||||
'flux' => $fluxHealth->check(),
|
||||
'clusters' => $this->clusters($currentTeam),
|
||||
'projects' => $projects,
|
||||
'selectedProjectUuid' => $selectedProject['uuid'] ?? null,
|
||||
'selectedEnvironmentUuid' => $selectedEnvironment['uuid'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateSelection(Request $request): \Illuminate\Http\Response
|
||||
@@ -76,77 +82,43 @@ class HomeController extends Controller
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
public function bootstrapCoolify(Request $request, CoolifyCliBootstrap $coolifyCliBootstrap): JsonResponse
|
||||
public function storeCluster(Request $request): JsonResponse
|
||||
{
|
||||
$currentTeam = $request->attributes->get('v5.currentTeam');
|
||||
$validated = $request->validate([
|
||||
'host' => ['required', 'string', 'max:255'],
|
||||
'ssh_user' => ['required', 'string', 'max:64'],
|
||||
'ssh_port' => ['required', 'integer', 'min:1', 'max:65535'],
|
||||
'private_key_uuid' => ['required', 'string'],
|
||||
'wg_listen_port' => ['nullable', 'integer', 'min:1', 'max:65535'],
|
||||
'wg_endpoint' => ['nullable', 'string', 'max:255'],
|
||||
'enable_builder' => ['boolean'],
|
||||
'builder_capacity' => ['nullable', 'integer', 'min:0', 'max:100'],
|
||||
]);
|
||||
|
||||
if (! $currentTeam instanceof Team) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$privateKey = PrivateKey::query()
|
||||
->where('team_id', $currentTeam->id)
|
||||
->where('uuid', $validated['private_key_uuid'])
|
||||
->first();
|
||||
|
||||
if (! $privateKey instanceof PrivateKey) {
|
||||
return response()->json([
|
||||
'successful' => false,
|
||||
'label' => 'Private key unavailable',
|
||||
'message' => 'The selected private key is not available for the current team.',
|
||||
'output' => null,
|
||||
'errorOutput' => null,
|
||||
'exitCode' => null,
|
||||
], 403);
|
||||
}
|
||||
|
||||
$result = $coolifyCliBootstrap->run($validated, $privateKey);
|
||||
|
||||
if ($result['successful']) {
|
||||
$this->recordBootstrappedServer($request->user(), $currentTeam, $privateKey, $validated);
|
||||
}
|
||||
|
||||
return response()->json($result, $result['successful'] ? 200 : 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{host: string, ssh_user: string, ssh_port: int, enable_builder?: bool, builder_capacity?: int|null} $input
|
||||
*/
|
||||
private function recordBootstrappedServer(User $user, Team $team, PrivateKey $privateKey, array $input): void
|
||||
{
|
||||
$builderEnabled = (bool) ($input['enable_builder'] ?? config('coold.dev_builder_enabled', true));
|
||||
$builderCapacity = $builderEnabled ? (int) ($input['builder_capacity'] ?? config('coold.dev_builder_capacity', 2)) : 0;
|
||||
$capabilities = $builderEnabled ? ['coold', 'builder'] : ['coold'];
|
||||
|
||||
V5Server::query()->updateOrCreate([
|
||||
'team_id' => $team->id,
|
||||
'host' => $input['host'],
|
||||
'ssh_port' => $input['ssh_port'],
|
||||
], [
|
||||
'created_by_user_id' => $user->id,
|
||||
'private_key_id' => $privateKey->id,
|
||||
'name' => $input['host'],
|
||||
'ssh_user' => $input['ssh_user'],
|
||||
'status' => 'installed',
|
||||
'capabilities' => $capabilities,
|
||||
'builder_enabled' => $builderEnabled,
|
||||
'builder_capacity' => $builderCapacity,
|
||||
'last_bootstrapped_at' => now(),
|
||||
$validated = $request->validate([
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
Rule::unique('v5_clusters', 'name')->where('team_id', $currentTeam->id),
|
||||
],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
]);
|
||||
|
||||
$cluster = V5Cluster::query()->create([
|
||||
'team_id' => $currentTeam->id,
|
||||
'created_by_user_id' => $request->user()->id,
|
||||
'name' => $validated['name'],
|
||||
'description' => $validated['description'] ?? null,
|
||||
]);
|
||||
|
||||
$cluster->load(['servers' => fn ($query) => $query
|
||||
->with('privateKey')
|
||||
->orderBy('name')]);
|
||||
$cluster->loadCount('servers');
|
||||
|
||||
return response()->json([
|
||||
'cluster' => $this->serializeCluster($cluster),
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{id: string, name: string, description: string|null, serversCount: int, servers: array<int, array{id: string, name: string, host: string, status: string, capabilities: array<int, string>}>}>
|
||||
* @return array<int, array{id: string, name: string, description: string|null, serversCount: int, servers: array<int, array{id: string, name: string, host: string, sshUser: string, sshPort: int, status: string, capabilities: array<int, string>, builderEnabled: bool, builderCapacity: int, privateKeyName: string|null, lastBootstrappedAt: string|null}>}>
|
||||
*/
|
||||
private function clusters(mixed $currentTeam): array
|
||||
{
|
||||
@@ -156,26 +128,42 @@ class HomeController extends Controller
|
||||
|
||||
return V5Cluster::query()
|
||||
->where('team_id', $currentTeam->id)
|
||||
->with(['servers' => fn ($query) => $query->orderBy('name')])
|
||||
->with(['servers' => fn ($query) => $query
|
||||
->with('privateKey')
|
||||
->orderBy('name')])
|
||||
->withCount('servers')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn (V5Cluster $cluster) => [
|
||||
'id' => (string) $cluster->id,
|
||||
'name' => $cluster->name,
|
||||
'description' => $cluster->description,
|
||||
'serversCount' => $cluster->servers_count,
|
||||
'servers' => $cluster->servers->map(fn (V5Server $server) => [
|
||||
'id' => (string) $server->id,
|
||||
'name' => $server->name,
|
||||
'host' => $server->host,
|
||||
'status' => $server->status,
|
||||
'capabilities' => $server->capabilities ?? [],
|
||||
])->all(),
|
||||
])
|
||||
->map(fn (V5Cluster $cluster) => $this->serializeCluster($cluster))
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: string, name: string, description: string|null, serversCount: int, servers: array<int, array{id: string, name: string, host: string, sshUser: string, sshPort: int, status: string, capabilities: array<int, string>, builderEnabled: bool, builderCapacity: int, privateKeyName: string|null, lastBootstrappedAt: string|null}>}
|
||||
*/
|
||||
private function serializeCluster(V5Cluster $cluster): array
|
||||
{
|
||||
return [
|
||||
'id' => (string) $cluster->id,
|
||||
'name' => $cluster->name,
|
||||
'description' => $cluster->description,
|
||||
'serversCount' => $cluster->servers_count ?? $cluster->servers->count(),
|
||||
'servers' => $cluster->servers->map(fn (V5Server $server) => [
|
||||
'id' => (string) $server->id,
|
||||
'name' => $server->name,
|
||||
'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,
|
||||
'privateKeyName' => $server->privateKey?->name,
|
||||
'lastBootstrappedAt' => $server->last_bootstrapped_at?->toJSON(),
|
||||
])->all(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{uuid: string, name: string, environments: array<int, array{uuid: string, name: string}>}> $projects
|
||||
* @return array{0: array{uuid: string, name: string, environments: array<int, array{uuid: string, name: string}>}|null, 1: array{uuid: string, name: string}|null}
|
||||
|
||||
Reference in New Issue
Block a user