mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 14:06:15 +00:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
a862a603f2 | |||
3b42f93654 | |||
661292df5e | |||
0cfb938320 | |||
dd4a3d30c0 | |||
2b849c888e | |||
d9a791755e | |||
e3ea8f975f | |||
de468ae1ba | |||
30ef8ad5eb | |||
88223a61f9 | |||
1067a5fd33 | |||
4361305206 | |||
fe331fd2b3 | |||
bbe3ca802d | |||
765ac21916 | |||
016886f307 | |||
179aefefac |
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\CronJob;
|
||||
|
||||
use App\Enums\CronjobStatus;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
|
||||
@ -10,7 +11,9 @@ class DeleteCronJob
|
||||
public function delete(Server $server, CronJob $cronJob): void
|
||||
{
|
||||
$user = $cronJob->user;
|
||||
$cronJob->delete();
|
||||
$cronJob->status = CronjobStatus::DELETING;
|
||||
$cronJob->save();
|
||||
$server->cron()->update($cronJob->user, CronJob::crontab($server, $user));
|
||||
$cronJob->delete();
|
||||
}
|
||||
}
|
||||
|
20
app/Actions/CronJob/DisableCronJob.php
Executable file
20
app/Actions/CronJob/DisableCronJob.php
Executable file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CronJob;
|
||||
|
||||
use App\Enums\CronjobStatus;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
|
||||
class DisableCronJob
|
||||
{
|
||||
public function disable(Server $server, CronJob $cronJob): void
|
||||
{
|
||||
$cronJob->status = CronjobStatus::DISABLING;
|
||||
$cronJob->save();
|
||||
|
||||
$server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user));
|
||||
$cronJob->status = CronjobStatus::DISABLED;
|
||||
$cronJob->save();
|
||||
}
|
||||
}
|
20
app/Actions/CronJob/EnableCronJob.php
Executable file
20
app/Actions/CronJob/EnableCronJob.php
Executable file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\CronJob;
|
||||
|
||||
use App\Enums\CronjobStatus;
|
||||
use App\Models\CronJob;
|
||||
use App\Models\Server;
|
||||
|
||||
class EnableCronJob
|
||||
{
|
||||
public function enable(Server $server, CronJob $cronJob): void
|
||||
{
|
||||
$cronJob->status = CronjobStatus::ENABLING;
|
||||
$cronJob->save();
|
||||
|
||||
$server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user));
|
||||
$cronJob->status = CronjobStatus::READY;
|
||||
$cronJob->save();
|
||||
}
|
||||
}
|
@ -22,7 +22,9 @@ public function create(Server $server, array $input): Database
|
||||
'server_id' => $server->id,
|
||||
'name' => $input['name'],
|
||||
]);
|
||||
$server->database()->handler()->create($database->name);
|
||||
/** @var \App\SSH\Services\Database\Database */
|
||||
$databaseHandler = $server->database()->handler();
|
||||
$databaseHandler->create($database->name);
|
||||
$database->status = DatabaseStatus::READY;
|
||||
$database->save();
|
||||
|
||||
|
@ -14,7 +14,7 @@ public function create(User $user, array $input): Project
|
||||
$input['name'] = strtolower($input['name']);
|
||||
}
|
||||
|
||||
$this->validate($user, $input);
|
||||
$this->validate($input);
|
||||
|
||||
$project = new Project([
|
||||
'name' => $input['name'],
|
||||
@ -22,10 +22,12 @@ public function create(User $user, array $input): Project
|
||||
|
||||
$project->save();
|
||||
|
||||
$project->users()->attach($user);
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
private function validate(User $user, array $input): void
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => [
|
||||
|
@ -17,11 +17,15 @@ public function delete(User $user, Project $project): void
|
||||
}
|
||||
|
||||
if ($user->current_project_id == $project->id) {
|
||||
throw ValidationException::withMessages([
|
||||
'project' => __('Cannot delete your current project.'),
|
||||
]);
|
||||
}
|
||||
|
||||
/** @var Project $randomProject */
|
||||
$randomProject = $user->projects()->where('id', '!=', $project->id)->first();
|
||||
$randomProject = $user->projects()->where('project_id', '!=', $project->id)->first();
|
||||
$user->current_project_id = $randomProject->id;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
$project->delete();
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
use App\Enums\SslType;
|
||||
use App\Models\Site;
|
||||
use App\Models\Ssl;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@ -27,14 +28,23 @@ public function create(Site $site, array $input): void
|
||||
'expires_at' => $input['type'] === SslType::LETSENCRYPT ? now()->addMonths(3) : $input['expires_at'],
|
||||
'status' => SslStatus::CREATING,
|
||||
]);
|
||||
$ssl->domains = [$site->domain];
|
||||
if (isset($input['aliases']) && $input['aliases']) {
|
||||
$ssl->domains = array_merge($ssl->domains, $site->aliases);
|
||||
}
|
||||
$ssl->save();
|
||||
|
||||
dispatch(function () use ($site, $ssl) {
|
||||
$site->server->webserver()->handler()->setupSSL($ssl);
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $site->server->webserver()->handler();
|
||||
$webserver->setupSSL($ssl);
|
||||
$ssl->status = SslStatus::CREATED;
|
||||
$ssl->save();
|
||||
$site->type()->edit();
|
||||
});
|
||||
})->catch(function () use ($ssl) {
|
||||
$ssl->status = SslStatus::FAILED;
|
||||
$ssl->save();
|
||||
})->onConnection('ssh');
|
||||
}
|
||||
|
||||
/**
|
||||
|
32
app/Actions/Script/CreateScript.php
Normal file
32
app/Actions/Script/CreateScript.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Script;
|
||||
|
||||
use App\Models\Script;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class CreateScript
|
||||
{
|
||||
public function create(User $user, array $input): Script
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$script = new Script([
|
||||
'user_id' => $user->id,
|
||||
'name' => $input['name'],
|
||||
'content' => $input['content'],
|
||||
]);
|
||||
$script->save();
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'content' => ['required', 'string'],
|
||||
])->validate();
|
||||
}
|
||||
}
|
28
app/Actions/Script/EditScript.php
Normal file
28
app/Actions/Script/EditScript.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Script;
|
||||
|
||||
use App\Models\Script;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class EditScript
|
||||
{
|
||||
public function edit(Script $script, array $input): Script
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$script->name = $input['name'];
|
||||
$script->content = $input['content'];
|
||||
$script->save();
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'content' => ['required', 'string'],
|
||||
])->validate();
|
||||
}
|
||||
}
|
62
app/Actions/Script/ExecuteScript.php
Normal file
62
app/Actions/Script/ExecuteScript.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Script;
|
||||
|
||||
use App\Enums\ScriptExecutionStatus;
|
||||
use App\Models\Script;
|
||||
use App\Models\ScriptExecution;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerLog;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ExecuteScript
|
||||
{
|
||||
public function execute(Script $script, Server $server, array $input): ScriptExecution
|
||||
{
|
||||
$this->validate($server, $input);
|
||||
|
||||
$execution = new ScriptExecution([
|
||||
'script_id' => $script->id,
|
||||
'user' => $input['user'],
|
||||
'variables' => $input['variables'] ?? [],
|
||||
'status' => ScriptExecutionStatus::EXECUTING,
|
||||
]);
|
||||
$execution->save();
|
||||
|
||||
dispatch(function () use ($execution, $server, $script) {
|
||||
$content = $execution->getContent();
|
||||
$log = ServerLog::make($server, 'script-'.$script->id.'-'.strtotime('now'));
|
||||
$log->save();
|
||||
$execution->server_log_id = $log->id;
|
||||
$execution->save();
|
||||
$server->os()->runScript('~/', $content, $log, $execution->user);
|
||||
$execution->status = ScriptExecutionStatus::COMPLETED;
|
||||
$execution->save();
|
||||
})->catch(function () use ($execution) {
|
||||
$execution->status = ScriptExecutionStatus::FAILED;
|
||||
$execution->save();
|
||||
})->onConnection('ssh');
|
||||
|
||||
return $execution;
|
||||
}
|
||||
|
||||
private function validate(Server $server, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'user' => [
|
||||
'required',
|
||||
Rule::in([
|
||||
'root',
|
||||
$server->ssh_user,
|
||||
]),
|
||||
],
|
||||
'variables' => 'array',
|
||||
'variables.*' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Enums\ServerStatus;
|
||||
use App\Facades\Notifier;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\ServerDisconnected;
|
||||
@ -15,12 +16,12 @@ public function check(Server $server): Server
|
||||
try {
|
||||
$server->ssh()->connect();
|
||||
$server->refresh();
|
||||
if ($status == 'disconnected') {
|
||||
$server->status = 'ready';
|
||||
if (in_array($status, [ServerStatus::DISCONNECTED, ServerStatus::UPDATING])) {
|
||||
$server->status = ServerStatus::READY;
|
||||
$server->save();
|
||||
}
|
||||
} catch (Throwable) {
|
||||
$server->status = 'disconnected';
|
||||
$server->status = ServerStatus::DISCONNECTED;
|
||||
$server->save();
|
||||
Notifier::send($server, new ServerDisconnected($server));
|
||||
}
|
||||
|
25
app/Actions/Server/Update.php
Normal file
25
app/Actions/Server/Update.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Enums\ServerStatus;
|
||||
use App\Facades\Notifier;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\ServerUpdateFailed;
|
||||
|
||||
class Update
|
||||
{
|
||||
public function update(Server $server): void
|
||||
{
|
||||
$server->status = ServerStatus::UPDATING;
|
||||
$server->save();
|
||||
dispatch(function () use ($server) {
|
||||
$server->os()->upgrade();
|
||||
$server->checkConnection();
|
||||
$server->checkForUpdates();
|
||||
})->catch(function () use ($server) {
|
||||
Notifier::send($server, new ServerUpdateFailed($server));
|
||||
$server->checkConnection();
|
||||
})->onConnection('ssh');
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
class CreateSite
|
||||
{
|
||||
/**
|
||||
* @throws ValidationException
|
||||
* @throws SourceControlIsNotConnected
|
||||
*/
|
||||
public function create(Server $server, array $input): Site
|
||||
{
|
||||
@ -33,7 +33,7 @@ public function create(Server $server, array $input): Site
|
||||
'server_id' => $server->id,
|
||||
'type' => $input['type'],
|
||||
'domain' => $input['domain'],
|
||||
'aliases' => isset($input['alias']) ? [$input['alias']] : [],
|
||||
'aliases' => $input['aliases'] ?? [],
|
||||
'path' => '/home/'.$server->getSshUser().'/'.$input['domain'],
|
||||
'status' => SiteStatus::INSTALLING,
|
||||
]);
|
||||
@ -115,7 +115,7 @@ private function validateInputs(Server $server, array $input): void
|
||||
return $query->where('server_id', $server->id);
|
||||
}),
|
||||
],
|
||||
'alias' => [
|
||||
'aliases.*' => [
|
||||
new DomainRule(),
|
||||
],
|
||||
];
|
||||
|
@ -30,7 +30,7 @@ public function run(Site $site): Deployment
|
||||
'deployment_script_id' => $site->deploymentScript->id,
|
||||
'status' => DeploymentStatus::DEPLOYING,
|
||||
]);
|
||||
$lastCommit = $site->sourceControl()->provider()->getLastCommit($site->repository, $site->branch);
|
||||
$lastCommit = $site->sourceControl()?->provider()?->getLastCommit($site->repository, $site->branch);
|
||||
if ($lastCommit) {
|
||||
$deployment->commit_id = $lastCommit['commit_id'];
|
||||
$deployment->commit_data = $lastCommit['commit_data'];
|
||||
|
33
app/Actions/Site/UpdateAliases.php
Normal file
33
app/Actions/Site/UpdateAliases.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Models\Site;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use App\ValidationRules\DomainRule;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class UpdateAliases
|
||||
{
|
||||
public function update(Site $site, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$site->aliases = $input['aliases'] ?? [];
|
||||
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $site->server->webserver()->handler();
|
||||
$webserver->updateVHost($site, ! $site->hasSSL());
|
||||
|
||||
$site->save();
|
||||
}
|
||||
|
||||
private function validate(array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'aliases.*' => [
|
||||
new DomainRule(),
|
||||
],
|
||||
])->validate();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
namespace App\Actions\SourceControl;
|
||||
|
||||
use App\Models\SourceControl;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
@ -10,7 +11,7 @@
|
||||
|
||||
class ConnectSourceControl
|
||||
{
|
||||
public function connect(array $input): void
|
||||
public function connect(User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
@ -18,6 +19,7 @@ public function connect(array $input): void
|
||||
'provider' => $input['provider'],
|
||||
'profile' => $input['name'],
|
||||
'url' => Arr::has($input, 'url') ? $input['url'] : null,
|
||||
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||
]);
|
||||
|
||||
$this->validateProvider($sourceControl, $input);
|
||||
|
54
app/Actions/SourceControl/EditSourceControl.php
Normal file
54
app/Actions/SourceControl/EditSourceControl.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\SourceControl;
|
||||
|
||||
use App\Models\SourceControl;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditSourceControl
|
||||
{
|
||||
public function edit(SourceControl $sourceControl, User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$sourceControl->profile = $input['name'];
|
||||
$sourceControl->url = isset($input['url']) ? $input['url'] : null;
|
||||
$sourceControl->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$this->validateProvider($sourceControl, $input);
|
||||
|
||||
$sourceControl->provider_data = $sourceControl->provider()->createData($input);
|
||||
|
||||
if (! $sourceControl->provider()->connect()) {
|
||||
throw ValidationException::withMessages([
|
||||
'token' => __('Cannot connect to :provider or invalid token!', ['provider' => $sourceControl->provider]
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
$sourceControl->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validateProvider(SourceControl $sourceControl, array $input): void
|
||||
{
|
||||
Validator::make($input, $sourceControl->provider()->createRules($input))->validate();
|
||||
}
|
||||
}
|
@ -9,4 +9,10 @@ final class CronjobStatus
|
||||
const READY = 'ready';
|
||||
|
||||
const DELETING = 'deleting';
|
||||
|
||||
const ENABLING = 'enabling';
|
||||
|
||||
const DISABLING = 'disabling';
|
||||
|
||||
const DISABLED = 'disabled';
|
||||
}
|
||||
|
@ -9,4 +9,6 @@ final class OperatingSystem
|
||||
const UBUNTU20 = 'ubuntu_20';
|
||||
|
||||
const UBUNTU22 = 'ubuntu_22';
|
||||
|
||||
const UBUNTU24 = 'ubuntu_24';
|
||||
}
|
||||
|
12
app/Enums/ScriptExecutionStatus.php
Normal file
12
app/Enums/ScriptExecutionStatus.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
final class ScriptExecutionStatus
|
||||
{
|
||||
const EXECUTING = 'executing';
|
||||
|
||||
const COMPLETED = 'completed';
|
||||
|
||||
const FAILED = 'failed';
|
||||
}
|
@ -11,4 +11,6 @@ final class ServerStatus
|
||||
const INSTALLATION_FAILED = 'installation_failed';
|
||||
|
||||
const DISCONNECTED = 'disconnected';
|
||||
|
||||
const UPDATING = 'updating';
|
||||
}
|
||||
|
@ -94,12 +94,6 @@ public function connect(bool $sftp = false): void
|
||||
public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false): string
|
||||
{
|
||||
if (! $this->log && $log) {
|
||||
$this->log = $this->server->logs()->create([
|
||||
'site_id' => $siteId,
|
||||
'name' => $this->server->id.'-'.strtotime('now').'-'.$log.'.log',
|
||||
'type' => $log,
|
||||
'disk' => config('core.logs_disk'),
|
||||
]);
|
||||
$this->log = ServerLog::make($this->server, $log);
|
||||
if ($siteId) {
|
||||
$this->log->forSite($siteId);
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
use App\Actions\CronJob\CreateCronJob;
|
||||
use App\Actions\CronJob\DeleteCronJob;
|
||||
use App\Actions\CronJob\DisableCronJob;
|
||||
use App\Actions\CronJob\EnableCronJob;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\CronJob;
|
||||
@ -45,4 +47,26 @@ public function destroy(Server $server, CronJob $cronJob): RedirectResponse
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function enable(Server $server, CronJob $cronJob): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(EnableCronJob::class)->enable($server, $cronJob);
|
||||
|
||||
Toast::success('Cronjob enabled successfully.');
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function disable(Server $server, CronJob $cronJob): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(DisableCronJob::class)->disable($server, $cronJob);
|
||||
|
||||
Toast::success('Cronjob disabled successfully.');
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
|
111
app/Http/Controllers/ScriptController.php
Normal file
111
app/Http/Controllers/ScriptController.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Script\CreateScript;
|
||||
use App\Actions\Script\EditScript;
|
||||
use App\Actions\Script\ExecuteScript;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Script;
|
||||
use App\Models\ScriptExecution;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ScriptController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$this->authorize('viewAny', Script::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$data = [
|
||||
'scripts' => $user->scripts,
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editScript'] = $user->scripts()->findOrFail($request->input('edit'));
|
||||
}
|
||||
|
||||
if ($request->has('execute')) {
|
||||
$data['executeScript'] = $user->scripts()->findOrFail($request->input('execute'));
|
||||
}
|
||||
|
||||
return view('scripts.index', $data);
|
||||
}
|
||||
|
||||
public function show(Script $script): View
|
||||
{
|
||||
$this->authorize('view', $script);
|
||||
|
||||
return view('scripts.show', [
|
||||
'script' => $script,
|
||||
'executions' => $script->executions()->latest()->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('create', Script::class);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
app(CreateScript::class)->create($user, $request->input());
|
||||
|
||||
Toast::success('Script created.');
|
||||
|
||||
return htmx()->redirect(route('scripts.index'));
|
||||
}
|
||||
|
||||
public function edit(Request $request, Script $script): HtmxResponse
|
||||
{
|
||||
$this->authorize('update', $script);
|
||||
|
||||
app(EditScript::class)->edit($script, $request->input());
|
||||
|
||||
Toast::success('Script updated.');
|
||||
|
||||
return htmx()->redirect(route('scripts.index'));
|
||||
}
|
||||
|
||||
public function execute(Script $script, Request $request): HtmxResponse
|
||||
{
|
||||
$this->validate($request, [
|
||||
'server' => 'required|exists:servers,id',
|
||||
]);
|
||||
|
||||
$server = Server::findOrFail($request->input('server'));
|
||||
|
||||
$this->authorize('execute', [$script, $server]);
|
||||
|
||||
app(ExecuteScript::class)->execute($script, $server, $request->input());
|
||||
|
||||
Toast::success('Executing the script...');
|
||||
|
||||
return htmx()->redirect(route('scripts.show', $script));
|
||||
}
|
||||
|
||||
public function delete(Script $script): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $script);
|
||||
|
||||
$script->delete();
|
||||
|
||||
Toast::success('Script deleted.');
|
||||
|
||||
return redirect()->route('scripts.index');
|
||||
}
|
||||
|
||||
public function log(Script $script, ScriptExecution $execution): RedirectResponse
|
||||
{
|
||||
$this->authorize('view', $script);
|
||||
|
||||
return back()->with('content', $execution->serverLog?->getContent());
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\Server\EditServer;
|
||||
use App\Actions\Server\RebootServer;
|
||||
use App\Actions\Server\Update;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Server;
|
||||
@ -64,4 +65,24 @@ public function edit(Request $request, Server $server): RedirectResponse
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function checkUpdates(Server $server): RedirectResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$server->checkForUpdates();
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function update(Server $server): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(Update::class)->update($server);
|
||||
|
||||
Toast::info('Updating server. This may take a few minutes.');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ class ProjectController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$this->authorize('viewAny', Project::class);
|
||||
|
||||
return view('settings.projects.index', [
|
||||
'projects' => Project::all(),
|
||||
]);
|
||||
@ -26,6 +28,8 @@ public function index(): View
|
||||
|
||||
public function create(Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('create', Project::class);
|
||||
|
||||
app(CreateProject::class)->create($request->user(), $request->input());
|
||||
|
||||
Toast::success('Project created.');
|
||||
@ -35,8 +39,7 @@ public function create(Request $request): HtmxResponse
|
||||
|
||||
public function update(Request $request, Project $project): HtmxResponse
|
||||
{
|
||||
/** @var Project $project */
|
||||
$project = $request->user()->projects()->findOrFail($project->id);
|
||||
$this->authorize('update', $project);
|
||||
|
||||
app(UpdateProject::class)->update($project, $request->input());
|
||||
|
||||
@ -47,12 +50,11 @@ public function update(Request $request, Project $project): HtmxResponse
|
||||
|
||||
public function delete(Project $project): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $project);
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
/** @var Project $project */
|
||||
$project = $user->projects()->findOrFail($project->id);
|
||||
|
||||
try {
|
||||
app(DeleteProject::class)->delete($user, $project);
|
||||
} catch (ValidationException $e) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\SourceControl\ConnectSourceControl;
|
||||
use App\Actions\SourceControl\DeleteSourceControl;
|
||||
use App\Actions\SourceControl\EditSourceControl;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -14,16 +15,23 @@
|
||||
|
||||
class SourceControlController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('settings.source-controls.index', [
|
||||
'sourceControls' => SourceControl::query()->orderByDesc('id')->get(),
|
||||
]);
|
||||
$data = [
|
||||
'sourceControls' => SourceControl::getByCurrentProject(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editSourceControl'] = SourceControl::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.source-controls.index', $data);
|
||||
}
|
||||
|
||||
public function connect(Request $request): HtmxResponse
|
||||
{
|
||||
app(ConnectSourceControl::class)->connect(
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
@ -32,6 +40,19 @@ public function connect(Request $request): HtmxResponse
|
||||
return htmx()->redirect(route('settings.source-controls'));
|
||||
}
|
||||
|
||||
public function update(SourceControl $sourceControl, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditSourceControl::class)->edit(
|
||||
$sourceControl,
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Source control updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.source-controls'));
|
||||
}
|
||||
|
||||
public function delete(SourceControl $sourceControl): RedirectResponse
|
||||
{
|
||||
try {
|
||||
|
@ -7,6 +7,7 @@
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project;
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
@ -56,6 +57,18 @@ public function updateProjects(User $user, Request $request): HtmxResponse
|
||||
|
||||
$user->projects()->sync($request->projects);
|
||||
|
||||
if ($user->currentProject && ! $user->projects->contains($user->currentProject)) {
|
||||
$user->current_project_id = null;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
/** @var Project $firstProject */
|
||||
$firstProject = $user->projects->first();
|
||||
if (! $user->currentProject && $firstProject) {
|
||||
$user->current_project_id = $firstProject->id;
|
||||
$user->save();
|
||||
}
|
||||
|
||||
Toast::success('Projects updated successfully');
|
||||
|
||||
return htmx()->redirect(route('settings.users.show', $user));
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\Site\UpdateAliases;
|
||||
use App\Actions\Site\UpdateSourceControl;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
@ -83,10 +84,21 @@ public function updateSourceControl(Server $server, Site $site, Request $request
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
$site = app(UpdateSourceControl::class)->update($site, $request->input());
|
||||
app(UpdateSourceControl::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Source control updated successfully!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
|
||||
public function updateAliases(Server $server, Site $site, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(UpdateAliases::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Aliases updated successfully!');
|
||||
|
||||
return htmx()->back();
|
||||
}
|
||||
}
|
||||
|
@ -69,5 +69,6 @@ class Kernel extends HttpKernel
|
||||
'handle-ssh-errors' => HandleSSHErrors::class,
|
||||
'select-current-project' => SelectCurrentProject::class,
|
||||
'is-admin' => \App\Http\Middleware\IsAdmin::class,
|
||||
'must-have-current-project' => \App\Http\Middleware\MustHaveCurrentProject::class,
|
||||
];
|
||||
}
|
||||
|
31
app/Http/Middleware/MustHaveCurrentProject.php
Normal file
31
app/Http/Middleware/MustHaveCurrentProject.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Facades\Toast;
|
||||
use App\Models\User;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class MustHaveCurrentProject
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
|
||||
if (! $user->currentProject) {
|
||||
Toast::warning('Please select a project to continue');
|
||||
|
||||
return redirect()->route('profile');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ public function handle(Request $request, Closure $next): Response
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
|
||||
if ($server->project_id != $user->current_project_id) {
|
||||
if ($server->project_id != $user->current_project_id && $user->can('view', $server)) {
|
||||
$user->current_project_id = $server->project_id;
|
||||
$user->save();
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\CronjobStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
@ -41,7 +42,14 @@ public function server(): BelongsTo
|
||||
public static function crontab(Server $server, string $user): string
|
||||
{
|
||||
$data = '';
|
||||
$cronJobs = $server->cronJobs()->where('user', $user)->get();
|
||||
$cronJobs = $server->cronJobs()
|
||||
->where('user', $user)
|
||||
->whereIn('status', [
|
||||
CronjobStatus::READY,
|
||||
CronjobStatus::CREATING,
|
||||
CronjobStatus::ENABLING,
|
||||
])
|
||||
->get();
|
||||
foreach ($cronJobs as $key => $cronJob) {
|
||||
$data .= $cronJob->frequency.' '.$cronJob->command;
|
||||
if ($key != count($cronJobs) - 1) {
|
||||
|
@ -19,6 +19,7 @@
|
||||
* @property User $user
|
||||
* @property Collection<Server> $servers
|
||||
* @property Collection<NotificationChannel> $notificationChannels
|
||||
* @property Collection<SourceControl> $sourceControls
|
||||
*/
|
||||
class Project extends Model
|
||||
{
|
||||
@ -59,4 +60,9 @@ public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'user_project')->withTimestamps();
|
||||
}
|
||||
|
||||
public function sourceControls(): HasMany
|
||||
{
|
||||
return $this->hasMany(SourceControl::class);
|
||||
}
|
||||
}
|
||||
|
66
app/Models/Script.php
Normal file
66
app/Models/Script.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property string $name
|
||||
* @property string $content
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Collection<ScriptExecution> $executions
|
||||
* @property ?ScriptExecution $lastExecution
|
||||
*/
|
||||
class Script extends AbstractModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'name',
|
||||
'content',
|
||||
];
|
||||
|
||||
public static function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleting(function (Script $script) {
|
||||
$script->executions()->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function getVariables(): array
|
||||
{
|
||||
$variables = [];
|
||||
preg_match_all('/\${(.*?)}/', $this->content, $matches);
|
||||
foreach ($matches[1] as $match) {
|
||||
$variables[] = $match;
|
||||
}
|
||||
|
||||
return array_unique($variables);
|
||||
}
|
||||
|
||||
public function executions(): HasMany
|
||||
{
|
||||
return $this->hasMany(ScriptExecution::class);
|
||||
}
|
||||
|
||||
public function lastExecution(): HasOne
|
||||
{
|
||||
return $this->hasOne(ScriptExecution::class)->latest();
|
||||
}
|
||||
}
|
61
app/Models/ScriptExecution.php
Normal file
61
app/Models/ScriptExecution.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $script_id
|
||||
* @property int $server_log_id
|
||||
* @property string $user
|
||||
* @property array $variables
|
||||
* @property string $status
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Script $script
|
||||
* @property ?ServerLog $serverLog
|
||||
*/
|
||||
class ScriptExecution extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'script_id',
|
||||
'server_log_id',
|
||||
'user',
|
||||
'variables',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'script_id' => 'integer',
|
||||
'server_log_id' => 'integer',
|
||||
'variables' => 'array',
|
||||
];
|
||||
|
||||
public function script(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Script::class);
|
||||
}
|
||||
|
||||
public function getContent(): string
|
||||
{
|
||||
$content = $this->script->content;
|
||||
foreach ($this->variables as $variable => $value) {
|
||||
if (is_string($value) && ! empty($value)) {
|
||||
$content = str_replace('${'.$variable.'}', $value, $content);
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function serverLog(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ServerLog::class);
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
use App\SSH\Cron\Cron;
|
||||
use App\SSH\OS\OS;
|
||||
use App\SSH\Systemd\Systemd;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
@ -55,6 +56,8 @@
|
||||
* @property Queue[] $daemons
|
||||
* @property SshKey[] $sshKeys
|
||||
* @property string $hostname
|
||||
* @property int $updates
|
||||
* @property Carbon $last_update_check
|
||||
*/
|
||||
class Server extends AbstractModel
|
||||
{
|
||||
@ -82,6 +85,8 @@ class Server extends AbstractModel
|
||||
'security_updates',
|
||||
'progress',
|
||||
'progress_step',
|
||||
'updates',
|
||||
'last_update_check',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -95,6 +100,8 @@ class Server extends AbstractModel
|
||||
'available_updates' => 'integer',
|
||||
'security_updates' => 'integer',
|
||||
'progress' => 'integer',
|
||||
'updates' => 'integer',
|
||||
'last_update_check' => 'datetime',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@ -384,4 +391,11 @@ public function cron(): Cron
|
||||
{
|
||||
return new Cron($this);
|
||||
}
|
||||
|
||||
public function checkForUpdates(): void
|
||||
{
|
||||
$this->updates = $this->os()->availableUpdates();
|
||||
$this->last_update_check = now();
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
@ -278,4 +278,9 @@ public function getEnv(): string
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public function hasSSL(): bool
|
||||
{
|
||||
return $this->ssls->isNotEmpty();
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\SourceControlProviders\SourceControlProvider;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
@ -12,6 +14,7 @@
|
||||
* @property ?string $profile
|
||||
* @property ?string $url
|
||||
* @property string $access_token
|
||||
* @property ?int $project_id
|
||||
*/
|
||||
class SourceControl extends AbstractModel
|
||||
{
|
||||
@ -23,11 +26,13 @@ class SourceControl extends AbstractModel
|
||||
'profile',
|
||||
'url',
|
||||
'access_token',
|
||||
'project_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'access_token' => 'encrypted',
|
||||
'provider_data' => 'encrypted:array',
|
||||
'project_id' => 'integer',
|
||||
];
|
||||
|
||||
public function provider(): SourceControlProvider
|
||||
@ -46,4 +51,16 @@ public function sites(): HasMany
|
||||
{
|
||||
return $this->hasMany(Site::class);
|
||||
}
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public static function getByCurrentProject(): Collection
|
||||
{
|
||||
return self::query()
|
||||
->where('project_id', auth()->user()->current_project_id)
|
||||
->orWhereNull('project_id')->get();
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
* @property string $status
|
||||
* @property Site $site
|
||||
* @property string $ca_path
|
||||
* @property ?array $domains
|
||||
*/
|
||||
class Ssl extends AbstractModel
|
||||
{
|
||||
@ -30,6 +31,7 @@ class Ssl extends AbstractModel
|
||||
'ca',
|
||||
'expires_at',
|
||||
'status',
|
||||
'domains',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -38,6 +40,7 @@ class Ssl extends AbstractModel
|
||||
'pk' => 'encrypted',
|
||||
'ca' => 'encrypted',
|
||||
'expires_at' => 'datetime',
|
||||
'domains' => 'array',
|
||||
];
|
||||
|
||||
public function site(): BelongsTo
|
||||
@ -111,4 +114,16 @@ public function validateSetup(string $result): bool
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDomains(): array
|
||||
{
|
||||
if (! empty($this->domains) && is_array($this->domains)) {
|
||||
return $this->domains;
|
||||
}
|
||||
|
||||
$this->domains = [$this->site->domain];
|
||||
$this->save();
|
||||
|
||||
return $this->domains;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@ -160,4 +161,18 @@ public function isAdmin(): bool
|
||||
{
|
||||
return $this->role === UserRole::ADMIN;
|
||||
}
|
||||
|
||||
public function scripts(): HasMany
|
||||
{
|
||||
return $this->hasMany(Script::class);
|
||||
}
|
||||
|
||||
public function allServers(): Builder
|
||||
{
|
||||
return Server::query()->whereHas('project', function (Builder $query) {
|
||||
$query->whereHas('users', function ($query) {
|
||||
$query->where('user_id', $this->id);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,6 @@ public function toEmail(object $notifiable): MailMessage
|
||||
return (new MailMessage)
|
||||
->subject(__('Server disconnected!'))
|
||||
->line("We've disconnected from your server [".$this->server->name.'].')
|
||||
->line('Please check your sever is online and make sure that has our public keys in it');
|
||||
->line('Please check your server is online and make sure that has our public keys in it');
|
||||
}
|
||||
}
|
||||
|
33
app/Notifications/ServerUpdateFailed.php
Normal file
33
app/Notifications/ServerUpdateFailed.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
class ServerUpdateFailed extends AbstractNotification
|
||||
{
|
||||
protected Server $server;
|
||||
|
||||
public function __construct(Server $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
}
|
||||
|
||||
public function rawText(): string
|
||||
{
|
||||
return __("Update failed for server [:server] \nCheck your server's logs \n:logs", [
|
||||
'server' => $this->server->name,
|
||||
'logs' => url('/servers/'.$this->server->id.'/logs'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function toEmail(object $notifiable): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject(__('Server update failed!'))
|
||||
->line('Your server ['.$this->server->name.'] update has been failed.')
|
||||
->line('Check your server logs')
|
||||
->action('View Logs', url('/servers/'.$this->server->id.'/logs'));
|
||||
}
|
||||
}
|
43
app/Policies/ScriptPolicy.php
Normal file
43
app/Policies/ScriptPolicy.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Script;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class ScriptPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function view(User $user, Script $script): bool
|
||||
{
|
||||
return $user->id === $script->user_id;
|
||||
}
|
||||
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function update(User $user, Script $script): bool
|
||||
{
|
||||
return $user->id === $script->user_id;
|
||||
}
|
||||
|
||||
public function execute(User $user, Script $script, Server $server): bool
|
||||
{
|
||||
return $user->id === $script->user_id && $server->project->users->contains($user);
|
||||
}
|
||||
|
||||
public function delete(User $user, Script $script): bool
|
||||
{
|
||||
return $user->id === $script->user_id;
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ public function installDependencies(Site $site): void
|
||||
$site->server->ssh()->exec(
|
||||
$this->getScript('composer-install.sh', [
|
||||
'path' => $site->path,
|
||||
'php_version' => $site->php_version,
|
||||
]),
|
||||
'composer-install',
|
||||
$site->id
|
||||
|
@ -2,6 +2,6 @@ if ! cd __path__; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
if ! composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev; then
|
||||
if ! php__php_version__ /usr/local/bin/composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
@ -30,6 +30,19 @@ public function upgrade(): void
|
||||
);
|
||||
}
|
||||
|
||||
public function availableUpdates(): int
|
||||
{
|
||||
$result = $this->server->ssh()->exec(
|
||||
$this->getScript('available-updates.sh'),
|
||||
'check-available-updates'
|
||||
);
|
||||
|
||||
// -1 because the first line is not a package
|
||||
$availableUpdates = str($result)->after('Available updates:')->trim()->toInteger() - 1;
|
||||
|
||||
return max($availableUpdates, 0);
|
||||
}
|
||||
|
||||
public function createUser(string $user, string $password, string $key): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
@ -127,9 +140,9 @@ public function tail(string $path, int $lines): string
|
||||
);
|
||||
}
|
||||
|
||||
public function runScript(string $path, string $script, ?ServerLog $serverLog): ServerLog
|
||||
public function runScript(string $path, string $script, ?ServerLog $serverLog, ?string $user = null): ServerLog
|
||||
{
|
||||
$ssh = $this->server->ssh();
|
||||
$ssh = $this->server->ssh($user);
|
||||
if ($serverLog) {
|
||||
$ssh->setLog($serverLog);
|
||||
}
|
||||
|
5
app/SSH/OS/scripts/available-updates.sh
Normal file
5
app/SSH/OS/scripts/available-updates.sh
Normal file
@ -0,0 +1,5 @@
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
AVAILABLE_UPDATES=$(sudo DEBIAN_FRONTEND=noninteractive apt list --upgradable | wc -l)
|
||||
|
||||
echo "Available updates:$AVAILABLE_UPDATES"
|
@ -1,3 +1,8 @@
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common curl zip unzip git gcc openssl
|
||||
git config --global user.email "__email__"
|
||||
git config --global user.name "__name__"
|
||||
|
||||
# Install Node.js
|
||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -;
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y
|
||||
|
@ -2,8 +2,3 @@ sudo DEBIAN_FRONTEND=noninteractive apt-get clean
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove -y
|
||||
|
||||
# Install Node.js
|
||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -;
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y
|
||||
|
@ -117,9 +117,14 @@ public function changePHPVersion(Site $site, $version): void
|
||||
*/
|
||||
public function setupSSL(Ssl $ssl): void
|
||||
{
|
||||
$domains = '';
|
||||
foreach ($ssl->getDomains() as $domain) {
|
||||
$domains .= ' -d '.$domain;
|
||||
}
|
||||
$command = $this->getScript('nginx/create-letsencrypt-ssl.sh', [
|
||||
'email' => $ssl->site->server->creator->email,
|
||||
'domain' => $ssl->site->domain,
|
||||
'domains' => $domains,
|
||||
'web_directory' => $ssl->site->getWebDirectoryPath(),
|
||||
]);
|
||||
if ($ssl->type == 'custom') {
|
||||
|
@ -1,3 +1,3 @@
|
||||
if ! sudo certbot certonly --force-renewal --nginx --noninteractive --agree-tos --cert-name __domain__ -m __email__ -d __domain__ --verbose; then
|
||||
if ! sudo certbot certonly --force-renewal --nginx --noninteractive --agree-tos --cert-name __domain__ -m __email__ __domains__ --verbose; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
@ -1,10 +1,9 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
server_name __domain__ www.__domain__;
|
||||
server_name __domain__ __aliases__;
|
||||
root __path__/__web_directory__;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate __certificate__;
|
||||
ssl_certificate_key __private_key__;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name __domain__ www.__domain__;
|
||||
server_name __domain__ __aliases__;
|
||||
root __path__/__web_directory__;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
|
@ -1,31 +0,0 @@
|
||||
server {
|
||||
listen __port__;
|
||||
server_name _;
|
||||
root /home/vito/phpmyadmin;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
location = /robots.txt { access_log off; log_not_found off; }
|
||||
|
||||
error_page 404 /index.php;
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass unix:/var/run/php/php__php_version__-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
deny all;
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ server {
|
||||
server_name __domain__ __aliases__;
|
||||
root __path__;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate __certificate__;
|
||||
ssl_certificate_key __private_key__;
|
||||
|
||||
|
@ -4,7 +4,6 @@ server {
|
||||
server_name __domain__ __aliases__;
|
||||
root __path__/__web_directory__;
|
||||
|
||||
ssl on;
|
||||
ssl_certificate __certificate__;
|
||||
ssl_certificate_key __private_key__;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\SiteTypes;
|
||||
|
||||
use App\Enums\SiteFeature;
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PHPBlank extends PHPSite
|
||||
@ -42,7 +43,9 @@ public function data(array $input): array
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
$this->site->server->webserver()->handler()->createVHost($this->site);
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $this->site->server->webserver()->handler();
|
||||
$webserver->createVHost($this->site);
|
||||
$this->progress(65);
|
||||
$this->site->php()?->restart();
|
||||
}
|
||||
|
@ -41,9 +41,12 @@ public function data(array $input): array
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
$this->site->server->webserver()->handler()->createVHost($this->site);
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $this->site->server->webserver()->handler();
|
||||
$webserver->createVHost($this->site);
|
||||
$this->progress(30);
|
||||
app(\App\SSH\PHPMyAdmin\PHPMyAdmin::class)->install($this->site);
|
||||
$this->progress(65);
|
||||
$this->site->php()?->restart();
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,9 @@ public function data(array $input): array
|
||||
*/
|
||||
public function install(): void
|
||||
{
|
||||
$this->site->server->webserver()->handler()->createVHost($this->site);
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $this->site->server->webserver()->handler();
|
||||
$webserver->createVHost($this->site);
|
||||
$this->progress(15);
|
||||
$this->deployKey();
|
||||
$this->progress(30);
|
||||
|
@ -81,7 +81,9 @@ public function data(array $input): array
|
||||
|
||||
public function install(): void
|
||||
{
|
||||
$this->site->server->webserver()->handler()->createVHost($this->site);
|
||||
/** @var Webserver $webserver */
|
||||
$webserver = $this->site->server->webserver()->handler();
|
||||
$webserver->createVHost($this->site);
|
||||
$this->progress(30);
|
||||
/** @var Database $database */
|
||||
$database = app(CreateDatabase::class)->create($this->site->server, [
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace App\ValidationRules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
use phpseclib3\Exception\NoKeyLoadedException;
|
||||
|
||||
class SshKeyRule implements Rule
|
||||
{
|
||||
@ -15,29 +17,13 @@ class SshKeyRule implements Rule
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
$key_parts = explode(' ', $value, 3);
|
||||
if (count($key_parts) < 2) {
|
||||
return false;
|
||||
}
|
||||
if (count($key_parts) > 3) {
|
||||
return false;
|
||||
}
|
||||
$algorithm = $key_parts[0];
|
||||
$key = $key_parts[1];
|
||||
if (! in_array($algorithm, ['ssh-rsa', 'ssh-dss'])) {
|
||||
return false;
|
||||
}
|
||||
$key_base64_decoded = base64_decode($key, true);
|
||||
if ($key_base64_decoded == false) {
|
||||
return false;
|
||||
}
|
||||
$check = base64_decode(substr($key, 0, 16));
|
||||
$check = preg_replace("/[^\w\-]/", '', $check);
|
||||
if ((string) $check !== (string) $algorithm) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
PublicKeyLoader::load($value);
|
||||
|
||||
return true;
|
||||
} catch (NoKeyLoadedException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,7 @@
|
||||
'operating_systems' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20,
|
||||
\App\Enums\OperatingSystem::UBUNTU22,
|
||||
\App\Enums\OperatingSystem::UBUNTU24,
|
||||
],
|
||||
'webservers' => ['none', 'nginx'],
|
||||
'php_versions' => [
|
||||
@ -106,26 +107,32 @@
|
||||
'custom' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
|
||||
],
|
||||
'aws' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'ubuntu',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'ubuntu',
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => 'ubuntu',
|
||||
],
|
||||
'linode' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
|
||||
],
|
||||
'digitalocean' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
|
||||
],
|
||||
'vultr' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
|
||||
],
|
||||
'hetzner' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => 'root',
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => 'root',
|
||||
],
|
||||
],
|
||||
|
||||
@ -164,6 +171,9 @@
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'nginx',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'latest' => 'nginx',
|
||||
],
|
||||
],
|
||||
'mysql' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
@ -174,6 +184,10 @@
|
||||
'5.7' => 'mysql',
|
||||
'8.0' => 'mysql',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'5.7' => 'mysql',
|
||||
'8.0' => 'mysql',
|
||||
],
|
||||
],
|
||||
'mariadb' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
@ -184,6 +198,10 @@
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
],
|
||||
],
|
||||
'postgresql' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
@ -200,6 +218,13 @@
|
||||
'15' => 'postgresql',
|
||||
'16' => 'postgresql',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'12' => 'postgresql',
|
||||
'13' => 'postgresql',
|
||||
'14' => 'postgresql',
|
||||
'15' => 'postgresql',
|
||||
'16' => 'postgresql',
|
||||
],
|
||||
],
|
||||
'php' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
@ -225,6 +250,18 @@
|
||||
'8.2' => 'php8.2-fpm',
|
||||
'8.3' => 'php8.3-fpm',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'5.6' => 'php5.6-fpm',
|
||||
'7.0' => 'php7.0-fpm',
|
||||
'7.1' => 'php7.1-fpm',
|
||||
'7.2' => 'php7.2-fpm',
|
||||
'7.3' => 'php7.3-fpm',
|
||||
'7.4' => 'php7.4-fpm',
|
||||
'8.0' => 'php8.0-fpm',
|
||||
'8.1' => 'php8.1-fpm',
|
||||
'8.2' => 'php8.2-fpm',
|
||||
'8.3' => 'php8.3-fpm',
|
||||
],
|
||||
],
|
||||
'redis' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
@ -233,6 +270,9 @@
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'redis',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'latest' => 'redis',
|
||||
],
|
||||
],
|
||||
'supervisor' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
@ -241,6 +281,9 @@
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'supervisor',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'latest' => 'supervisor',
|
||||
],
|
||||
],
|
||||
'ufw' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
@ -249,6 +292,9 @@
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'ufw',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'latest' => 'ufw',
|
||||
],
|
||||
],
|
||||
'vito-agent' => [
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
@ -257,6 +303,9 @@
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'latest' => 'vito-agent',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'latest' => 'vito-agent',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
@ -58,206 +58,267 @@
|
||||
],
|
||||
'regions' => [
|
||||
[
|
||||
'title' => 'US East (N. Virginia) (us-east-1)',
|
||||
'value' => 'us-east-1',
|
||||
],
|
||||
[
|
||||
'title' => 'US East (Ohio) (us-east-2)',
|
||||
'title' => 'US East (Ohio)',
|
||||
'value' => 'us-east-2',
|
||||
],
|
||||
[
|
||||
'title' => 'US West (N. California) (us-west-1)',
|
||||
'title' => 'US East (Virginia)',
|
||||
'value' => 'us-east-1',
|
||||
],
|
||||
[
|
||||
'title' => 'US West (N. California)',
|
||||
'value' => 'us-west-1',
|
||||
],
|
||||
[
|
||||
'title' => 'US West (Oregon) (us-west-2)',
|
||||
'title' => 'US West (Oregon)',
|
||||
'value' => 'us-west-2',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Hong Kong) (ap-east-1)',
|
||||
'title' => 'Africa (Cape Town)',
|
||||
'value' => 'af-south-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Hong Kong)',
|
||||
'value' => 'ap-east-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Mumbai) (ap-south-1)',
|
||||
'title' => 'Asia Pacific (Hyderabad)',
|
||||
'value' => 'ap-south-2',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Jakarta)',
|
||||
'value' => 'ap-southeast-3',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Melbourne)',
|
||||
'value' => 'ap-southeast-4',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Mumbai)',
|
||||
'value' => 'ap-south-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Singapore) (ap-southeast-1',
|
||||
'value' => 'ap-southeast-1',
|
||||
'title' => 'Asia Pacific (Osaka)',
|
||||
'value' => 'ap-northeast-3',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Seoul) (ap-northeast-2)',
|
||||
'title' => 'Asia Pacific (Seoul)',
|
||||
'value' => 'ap-northeast-2',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Tokyo) (ap-northeast-1)',
|
||||
'value' => 'ap-northeast-1',
|
||||
'title' => 'Asia Pacific (Singapore)',
|
||||
'value' => 'ap-southeast-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Asia Pacific (Sydney) (ap-southeast-2)',
|
||||
'title' => 'Asia Pacific (Sydney)',
|
||||
'value' => 'ap-southeast-2',
|
||||
],
|
||||
[
|
||||
'title' => 'Canada (Central) (ca-central-1)',
|
||||
'title' => 'Asia Pacific (Tokyo)',
|
||||
'value' => 'ap-northeast-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Canada (Central)',
|
||||
'value' => 'ca-central-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (Frankfurt) (eu-central-1)',
|
||||
'title' => 'Canada West (Calgary)',
|
||||
'value' => 'ca-west-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (Frankfurt)',
|
||||
'value' => 'eu-central-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (Ireland) (eu-west-1)',
|
||||
'title' => 'Europe (Ireland)',
|
||||
'value' => 'eu-west-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (London) (eu-west-2)',
|
||||
'title' => 'Europe (London)',
|
||||
'value' => 'eu-west-2',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (Paris) (eu-west-3)',
|
||||
'value' => 'eu-west-3',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (Milan) (eu-south-1)',
|
||||
'title' => 'Europe (Milan)',
|
||||
'value' => 'eu-south-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (Stockholm) (eu-north-1)',
|
||||
'title' => 'Europe (Paris)',
|
||||
'value' => 'eu-west-3',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (Spain)',
|
||||
'value' => 'eu-south-2',
|
||||
],
|
||||
[
|
||||
'title' => 'Europe (Stockholm)',
|
||||
'value' => 'eu-north-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Middle East (Bahrain) (me-south-1)',
|
||||
'title' => 'Europe (Zurich)',
|
||||
'value' => 'eu-central-2',
|
||||
],
|
||||
[
|
||||
'title' => 'Israel (Tel Aviv)',
|
||||
'value' => 'il-central-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Middle East (Bahrain)',
|
||||
'value' => 'me-south-1',
|
||||
],
|
||||
[
|
||||
'title' => 'South America (São Paulo) (sa-east-1)',
|
||||
'value' => 'sa-east-1',
|
||||
'title' => 'Middle East (UAE)',
|
||||
'value' => 'me-central-1',
|
||||
],
|
||||
[
|
||||
'title' => 'Africa (Cape Town) (af-south-1)',
|
||||
'value' => 'af-south-1',
|
||||
'title' => 'South America (São Paulo)',
|
||||
'value' => 'sa-east-1',
|
||||
],
|
||||
],
|
||||
'images' => [
|
||||
'af-south-1' => [
|
||||
'ubuntu_20' => 'ami-03684d4c2541e5333',
|
||||
'ubuntu_22' => 'ami-05759acc7d8973892',
|
||||
],
|
||||
'ap-east-1' => [
|
||||
'ubuntu_20' => 'ami-0b19a97bf326b4931',
|
||||
'ubuntu_22' => 'ami-03490b1b7425e5fe3',
|
||||
],
|
||||
'ap-northeast-1' => [
|
||||
'ubuntu_20' => 'ami-0e25df74d27e028e6',
|
||||
'ubuntu_22' => 'ami-09a81b370b76de6a2',
|
||||
],
|
||||
'ap-northeast-2' => [
|
||||
'ubuntu_20' => 'ami-003a709e1e4ce3729',
|
||||
'ubuntu_22' => 'ami-086cae3329a3f7d75',
|
||||
],
|
||||
'ap-northeast-3' => [
|
||||
'ubuntu_20' => 'ami-06c1367bd83de7d47',
|
||||
'ubuntu_22' => 'ami-0690c54203f5f67da',
|
||||
],
|
||||
'ap-south-1' => [
|
||||
'ubuntu_20' => 'ami-0b88997c830e88c76',
|
||||
'ubuntu_22' => 'ami-0287a05f0ef0e9d9a',
|
||||
],
|
||||
'ap-south-2' => [
|
||||
'ubuntu_20' => 'ami-049e2ae605332dba6',
|
||||
'ubuntu_22' => 'ami-06fe902e167e67d33',
|
||||
],
|
||||
'ap-southeast-1' => [
|
||||
'ubuntu_20' => 'ami-0a6461ddb52e9db63',
|
||||
'ubuntu_22' => 'ami-078c1149d8ad719a7',
|
||||
],
|
||||
'ap-southeast-2' => [
|
||||
'ubuntu_20' => 'ami-0a9fb81cc3289919c',
|
||||
'ubuntu_22' => 'ami-0df4b2961410d4cff',
|
||||
],
|
||||
'ap-southeast-3' => [
|
||||
'ubuntu_20' => 'ami-05ee5bed682a3fff0',
|
||||
'ubuntu_22' => 'ami-0fb6d1fdeeea10488',
|
||||
],
|
||||
'ap-southeast-4' => [
|
||||
'ubuntu_20' => 'ami-02f9759882b112414',
|
||||
'ubuntu_22' => 'ami-043a030d3eeabec75',
|
||||
],
|
||||
'ca-central-1' => [
|
||||
'ubuntu_20' => 'ami-0daaea212e620de87',
|
||||
'ubuntu_22' => 'ami-06873c81b882339ac',
|
||||
],
|
||||
'cn-north-1' => [
|
||||
'ubuntu_20' => 'ami-0c8bcac1fe3389a72',
|
||||
'ubuntu_22' => 'ami-0728a1a4cc9e07753',
|
||||
],
|
||||
'cn-northwest-1' => [
|
||||
'ubuntu_20' => 'ami-0415bfb3ea62e17c0',
|
||||
'ubuntu_22' => 'ami-05529cf859783e600',
|
||||
],
|
||||
'eu-central-1' => [
|
||||
'ubuntu_20' => 'ami-0b369586722023326',
|
||||
'ubuntu_22' => 'ami-06dd92ecc74fdfb36',
|
||||
],
|
||||
'eu-central-2' => [
|
||||
'ubuntu_20' => 'ami-070c78d5ed65f11c8',
|
||||
'ubuntu_22' => 'ami-07cf963e6321c9e6a',
|
||||
],
|
||||
'eu-north-1' => [
|
||||
'ubuntu_20' => 'ami-0c5863072fc83557e',
|
||||
'ubuntu_22' => 'ami-0fe8bec493a81c7da',
|
||||
],
|
||||
'eu-south-1' => [
|
||||
'ubuntu_20' => 'ami-0966ff128f1497260',
|
||||
'ubuntu_22' => 'ami-0b03947fd0ce0eed2',
|
||||
],
|
||||
'eu-south-2' => [
|
||||
'ubuntu_20' => 'ami-087296a5b46cb95ce',
|
||||
'ubuntu_22' => 'ami-03486abd2962c176f',
|
||||
'ubuntu_24' => 'ami-0a50f993202fe4f22',
|
||||
'ubuntu_22' => 'ami-043e9941c6aec0f52',
|
||||
'ubuntu_20' => 'ami-086f353893612e446',
|
||||
],
|
||||
'eu-west-1' => [
|
||||
'ubuntu_20' => 'ami-0e3e7f215a53e2a86',
|
||||
'ubuntu_22' => 'ami-0694d931cee176e7d',
|
||||
'ubuntu_24' => 'ami-0776c814353b4814d',
|
||||
'ubuntu_22' => 'ami-0d0fa503c811361ab',
|
||||
'ubuntu_20' => 'ami-0008aa5cb0cde3400',
|
||||
],
|
||||
'af-south-1' => [
|
||||
'ubuntu_24' => 'ami-0bfda59e8f84ff5ed',
|
||||
'ubuntu_22' => 'ami-0d06a4031539a9be6',
|
||||
'ubuntu_20' => 'ami-0ea465fccfaf199ce',
|
||||
],
|
||||
'eu-west-2' => [
|
||||
'ubuntu_20' => 'ami-0b22eee5ba6bb6772',
|
||||
'ubuntu_22' => 'ami-0505148b3591e4c07',
|
||||
'ubuntu_24' => 'ami-053a617c6207ecc7b',
|
||||
'ubuntu_22' => 'ami-0eb5c35d7b89f3488',
|
||||
'ubuntu_20' => 'ami-0608dbf22649c0159',
|
||||
],
|
||||
'eu-west-3' => [
|
||||
'ubuntu_20' => 'ami-0f14fa1f9c69f4111',
|
||||
'ubuntu_22' => 'ami-00983e8a26e4c9bd9',
|
||||
'eu-south-1' => [
|
||||
'ubuntu_24' => 'ami-0355c99d0faba8847',
|
||||
'ubuntu_22' => 'ami-0bdcf995dcfebf29c',
|
||||
'ubuntu_20' => 'ami-034ea9dc86027e603',
|
||||
],
|
||||
'ap-south-1' => [
|
||||
'ubuntu_24' => 'ami-0f58b397bc5c1f2e8',
|
||||
'ubuntu_22' => 'ami-0f16c6c3de733b474',
|
||||
'ubuntu_20' => 'ami-02f829375c976f810',
|
||||
],
|
||||
'il-central-1' => [
|
||||
'ubuntu_20' => 'ami-0703881563bf5fab7',
|
||||
'ubuntu_22' => 'ami-03869c813f5a2e20c',
|
||||
'ubuntu_24' => 'ami-04a4b28d712600827',
|
||||
'ubuntu_22' => 'ami-09cd8eea397932e88',
|
||||
'ubuntu_20' => 'ami-03988803bd4e18212',
|
||||
],
|
||||
'eu-north-1' => [
|
||||
'ubuntu_24' => 'ami-0705384c0b33c194c',
|
||||
'ubuntu_22' => 'ami-0fff1012fc5cb9f25',
|
||||
'ubuntu_20' => 'ami-07cca21629288f454',
|
||||
],
|
||||
'me-central-1' => [
|
||||
'ubuntu_20' => 'ami-04a5bde3b044c7c21',
|
||||
'ubuntu_22' => 'ami-02168d82d5c12118f',
|
||||
'ubuntu_24' => 'ami-048798fd481c4c791',
|
||||
'ubuntu_22' => 'ami-042fcc4c33a3b6429',
|
||||
'ubuntu_20' => 'ami-0f98fff9d77968c80',
|
||||
],
|
||||
'ca-central-1' => [
|
||||
'ubuntu_24' => 'ami-0c4596ce1e7ae3e68',
|
||||
'ubuntu_22' => 'ami-04fea581fe25e2675',
|
||||
'ubuntu_20' => 'ami-05690acfbddfbeaf6',
|
||||
],
|
||||
'eu-west-3' => [
|
||||
'ubuntu_24' => 'ami-00ac45f3035ff009e',
|
||||
'ubuntu_22' => 'ami-0b020d95f579c8f43',
|
||||
'ubuntu_20' => 'ami-0130b7d3ec1d07e4f',
|
||||
],
|
||||
'ap-south-2' => [
|
||||
'ubuntu_24' => 'ami-008616ec4a2c6975e',
|
||||
'ubuntu_22' => 'ami-088e75eecea53e53e',
|
||||
'ubuntu_20' => 'ami-0688d182e7c22ec3f',
|
||||
],
|
||||
'ca-west-1' => [
|
||||
'ubuntu_24' => 'ami-07022089d2e36ace0',
|
||||
'ubuntu_22' => 'ami-02e22cefcad05a835',
|
||||
'ubuntu_20' => 'ami-03890126b7675fac8',
|
||||
],
|
||||
'eu-central-1' => [
|
||||
'ubuntu_24' => 'ami-01e444924a2233b07',
|
||||
'ubuntu_22' => 'ami-01a93368cab494eb5',
|
||||
'ubuntu_20' => 'ami-07fd6b7604806e876',
|
||||
],
|
||||
'me-south-1' => [
|
||||
'ubuntu_20' => 'ami-0165b692f5714e330',
|
||||
'ubuntu_22' => 'ami-0f8d2a6080634ee69',
|
||||
'ubuntu_24' => 'ami-087f3ec3fdda67295',
|
||||
'ubuntu_22' => 'ami-03ae386fab11fa0a1',
|
||||
'ubuntu_20' => 'ami-0f65a186b3552f348',
|
||||
],
|
||||
'sa-east-1' => [
|
||||
'ubuntu_20' => 'ami-095ca107fb46b81e6',
|
||||
'ubuntu_22' => 'ami-0b6c2d49148000cd5',
|
||||
'ap-northeast-1' => [
|
||||
'ubuntu_24' => 'ami-01bef798938b7644d',
|
||||
'ubuntu_22' => 'ami-08e32db9e33e28876',
|
||||
'ubuntu_20' => 'ami-0ed286a950292f370',
|
||||
],
|
||||
'us-east-1' => [
|
||||
'ubuntu_20' => 'ami-0fe0238291c8e3f07',
|
||||
'ubuntu_22' => 'ami-0fc5d935ebf8bc3bc',
|
||||
],
|
||||
'us-east-2' => [
|
||||
'ubuntu_20' => 'ami-0b6968e5c7117349a',
|
||||
'ubuntu_22' => 'ami-0e83be366243f524a',
|
||||
'ap-southeast-1' => [
|
||||
'ubuntu_24' => 'ami-003c463c8207b4dfa',
|
||||
'ubuntu_22' => 'ami-084cab24460184bd3',
|
||||
'ubuntu_20' => 'ami-081ee02c4cdf3917c',
|
||||
],
|
||||
'us-west-1' => [
|
||||
'ubuntu_20' => 'ami-092efbcc9a2d2be8a',
|
||||
'ubuntu_22' => 'ami-0cbd40f694b804622',
|
||||
'ubuntu_24' => 'ami-08012c0a9ee8e21c4',
|
||||
'ubuntu_22' => 'ami-023f8bebe991375fd',
|
||||
'ubuntu_20' => 'ami-0344f34a6875de16a',
|
||||
],
|
||||
'ap-southeast-3' => [
|
||||
'ubuntu_24' => 'ami-00c31062c5966e820',
|
||||
'ubuntu_22' => 'ami-0fd547652d1673e30',
|
||||
'ubuntu_20' => 'ami-0699dddffd3542faf',
|
||||
],
|
||||
'ap-northeast-2' => [
|
||||
'ubuntu_24' => 'ami-0e6f2b2fa0ca704d0',
|
||||
'ubuntu_22' => 'ami-0720c7fcba4b88b36',
|
||||
'ubuntu_20' => 'ami-03ec7d02334d21d49',
|
||||
],
|
||||
'ap-southeast-2' => [
|
||||
'ubuntu_24' => 'ami-080660c9757080771',
|
||||
'ubuntu_22' => 'ami-0d9d3b991cfa8ac6e',
|
||||
'ubuntu_20' => 'ami-06c7a70c38594fef6',
|
||||
],
|
||||
'us-east-1' => [
|
||||
'ubuntu_24' => 'ami-04b70fa74e45c3917',
|
||||
'ubuntu_22' => 'ami-0cfa2ad4242c3168d',
|
||||
'ubuntu_20' => 'ami-0e3a6d8ff4c8fe246',
|
||||
],
|
||||
'us-west-2' => [
|
||||
'ubuntu_20' => 'ami-0a55cdf919d10eac9',
|
||||
'ubuntu_22' => 'ami-0efcece6bed30fd98',
|
||||
'ubuntu_24' => 'ami-0cf2b4e024cdb6960',
|
||||
'ubuntu_22' => 'ami-09c3a3c2cf6003f6c',
|
||||
'ubuntu_20' => 'ami-091c4300a778841cc',
|
||||
],
|
||||
'ap-east-1' => [
|
||||
'ubuntu_24' => 'ami-026789b06a607b9a5',
|
||||
'ubuntu_22' => 'ami-0361acb22fef7522b',
|
||||
'ubuntu_20' => 'ami-0c0665dcea29a292d',
|
||||
],
|
||||
'eu-central-2' => [
|
||||
'ubuntu_24' => 'ami-053ea2f9d1d6ac54c',
|
||||
'ubuntu_22' => 'ami-09407f9985de426af',
|
||||
'ubuntu_20' => 'ami-00c9866441e3616dd',
|
||||
],
|
||||
'us-east-2' => [
|
||||
'ubuntu_24' => 'ami-09040d770ffe2224f',
|
||||
'ubuntu_22' => 'ami-0b986fc833876b42e',
|
||||
'ubuntu_20' => 'ami-010e55fe08af05fa7',
|
||||
],
|
||||
'ap-northeast-3' => [
|
||||
'ubuntu_24' => 'ami-0b9bc7dcdbcff394e',
|
||||
'ubuntu_22' => 'ami-063600dcf13c07ebc',
|
||||
'ubuntu_20' => 'ami-0b7108d627f57c7c8',
|
||||
],
|
||||
'sa-east-1' => [
|
||||
'ubuntu_24' => 'ami-04716897be83e3f04',
|
||||
'ubuntu_22' => 'ami-0e6dfcf4e0e4dfc52',
|
||||
'ubuntu_20' => 'ami-050e1159c5a10dd81',
|
||||
],
|
||||
'ap-southeast-4' => [
|
||||
'ubuntu_24' => 'ami-0396cf525fd0aa5c1',
|
||||
'ubuntu_22' => 'ami-097638dc9b6250206',
|
||||
'ubuntu_20' => 'ami-02d0fccf5cdcdd8c5',
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -426,6 +487,7 @@
|
||||
'ubuntu_18' => 'linode/ubuntu18.04',
|
||||
'ubuntu_20' => 'linode/ubuntu20.04',
|
||||
'ubuntu_22' => 'linode/ubuntu22.04',
|
||||
'ubuntu_24' => 'linode/ubuntu24.04',
|
||||
],
|
||||
],
|
||||
'digitalocean' => [
|
||||
@ -557,6 +619,7 @@
|
||||
'ubuntu_18' => '112929540',
|
||||
'ubuntu_20' => '112929454',
|
||||
'ubuntu_22' => '129211873',
|
||||
'ubuntu_24' => '155133621',
|
||||
],
|
||||
],
|
||||
'vultr' => [
|
||||
@ -728,6 +791,7 @@
|
||||
'ubuntu_18' => '270',
|
||||
'ubuntu_20' => '387',
|
||||
'ubuntu_22' => '1743',
|
||||
'ubuntu_24' => '2284',
|
||||
],
|
||||
],
|
||||
'hetzner' => [
|
||||
@ -859,6 +923,7 @@
|
||||
'ubuntu_18' => 'ubuntu-18.04',
|
||||
'ubuntu_20' => 'ubuntu-20.04',
|
||||
'ubuntu_22' => 'ubuntu-22.04',
|
||||
'ubuntu_24' => 'ubuntu-24.04',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
24
database/factories/ScriptExecutionFactory.php
Normal file
24
database/factories/ScriptExecutionFactory.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\ScriptExecutionStatus;
|
||||
use App\Models\ScriptExecution;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class ScriptExecutionFactory extends Factory
|
||||
{
|
||||
protected $model = ScriptExecution::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'user' => 'root',
|
||||
'variables' => [],
|
||||
'status' => ScriptExecutionStatus::EXECUTING,
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
}
|
22
database/factories/ScriptFactory.php
Normal file
22
database/factories/ScriptFactory.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Script;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class ScriptFactory extends Factory
|
||||
{
|
||||
protected $model = Script::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->name(),
|
||||
'content' => 'ls -la',
|
||||
'created_at' => Carbon::now(),
|
||||
'updated_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ public function definition(): array
|
||||
'ca' => $this->faker->word(),
|
||||
'expires_at' => Carbon::now()->addDay(),
|
||||
'status' => SslStatus::CREATED,
|
||||
'domains' => 'example.com',
|
||||
'domains' => ['example.com'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?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('source_controls', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('project_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('source_controls', function (Blueprint $table) {
|
||||
$table->dropColumn('project_id');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
$table->integer('updates')->default(0);
|
||||
$table->timestamp('last_update_check')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
$table->dropColumn('updates');
|
||||
$table->dropColumn('last_update_check');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::dropIfExists('script_executions');
|
||||
|
||||
Schema::create('script_executions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('script_id');
|
||||
$table->unsignedBigInteger('server_log_id')->nullable();
|
||||
$table->string('user');
|
||||
$table->json('variables')->nullable();
|
||||
$table->string('status');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('script_executions');
|
||||
}
|
||||
};
|
File diff suppressed because one or more lines are too long
1
public/build/assets/app-7f487305.css
Normal file
1
public/build/assets/app-7f487305.css
Normal file
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"resources/css/app.css": {
|
||||
"file": "assets/app-268661bd.css",
|
||||
"file": "assets/app-7f487305.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/css/app.css"
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="mx-auto mb-10">
|
||||
<div {{ $attributes->merge(["class" => "mx-auto mb-10"]) }}>
|
||||
<x-card-header>
|
||||
@if (isset($title))
|
||||
<x-slot name="title">{{ $title }}</x-slot>
|
||||
|
19
resources/views/components/checkbox.blade.php
Normal file
19
resources/views/components/checkbox.blade.php
Normal file
@ -0,0 +1,19 @@
|
||||
@props([
|
||||
"disabled" => false,
|
||||
"id",
|
||||
"name",
|
||||
"value",
|
||||
])
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="{{ $id }}"
|
||||
name="{{ $name }}"
|
||||
type="checkbox"
|
||||
value="{{ $value }}"
|
||||
{{ $attributes->merge(["disabled" => $disabled, "class" => "rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:focus:ring-indigo-600 dark:focus:ring-offset-gray-800"]) }}
|
||||
/>
|
||||
<label for="{{ $id }}" class="ms-2 text-sm font-medium text-gray-900 dark:text-gray-300">
|
||||
{{ $slot }}
|
||||
</label>
|
||||
</div>
|
14
resources/views/components/heroicons/o-bolt.blade.php
Normal file
14
resources/views/components/heroicons/o-bolt.blade.php
Normal file
@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 316 B |
@ -18,7 +18,8 @@
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
show: @js($show),
|
||||
forceShow: @js($show),
|
||||
show: false,
|
||||
focusables() {
|
||||
// All focusable element types...
|
||||
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
||||
@ -34,12 +35,14 @@
|
||||
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
|
||||
}"
|
||||
x-init="
|
||||
setTimeout(() => (show = forceShow), 100)
|
||||
$watch('show', (value) => {
|
||||
if (value) {
|
||||
document.body.classList.add('overflow-y-hidden')
|
||||
{{ $attributes->has("focusable") ? "setTimeout(() => firstFocusable().focus(), 100)" : "" }}
|
||||
} else {
|
||||
document.body.classList.remove('overflow-y-hidden')
|
||||
$dispatch('modal-{{ $name }}-closed')
|
||||
}
|
||||
})
|
||||
"
|
||||
@ -52,6 +55,7 @@
|
||||
x-show="show"
|
||||
class="fixed inset-0 z-50 overflow-y-auto px-4 py-6 sm:px-0"
|
||||
style="display: {{ $show ? "block" : "none" }}"
|
||||
{{ $attributes }}
|
||||
>
|
||||
<div
|
||||
x-show="show"
|
||||
|
@ -4,11 +4,11 @@
|
||||
|
||||
@php
|
||||
$class = [
|
||||
"success" => "rounded-md border border-green-300 bg-green-50 px-2 py-1 text-xs uppercase text-green-500 dark:border-green-600 dark:bg-green-500 dark:bg-opacity-10",
|
||||
"danger" => "rounded-md border border-red-300 bg-red-50 px-2 py-1 text-xs uppercase text-red-500 dark:border-red-600 dark:bg-red-500 dark:bg-opacity-10",
|
||||
"warning" => "rounded-md border border-yellow-300 bg-yellow-50 px-2 py-1 text-xs uppercase text-yellow-500 dark:border-yellow-600 dark:bg-yellow-500 dark:bg-opacity-10",
|
||||
"disabled" => "rounded-md border border-gray-300 bg-gray-50 px-2 py-1 text-xs uppercase text-gray-500 dark:border-gray-600 dark:bg-gray-500 dark:bg-opacity-30 dark:text-gray-400",
|
||||
"info" => "rounded-md border border-primary-300 bg-primary-50 px-2 py-1 text-xs uppercase text-primary-500 dark:border-primary-600 dark:bg-primary-500 dark:bg-opacity-10",
|
||||
"success" => "max-w-max rounded-md border border-green-300 bg-green-50 px-2 py-1 text-xs uppercase text-green-500 dark:border-green-600 dark:bg-green-500 dark:bg-opacity-10",
|
||||
"danger" => "max-w-max rounded-md border border-red-300 bg-red-50 px-2 py-1 text-xs uppercase text-red-500 dark:border-red-600 dark:bg-red-500 dark:bg-opacity-10",
|
||||
"warning" => "max-w-max rounded-md border border-yellow-300 bg-yellow-50 px-2 py-1 text-xs uppercase text-yellow-500 dark:border-yellow-600 dark:bg-yellow-500 dark:bg-opacity-10",
|
||||
"disabled" => "max-w-max rounded-md border border-gray-300 bg-gray-50 px-2 py-1 text-xs uppercase text-gray-500 dark:border-gray-600 dark:bg-gray-500 dark:bg-opacity-30 dark:text-gray-400",
|
||||
"info" => "max-w-max rounded-md border border-primary-300 bg-primary-50 px-2 py-1 text-xs uppercase text-primary-500 dark:border-primary-600 dark:bg-primary-500 dark:bg-opacity-10",
|
||||
];
|
||||
@endphp
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<td
|
||||
{!! $attributes->merge(["class" => "whitespace-nowrap px-6 py-4 text-gray-700 dark:text-gray-300 w-1"]) !!}
|
||||
{!! $attributes->merge(["class" => "text-sm whitespace-nowrap px-6 py-4 text-gray-700 dark:text-gray-300 w-1"]) !!}
|
||||
>
|
||||
{{ $slot }}
|
||||
</td>
|
||||
|
@ -432,7 +432,7 @@ class="-ml-1 mr-1.5 h-[18px] w-[18px]"
|
||||
></path>
|
||||
</svg>
|
||||
<p
|
||||
class="text-[13px] font-medium leading-none text-gray-800 dark:text-white"
|
||||
class="text-sm font-medium leading-none text-gray-800 dark:text-white"
|
||||
x-text="toast.message"
|
||||
></p>
|
||||
</div>
|
||||
|
@ -30,29 +30,18 @@ class="p-6"
|
||||
type="text"
|
||||
class="mt-1 w-full"
|
||||
/>
|
||||
<x-input-help class="mt-2">
|
||||
<a href="https://vitodeploy.com/servers/cronjobs.html" target="_blank" class="text-primary-500">
|
||||
How the command should look like?
|
||||
</a>
|
||||
</x-input-help>
|
||||
@error("command")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
@php
|
||||
$user = old("user", "vito");
|
||||
@endphp
|
||||
|
||||
<x-input-label for="user" :value="__('User')" />
|
||||
<x-select-input id="user" name="user" class="mt-1 w-full">
|
||||
<option value="" selected disabled>
|
||||
{{ __("Select") }}
|
||||
</option>
|
||||
<option value="root" @if($user === 'root') selected @endif>root</option>
|
||||
<option value="{{ $server->getSshUser() }}" @if($user === $server->getSshUser()) selected @endif>
|
||||
{{ $server->getSshUser() }}
|
||||
</option>
|
||||
</x-select-input>
|
||||
@error("user")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
@include("fields.user", ["value" => old("user")])
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
|
@ -12,19 +12,50 @@
|
||||
<div x-data="" class="space-y-3">
|
||||
@if (count($cronjobs) > 0)
|
||||
@foreach ($cronjobs as $cronjob)
|
||||
<x-item-card>
|
||||
<x-item-card id="cronjob-{{ $cronjob->id }}">
|
||||
<div class="flex flex-grow flex-col items-start justify-center">
|
||||
<span class="mb-1 flex items-center lowercase text-red-600">
|
||||
<div class="mb-1 text-left text-xs lowercase text-red-600 md:text-sm">
|
||||
{{ $cronjob->command }}
|
||||
</span>
|
||||
<span class="text-sm text-gray-400">
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 md:text-sm">
|
||||
{{ $cronjob->frequencyLabel() }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
@include("cronjobs.partials.status", ["status" => $cronjob->status])
|
||||
<div class="inline">
|
||||
<div class="ml-1 inline">
|
||||
@if ($cronjob->status == \App\Enums\CronjobStatus::READY)
|
||||
<x-icon-button
|
||||
id="disable-cronjob-{{ $cronjob->id }}"
|
||||
data-tooltip="Disable Cronjob"
|
||||
hx-post="{{ route('servers.cronjobs.disable', ['server' => $server, 'cronJob' => $cronjob]) }}"
|
||||
hx-target="#cronjob-{{ $cronjob->id }}"
|
||||
hx-select="#cronjob-{{ $cronjob->id }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#disable-cronjob-{{ $cronjob->id }}"
|
||||
>
|
||||
<x-heroicon name="o-stop" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
@endif
|
||||
|
||||
@if ($cronjob->status == \App\Enums\CronjobStatus::DISABLED)
|
||||
<x-icon-button
|
||||
id="enable-cronjob-{{ $cronjob->id }}"
|
||||
data-tooltip="Enable Cronjob"
|
||||
hx-post="{{ route('servers.cronjobs.enable', ['server' => $server, 'cronJob' => $cronjob]) }}"
|
||||
hx-target="#cronjob-{{ $cronjob->id }}"
|
||||
hx-select="#cronjob-{{ $cronjob->id }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#enable-cronjob-{{ $cronjob->id }}"
|
||||
>
|
||||
<x-heroicon name="o-play" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
@endif
|
||||
|
||||
<x-icon-button
|
||||
data-tooltip="Delete Cronjob"
|
||||
x-on:click="deleteAction = '{{ route('servers.cronjobs.destroy', ['server' => $server, 'cronJob' => $cronjob]) }}'; $dispatch('open-modal', 'delete-cronjob')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
|
@ -6,6 +6,18 @@
|
||||
<x-status status="warning">{{ $status }}</x-status>
|
||||
@endif
|
||||
|
||||
@if ($status == \App\Enums\CronjobStatus::DISABLING)
|
||||
<x-status status="warning">{{ $status }}</x-status>
|
||||
@endif
|
||||
|
||||
@if ($status == \App\Enums\CronjobStatus::DISABLED)
|
||||
<x-status status="disabled">{{ $status }}</x-status>
|
||||
@endif
|
||||
|
||||
@if ($status == \App\Enums\CronjobStatus::ENABLING)
|
||||
<x-status status="warning">{{ $status }}</x-status>
|
||||
@endif
|
||||
|
||||
@if ($status == \App\Enums\CronjobStatus::DELETING)
|
||||
<x-status status="danger">{{ $status }}</x-status>
|
||||
@endif
|
||||
|
15
resources/views/fields/user.blade.php
Normal file
15
resources/views/fields/user.blade.php
Normal file
@ -0,0 +1,15 @@
|
||||
<x-input-label for="user" :value="__('User')" />
|
||||
<x-select-input id="user" name="user" class="mt-1 w-full">
|
||||
<option value="" selected disabled>
|
||||
{{ __("Select") }}
|
||||
</option>
|
||||
<option value="root" @if($value === 'root') selected @endif>root</option>
|
||||
@if (isset($server))
|
||||
<option value="{{ $server->getSshUser() }}" @if($value === $server->getSshUser()) selected @endif>
|
||||
{{ $server->getSshUser() }}
|
||||
</option>
|
||||
@endif
|
||||
</x-select-input>
|
||||
@error("user")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
@ -161,6 +161,13 @@ class="fixed left-0 top-0 z-40 h-screen w-64 -translate-x-full border-r border-g
|
||||
<x-hr />
|
||||
@endif
|
||||
|
||||
<li>
|
||||
<x-sidebar-link :href="route('scripts.index')" :active="request()->routeIs('scripts.*')">
|
||||
<x-heroicon name="o-bolt" class="h-6 w-6" />
|
||||
<span class="ml-2">Scripts</span>
|
||||
</x-sidebar-link>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<x-sidebar-link :href="route('profile')" :active="request()->routeIs('profile')">
|
||||
<x-heroicon name="o-user-circle" class="h-6 w-6" />
|
||||
|
5
resources/views/scripts/index.blade.php
Normal file
5
resources/views/scripts/index.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="pageTitle">{{ __("Scripts") }}</x-slot>
|
||||
|
||||
@include("scripts.partials.scripts-list")
|
||||
</x-app-layout>
|
37
resources/views/scripts/partials/create-script.blade.php
Normal file
37
resources/views/scripts/partials/create-script.blade.php
Normal file
@ -0,0 +1,37 @@
|
||||
<div>
|
||||
<x-primary-button x-data="" x-on:click.prevent="$dispatch('open-modal', 'create-script')">
|
||||
{{ __("Create Script") }}
|
||||
</x-primary-button>
|
||||
|
||||
<x-modal name="create-script">
|
||||
<form
|
||||
id="create-script-form"
|
||||
hx-post="{{ route("scripts.store") }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#create-script-form"
|
||||
class="p-6"
|
||||
>
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Create script") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
@include("scripts.partials.fields.name", ["value" => old("name")])
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
@include("scripts.partials.fields.content", ["value" => old("content")])
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
||||
{{ __("Cancel") }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-primary-button class="ml-3" hx-disable>
|
||||
{{ __("Create") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
||||
</div>
|
18
resources/views/scripts/partials/delete-script.blade.php
Normal file
18
resources/views/scripts/partials/delete-script.blade.php
Normal file
@ -0,0 +1,18 @@
|
||||
<x-modal name="delete-script" :show="$errors->isNotEmpty()">
|
||||
<form id="delete-script-form" method="post" x-bind:action="deleteAction" class="p-6">
|
||||
@csrf
|
||||
@method("delete")
|
||||
|
||||
<h2 class="text-lg font-medium">Are you sure that you want to delete this script?</h2>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
||||
{{ __("Cancel") }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-danger-button class="ml-3">
|
||||
{{ __("Delete") }}
|
||||
</x-danger-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
35
resources/views/scripts/partials/edit-script.blade.php
Normal file
35
resources/views/scripts/partials/edit-script.blade.php
Normal file
@ -0,0 +1,35 @@
|
||||
<x-modal
|
||||
name="edit-script"
|
||||
:show="true"
|
||||
x-on:modal-edit-script-closed.window="window.history.pushState('', '', '{{ route('scripts.index') }}');"
|
||||
>
|
||||
<form
|
||||
id="edit-script-form"
|
||||
hx-post="{{ route("scripts.edit", ["script" => $script]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#edit-script-form"
|
||||
class="p-6"
|
||||
>
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Edit script") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
@include("scripts.partials.fields.name", ["value" => old("name", $script->name)])
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
@include("scripts.partials.fields.content", ["value" => old("content", $script->content)])
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
||||
{{ __("Cancel") }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-primary-button class="ml-3" hx-disable>
|
||||
{{ __("Save") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
109
resources/views/scripts/partials/execute-script.blade.php
Normal file
109
resources/views/scripts/partials/execute-script.blade.php
Normal file
@ -0,0 +1,109 @@
|
||||
<x-modal
|
||||
name="execute-script"
|
||||
:show="true"
|
||||
x-on:modal-execute-script-closed.window="window.history.pushState('', '', '{{ route('scripts.index') }}');"
|
||||
>
|
||||
<div
|
||||
x-data="{
|
||||
server: '',
|
||||
selectServer() {
|
||||
let url =
|
||||
'{{ route("scripts.index", ["execute" => $script->id]) }}&server=' +
|
||||
this.server
|
||||
window.history.pushState('', '', url)
|
||||
htmx.ajax('GET', url, {
|
||||
target: '#select-user',
|
||||
swap: 'outerHTML',
|
||||
select: '#select-user',
|
||||
})
|
||||
},
|
||||
}"
|
||||
>
|
||||
<form
|
||||
id="execute-script-form"
|
||||
hx-post="{{ route("scripts.execute", ["script" => $script]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#execute-script-form"
|
||||
class="p-6"
|
||||
>
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Execute script") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="server" :value="__('Select a server to execute')" />
|
||||
<x-select-input
|
||||
id="server"
|
||||
name="server"
|
||||
x-model="server"
|
||||
x-on:change="selectServer"
|
||||
class="mt-1 w-full"
|
||||
>
|
||||
<option value="" selected disabled>
|
||||
{{ __("Select") }}
|
||||
</option>
|
||||
@php
|
||||
$executeServers = auth()
|
||||
->user()
|
||||
->allServers()
|
||||
->get();
|
||||
@endphp
|
||||
|
||||
@foreach ($executeServers as $executeServer)
|
||||
<option value="{{ $executeServer->id }}">
|
||||
{{ $executeServer->name }} [{{ $executeServer->project->name }}]
|
||||
</option>
|
||||
@endforeach
|
||||
</x-select-input>
|
||||
@error("server")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6" id="select-user">
|
||||
@php
|
||||
$s = null;
|
||||
if (request()->has("server")) {
|
||||
$s = auth()
|
||||
->user()
|
||||
->allServers()
|
||||
->findOrFail(request("server"));
|
||||
}
|
||||
@endphp
|
||||
|
||||
@include("fields.user", ["value" => old("user"), "server" => $s])
|
||||
</div>
|
||||
|
||||
@if (count($script->getVariables()) > 0)
|
||||
<x-input-label class="mt-6" value="Variables" />
|
||||
|
||||
<div class="mt-2 space-y-6 border-2 border-dashed border-gray-200 px-2 py-3 dark:border-gray-700">
|
||||
@foreach ($script->getVariables() as $variable)
|
||||
<div>
|
||||
<x-input-label :for="'variable-' . $variable" :value="$variable" />
|
||||
<x-text-input
|
||||
id="variable-{{ $variable }}"
|
||||
name="variables[{{ $variable }}]"
|
||||
class="mt-1 w-full"
|
||||
value="{{ old('variables.' . $variable) }}"
|
||||
/>
|
||||
</div>
|
||||
@error("variables." . $variable)
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
||||
{{ __("Cancel") }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-primary-button class="ml-3" hx-disable>
|
||||
{{ __("Execute") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</x-modal>
|
10
resources/views/scripts/partials/fields/content.blade.php
Normal file
10
resources/views/scripts/partials/fields/content.blade.php
Normal file
@ -0,0 +1,10 @@
|
||||
<x-input-label for="content" :value="__('Content')" />
|
||||
<x-textarea id="content" name="content" class="mt-1 min-h-[400px] w-full">
|
||||
{{ $value }}
|
||||
</x-textarea>
|
||||
@error("content")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
|
||||
<x-input-help>You can use variables like ${VARIABLE_NAME} in the script</x-input-help>
|
||||
<x-input-help>The variables will be asked when executing the script</x-input-help>
|
5
resources/views/scripts/partials/fields/name.blade.php
Normal file
5
resources/views/scripts/partials/fields/name.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input value="{{ $value }}" id="name" name="name" type="text" class="mt-1 w-full" />
|
||||
@error("name")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
@ -0,0 +1,11 @@
|
||||
@if ($status == \App\Enums\ScriptExecutionStatus::EXECUTING)
|
||||
<x-status status="warning">{{ $status }}</x-status>
|
||||
@endif
|
||||
|
||||
@if ($status == \App\Enums\ScriptExecutionStatus::COMPLETED)
|
||||
<x-status status="success">{{ $status }}</x-status>
|
||||
@endif
|
||||
|
||||
@if ($status == \App\Enums\ScriptExecutionStatus::FAILED)
|
||||
<x-status status="danger">{{ $status }}</x-status>
|
||||
@endif
|
@ -0,0 +1,67 @@
|
||||
<x-container>
|
||||
<x-card-header>
|
||||
<x-slot name="title">Script Executions</x-slot>
|
||||
<x-slot name="description">Here you can see the list of the latest executions of your script</x-slot>
|
||||
</x-card-header>
|
||||
|
||||
<x-live id="script-executions" interval="5s">
|
||||
@if (count($executions) > 0)
|
||||
<div id="scripts-list" x-data="{}">
|
||||
<x-table>
|
||||
<x-thead>
|
||||
<x-tr>
|
||||
<x-th>Date</x-th>
|
||||
<x-th>Status</x-th>
|
||||
<x-th></x-th>
|
||||
</x-tr>
|
||||
</x-thead>
|
||||
<x-tbody>
|
||||
@foreach ($executions as $execution)
|
||||
<x-tr>
|
||||
<x-td>
|
||||
<x-datetime :value="$execution->created_at" />
|
||||
</x-td>
|
||||
<x-td>
|
||||
@include("scripts.partials.script-execution-status", ["status" => $execution->status])
|
||||
</x-td>
|
||||
<x-td class="text-right">
|
||||
<x-icon-button
|
||||
x-on:click="$dispatch('open-modal', 'show-log')"
|
||||
id="show-log-{{ $execution->id }}"
|
||||
hx-get="{{ route('scripts.log', ['script' => $script, 'execution' => $execution]) }}"
|
||||
hx-target="#show-log-content"
|
||||
hx-select="#show-log-content"
|
||||
hx-swap="outerHTML"
|
||||
data-tooltip="Logs"
|
||||
>
|
||||
<x-heroicon name="o-eye" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
</x-td>
|
||||
</x-tr>
|
||||
@endforeach
|
||||
</x-tbody>
|
||||
</x-table>
|
||||
</div>
|
||||
@else
|
||||
<x-simple-card>
|
||||
<div class="text-center">This script hasn't been executed yet!</div>
|
||||
</x-simple-card>
|
||||
@endif
|
||||
<div class="mt-5">
|
||||
{{ $executions->withQueryString()->links() }}
|
||||
</div>
|
||||
</x-live>
|
||||
<x-modal name="show-log" max-width="4xl">
|
||||
<div class="p-6" id="show-log-content">
|
||||
<h2 class="mb-5 text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("View Log") }}
|
||||
</h2>
|
||||
<x-console-view>{{ session()->get("content") }}</x-console-view>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
||||
{{ __("Close") }}
|
||||
</x-secondary-button>
|
||||
</div>
|
||||
</div>
|
||||
</x-modal>
|
||||
</x-container>
|
94
resources/views/scripts/partials/scripts-list.blade.php
Normal file
94
resources/views/scripts/partials/scripts-list.blade.php
Normal file
@ -0,0 +1,94 @@
|
||||
<x-container>
|
||||
<x-card-header>
|
||||
<x-slot name="title">Scripts</x-slot>
|
||||
<x-slot name="description">Your scripts are here. Create/Edit/Delete and Execute them on your servers.</x-slot>
|
||||
<x-slot name="aside">
|
||||
@include("scripts.partials.create-script")
|
||||
</x-slot>
|
||||
</x-card-header>
|
||||
|
||||
@if (count($scripts) > 0)
|
||||
<div id="scripts-list" x-data="{ deleteAction: '' }">
|
||||
<x-table>
|
||||
<x-thead>
|
||||
<x-tr>
|
||||
<x-th>ID</x-th>
|
||||
<x-th>Name</x-th>
|
||||
<x-th>Last Executed At</x-th>
|
||||
<x-th></x-th>
|
||||
</x-tr>
|
||||
</x-thead>
|
||||
<x-tbody>
|
||||
@foreach ($scripts as $script)
|
||||
<x-tr>
|
||||
<x-td>{{ $script->id }}</x-td>
|
||||
<x-td>{{ $script->name }}</x-td>
|
||||
<x-td>
|
||||
@if ($script->lastExecution)
|
||||
<x-datetime :value="$script->lastExecution->created_at" />
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</x-td>
|
||||
<x-td class="text-right">
|
||||
<x-icon-button :href="route('scripts.show', $script)" data-tooltip="Executions">
|
||||
<x-heroicon name="o-eye" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
<x-icon-button
|
||||
data-tooltip="Execute"
|
||||
id="execute-{{ $script->id }}"
|
||||
hx-get="{{ route('scripts.index', ['execute' => $script->id]) }}"
|
||||
hx-replace-url="true"
|
||||
hx-select="#execute"
|
||||
hx-target="#execute"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#execute-{{ $script->id }}"
|
||||
>
|
||||
<x-heroicon name="o-bolt" class="h-5 w-5 text-primary-500" />
|
||||
</x-icon-button>
|
||||
<x-icon-button
|
||||
data-tooltip="Edit"
|
||||
id="edit-{{ $script->id }}"
|
||||
hx-get="{{ route('scripts.index', ['edit' => $script->id]) }}"
|
||||
hx-replace-url="true"
|
||||
hx-select="#edit"
|
||||
hx-target="#edit"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#edit-{{ $script->id }}"
|
||||
>
|
||||
<x-heroicon name="o-pencil" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
<x-icon-button
|
||||
data-tooltip="Delete"
|
||||
x-on:click="deleteAction = '{{ route('scripts.delete', $script->id) }}'; $dispatch('open-modal', 'delete-script')"
|
||||
>
|
||||
<x-heroicon name="o-trash" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
</x-td>
|
||||
</x-tr>
|
||||
@endforeach
|
||||
</x-tbody>
|
||||
</x-table>
|
||||
|
||||
@include("scripts.partials.delete-script")
|
||||
|
||||
<div id="edit">
|
||||
@if (isset($editScript))
|
||||
@include("scripts.partials.edit-script", ["script" => $editScript])
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div id="execute">
|
||||
@if (isset($executeScript))
|
||||
@include("scripts.partials.execute-script", ["script" => $executeScript])
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<x-simple-card>
|
||||
<div class="text-center">
|
||||
{{ __("You don't have any scripts yet!") }}
|
||||
</div>
|
||||
</x-simple-card>
|
||||
@endif
|
||||
</x-container>
|
5
resources/views/scripts/show.blade.php
Normal file
5
resources/views/scripts/show.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="pageTitle">{{ $script->name }}</x-slot>
|
||||
|
||||
@include("scripts.partials.script-executions-list")
|
||||
</x-app-layout>
|
@ -1,4 +1,4 @@
|
||||
<x-card>
|
||||
<x-card id="server-details">
|
||||
<x-slot name="title">{{ __("Details") }}</x-slot>
|
||||
<x-slot name="description">
|
||||
{{ __("More details about your server") }}
|
||||
@ -14,6 +14,57 @@
|
||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>{{ __("Last Update Checked") }}</div>
|
||||
<div>
|
||||
@if ($server->last_update_check)
|
||||
<x-datetime :value="$server->last_update_check" />
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="py-5">
|
||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="available-updates" class="flex items-center justify-between">
|
||||
<div>{{ __("Available Updates") }} ({{ $server->updates }})</div>
|
||||
<div class="flex flex-col items-end md:flex-row md:items-center">
|
||||
@if ($server->updates > 0)
|
||||
<x-primary-button
|
||||
id="btn-update-server"
|
||||
hx-post="{{ route('servers.settings.update', $server) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#server-details"
|
||||
hx-select="#server-details"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-update-server"
|
||||
>
|
||||
{{ __("Update") }}
|
||||
</x-primary-button>
|
||||
@endif
|
||||
|
||||
<x-secondary-button
|
||||
id="btn-check-updates"
|
||||
class="mt-2 md:ml-2 md:mt-0"
|
||||
hx-post="{{ route('servers.settings.check-updates', $server) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#server-details"
|
||||
hx-select="#server-details"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-check-updates"
|
||||
>
|
||||
{{ __("Check") }}
|
||||
</x-secondary-button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="py-5">
|
||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>{{ __("Provider") }}</div>
|
||||
<div class="capitalize">{{ $server->provider }}</div>
|
||||
|
@ -183,7 +183,7 @@ class="mt-1 block w-full"
|
||||
@foreach (config("core.operating_systems") as $operatingSystem)
|
||||
<option
|
||||
value="{{ $operatingSystem }}"
|
||||
@if($operatingSystem == old('os', 'ubuntu_22')) selected @endif
|
||||
@if($operatingSystem == old('os', \App\Enums\OperatingSystem::UBUNTU24)) selected @endif
|
||||
>
|
||||
{{ str($operatingSystem)->replace("_", " ")->ucfirst() }}
|
||||
LTS
|
||||
|
@ -14,4 +14,8 @@
|
||||
@if ($server->status == \App\Enums\ServerStatus::INSTALLATION_FAILED)
|
||||
<x-status status="danger">{{ $server->status }}</x-status>
|
||||
@endif
|
||||
|
||||
@if ($server->status == \App\Enums\ServerStatus::UPDATING)
|
||||
<x-status status="warning">{{ $server->status }}</x-status>
|
||||
@endif
|
||||
</div>
|
||||
|
@ -131,6 +131,15 @@ class="text-primary-500"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-checkbox id="global" name="global" :checked="old('global')" value="1">
|
||||
Is Global (Accessible in all projects)
|
||||
</x-checkbox>
|
||||
@error("global")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
||||
{{ __("Cancel") }}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user