This commit is contained in:
Saeed Vaziry
2025-06-04 08:08:20 +02:00
parent efacadba10
commit c3f69f3247
114 changed files with 4032 additions and 765 deletions

View File

@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Site\Deploy;
use App\Actions\Site\UpdateDeploymentScript;
use App\Actions\Site\UpdateEnv;
use App\Actions\Site\UpdateLoadBalancer;
use App\Exceptions\DeploymentScriptIsEmptyException;
use App\Exceptions\FailedToDestroyGitHook;
use App\Exceptions\SourceControlIsNotConnected;
use App\Exceptions\SSHError;
use App\Http\Resources\DeploymentResource;
use App\Http\Resources\LoadBalancerServerResource;
use App\Models\Server;
use App\Models\Site;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
use Spatie\RouteAttributes\Attributes\Put;
#[Prefix('/servers/{server}/sites/{site}')]
#[Middleware(['auth', 'has-project'])]
class ApplicationController extends Controller
{
#[Get('/', name: 'application')]
public function index(Server $server, Site $site): Response
{
$this->authorize('view', [$site, $server]);
return Inertia::render('application/index', [
'deployments' => DeploymentResource::collection($site->deployments()->latest()->simplePaginate(config('web.pagination_size'))),
'deploymentScript' => $site->deploymentScript?->content,
'loadBalancerServers' => LoadBalancerServerResource::collection($site->loadBalancerServers)
]);
}
#[Put('/deployment-script', name: 'application.update-deployment-script')]
public function updateDeploymentScript(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
app(UpdateDeploymentScript::class)->update($site, $request->input());
return back()->with('success', 'Deployment script updated successfully.');
}
/**
* @throws DeploymentScriptIsEmptyException
*/
#[Post('/deploy', name: 'application.deploy')]
public function deploy(Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
app(Deploy::class)->run($site);
return back()->with('info', 'Deployment started, please wait...');
}
#[Get('/env', name: 'application.env')]
public function env(Server $server, Site $site): JsonResponse
{
$this->authorize('view', [$site, $server]);
$env = $site->getEnv();
return response()->json([
'env' => $env,
]);
}
/**
* @throws SSHError
*/
#[Put('/env', name: 'application.update-env')]
public function updateEnv(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
app(UpdateEnv::class)->update($site, $request->input());
return back()->with('success', '.env file updated successfully.');
}
/**
* @throws SourceControlIsNotConnected
*/
#[Post('/enable-auto-deployment', name: 'application.enable-auto-deployment')]
public function enableAutoDeployment(Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
if (! $site->sourceControl) {
return back()->with('error', 'Cannot find source control for this site.');
}
$site->enableAutoDeployment();
return back()->with('success', 'Auto deployment enabled successfully.');
}
/**
* @throws SourceControlIsNotConnected
* @throws FailedToDestroyGitHook
*/
#[Post('/disable-auto-deployment', name: 'application.disable-auto-deployment')]
public function disableAutoDeployment(Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
if (! $site->sourceControl) {
return back()->with('error', 'Cannot find source control for this site.');
}
$site->disableAutoDeployment();
return back()->with('success', 'Auto deployment disabled successfully.');
}
#[Post('/load-balancer', name: 'application.update-load-balancer')]
public function updateLoadBalancer(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
app(UpdateLoadBalancer::class)->update($site, $request->input());
return back()->with('success', 'Load balancer updated successfully.');
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Site\CreateCommand;
use App\Actions\Site\EditCommand;
use App\Actions\Site\ExecuteCommand;
use App\Http\Resources\CommandExecutionResource;
use App\Http\Resources\CommandResource;
use App\Models\Command;
use App\Models\Server;
use App\Models\Site;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
use Spatie\RouteAttributes\Attributes\Put;
#[Prefix('/servers/{server}/sites/{site}/commands')]
#[Middleware(['auth', 'has-project'])]
class CommandController extends Controller
{
#[Get('/', name: 'commands')]
public function index(Server $server, Site $site): Response
{
$this->authorize('viewAny', [Command::class, $site, $server]);
return Inertia::render('commands/index', [
'commands' => CommandResource::collection($site->commands()->latest()->simplePaginate(config('web.pagination_size'))),
]);
}
#[Post('/', name: 'commands.store')]
public function store(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('create', [Command::class, $site, $server]);
app(CreateCommand::class)->create($site, $request->input());
return back()
->with('success', 'Command created successfully.');
}
#[Get('/{command}', name: 'commands.show')]
public function show(Server $server, Site $site, Command $command): Response
{
$this->authorize('view', [$command, $site, $server]);
return Inertia::render('commands/show', [
'command' => new CommandResource($command),
'executions' => CommandExecutionResource::collection($command->executions()->latest()->simplePaginate(config('web.pagination_size'))),
]);
}
#[Put('/{command}', name: 'commands.update')]
public function update(Request $request, Server $server, Site $site, Command $command): RedirectResponse
{
$this->authorize('update', [$command, $site, $server]);
app(EditCommand::class)->edit($command, $request->input());
return back()
->with('success', 'Command updated successfully.');
}
#[Delete('/{command}', name: 'commands.destroy')]
public function destroy(Server $server, Site $site, Command $command): RedirectResponse
{
$this->authorize('delete', [$command, $site, $server]);
$command->delete();
return back()
->with('success', 'Command deleted successfully.');
}
#[Post('/{command}/execute', name: 'commands.execute')]
public function execute(Request $request, Server $server, Site $site, Command $command): RedirectResponse
{
$this->authorize('update', [$site, $server]);
app(ExecuteCommand::class)->execute($command, user(), $request->input());
return redirect()->route('commands.show', ['server' => $server, 'site' => $site, 'command' => $command])
->with('info', 'Command is being executed.');
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Redirect\CreateRedirect;
use App\Actions\Redirect\DeleteRedirect;
use App\Http\Resources\RedirectResource;
use App\Models\Redirect;
use App\Models\Server;
use App\Models\Site;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('/servers/{server}/sites/{site}/redirects')]
#[Middleware(['auth', 'has-project'])]
class RedirectController extends Controller
{
#[Get('/', name: 'redirects')]
public function index(Server $server, Site $site): Response
{
$this->authorize('viewAny', [Redirect::class, $site, $server]);
return Inertia::render('redirects/index', [
'redirects' => RedirectResource::collection($site->redirects()->latest()->simplePaginate(config('web.pagination_size'))),
]);
}
#[Post('/', name: 'redirects.store')]
public function store(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('create', [Redirect::class, $site, $server]);
app(CreateRedirect::class)->create($site, $request->input());
return back()
->with('info', 'Creating the redirect');
}
#[Delete('/{redirect}', name: 'redirects.destroy')]
public function destroy(Server $server, Site $site, Redirect $redirect): RedirectResponse
{
$this->authorize('delete', [$redirect, $site, $server]);
app(DeleteRedirect::class)->delete($site, $redirect);
return back()
->with('info', 'Deleting the redirect');
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers;
use App\Actions\SSL\ActivateSSL;
use App\Actions\SSL\CreateSSL;
use App\Actions\SSL\DeactivateSSL;
use App\Actions\SSL\DeleteSSL;
use App\Http\Resources\SslResource;
use App\Models\Server;
use App\Models\Site;
use App\Models\Ssl;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
use Spatie\RouteAttributes\Attributes\Prefix;
#[Prefix('/servers/{server}/sites/{site}/ssl')]
#[Middleware(['auth', 'has-project'])]
class SSLController extends Controller
{
#[Get('/', name: 'ssls')]
public function index(Server $server, Site $site): Response
{
$this->authorize('viewAny', [Ssl::class, $site, $server]);
return Inertia::render('ssls/index', [
'ssls' => SslResource::collection($site->ssls()->latest()->simplePaginate(config('web.pagination_size'))),
]);
}
#[Post('/', name: 'ssls.store')]
public function store(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('create', [Ssl::class, $site, $server]);
app(CreateSSL::class)->create($site, $request->input());
return back()
->with('info', 'Setting up SSL.');
}
#[Delete('/{ssl}', name: 'ssls.destroy')]
public function destroy(Server $server, Site $site, Ssl $ssl): RedirectResponse
{
$this->authorize('delete', [$ssl, $site, $server]);
app(DeleteSSL::class)->delete($ssl);
return back()
->with('success', 'SSL deleted successfully.');
}
#[Post('/enable-force-ssl', name: 'ssls.enable-force-ssl')]
public function enableForceSSL(Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
$site->force_ssl = true;
$site->save();
$site->webserver()->updateVHost($site);
return back()
->with('success', 'Force SSL enabled successfully.');
}
#[Post('/disable-force-ssl', name: 'ssls.disable-force-ssl')]
public function disableForceSSL(Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
$site->force_ssl = false;
$site->save();
$site->webserver()->updateVHost($site);
return back()
->with('success', 'Force SSL disabled successfully.');
}
#[Post('/{ssl}/activate', name: 'ssls.activate')]
public function activate(Server $server, Site $site, Ssl $ssl): RedirectResponse
{
$this->authorize('update', [$ssl, $site, $server]);
app(ActivateSSL::class)->activate($ssl);
return back()
->with('success', 'SSL activated successfully.');
}
#[Post('/{ssl}/deactivate', name: 'ssls.deactivate')]
public function deactivate(Server $server, Site $site, Ssl $ssl): RedirectResponse
{
$this->authorize('update', [$ssl, $site, $server]);
app(DeactivateSSL::class)->deactivate($ssl);
return back()
->with('success', 'SSL deactivated successfully.');
}
}

View File

@ -3,8 +3,6 @@
namespace App\Http\Controllers;
use App\Actions\Site\CreateSite;
use App\Actions\Site\DeleteSite;
use App\Exceptions\SSHError;
use App\Http\Resources\ServerLogResource;
use App\Http\Resources\SiteResource;
use App\Models\Server;
@ -14,7 +12,6 @@
use Illuminate\Support\Facades\URL;
use Inertia\Inertia;
use Inertia\Response;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Post;
@ -43,17 +40,6 @@ public function server(Server $server): Response
]);
}
#[Get('/servers/{server}/sites/{site}', name: 'sites.show')]
public function show(Server $server, Site $site): Response
{
$this->authorize('view', [$site, $server]);
return Inertia::render('sites/show', [
'site' => SiteResource::make($site),
'logs' => ServerLogResource::collection($site->logs()->latest()->simplePaginate(config('web.pagination_size'), pageName: 'logsPage')),
]);
}
/**
* @throws Throwable
*/
@ -64,7 +50,7 @@ public function store(Request $request, Server $server): RedirectResponse
$site = app(CreateSite::class)->create($server, $request->all());
return redirect()->route('sites.show', ['server' => $server, 'site' => $site])
return redirect()->route('application', ['server' => $server, 'site' => $site])
->with('info', 'Installing site, please wait...');
}
@ -79,26 +65,22 @@ public function switch(Server $server, Site $site): RedirectResponse
if ($previousRoute->hasParameter('site')) {
if (count($previousRoute->parameters()) > 2) {
return redirect()->route('sites.show', ['server' => $server->id, 'site' => $site->id]);
return redirect()->route('application', ['server' => $server->id, 'site' => $site->id]);
}
return redirect()->route($previousRoute->getName(), ['server' => $server, 'site' => $site->id]);
}
return redirect()->route('sites.show', ['server' => $server->id, 'site' => $site->id]);
return redirect()->route('application', ['server' => $server->id, 'site' => $site->id]);
}
/**
* @throws SSHError
*/
#[Delete('/servers/{server}/sites/{site}', name: 'sites.destroy')]
public function destroy(Server $server, Site $site): RedirectResponse
#[Get('/servers/{server}/sites/{site}/logs', name: 'sites.logs')]
public function logs(Server $server, Site $site): Response
{
$this->authorize('delete', [$site, $server]);
$this->authorize('view', [$site, $server]);
app(DeleteSite::class)->delete($site);
return redirect()->route('sites', ['server' => $server])
->with('success', 'Site deleted successfully.');
return Inertia::render('sites/logs', [
'logs' => ServerLogResource::collection($site->logs()->latest()->simplePaginate(config('web.pagination_size'))),
]);
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Site\DeleteSite;
use App\Actions\Site\UpdateBranch;
use App\Actions\Site\UpdatePHPVersion;
use App\Actions\Site\UpdateSourceControl;
use App\Exceptions\SSHError;
use App\Http\Resources\SourceControlResource;
use App\Models\Server;
use App\Models\Site;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use Spatie\RouteAttributes\Attributes\Delete;
use Spatie\RouteAttributes\Attributes\Get;
use Spatie\RouteAttributes\Attributes\Middleware;
use Spatie\RouteAttributes\Attributes\Patch;
use Spatie\RouteAttributes\Attributes\Prefix;
use Spatie\RouteAttributes\Attributes\Put;
#[Prefix('/servers/{server}/sites/{site}/settings')]
#[Middleware(['auth', 'has-project'])]
class SiteSettingController extends Controller
{
#[Get('/', name: 'site-settings')]
public function index(Server $server, Site $site): Response
{
return Inertia::render('site-settings/index', [
'sourceControl' => $site->sourceControl ? SourceControlResource::make($site->sourceControl) : null,
]);
}
/**
* @throws SSHError
*/
#[Patch('/branch', name: 'site-settings.update-branch')]
public function updateBranch(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
app(UpdateBranch::class)->update($site, $request->input());
return back()->with('success', 'Branch updated successfully.');
}
#[Patch('/source-control', name: 'site-settings.update-source-control')]
public function updateSourceControl(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
app(UpdateSourceControl::class)->update($site, $request->input());
return back()->with('success', 'Source control updated successfully.');
}
/**
* @throws SSHError
*/
#[Patch('/php-version', name: 'site-settings.update-php-version')]
public function updatePHPVersion(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
app(UpdatePHPVersion::class)->update($site, $request->input());
return back()->with('success', 'PHP version updated successfully.');
}
#[Get('/vhost', name: 'site-settings.vhost')]
public function vhost(Server $server, Site $site): JsonResponse
{
$this->authorize('update', [$site, $server]);
return response()->json([
'vhost' => $site->webserver()->getVHost($site),
]);
}
#[Put('/vhost', name: 'site-settings.update-vhost')]
public function updateVhost(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('update', [$site, $server]);
$this->validate($request, [
'vhost' => 'required|string',
]);
$site->webserver()->updateVHost($site, $request->input('vhost'));
return back()->with('success', 'VHost updated successfully.');
}
/**
* @throws SSHError
*/
#[Delete('/', name: 'site-settings.destroy')]
public function destroy(Request $request, Server $server, Site $site): RedirectResponse
{
$this->authorize('delete', [$site, $server]);
app(DeleteSite::class)->delete($site, $request->input());
return redirect()->route('sites', ['server' => $server])
->with('success', 'Site deleted successfully.');
}
}

View File

@ -39,7 +39,7 @@ public function index(Server $server): Response
]);
}
#[Get('/sites/{site}/workers', name: 'sites.workers')]
#[Get('/sites/{site}/workers', name: 'workers.site')]
public function site(Server $server, Site $site): Response
{
$this->authorize('viewAny', [Worker::class, $server, $site]);

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources;
use App\Models\CommandExecution;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin CommandExecution */
class CommandExecutionResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'command_id' => $this->command_id,
'server_id' => $this->server_id,
'user_id' => $this->user_id,
'server_log_id' => $this->server_log_id,
'log' => ServerLogResource::make($this->serverLog),
'variables' => $this->variables,
'status' => $this->status,
'status_color' => CommandExecution::$statusColors[$this->status] ?? 'gray',
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Resources;
use App\Models\Command;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin Command */
class CommandResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'server_id' => $this->site->server_id,
'site_id' => $this->site_id,
'name' => $this->name,
'command' => $this->command,
'variables' => $this->getVariables(),
'updated_at' => $this->updated_at,
'created_at' => $this->created_at,
];
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Resources;
use App\Models\Deployment;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin Deployment */
class DeploymentResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'site_id' => $this->site_id,
'deployment_script_id' => $this->deployment_script_id,
'log_id' => $this->log_id,
'log' => new ServerLogResource($this->log),
'commit_id' => $this->commit_id,
'commit_id_short' => $this->commit_id_short,
'commit_data' => $this->commit_data,
'status' => $this->status,
'status_color' => Deployment::$statusColors[$this->status] ?? 'gray',
'updated_at' => $this->updated_at,
'created_at' => $this->created_at,
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Resources;
use App\Models\LoadBalancerServer;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin LoadBalancerServer */
class LoadBalancerServerResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'load_balancer_id' => $this->load_balancer_id,
'ip' => $this->ip,
'port' => $this->port,
'weight' => $this->weight,
'backup' => $this->backup,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

View File

@ -16,11 +16,13 @@ public function toArray(Request $request): array
{
return [
'id' => $this->id,
'server_id' => $this->site->server_id,
'site_id' => $this->site_id,
'mode' => $this->mode,
'from' => $this->from,
'to' => $this->to,
'mode' => $this->mode,
'status' => $this->status,
'status_color' => Redirect::$statusColors[$this->status] ?? 'gray',
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];

View File

@ -17,6 +17,7 @@ public function toArray(Request $request): array
return [
'id' => $this->id,
'project_id' => $this->project_id,
'services' => $this->services()->pluck('name', 'type'),
'user_id' => $this->user_id,
'provider_id' => $this->provider_id,
'name' => $this->name,

View File

@ -21,18 +21,22 @@ public function toArray(Request $request): array
'source_control_id' => $this->source_control_id,
'type' => $this->type,
'type_data' => $this->type_data,
'features' => $this->type()->supportedFeatures(),
'domain' => $this->domain,
'aliases' => $this->aliases,
'web_directory' => $this->web_directory,
'webserver' => $this->webserver()->name(),
'path' => $this->path,
'php_version' => $this->php_version,
'repository' => $this->repository,
'branch' => $this->branch,
'status' => $this->status,
'status_color' => Site::$statusColors[$this->status] ?? 'default',
'auto_deploy' => $this->isAutoDeployment(),
'port' => $this->port,
'user' => $this->user,
'url' => $this->getUrl(),
'force_ssl' => $this->force_ssl,
'progress' => $this->progress,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources;
use App\Models\Ssl;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin Ssl */
class SslResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'server_id' => $this->site->server_id,
'site_id' => $this->site_id,
'is_active' => $this->is_active,
'type' => $this->type,
'status' => $this->status,
'log' => $this->log_id ? ServerLogResource::make($this->log) : null,
'status_color' => Ssl::$statusColors[$this->status] ?? 'secondary',
'expires_at' => $this->expires_at,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}