feat(v5): add mesh app canvas

This commit is contained in:
Andras Bacsai committed 2026-06-20 09:23:16 +02:00
1 parent 155b07378c
commit 86156b6f7a
53 files changed
+6037 -113

No files matched your search

@@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('v5_applications', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained('teams')->cascadeOnDelete();
$table->foreignId('project_id')->constrained('projects')->cascadeOnDelete();
$table->foreignId('environment_id')->constrained('environments')->cascadeOnDelete();
$table->foreignId('server_id')->nullable()->constrained('v5_servers')->nullOnDelete();
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete();
$table->string('name');
$table->string('image');
$table->string('container_name')->unique();
$table->string('status')->default('creating');
$table->text('status_message')->nullable();
$table->string('runtime_container_id')->nullable();
$table->string('mesh_namespace')->default('default');
$table->integer('canvas_x')->default(0);
$table->integer('canvas_y')->default(0);
$table->timestamps();
$table->index(['team_id', 'status']);
$table->index(['team_id', 'project_id', 'environment_id']);
$table->index(['team_id', 'server_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('v5_applications');
}
};
@@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->integer('canvas_x')->nullable()->after('container_subnets');
$table->integer('canvas_y')->nullable()->after('canvas_x');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->dropColumn(['canvas_x', 'canvas_y']);
});
}
};
@@ -0,0 +1,58 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('v5_resource_connections', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained('teams')->cascadeOnDelete();
$table->foreignId('project_id')->constrained('projects')->cascadeOnDelete();
$table->foreignId('environment_id')->constrained('environments')->cascadeOnDelete();
$table->morphs('resource_one');
$table->morphs('resource_two');
$table->string('resource_pair_key');
$table->foreignId('created_by_user_id')->constrained('users')->cascadeOnDelete();
$table->timestamps();
$table->unique(['team_id', 'resource_pair_key']);
$table->index(['team_id', 'project_id', 'environment_id']);
});
Schema::create('v5_resource_connection_rules', function (Blueprint $table) {
$table->id();
$table->foreignId('connection_id')->constrained('v5_resource_connections')->cascadeOnDelete();
$table->morphs('source_resource');
$table->morphs('target_resource');
$table->string('protocol')->default('tcp');
$table->unsignedSmallInteger('port');
$table->timestamps();
$table->unique([
'connection_id',
'source_resource_type',
'source_resource_id',
'target_resource_type',
'target_resource_id',
'protocol',
'port',
], 'v5_resource_connection_rules_unique_direction_port');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('v5_resource_connection_rules');
Schema::dropIfExists('v5_resource_connections');
}
};
@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->string('caddy_ingress_status')->nullable()->after('status');
});
DB::table('v5_servers')
->where('status', 'installed')
->where(function ($query) {
$query
->where('capabilities', 'like', '%"ingress"%')
->orWhere('capabilities', 'like', '%ingress%');
})
->update(['caddy_ingress_status' => 'running']);
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('v5_servers', function (Blueprint $table) {
$table->dropColumn('caddy_ingress_status');
});
}
};
@@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('v5_container_statuses', function (Blueprint $table) {
$table->id();
$table->foreignId('team_id')->constrained('teams')->cascadeOnDelete();
$table->foreignId('server_id')->constrained('v5_servers')->cascadeOnDelete();
$table->string('container_id');
$table->string('container_name')->nullable();
$table->string('image')->nullable();
$table->string('status')->default('unknown');
$table->text('status_message')->nullable();
$table->timestamp('last_seen_at')->nullable();
$table->timestamps();
$table->unique(['server_id', 'container_id']);
$table->index(['team_id', 'server_id']);
$table->index(['team_id', 'status']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('v5_container_statuses');
}
};
+42
View File
@@ -1363,6 +1363,7 @@ CREATE TABLE IF NOT EXISTS "v5_servers" (
"ssh_user" TEXT NOT NULL,
"ssh_port" INTEGER DEFAULT '22' NOT NULL,
"status" TEXT DEFAULT 'installed' NOT NULL,
"caddy_ingress_status" TEXT,
"capabilities" TEXT,
"builder_enabled" INTEGER DEFAULT false NOT NULL,
"builder_capacity" INTEGER DEFAULT '0' NOT NULL,
@@ -1373,6 +1374,8 @@ CREATE TABLE IF NOT EXISTS "v5_servers" (
"wireguard_management_ip" TEXT,
"wireguard_public_key" TEXT,
"container_subnets" JSON,
"canvas_x" INTEGER,
"canvas_y" INTEGER,
"last_bootstrapped_at" TEXT,
"last_bootstrap_action" TEXT,
"last_bootstrap_status" TEXT,
@@ -1385,6 +1388,40 @@ CREATE TABLE IF NOT EXISTS "v5_servers" (
"updated_at" TEXT
);
CREATE TABLE IF NOT EXISTS "v5_container_statuses" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"team_id" INTEGER NOT NULL,
"server_id" INTEGER NOT NULL,
"container_id" TEXT NOT NULL,
"container_name" TEXT,
"image" TEXT,
"status" TEXT DEFAULT 'unknown' NOT NULL,
"status_message" TEXT,
"last_seen_at" TEXT,
"created_at" TEXT,
"updated_at" TEXT
);
CREATE TABLE IF NOT EXISTS "v5_applications" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"team_id" INTEGER NOT NULL,
"project_id" INTEGER NOT NULL,
"environment_id" INTEGER NOT NULL,
"server_id" INTEGER,
"created_by_user_id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"image" TEXT NOT NULL,
"container_name" TEXT NOT NULL,
"status" TEXT DEFAULT 'creating' NOT NULL,
"status_message" TEXT,
"runtime_container_id" TEXT,
"mesh_namespace" TEXT DEFAULT 'default' NOT NULL,
"canvas_x" INTEGER DEFAULT '0' NOT NULL,
"canvas_y" INTEGER DEFAULT '0' NOT NULL,
"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,
@@ -1499,6 +1536,7 @@ CREATE INDEX IF NOT EXISTS "user_changelog_reads_release_tag_index" ON "user_cha
CREATE INDEX IF NOT EXISTS "user_changelog_reads_user_id_index" ON "user_changelog_reads" (user_id);
CREATE UNIQUE INDEX IF NOT EXISTS "user_changelog_reads_user_id_release_tag_unique" ON "user_changelog_reads" (user_id, release_tag);
CREATE UNIQUE INDEX IF NOT EXISTS "users_email_unique" ON "users" (email);
CREATE UNIQUE INDEX IF NOT EXISTS "v5_applications_container_name_unique" ON "v5_applications" (container_name);
CREATE UNIQUE INDEX IF NOT EXISTS "v5_servers_uuid_unique" ON "v5_servers" (uuid);
CREATE UNIQUE INDEX IF NOT EXISTS "webhook_notification_settings_team_id_unique" ON "webhook_notification_settings" (team_id);
@@ -1819,3 +1857,7 @@ INSERT INTO "migrations" ("id", "migration", "batch") VALUES (313, '2025_12_17_0
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 (316, '2026_06_16_130649_v5_create_clusters_table', 316);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (317, '2026_06_16_130650_v5_create_servers_table', 317);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (318, '2026_06_19_140000_v5_create_applications_table', 318);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (319, '2026_06_19_141231_add_canvas_position_to_v5_servers_table', 319);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (320, '2026_06_19_173933_add_caddy_ingress_status_to_v5_servers_table', 320);
INSERT INTO "migrations" ("id", "migration", "batch") VALUES (321, '2026_06_19_182231_create_container_statuses_table', 321);
+18 -33
View File
@@ -2,11 +2,10 @@
namespace Database\Seeders;
use App\Actions\V5\Server\SyncDevLimaServers;
use App\Models\PrivateKey;
use App\Models\Team;
use App\Models\User;
use App\Models\V5\Cluster;
use App\Models\V5\Server;
use Illuminate\Database\Seeder;
class V5DevLimaSeeder extends Seeder
@@ -25,14 +24,6 @@ class V5DevLimaSeeder extends Seeder
return;
}
$cluster = Cluster::query()->updateOrCreate([
'team_id' => $team->id,
'name' => self::CLUSTER_NAME,
], [
'created_by_user_id' => $user->id,
'description' => 'Local Lima development cluster managed by scripts/dev.sh.',
]);
$privateKey = PrivateKey::query()
->where('team_id', $team->id)
->where('is_git_related', false)
@@ -40,34 +31,26 @@ class V5DevLimaSeeder extends Seeder
->first();
$builderCapacity = max(0, (int) config('coold.dev_builder_capacity', 2));
$builderEnabled = $builderCapacity > 0;
$capabilities = $builderEnabled ? ['coold', 'builder'] : ['coold'];
$sshUser = (string) config('coold.dev_ssh_user', get_current_user());
foreach ($this->servers() as $server) {
Server::query()->updateOrCreate([
'team_id' => $team->id,
'host' => $server['host'],
'ssh_port' => $server['ssh_port'],
], [
'cluster_id' => $cluster->id,
'created_by_user_id' => $user->id,
'private_key_id' => $privateKey?->id,
'name' => $server['name'],
$servers = collect($this->servers())
->map(fn (array $server): array => [
...$server,
'ssh_user' => $sshUser,
'status' => 'installed',
'capabilities' => $capabilities,
'builder_enabled' => $builderEnabled,
'builder_capacity' => $builderCapacity,
'wireguard_listen_port_override' => $server['wireguard_listen_port_override'],
'wireguard_endpoint_override' => $server['wireguard_endpoint_override'],
'last_bootstrapped_at' => now(),
]);
}
])
->all();
SyncDevLimaServers::run(
team: $team,
user: $user,
privateKey: $privateKey,
clusterName: self::CLUSTER_NAME,
builderCapacity: $builderCapacity,
servers: $servers,
);
}
/**
* @return array<int, array{name: string, host: string, ssh_port: int, wireguard_listen_port_override: int, wireguard_endpoint_override: string}>
* @return array<int, array{name: string, host: string, ssh_port: int, wireguard_management_ip: string, wireguard_listen_port_override: int, wireguard_endpoint_override: string}>
*/
private function servers(): array
{
@@ -76,6 +59,7 @@ class V5DevLimaSeeder extends Seeder
'name' => 'coold-dev',
'host' => 'host.docker.internal',
'ssh_port' => 60001,
'wireguard_management_ip' => '100.64.0.1',
'wireguard_listen_port_override' => 51821,
'wireguard_endpoint_override' => 'host.lima.internal:51821',
],
@@ -83,6 +67,7 @@ class V5DevLimaSeeder extends Seeder
'name' => 'coold-dev-2',
'host' => 'host.docker.internal',
'ssh_port' => 60002,
'wireguard_management_ip' => '100.64.0.2',
'wireguard_listen_port_override' => 51822,
'wireguard_endpoint_override' => 'host.lima.internal:51822',
],