Refactor to remove slices and environments, replace with instances.
This commit is contained in:
11
.instructions/project-refactor.md
Normal file
11
.instructions/project-refactor.md
Normal file
@@ -0,0 +1,11 @@
|
||||
The project's structure is changing. The current setup is Applications have Environments and those environments have slices that belong to them.
|
||||
|
||||
We're no longer doing this. The application is changing to be more server-centric.
|
||||
|
||||
Slices and Environments can be removed.
|
||||
|
||||
Servers are going to have services, which can be selected on server provision. Once the server has finished provisioning, we run deployments for all of the services the user has selected for that server.
|
||||
|
||||
Applications can be installed on a server. The application could be installed across multiple servers, and deployed at the same time. The application isn't tied directly to the server, instead it has an Instance model that acts as an intermediary. This means that the user can deploy an Application which will update both instances.
|
||||
|
||||
Create a plan for refactoring the project to support this new structure.
|
||||
11
.mcp.json
Normal file
11
.mcp.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"laravel-boost": {
|
||||
"command": "php",
|
||||
"args": [
|
||||
"artisan",
|
||||
"boost:mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
24
app/Actions/Applications/CreateInstance.php
Normal file
24
app/Actions/Applications/CreateInstance.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Applications;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Instance;
|
||||
use App\Models\Server;
|
||||
|
||||
class CreateInstance
|
||||
{
|
||||
public function execute(
|
||||
Application $application,
|
||||
Server $server,
|
||||
string $branch = 'main',
|
||||
array $config = []
|
||||
): Instance {
|
||||
return $application->instances()->create([
|
||||
'server_id' => $server->id,
|
||||
'branch' => $branch,
|
||||
'status' => 'pending',
|
||||
'config' => $config,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data\Slices;
|
||||
|
||||
class CaddySliceData
|
||||
{
|
||||
public function __construct(
|
||||
public string $domain,
|
||||
public string $type,
|
||||
public array $targets
|
||||
) {}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\ServerStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\Organisation;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -9,7 +11,7 @@ class ApplicationController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$organisation = Organisation::with('applications')->findOrFail($request->route('organisation'));
|
||||
$organisation = Organisation::with('applications.instances.server')->findOrFail($request->route('organisation'));
|
||||
|
||||
return inertia('applications/Index', [
|
||||
'applications' => $organisation->applications,
|
||||
@@ -19,7 +21,18 @@ class ApplicationController extends Controller
|
||||
public function show(Request $request)
|
||||
{
|
||||
$id = $request->route('application');
|
||||
$application = Application::with(['instances.server', 'organisation'])->findOrFail($id);
|
||||
|
||||
return inertia('applications/Show');
|
||||
return inertia('applications/Show', [
|
||||
'application' => $application,
|
||||
'servers' => inertia()->optional(function () use ($application) {
|
||||
return $application
|
||||
->organisation
|
||||
?->servers()
|
||||
->where('status', ServerStatus::ACTIVE)
|
||||
->with('services')
|
||||
->get() ?? [];
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\ServerStatus;
|
||||
use App\Models\Environment;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EnvironmentController extends Controller
|
||||
{
|
||||
public function show(Request $request)
|
||||
{
|
||||
$id = $request->route('environment');
|
||||
$environment = Environment::with('application')->findOrFail($id);
|
||||
|
||||
return inertia('environments/Show', [
|
||||
'environment' => $environment,
|
||||
'servers' => inertia()->optional(function () use ($environment) {
|
||||
return $environment
|
||||
->application
|
||||
?->organisation
|
||||
?->servers()
|
||||
->where('status', ServerStatus::ACTIVE)
|
||||
->with('services')
|
||||
->get() ?? [];
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
app/Http/Controllers/InstanceController.php
Normal file
36
app/Http/Controllers/InstanceController.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Applications\CreateInstance;
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class InstanceController extends Controller
|
||||
{
|
||||
public function store(Request $request, Application $application)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'server_id' => 'required|exists:servers,id',
|
||||
'branch' => 'required|string|max:255',
|
||||
'config' => 'sometimes|array',
|
||||
]);
|
||||
|
||||
$server = Server::findOrFail($validated['server_id']);
|
||||
|
||||
$instance = (new CreateInstance())->execute(
|
||||
$application,
|
||||
$server,
|
||||
$validated['branch'],
|
||||
$validated['config'] ?? []
|
||||
);
|
||||
|
||||
return redirect()
|
||||
->route('applications.show', [
|
||||
'organisation' => $application->organisation_id,
|
||||
'application' => $application->id
|
||||
])
|
||||
->with('success', 'Instance created successfully');
|
||||
}
|
||||
}
|
||||
59
app/Jobs/Applications/DeployApplication.php
Normal file
59
app/Jobs/Applications/DeployApplication.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Applications;
|
||||
|
||||
use App\Enums\DeploymentStatus;
|
||||
use App\Models\Application;
|
||||
use App\Models\Deployment;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
|
||||
class DeployApplication implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
protected Deployment $deployment;
|
||||
|
||||
public function __construct(
|
||||
public Application $application,
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$this->deployment = $this->application->deployments()->create([
|
||||
'status' => DeploymentStatus::PENDING,
|
||||
]);
|
||||
|
||||
foreach ($this->application->instances as $instance) {
|
||||
$step = $this->deployment->steps()->create([
|
||||
'name' => "Deploy to {$instance->server->name}",
|
||||
'order' => $instance->id,
|
||||
'status' => DeploymentStatus::PENDING,
|
||||
'script' => $this->getDeploymentScript($instance),
|
||||
'secrets' => [],
|
||||
]);
|
||||
|
||||
$step->dispatchJob();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDeploymentScript($instance): string
|
||||
{
|
||||
return "#!/bin/bash\n" .
|
||||
"cd /opt/apps/{$this->application->name}-{$instance->id}\n" .
|
||||
"git fetch origin\n" .
|
||||
"git checkout {$instance->branch}\n" .
|
||||
"git pull origin {$instance->branch}\n";
|
||||
}
|
||||
|
||||
public function failed(\Throwable $exception): void
|
||||
{
|
||||
if (isset($this->deployment)) {
|
||||
$this->deployment->update([
|
||||
'status' => DeploymentStatus::FAILED,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ use App\Enums\RepositoryType;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
class Application extends Model
|
||||
{
|
||||
@@ -23,8 +25,18 @@ class Application extends Model
|
||||
return $this->belongsTo(Organisation::class);
|
||||
}
|
||||
|
||||
public function environments(): HasMany
|
||||
public function instances(): HasMany
|
||||
{
|
||||
return $this->hasMany(Environment::class);
|
||||
return $this->hasMany(Instance::class);
|
||||
}
|
||||
|
||||
public function servers(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Server::class, Instance::class);
|
||||
}
|
||||
|
||||
public function deployments(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Deployment::class, 'target');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
|
||||
class Environment extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function application(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Application::class);
|
||||
}
|
||||
|
||||
public function slices(): HasMany
|
||||
{
|
||||
return $this->hasMany(Slice::class);
|
||||
}
|
||||
|
||||
public function services(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Service::class, Slice::class);
|
||||
}
|
||||
}
|
||||
34
app/Models/Instance.php
Normal file
34
app/Models/Instance.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
class Instance extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'config' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function application(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Application::class);
|
||||
}
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function deployments(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Deployment::class, 'target');
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,16 @@ class Server extends Model
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
|
||||
public function instances(): HasMany
|
||||
{
|
||||
return $this->hasMany(Instance::class);
|
||||
}
|
||||
|
||||
public function applications(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Application::class, Instance::class);
|
||||
}
|
||||
|
||||
public function firewallRules(): HasMany
|
||||
{
|
||||
return $this->hasMany(FirewallRule::class);
|
||||
@@ -64,14 +74,14 @@ class Server extends Model
|
||||
)->where('target_type', (new Service)->getMorphClass());
|
||||
}
|
||||
|
||||
public function environmentDeployments(): HasManyThrough
|
||||
public function applicationDeployments(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
Deployment::class,
|
||||
Environment::class,
|
||||
Application::class,
|
||||
'server_id',
|
||||
'target_id',
|
||||
)->where('target_type', (new Environment)->getMorphClass());
|
||||
)->where('target_type', (new Application)->getMorphClass());
|
||||
}
|
||||
|
||||
public function sshClient(string $user = 'root'): Ssh
|
||||
|
||||
@@ -10,7 +10,6 @@ use App\Enums\ServiceType;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
class Service extends Model
|
||||
@@ -41,11 +40,6 @@ class Service extends Model
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function slices(): HasMany
|
||||
{
|
||||
return $this->hasMany(Slice::class);
|
||||
}
|
||||
|
||||
public function deployments(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Deployment::class, 'target');
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Slice extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function service(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Service::class);
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,11 @@ namespace App\Providers;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Deployment;
|
||||
use App\Models\Environment;
|
||||
use App\Models\Instance;
|
||||
use App\Models\Organisation;
|
||||
use App\Models\OrganisationUser;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\Slice;
|
||||
use App\Models\Step;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
@@ -33,12 +32,11 @@ class AppServiceProvider extends ServiceProvider
|
||||
Relation::enforceMorphMap([
|
||||
'application' => Application::class,
|
||||
'deployment' => Deployment::class,
|
||||
'environment' => Environment::class,
|
||||
'instance' => Instance::class,
|
||||
'organisation' => Organisation::class,
|
||||
'organisation-user' => OrganisationUser::class,
|
||||
'server' => Server::class,
|
||||
'service' => Service::class,
|
||||
'slice' => Slice::class,
|
||||
'step' => Step::class,
|
||||
'user' => User::class,
|
||||
]);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/boost": "^1.1",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.18",
|
||||
"laravel/sail": "^1.41",
|
||||
|
||||
192
composer.lock
generated
192
composer.lock
generated
@@ -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": "0ad069ff3461e30a7d92cfea145f68e7",
|
||||
"content-hash": "69f6de114270a8beb46d9283a2acd24d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -6600,6 +6600,135 @@
|
||||
},
|
||||
"time": "2025-03-19T14:43:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/boost",
|
||||
"version": "v1.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/boost.git",
|
||||
"reference": "70f909465bf73dad7e791fad8b7716b3b2712076"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/boost/zipball/70f909465bf73dad7e791fad8b7716b3b2712076",
|
||||
"reference": "70f909465bf73dad7e791fad8b7716b3b2712076",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"illuminate/console": "^10.0|^11.0|^12.0",
|
||||
"illuminate/contracts": "^10.0|^11.0|^12.0",
|
||||
"illuminate/routing": "^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"laravel/mcp": "^0.1.1",
|
||||
"laravel/prompts": "^0.1.9|^0.3",
|
||||
"laravel/roster": "^0.2.5",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.14",
|
||||
"mockery/mockery": "^1.6",
|
||||
"orchestra/testbench": "^8.22.0|^9.0|^10.0",
|
||||
"pestphp/pest": "^2.0|^3.0",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Boost\\BoostServiceProvider"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Boost\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Laravel Boost accelerates AI-assisted development to generate high-quality, Laravel-specific code.",
|
||||
"homepage": "https://github.com/laravel/boost",
|
||||
"keywords": [
|
||||
"ai",
|
||||
"dev",
|
||||
"laravel"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/boost/issues",
|
||||
"source": "https://github.com/laravel/boost"
|
||||
},
|
||||
"time": "2025-09-04T12:16:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/mcp",
|
||||
"version": "v0.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/mcp.git",
|
||||
"reference": "6d6284a491f07c74d34f48dfd999ed52c567c713"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/mcp/zipball/6d6284a491f07c74d34f48dfd999ed52c567c713",
|
||||
"reference": "6d6284a491f07c74d34f48dfd999ed52c567c713",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/console": "^10.0|^11.0|^12.0",
|
||||
"illuminate/contracts": "^10.0|^11.0|^12.0",
|
||||
"illuminate/http": "^10.0|^11.0|^12.0",
|
||||
"illuminate/routing": "^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"illuminate/validation": "^10.0|^11.0|^12.0",
|
||||
"php": "^8.1|^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.14",
|
||||
"orchestra/testbench": "^8.22.0|^9.0|^10.0",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Mcp": "Laravel\\Mcp\\Server\\Facades\\Mcp"
|
||||
},
|
||||
"providers": [
|
||||
"Laravel\\Mcp\\Server\\McpServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Mcp\\": "src/",
|
||||
"Workbench\\App\\": "workbench/app/",
|
||||
"Laravel\\Mcp\\Tests\\": "tests/",
|
||||
"Laravel\\Mcp\\Server\\": "src/Server/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "The easiest way to add MCP servers to your Laravel app.",
|
||||
"homepage": "https://github.com/laravel/mcp",
|
||||
"keywords": [
|
||||
"dev",
|
||||
"laravel",
|
||||
"mcp"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/mcp/issues",
|
||||
"source": "https://github.com/laravel/mcp"
|
||||
},
|
||||
"time": "2025-08-16T09:50:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/pail",
|
||||
"version": "v1.2.2",
|
||||
@@ -6744,6 +6873,67 @@
|
||||
},
|
||||
"time": "2025-03-14T22:31:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/roster",
|
||||
"version": "v0.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/roster.git",
|
||||
"reference": "5615acdf860c5a5c61d04aba44f2d3312550c514"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/roster/zipball/5615acdf860c5a5c61d04aba44f2d3312550c514",
|
||||
"reference": "5615acdf860c5a5c61d04aba44f2d3312550c514",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/console": "^10.0|^11.0|^12.0",
|
||||
"illuminate/contracts": "^10.0|^11.0|^12.0",
|
||||
"illuminate/routing": "^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"php": "^8.1|^8.2",
|
||||
"symfony/yaml": "^6.4|^7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.14",
|
||||
"mockery/mockery": "^1.6",
|
||||
"orchestra/testbench": "^8.22.0|^9.0|^10.0",
|
||||
"pestphp/pest": "^2.0|^3.0",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Laravel\\Roster\\RosterServiceProvider"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Roster\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Detect packages & approaches in use within a Laravel project",
|
||||
"homepage": "https://github.com/laravel/roster",
|
||||
"keywords": [
|
||||
"dev",
|
||||
"laravel"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/roster/issues",
|
||||
"source": "https://github.com/laravel/roster"
|
||||
},
|
||||
"time": "2025-09-04T07:31:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sail",
|
||||
"version": "v1.41.0",
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\Service;
|
||||
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('slices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(Service::class);
|
||||
$table->foreignIdFor(Environment::class);
|
||||
$table->json('data')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('slices');
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -9,19 +10,19 @@ return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('environments', function (Blueprint $table) {
|
||||
Schema::create('instances', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(Application::class);
|
||||
$table->string('name');
|
||||
$table->foreignIdFor(Server::class);
|
||||
$table->string('branch');
|
||||
$table->string('url');
|
||||
$table->string('status');
|
||||
$table->json('config')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('environments');
|
||||
Schema::dropIfExists('instances');
|
||||
}
|
||||
};
|
||||
8
opencode.json
Normal file
8
opencode.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tools": {
|
||||
"laravel-boost": {
|
||||
"command": "php",
|
||||
"args": ["artisan", "boost:mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import { Head, Link } from '@inertiajs/vue3';
|
||||
import { Layers2Icon } from 'lucide-vue-next';
|
||||
import { ServerIcon } from 'lucide-vue-next';
|
||||
|
||||
const props = defineProps({
|
||||
application: {
|
||||
@@ -38,43 +38,35 @@ const props = defineProps({
|
||||
|
||||
<div>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h3 class="text-2xl font-semibold tracking-tight">Environments</h3>
|
||||
<h3 class="text-2xl font-semibold tracking-tight">Server Instances</h3>
|
||||
<div>
|
||||
<!-- <Button
|
||||
:as="Link"
|
||||
:href="
|
||||
route('environments.create', {
|
||||
organisation: $page.props.organisation.id,
|
||||
server: application.id,
|
||||
})
|
||||
"
|
||||
size="xs"
|
||||
>
|
||||
<PlusIcon class="size-4" />
|
||||
Add
|
||||
</Button> -->
|
||||
<!-- Add instance button would go here -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<Card v-for="environment in application.environments" :key="environment.id" class="relative">
|
||||
<Link class="absolute inset-0" :href="route('environments.show', {
|
||||
organisation: $page.props.organisation.id,
|
||||
application: application.id,
|
||||
environment: environment.id,
|
||||
})"></Link>
|
||||
<Card v-for="instance in application.instances" :key="instance.id" class="relative">
|
||||
<Link
|
||||
class="absolute inset-0"
|
||||
:href="
|
||||
route('servers.show', {
|
||||
organisation: $page.props.organisation.id,
|
||||
server: instance.server.id,
|
||||
})
|
||||
"
|
||||
></Link>
|
||||
<CardHeader>
|
||||
<div class="flex items-center gap-2">
|
||||
<CardTitle>{{ environment.name }}</CardTitle>
|
||||
<Badge :variant="environment.status === 'active' ? 'success' : 'secondary'">{{ environment.status.replace('-', ' ') }}</Badge>
|
||||
<ServerIcon class="size-4" />
|
||||
<CardTitle>{{ instance.server.name }}</CardTitle>
|
||||
<Badge :variant="instance.status === 'active' ? 'success' : 'secondary'">{{
|
||||
instance.status.replace('-', ' ')
|
||||
}}</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
<span class="capitalize">{{ environment.type }}</span> {{ environment.version }}
|
||||
</CardDescription>
|
||||
<CardDescription> Branch: {{ instance.branch }} </CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
{{ application }}
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
<script setup>
|
||||
import ServerSelector from '@/components/ServerSelector.vue';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import ServiceCategory from '@/enums/ServiceCategory';
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import { Head, router, usePage } from '@inertiajs/vue3';
|
||||
import { PlusIcon } from 'lucide-vue-next';
|
||||
|
||||
defineProps({
|
||||
environment: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
servers: {
|
||||
type: Array,
|
||||
required: false,
|
||||
}
|
||||
});
|
||||
|
||||
function selectServer(server, serviceCategory = null) {
|
||||
if (serviceCategory) {
|
||||
if (server.services?.find((s) => s.category === serviceCategory)) {
|
||||
// service is installed
|
||||
return;
|
||||
} else {
|
||||
router.visit(route('servers.show', {
|
||||
organisation: usePage().props.organisation.id,
|
||||
server: server.id,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head :title="environment.name" />
|
||||
|
||||
<AppLayout
|
||||
:breadcrumbs="[
|
||||
{
|
||||
title: 'Applications',
|
||||
href: route('applications.index', { organisation: $page.props.organisation.id }),
|
||||
},
|
||||
{
|
||||
title: environment.application.name,
|
||||
href: route('applications.show', {
|
||||
organisation: $page.props.organisation.id,
|
||||
application: environment.application.id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'Environments',
|
||||
href: route('applications.show', {
|
||||
organisation: $page.props.organisation.id,
|
||||
application: environment.application.id,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: environment.name,
|
||||
href: route('environments.show', {
|
||||
organisation: $page.props.organisation.id,
|
||||
application: environment.application.id,
|
||||
environment: environment.id,
|
||||
}),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<div class="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
|
||||
<Card class="pattern-graph-paper grid grid-cols-3 gap-6 p-6">
|
||||
<div class="space-y-2">
|
||||
<ServerSelector :servers="servers" :service-category="ServiceCategory.GATEWAY" @select="selectServer($event, ServiceCategory.GATEWAY)">
|
||||
<template #trigger>
|
||||
<Card class="group flex select-none items-center gap-2 bg-card/30 p-4 text-sm backdrop-blur-sm">
|
||||
<PlusIcon class="size-4 opacity-50" />
|
||||
<span class="opacity-50 transition group-hover:opacity-100">Install a gateway</span>
|
||||
</Card>
|
||||
</template>
|
||||
</ServerSelector>
|
||||
<!-- <ServiceCard :icon="DoorOpenIcon" :service-type="ServiceType.CADDY" :service-category="ServiceCategory.GATEWAY" :status="ServiceStatus.NOT_INSTALLED" /> -->
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Card class="group flex select-none items-center gap-2 bg-card/30 p-4 text-sm backdrop-blur-sm">
|
||||
<PlusIcon class="size-4 opacity-50" />
|
||||
<span class="opacity-50 transition group-hover:opacity-100">Install your application on a server</span>
|
||||
</Card>
|
||||
<!-- <ServiceCard
|
||||
:icon="AppWindowIcon"
|
||||
:service-type="ServiceType.FRANKENPHP"
|
||||
:service-category="ServiceCategory.APPLICATION"
|
||||
:status="ServiceStatus.NOT_INSTALLED"
|
||||
/> -->
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<!-- <ServiceCard :icon="DatabaseIcon" :service-type="ServiceType.POSTGRES" :service-category="ServiceCategory.DATABASE" :status="ServiceStatus.NOT_INSTALLED" /> -->
|
||||
<!-- <ServiceCard :icon="DatabaseZap" :service-type="ServiceType.REDIS" :service-category="ServiceCategory.CACHE" :status="ServiceStatus.NOT_INSTALLED" /> -->
|
||||
<Card class="group flex select-none items-center gap-2 bg-card/30 p-4 text-sm backdrop-blur-sm">
|
||||
<PlusIcon class="size-4 opacity-50" />
|
||||
<span class="opacity-50 transition group-hover:opacity-100">Add a database</span>
|
||||
</Card>
|
||||
<Card class="group flex select-none items-center gap-2 bg-card/30 p-4 text-sm backdrop-blur-sm">
|
||||
<PlusIcon class="size-4 opacity-50" />
|
||||
<span class="opacity-50 transition group-hover:opacity-100">Add cache</span>
|
||||
</Card>
|
||||
</div>
|
||||
</Card>
|
||||
{{ environment }}
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -21,11 +21,14 @@ const tabValue = ref(new URL(window.location.href).hash?.replace('#', '') || 'da
|
||||
watch(tabValue, () => {
|
||||
window.history.pushState({}, '', `#${tabValue.value}`);
|
||||
});
|
||||
watch(() => window.location.hash, (newHash) => {
|
||||
if (newHash) {
|
||||
tabValue.value = newHash.replace('#', '');
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => window.location.hash,
|
||||
(newHash) => {
|
||||
if (newHash) {
|
||||
tabValue.value = newHash.replace('#', '');
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -93,7 +96,7 @@ watch(() => window.location.hash, (newHash) => {
|
||||
<template #fallback> Loading... </template>
|
||||
<h3 class="mt-4 text-2xl font-bold tracking-tight">Server Providers</h3>
|
||||
<p class="mb-4 text-sm text-muted-foreground">Manage your server providers.</p>
|
||||
<div class="border-muted-background divide-y-muted-background divide-y rounded-md border max-w-80">
|
||||
<div class="border-muted-background divide-y-muted-background max-w-80 divide-y rounded-md border">
|
||||
<div v-for="provider in providers" class="flex items-center gap-2 px-2 py-1">
|
||||
{{ provider.name }}
|
||||
<span class="ml-auto text-xs uppercase text-muted-foreground">{{ provider.type }}</span>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\ApplicationController;
|
||||
use App\Http\Controllers\EnvironmentController;
|
||||
use App\Http\Controllers\OrganisationController;
|
||||
use App\Http\Controllers\ProvisionCallback;
|
||||
use App\Http\Controllers\ProvisionScript;
|
||||
@@ -39,12 +38,6 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
||||
->only('show', 'index')
|
||||
->name('index', 'applications.index')
|
||||
->name('show', 'applications.show');
|
||||
|
||||
Route::prefix('applications/{application}')->group(function () {
|
||||
Route::resource('environments', EnvironmentController::class)
|
||||
->only('show')
|
||||
->name('show', 'environments.show');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user