mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-01 05:56:16 +00:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
9ac5f9ebb3 | |||
ed8965b92b | |||
9473d198e1 | |||
55269dbcde | |||
3d67153912 | |||
11e3b167cc | |||
ad027eb033 | |||
e031bafba5 | |||
b5c8d99ef8 | |||
109d644ad8 | |||
5ccbab74b1 | |||
7d367465ff | |||
eec83f577c | |||
fd77368cf3 |
@ -102,7 +102,7 @@ private function getInterval(array $input): Expression
|
||||
)->diffInHours();
|
||||
}
|
||||
|
||||
if ($periodInHours <= 1) {
|
||||
if (abs($periodInHours) <= 1) {
|
||||
return DB::raw("strftime('%Y-%m-%d %H:%M:00', created_at) as date_interval");
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ public function add(User $user, array $input): void
|
||||
'user_id' => $user->id,
|
||||
'provider' => $input['provider'],
|
||||
'label' => $input['label'],
|
||||
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||
]);
|
||||
$this->validateType($channel, $input);
|
||||
$channel->data = $channel->provider()->createData($input);
|
||||
|
34
app/Actions/NotificationChannels/EditChannel.php
Normal file
34
app/Actions/NotificationChannels/EditChannel.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\NotificationChannels;
|
||||
|
||||
use App\Models\NotificationChannel;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditChannel
|
||||
{
|
||||
public function edit(NotificationChannel $notificationChannel, User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$notificationChannel->label = $input['label'];
|
||||
$notificationChannel->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$notificationChannel->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'label' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
}
|
@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Actions\PHP;
|
||||
|
||||
use App\Enums\PHPIniType;
|
||||
use App\Models\Server;
|
||||
use App\SSH\Services\PHP\PHP;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class GetPHPIni
|
||||
@ -18,7 +21,7 @@ public function getIni(Server $server, array $input): string
|
||||
/** @var PHP $handler */
|
||||
$handler = $php->handler();
|
||||
|
||||
return $handler->getPHPIni();
|
||||
return $handler->getPHPIni($input['type']);
|
||||
} catch (\Throwable $e) {
|
||||
throw ValidationException::withMessages(
|
||||
['ini' => $e->getMessage()]
|
||||
@ -28,6 +31,13 @@ public function getIni(Server $server, array $input): string
|
||||
|
||||
public function validate(Server $server, array $input): void
|
||||
{
|
||||
Validator::make($input, [
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in([PHPIniType::CLI, PHPIniType::FPM]),
|
||||
],
|
||||
])->validate();
|
||||
|
||||
if (! isset($input['version']) || ! in_array($input['version'], $server->installedPHPVersions())) {
|
||||
throw ValidationException::withMessages(
|
||||
['version' => __('This version is not installed')]
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
namespace App\Actions\PHP;
|
||||
|
||||
use App\Enums\PHPIniType;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
|
||||
@ -22,19 +25,19 @@ public function update(Server $server, array $input): void
|
||||
|
||||
$tmpName = Str::random(10).strtotime('now');
|
||||
try {
|
||||
/** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */
|
||||
/** @var FilesystemAdapter $storageDisk */
|
||||
$storageDisk = Storage::disk('local');
|
||||
|
||||
$storageDisk->put($tmpName, $input['ini']);
|
||||
$service->server->ssh('root')->upload(
|
||||
$storageDisk->path($tmpName),
|
||||
"/etc/php/$service->version/cli/php.ini"
|
||||
sprintf('/etc/php/%s/%s/php.ini', $service->version, $input['type'])
|
||||
);
|
||||
$this->deleteTempFile($tmpName);
|
||||
} catch (Throwable) {
|
||||
$this->deleteTempFile($tmpName);
|
||||
throw ValidationException::withMessages([
|
||||
'ini' => __("Couldn't update php.ini file!"),
|
||||
'ini' => __("Couldn't update php.ini (:type) file!", ['type' => $input['type']]),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -56,6 +59,10 @@ public function validate(Server $server, array $input): void
|
||||
'string',
|
||||
],
|
||||
'version' => 'required|string',
|
||||
'type' => [
|
||||
'required',
|
||||
Rule::in([PHPIniType::CLI, PHPIniType::FPM]),
|
||||
],
|
||||
])->validate();
|
||||
|
||||
if (! in_array($input['version'], $server->installedPHPVersions())) {
|
||||
|
@ -38,6 +38,7 @@ public function create(User $user, array $input): ServerProvider
|
||||
$serverProvider->profile = $input['name'];
|
||||
$serverProvider->provider = $input['provider'];
|
||||
$serverProvider->credentials = $provider->credentialData($input);
|
||||
$serverProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
$serverProvider->save();
|
||||
|
||||
return $serverProvider;
|
||||
|
34
app/Actions/ServerProvider/EditServerProvider.php
Normal file
34
app/Actions/ServerProvider/EditServerProvider.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\ServerProvider;
|
||||
|
||||
use App\Models\ServerProvider;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditServerProvider
|
||||
{
|
||||
public function edit(ServerProvider $serverProvider, User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$serverProvider->profile = $input['name'];
|
||||
$serverProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$serverProvider->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
}
|
@ -44,7 +44,12 @@ public function run(Site $site): Deployment
|
||||
$log->save();
|
||||
$deployment->log_id = $log->id;
|
||||
$deployment->save();
|
||||
$site->server->os()->runScript($site->path, $site->deploymentScript->content, $log);
|
||||
$site->server->os()->runScript(
|
||||
path: $site->path,
|
||||
script: $site->deploymentScript->content,
|
||||
serverLog: $log,
|
||||
variables: $site->environmentVariables($deployment)
|
||||
);
|
||||
$deployment->status = DeploymentStatus::FINISHED;
|
||||
$deployment->save();
|
||||
})->catch(function () use ($deployment) {
|
||||
|
@ -2,10 +2,14 @@
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Exceptions\SSHUploadFailed;
|
||||
use App\Models\Site;
|
||||
|
||||
class UpdateEnv
|
||||
{
|
||||
/**
|
||||
* @throws SSHUploadFailed
|
||||
*/
|
||||
public function update(Site $site, array $input): void
|
||||
{
|
||||
$site->server->os()->editFile(
|
||||
|
@ -14,7 +14,7 @@ public function edit(SourceControl $sourceControl, User $user, array $input): vo
|
||||
$this->validate($input);
|
||||
|
||||
$sourceControl->profile = $input['name'];
|
||||
$sourceControl->url = isset($input['url']) ? $input['url'] : null;
|
||||
$sourceControl->url = $input['url'] ?? null;
|
||||
$sourceControl->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$this->validateProvider($sourceControl, $input);
|
||||
|
@ -21,6 +21,7 @@ public function create(User $user, array $input): void
|
||||
'user_id' => $user->id,
|
||||
'provider' => $input['provider'],
|
||||
'profile' => $input['name'],
|
||||
'project_id' => isset($input['global']) && $input['global'] ? null : $user->current_project_id,
|
||||
]);
|
||||
|
||||
$this->validateProvider($input, $storageProvider->provider()->validationRules());
|
||||
|
34
app/Actions/StorageProvider/EditStorageProvider.php
Normal file
34
app/Actions/StorageProvider/EditStorageProvider.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\StorageProvider;
|
||||
|
||||
use App\Models\StorageProvider;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class EditStorageProvider
|
||||
{
|
||||
public function edit(StorageProvider $storageProvider, User $user, array $input): void
|
||||
{
|
||||
$this->validate($input);
|
||||
|
||||
$storageProvider->profile = $input['name'];
|
||||
$storageProvider->project_id = isset($input['global']) && $input['global'] ? null : $user->current_project_id;
|
||||
|
||||
$storageProvider->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validate(array $input): void
|
||||
{
|
||||
$rules = [
|
||||
'name' => [
|
||||
'required',
|
||||
],
|
||||
];
|
||||
Validator::make($input, $rules)->validate();
|
||||
}
|
||||
}
|
10
app/Enums/PHPIniType.php
Normal file
10
app/Enums/PHPIniType.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
final class PHPIniType
|
||||
{
|
||||
const CLI = 'cli';
|
||||
|
||||
const FPM = 'fpm';
|
||||
}
|
@ -4,6 +4,4 @@
|
||||
|
||||
use Exception;
|
||||
|
||||
class DeploymentScriptIsEmptyException extends Exception
|
||||
{
|
||||
}
|
||||
class DeploymentScriptIsEmptyException extends Exception {}
|
||||
|
8
app/Exceptions/SSHUploadFailed.php
Executable file
8
app/Exceptions/SSHUploadFailed.php
Executable file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class SSHUploadFailed extends SSHError
|
||||
{
|
||||
//
|
||||
}
|
@ -4,6 +4,4 @@
|
||||
|
||||
use Exception;
|
||||
|
||||
class SourceControlIsNotConnected extends Exception
|
||||
{
|
||||
}
|
||||
class SourceControlIsNotConnected extends Exception {}
|
||||
|
@ -16,6 +16,8 @@
|
||||
* @method static string exec(string $command, string $log = '', int $siteId = null, ?bool $stream = false)
|
||||
* @method static string assertExecuted(array|string $commands)
|
||||
* @method static string assertExecutedContains(string $command)
|
||||
* @method static string assertFileUploaded(string $toPath, ?string $content = null)
|
||||
* @method static string getUploadedLocalPath()
|
||||
* @method static disconnect()
|
||||
*/
|
||||
class SSH extends FacadeAlias
|
||||
|
@ -10,6 +10,7 @@
|
||||
use App\Exceptions\RepositoryNotFound;
|
||||
use App\Exceptions\RepositoryPermissionDenied;
|
||||
use App\Exceptions\SourceControlIsNotConnected;
|
||||
use App\Exceptions\SSHUploadFailed;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Models\Deployment;
|
||||
@ -81,9 +82,12 @@ public function updateEnv(Server $server, Site $site, Request $request): Redirec
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
app(UpdateEnv::class)->update($site, $request->input());
|
||||
|
||||
Toast::success('Env updated!');
|
||||
try {
|
||||
app(UpdateEnv::class)->update($site, $request->input());
|
||||
Toast::success('Env updated!');
|
||||
} catch (SSHUploadFailed) {
|
||||
Toast::error('Failed to update .env file!');
|
||||
}
|
||||
|
||||
return back();
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ public function updateIni(Server $server, Request $request): RedirectResponse
|
||||
|
||||
app(UpdatePHPIni::class)->update($server, $request->input());
|
||||
|
||||
Toast::success('PHP ini updated!');
|
||||
Toast::success(__('PHP ini (:type) updated!', ['type' => $request->input('type')]));
|
||||
|
||||
return back()->with([
|
||||
'ini' => $request->input('ini'),
|
||||
|
@ -29,7 +29,7 @@ public function store(Server $server, Request $request): HtmxResponse
|
||||
{
|
||||
$this->authorize('manage', $server);
|
||||
|
||||
/** @var \App\Models\SshKey $key */
|
||||
/** @var SshKey $key */
|
||||
$key = app(CreateSshKey::class)->create(
|
||||
$request->user(),
|
||||
$request->input()
|
||||
|
@ -35,7 +35,9 @@ public function create(Request $request): View
|
||||
$this->authorize('create', [Server::class, $user->currentProject]);
|
||||
|
||||
$provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM));
|
||||
$serverProviders = ServerProvider::query()->where('provider', $provider)->get();
|
||||
$serverProviders = ServerProvider::getByProjectId(auth()->user()->current_project_id)
|
||||
->where('provider', $provider)
|
||||
->get();
|
||||
|
||||
return view('servers.create', [
|
||||
'serverProviders' => $serverProviders,
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\Settings;
|
||||
|
||||
use App\Actions\NotificationChannels\AddChannel;
|
||||
use App\Actions\NotificationChannels\EditChannel;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -13,11 +14,17 @@
|
||||
|
||||
class NotificationChannelController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('settings.notification-channels.index', [
|
||||
'channels' => NotificationChannel::query()->latest()->get(),
|
||||
]);
|
||||
$data = [
|
||||
'channels' => NotificationChannel::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editChannel'] = NotificationChannel::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.notification-channels.index', $data);
|
||||
}
|
||||
|
||||
public function add(Request $request): HtmxResponse
|
||||
@ -32,6 +39,19 @@ public function add(Request $request): HtmxResponse
|
||||
return htmx()->redirect(route('settings.notification-channels'));
|
||||
}
|
||||
|
||||
public function update(NotificationChannel $notificationChannel, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditChannel::class)->edit(
|
||||
$notificationChannel,
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Channel updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.notification-channels'));
|
||||
}
|
||||
|
||||
public function delete(int $id): RedirectResponse
|
||||
{
|
||||
$channel = NotificationChannel::query()->findOrFail($id);
|
||||
|
@ -68,7 +68,7 @@ public function delete(Project $project): RedirectResponse
|
||||
return back();
|
||||
}
|
||||
|
||||
public function switch($projectId): RedirectResponse
|
||||
public function switch(Request $request, $projectId): RedirectResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
@ -81,6 +81,11 @@ public function switch($projectId): RedirectResponse
|
||||
$user->current_project_id = $project->id;
|
||||
$user->save();
|
||||
|
||||
// check if the referer is settings/*
|
||||
if (str_contains($request->headers->get('referer'), 'settings')) {
|
||||
return redirect()->to($request->headers->get('referer'));
|
||||
}
|
||||
|
||||
return redirect()->route('servers');
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\ServerProvider\CreateServerProvider;
|
||||
use App\Actions\ServerProvider\DeleteServerProvider;
|
||||
use App\Actions\ServerProvider\EditServerProvider;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -14,11 +15,17 @@
|
||||
|
||||
class ServerProviderController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('settings.server-providers.index', [
|
||||
'providers' => auth()->user()->serverProviders,
|
||||
]);
|
||||
$data = [
|
||||
'providers' => ServerProvider::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editProvider'] = ServerProvider::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.server-providers.index', $data);
|
||||
}
|
||||
|
||||
public function connect(Request $request): HtmxResponse
|
||||
@ -33,6 +40,19 @@ public function connect(Request $request): HtmxResponse
|
||||
return htmx()->redirect(route('settings.server-providers'));
|
||||
}
|
||||
|
||||
public function update(ServerProvider $serverProvider, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditServerProvider::class)->edit(
|
||||
$serverProvider,
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Provider updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.server-providers'));
|
||||
}
|
||||
|
||||
public function delete(ServerProvider $serverProvider): RedirectResponse
|
||||
{
|
||||
try {
|
||||
|
@ -18,7 +18,7 @@ class SourceControlController extends Controller
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$data = [
|
||||
'sourceControls' => SourceControl::getByCurrentProject(),
|
||||
'sourceControls' => SourceControl::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Actions\StorageProvider\CreateStorageProvider;
|
||||
use App\Actions\StorageProvider\DeleteStorageProvider;
|
||||
use App\Actions\StorageProvider\EditStorageProvider;
|
||||
use App\Facades\Toast;
|
||||
use App\Helpers\HtmxResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -14,11 +15,17 @@
|
||||
|
||||
class StorageProviderController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
public function index(Request $request): View
|
||||
{
|
||||
return view('settings.storage-providers.index', [
|
||||
'providers' => auth()->user()->storageProviders,
|
||||
]);
|
||||
$data = [
|
||||
'providers' => StorageProvider::getByProjectId(auth()->user()->current_project_id)->get(),
|
||||
];
|
||||
|
||||
if ($request->has('edit')) {
|
||||
$data['editProvider'] = StorageProvider::find($request->input('edit'));
|
||||
}
|
||||
|
||||
return view('settings.storage-providers.index', $data);
|
||||
}
|
||||
|
||||
public function connect(Request $request): HtmxResponse
|
||||
@ -33,6 +40,19 @@ public function connect(Request $request): HtmxResponse
|
||||
return htmx()->redirect(route('settings.storage-providers'));
|
||||
}
|
||||
|
||||
public function update(StorageProvider $storageProvider, Request $request): HtmxResponse
|
||||
{
|
||||
app(EditStorageProvider::class)->edit(
|
||||
$storageProvider,
|
||||
$request->user(),
|
||||
$request->input(),
|
||||
);
|
||||
|
||||
Toast::success('Provider updated.');
|
||||
|
||||
return htmx()->redirect(route('settings.storage-providers'));
|
||||
}
|
||||
|
||||
public function delete(StorageProvider $storageProvider): RedirectResponse
|
||||
{
|
||||
try {
|
||||
|
@ -20,7 +20,7 @@ class TrustProxies extends Middleware
|
||||
* @var int
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
|
@ -3,7 +3,9 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Notifications\NotificationInterface;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
/**
|
||||
@ -12,6 +14,7 @@
|
||||
* @property array data
|
||||
* @property string label
|
||||
* @property bool connected
|
||||
* @property int $project_id
|
||||
*/
|
||||
class NotificationChannel extends AbstractModel
|
||||
{
|
||||
@ -24,6 +27,7 @@ class NotificationChannel extends AbstractModel
|
||||
'data',
|
||||
'connected',
|
||||
'is_default',
|
||||
'project_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -47,4 +51,16 @@ public static function notifyAll(NotificationInterface $notification): void
|
||||
$channel->notify($notification);
|
||||
}
|
||||
}
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public static function getByProjectId(int $projectId): Builder
|
||||
{
|
||||
return self::query()
|
||||
->where('project_id', $projectId)
|
||||
->orWhereNull('project_id');
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@ -13,6 +14,7 @@
|
||||
* @property array $credentials
|
||||
* @property bool $connected
|
||||
* @property User $user
|
||||
* @property ?int $project_id
|
||||
*/
|
||||
class ServerProvider extends AbstractModel
|
||||
{
|
||||
@ -24,12 +26,14 @@ class ServerProvider extends AbstractModel
|
||||
'provider',
|
||||
'credentials',
|
||||
'connected',
|
||||
'project_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
'credentials' => 'encrypted:array',
|
||||
'connected' => 'boolean',
|
||||
'project_id' => 'integer',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
@ -46,4 +50,16 @@ public function servers(): HasMany
|
||||
{
|
||||
return $this->hasMany(Server::class, 'provider_id');
|
||||
}
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public static function getByProjectId(int $projectId): Builder
|
||||
{
|
||||
return self::query()
|
||||
->where('project_id', $projectId)
|
||||
->orWhereNull('project_id');
|
||||
}
|
||||
}
|
||||
|
@ -283,4 +283,17 @@ public function hasSSL(): bool
|
||||
{
|
||||
return $this->ssls->isNotEmpty();
|
||||
}
|
||||
|
||||
public function environmentVariables(?Deployment $deployment = null): array
|
||||
{
|
||||
return [
|
||||
'SITE_PATH' => $this->path,
|
||||
'DOMAIN' => $this->domain,
|
||||
'BRANCH' => $this->branch ?? '',
|
||||
'REPOSITORY' => $this->repository ?? '',
|
||||
'COMMIT_ID' => $deployment?->commit_id ?? '',
|
||||
'PHP_VERSION' => $this->php_version,
|
||||
'PHP_PATH' => '/usr/bin/php'.$this->php_version,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\SourceControlProviders\SourceControlProvider;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@ -57,10 +57,10 @@ public function project(): BelongsTo
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public static function getByCurrentProject(): Collection
|
||||
public static function getByProjectId(int $projectId): Builder
|
||||
{
|
||||
return self::query()
|
||||
->where('project_id', auth()->user()->current_project_id)
|
||||
->orWhereNull('project_id')->get();
|
||||
->where('project_id', $projectId)
|
||||
->orWhereNull('project_id');
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@ -12,6 +13,7 @@
|
||||
* @property string $provider
|
||||
* @property array $credentials
|
||||
* @property User $user
|
||||
* @property int $project_id
|
||||
*/
|
||||
class StorageProvider extends AbstractModel
|
||||
{
|
||||
@ -22,11 +24,13 @@ class StorageProvider extends AbstractModel
|
||||
'profile',
|
||||
'provider',
|
||||
'credentials',
|
||||
'project_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
'credentials' => 'encrypted:array',
|
||||
'project_id' => 'integer',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
@ -45,4 +49,16 @@ public function backups(): HasMany
|
||||
{
|
||||
return $this->hasMany(Backup::class, 'storage_id');
|
||||
}
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public static function getByProjectId(int $projectId): Builder
|
||||
{
|
||||
return self::query()
|
||||
->where('project_id', $projectId)
|
||||
->orWhereNull('project_id');
|
||||
}
|
||||
}
|
||||
|
@ -106,24 +106,6 @@ public function storageProvider(string $provider): HasOne
|
||||
return $this->hasOne(StorageProvider::class)->where('provider', $provider);
|
||||
}
|
||||
|
||||
public function connectedStorageProviders(): HasMany
|
||||
{
|
||||
return $this->storageProviders()->where('connected', true);
|
||||
}
|
||||
|
||||
public function connectedSourceControls(): array
|
||||
{
|
||||
$connectedSourceControls = [];
|
||||
$sourceControls = $this->sourceControls()
|
||||
->where('connected', 1)
|
||||
->get(['provider']);
|
||||
foreach ($sourceControls as $sourceControl) {
|
||||
$connectedSourceControls[] = $sourceControl->provider;
|
||||
}
|
||||
|
||||
return $connectedSourceControls;
|
||||
}
|
||||
|
||||
public function projects(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Project::class, 'user_project')->withTimestamps();
|
||||
@ -134,11 +116,6 @@ public function currentProject(): HasOne
|
||||
return $this->HasOne(Project::class, 'id', 'current_project_id');
|
||||
}
|
||||
|
||||
public function isMemberOfProject(Project $project): bool
|
||||
{
|
||||
return $project->user_id === $this->id;
|
||||
}
|
||||
|
||||
public function createDefaultProject(): Project
|
||||
{
|
||||
$project = $this->projects()->first();
|
||||
|
@ -7,7 +7,5 @@
|
||||
|
||||
abstract class AbstractNotificationChannel implements NotificationChannelInterface
|
||||
{
|
||||
public function __construct(protected NotificationChannel $notificationChannel)
|
||||
{
|
||||
}
|
||||
public function __construct(protected NotificationChannel $notificationChannel) {}
|
||||
}
|
||||
|
@ -7,9 +7,7 @@
|
||||
|
||||
class SiteInstallationFailed extends AbstractNotification
|
||||
{
|
||||
public function __construct(protected Site $site)
|
||||
{
|
||||
}
|
||||
public function __construct(protected Site $site) {}
|
||||
|
||||
public function rawText(): string
|
||||
{
|
||||
|
@ -7,9 +7,7 @@
|
||||
|
||||
class SiteInstallationSucceed extends AbstractNotification
|
||||
{
|
||||
public function __construct(protected Site $site)
|
||||
{
|
||||
}
|
||||
public function __construct(protected Site $site) {}
|
||||
|
||||
public function rawText(): string
|
||||
{
|
||||
|
@ -7,9 +7,7 @@
|
||||
|
||||
class SourceControlDisconnected extends AbstractNotification
|
||||
{
|
||||
public function __construct(protected SourceControl $sourceControl)
|
||||
{
|
||||
}
|
||||
public function __construct(protected SourceControl $sourceControl) {}
|
||||
|
||||
public function rawText(): string
|
||||
{
|
||||
|
@ -6,9 +6,7 @@
|
||||
|
||||
class Cron
|
||||
{
|
||||
public function __construct(protected Server $server)
|
||||
{
|
||||
}
|
||||
public function __construct(protected Server $server) {}
|
||||
|
||||
public function update(string $user, string $cron): void
|
||||
{
|
||||
|
@ -2,17 +2,20 @@
|
||||
|
||||
namespace App\SSH\OS;
|
||||
|
||||
use App\Exceptions\SSHUploadFailed;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerLog;
|
||||
use App\SSH\HasScripts;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
class OS
|
||||
{
|
||||
use HasScripts;
|
||||
|
||||
public function __construct(protected Server $server)
|
||||
{
|
||||
}
|
||||
public function __construct(protected Server $server) {}
|
||||
|
||||
public function installDependencies(): void
|
||||
{
|
||||
@ -111,14 +114,25 @@ public function reboot(): void
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws SSHUploadFailed
|
||||
*/
|
||||
public function editFile(string $path, ?string $content = null): void
|
||||
{
|
||||
$this->server->ssh()->exec(
|
||||
$this->getScript('edit-file.sh', [
|
||||
'path' => $path,
|
||||
'content' => $content ?? '',
|
||||
]),
|
||||
);
|
||||
$tmpName = Str::random(10).strtotime('now');
|
||||
try {
|
||||
/** @var FilesystemAdapter $storageDisk */
|
||||
$storageDisk = Storage::disk('local');
|
||||
$storageDisk->put($tmpName, $content);
|
||||
$this->server->ssh()->upload(
|
||||
$storageDisk->path($tmpName),
|
||||
$path
|
||||
);
|
||||
} catch (Throwable) {
|
||||
throw new SSHUploadFailed();
|
||||
} finally {
|
||||
$this->deleteTempFile($tmpName);
|
||||
}
|
||||
}
|
||||
|
||||
public function readFile(string $path): string
|
||||
@ -140,19 +154,23 @@ public function tail(string $path, int $lines): string
|
||||
);
|
||||
}
|
||||
|
||||
public function runScript(string $path, string $script, ?ServerLog $serverLog, ?string $user = null): ServerLog
|
||||
public function runScript(string $path, string $script, ?ServerLog $serverLog, ?string $user = null, ?array $variables = []): ServerLog
|
||||
{
|
||||
$ssh = $this->server->ssh($user);
|
||||
if ($serverLog) {
|
||||
$ssh->setLog($serverLog);
|
||||
}
|
||||
$ssh->exec(
|
||||
$this->getScript('run-script.sh', [
|
||||
'path' => $path,
|
||||
'script' => $script,
|
||||
]),
|
||||
'run-script'
|
||||
);
|
||||
$command = '';
|
||||
foreach ($variables as $key => $variable) {
|
||||
$command .= "$key=$variable".PHP_EOL;
|
||||
}
|
||||
$command .= $this->getScript('run-script.sh', [
|
||||
'path' => $path,
|
||||
'script' => $script,
|
||||
]);
|
||||
$ssh->exec($command, 'run-script');
|
||||
|
||||
info($command);
|
||||
|
||||
return $ssh->log;
|
||||
}
|
||||
@ -198,4 +216,11 @@ public function resourceInfo(): array
|
||||
'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(),
|
||||
];
|
||||
}
|
||||
|
||||
private function deleteTempFile(string $name): void
|
||||
{
|
||||
if (Storage::disk('local')->exists($name)) {
|
||||
Storage::disk('local')->delete($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
if ! echo "__content__" | tee __path__; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
@ -6,9 +6,7 @@
|
||||
|
||||
abstract class AbstractService implements ServiceInterface
|
||||
{
|
||||
public function __construct(protected Service $service)
|
||||
{
|
||||
}
|
||||
public function __construct(protected Service $service) {}
|
||||
|
||||
public function creationRules(array $input): array
|
||||
{
|
||||
|
14
app/SSH/Services/Database/scripts/mariadb/install-10.11.sh
Executable file
14
app/SSH/Services/Database/scripts/mariadb/install-10.11.sh
Executable file
@ -0,0 +1,14 @@
|
||||
wget https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
|
||||
|
||||
chmod +x mariadb_repo_setup
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive ./mariadb_repo_setup \
|
||||
--mariadb-server-version="mariadb-10.11"
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql start
|
14
app/SSH/Services/Database/scripts/mariadb/install-10.6.sh
Executable file
14
app/SSH/Services/Database/scripts/mariadb/install-10.6.sh
Executable file
@ -0,0 +1,14 @@
|
||||
wget https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
|
||||
|
||||
chmod +x mariadb_repo_setup
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive ./mariadb_repo_setup \
|
||||
--mariadb-server-version="mariadb-10.6"
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql start
|
14
app/SSH/Services/Database/scripts/mariadb/install-11.4.sh
Executable file
14
app/SSH/Services/Database/scripts/mariadb/install-11.4.sh
Executable file
@ -0,0 +1,14 @@
|
||||
wget https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
|
||||
|
||||
chmod +x mariadb_repo_setup
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive ./mariadb_repo_setup \
|
||||
--mariadb-server-version="mariadb-11.4"
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
||||
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install mariadb-server mariadb-backup -y
|
||||
|
||||
sudo systemctl unmask mysql.service
|
||||
|
||||
sudo service mysql start
|
@ -4,6 +4,4 @@
|
||||
|
||||
use App\SSH\Services\AbstractService;
|
||||
|
||||
abstract class AbstractFirewall extends AbstractService implements Firewall
|
||||
{
|
||||
}
|
||||
abstract class AbstractFirewall extends AbstractService implements Firewall {}
|
||||
|
@ -102,12 +102,10 @@ public function installComposer(): void
|
||||
);
|
||||
}
|
||||
|
||||
public function getPHPIni(): string
|
||||
public function getPHPIni(string $type): string
|
||||
{
|
||||
return $this->service->server->ssh()->exec(
|
||||
$this->getScript('get-php-ini.sh', [
|
||||
'version' => $this->service->version,
|
||||
])
|
||||
return $this->service->server->os()->readFile(
|
||||
sprintf('/etc/php/%s/%s/php.ini', $this->service->version, $type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
if ! cat /etc/php/__version__/cli/php.ini; then
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
@ -4,6 +4,4 @@
|
||||
|
||||
use App\SSH\Services\AbstractService;
|
||||
|
||||
abstract class AbstractWebserver extends AbstractService implements Webserver
|
||||
{
|
||||
}
|
||||
abstract class AbstractWebserver extends AbstractService implements Webserver {}
|
||||
|
@ -27,6 +27,7 @@ server {
|
||||
fastcgi_pass unix:/var/run/php/php__php_version__-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
|
@ -23,6 +23,7 @@ server {
|
||||
fastcgi_pass unix:/var/run/php/php__php_version__-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||
include fastcgi_params;
|
||||
fastcgi_hide_header X-Powered-By;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known).* {
|
||||
|
@ -7,7 +7,5 @@
|
||||
|
||||
abstract class AbstractStorage implements Storage
|
||||
{
|
||||
public function __construct(protected Server $server, protected StorageProvider $storageProvider)
|
||||
{
|
||||
}
|
||||
public function __construct(protected Server $server, protected StorageProvider $storageProvider) {}
|
||||
}
|
||||
|
@ -6,9 +6,7 @@
|
||||
|
||||
class Systemd
|
||||
{
|
||||
public function __construct(protected Server $server)
|
||||
{
|
||||
}
|
||||
public function __construct(protected Server $server) {}
|
||||
|
||||
public function status(string $unit): string
|
||||
{
|
||||
|
@ -24,4 +24,4 @@ if ! wp --path=__path__ core install --url='http://__domain__' --title="__title_
|
||||
echo 'VITO_SSH_ERROR' && exit 1
|
||||
fi
|
||||
|
||||
print "Wordpress installed!"
|
||||
echo "Wordpress installed!"
|
||||
|
@ -2,6 +2,4 @@
|
||||
|
||||
namespace App\SiteTypes;
|
||||
|
||||
class Laravel extends PHPSite
|
||||
{
|
||||
}
|
||||
class Laravel extends PHPSite {}
|
||||
|
@ -17,6 +17,12 @@ class SSHFake extends SSH
|
||||
|
||||
protected bool $connectionWillFail = false;
|
||||
|
||||
protected string $uploadedLocalPath;
|
||||
|
||||
protected string $uploadedRemotePath;
|
||||
|
||||
protected string $uploadedContent;
|
||||
|
||||
public function __construct(?string $output = null)
|
||||
{
|
||||
$this->output = $output;
|
||||
@ -63,6 +69,9 @@ public function exec(string $command, string $log = '', ?int $siteId = null, ?bo
|
||||
|
||||
public function upload(string $local, string $remote): void
|
||||
{
|
||||
$this->uploadedLocalPath = $local;
|
||||
$this->uploadedRemotePath = $remote;
|
||||
$this->uploadedContent = file_get_contents($local);
|
||||
$this->log = null;
|
||||
}
|
||||
|
||||
@ -105,4 +114,22 @@ public function assertExecutedContains(string $command): void
|
||||
}
|
||||
Assert::assertTrue(true, $executed);
|
||||
}
|
||||
|
||||
public function assertFileUploaded(string $toPath, ?string $content = null): void
|
||||
{
|
||||
if (! $this->uploadedLocalPath || ! $this->uploadedRemotePath) {
|
||||
Assert::fail('File is not uploaded');
|
||||
}
|
||||
|
||||
Assert::assertEquals($toPath, $this->uploadedRemotePath);
|
||||
|
||||
if ($content) {
|
||||
Assert::assertEquals($content, $this->uploadedContent);
|
||||
}
|
||||
}
|
||||
|
||||
public function getUploadedLocalPath(): string
|
||||
{
|
||||
return $this->uploadedLocalPath;
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,9 @@
|
||||
namespace App\ValidationRules;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class CronRule implements Rule
|
||||
class CronRule implements ValidationRule
|
||||
{
|
||||
private bool $acceptCustom;
|
||||
|
||||
@ -14,13 +14,14 @@ public function __construct(bool $acceptCustom = false)
|
||||
$this->acceptCustom = $acceptCustom;
|
||||
}
|
||||
|
||||
public function passes($attribute, $value): bool
|
||||
public function validate(string $attribute, mixed $value, \Closure $fail): void
|
||||
{
|
||||
return CronExpression::isValidExpression($value) || ($this->acceptCustom && $value === 'custom');
|
||||
}
|
||||
|
||||
public function message(): string
|
||||
{
|
||||
return __('Invalid frequency');
|
||||
if (CronExpression::isValidExpression($value)) {
|
||||
return;
|
||||
}
|
||||
if ($this->acceptCustom && $value === 'custom') {
|
||||
return;
|
||||
}
|
||||
$fail('Invalid frequency')->translate();
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,19 @@
|
||||
|
||||
namespace App\ValidationRules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class DomainRule implements Rule
|
||||
class DomainRule implements ValidationRule
|
||||
{
|
||||
public function passes($attribute, $value): bool
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
if ($value) {
|
||||
return preg_match("/^(?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/", $value);
|
||||
if (! $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function message(): string
|
||||
{
|
||||
return __('Domain is not valid');
|
||||
if (preg_match("/^(?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/", $value) === 1) {
|
||||
return;
|
||||
}
|
||||
$fail('Domain is not valid')->translate();
|
||||
}
|
||||
}
|
||||
|
@ -2,27 +2,16 @@
|
||||
|
||||
namespace App\ValidationRules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class RestrictedIPAddressesRule implements Rule
|
||||
class RestrictedIPAddressesRule implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
return ! in_array($value, config('core.restricted_ip_addresses'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|\Illuminate\Contracts\Translation\Translator|string|null
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return __('IP address is restricted.');
|
||||
if (! in_array($value, config('core.restricted_ip_addresses'))) {
|
||||
return;
|
||||
}
|
||||
$fail('IP address is restricted')->translate();
|
||||
}
|
||||
}
|
||||
|
@ -2,35 +2,21 @@
|
||||
|
||||
namespace App\ValidationRules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
use phpseclib3\Exception\NoKeyLoadedException;
|
||||
|
||||
class SshKeyRule implements Rule
|
||||
class SshKeyRule implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
try {
|
||||
PublicKeyLoader::load($value);
|
||||
|
||||
return true;
|
||||
} catch (NoKeyLoadedException $e) {
|
||||
return false;
|
||||
return;
|
||||
} catch (NoKeyLoadedException) {
|
||||
$fail('Invalid key')->translate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|\Illuminate\Contracts\Translation\Translator|string|null
|
||||
*/
|
||||
public function message()
|
||||
{
|
||||
return __('Invalid key');
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,7 @@
|
||||
|
||||
class ServerLayout extends Component
|
||||
{
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
public function __construct(public Server $server) {}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
|
@ -8,9 +8,7 @@
|
||||
|
||||
class SiteLayout extends Component
|
||||
{
|
||||
public function __construct(public Site $site)
|
||||
{
|
||||
}
|
||||
public function __construct(public Site $site) {}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
|
@ -12,7 +12,7 @@
|
||||
"ext-ftp": "*",
|
||||
"aws/aws-sdk-php": "^3.158",
|
||||
"laravel/fortify": "^1.17",
|
||||
"laravel/framework": "^10.0",
|
||||
"laravel/framework": "^11.0",
|
||||
"laravel/tinker": "^2.8",
|
||||
"phpseclib/phpseclib": "~3.0"
|
||||
},
|
||||
@ -21,7 +21,7 @@
|
||||
"laravel/pint": "^1.10",
|
||||
"laravel/sail": "^1.18",
|
||||
"mockery/mockery": "^1.4.4",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"nunomaduro/collision": "^8.1",
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"spatie/laravel-ignition": "^2.0"
|
||||
},
|
||||
|
1386
composer.lock
generated
1386
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -38,6 +38,9 @@
|
||||
'mysql80',
|
||||
'mariadb103',
|
||||
'mariadb104',
|
||||
'mariadb106',
|
||||
'mariadb1011',
|
||||
'mariadb114',
|
||||
'postgresql12',
|
||||
'postgresql13',
|
||||
'postgresql14',
|
||||
@ -50,6 +53,9 @@
|
||||
'mysql80' => 'mysql',
|
||||
'mariadb103' => 'mariadb',
|
||||
'mariadb104' => 'mariadb',
|
||||
'mariadb106' => 'mariadb',
|
||||
'mariadb1011' => 'mariadb',
|
||||
'mariadb114' => 'mariadb',
|
||||
'postgresql12' => 'postgresql',
|
||||
'postgresql13' => 'postgresql',
|
||||
'postgresql14' => 'postgresql',
|
||||
@ -63,6 +69,9 @@
|
||||
'mariadb' => '10.3',
|
||||
'mariadb103' => '10.3',
|
||||
'mariadb104' => '10.4',
|
||||
'mariadb106' => '10.6',
|
||||
'mariadb1011' => '10.11',
|
||||
'mariadb114' => '11.4',
|
||||
'postgresql12' => '12',
|
||||
'postgresql13' => '13',
|
||||
'postgresql14' => '14',
|
||||
@ -193,14 +202,23 @@
|
||||
\App\Enums\OperatingSystem::UBUNTU20 => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
'10.6' => 'mariadb',
|
||||
'10.11' => 'mariadb',
|
||||
'11.4' => 'mariadb',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU22 => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
'10.6' => 'mariadb',
|
||||
'10.11' => 'mariadb',
|
||||
'11.4' => 'mariadb',
|
||||
],
|
||||
\App\Enums\OperatingSystem::UBUNTU24 => [
|
||||
'10.3' => 'mariadb',
|
||||
'10.4' => 'mariadb',
|
||||
'10.6' => 'mariadb',
|
||||
'10.11' => 'mariadb',
|
||||
'11.4' => 'mariadb',
|
||||
],
|
||||
],
|
||||
'postgresql' => [
|
||||
|
@ -796,26 +796,64 @@
|
||||
],
|
||||
'hetzner' => [
|
||||
'plans' => [
|
||||
|
||||
/* Shared vCPUs x86 */
|
||||
[
|
||||
'title' => 'CX11 - 1 Cores - 2 Memory - 20 Disk',
|
||||
'title' => 'CX11 - 1 Cores (Intel) - 2 Memory - 20 Disk (eu only)',
|
||||
'value' => 'cx11',
|
||||
],
|
||||
[
|
||||
'title' => 'CX21 - 2 Cores - 4 Memory - 40 Disk',
|
||||
'title' => 'CX22 - 2 Cores (Intel) - 4 Memory - 40 Disk (eu only)',
|
||||
'value' => 'cx22',
|
||||
],
|
||||
[
|
||||
'title' => 'CPX11 - 2 Cores (AMD) - 2 Memory - 40 Disk (eu only)',
|
||||
'value' => 'cpx11',
|
||||
],
|
||||
[
|
||||
'title' => 'CX21 - 2 Cores (Intel) - 4 Memory - 40 Disk (eu only)',
|
||||
'value' => 'cx21',
|
||||
],
|
||||
[
|
||||
'title' => 'CX31 - 2 Cores - 8 Memory - 80 Disk',
|
||||
'title' => 'CX32 - 4 Cores (Intel) - 8 Memory - 80 Disk (eu only)',
|
||||
'value' => 'cx32',
|
||||
],
|
||||
[
|
||||
'title' => 'CPX21 - 3 Cores (AMD) - 4 Memory - 80 Disk',
|
||||
'value' => 'cpx21',
|
||||
],
|
||||
[
|
||||
'title' => 'CX31 - 2 Cores (Intel) - 8 Memory - 80 Disk (eu only)',
|
||||
'value' => 'cx31',
|
||||
],
|
||||
[
|
||||
'title' => 'CX41 - 4 Cores - 16 Memory - 160 Disk',
|
||||
'title' => 'CPX31 - 4 Cores (AMD) - 8 Memory - 160 Disk',
|
||||
'value' => 'cpx31',
|
||||
],
|
||||
[
|
||||
'title' => 'CX42 - 8 Cores (Intel) - 16 Memory - 160 Disk (eu only)',
|
||||
'value' => 'cx42',
|
||||
],
|
||||
[
|
||||
'title' => 'CX41 - 4 Cores (Intel) - 16 Memory - 160 Disk (eu only)',
|
||||
'value' => 'cx41',
|
||||
],
|
||||
[
|
||||
'title' => 'CX51 - 8 Cores - 32 Memory - 240 Disk',
|
||||
'title' => 'CPX41 - 8 Cores (AMD) - 16 Memory - 240 Disk',
|
||||
'value' => 'cpx41',
|
||||
],
|
||||
[
|
||||
'title' => 'CX52 - 16 Cores (Intel) - 32 Memory - 320 Disk (eu only)',
|
||||
'value' => 'cx52',
|
||||
],
|
||||
[
|
||||
'title' => 'CX51 - 8 Cores (Intel) - 32 Memory - 240 Disk (eu only)',
|
||||
'value' => 'cx51',
|
||||
],
|
||||
[
|
||||
'title' => 'CPX51 - 16 Cores (AMD) - 32 Memory - 360 Disk',
|
||||
'value' => 'cpx51',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX11 Dedicated CPU - 2 Cores - 8 Memory - 80 Disk',
|
||||
'value' => 'ccx11',
|
||||
@ -836,66 +874,50 @@
|
||||
'title' => 'CCX51 Dedicated CPU - 32 Cores - 128 Memory - 600 Disk',
|
||||
'value' => 'ccx51',
|
||||
],
|
||||
|
||||
/* Shared vCPUs Arm64 */
|
||||
[
|
||||
'title' => 'CPX 11 - 2 Cores - 2 Memory - 40 Disk',
|
||||
'value' => 'cpx11',
|
||||
],
|
||||
[
|
||||
'title' => 'CPX 21 - 3 Cores - 4 Memory - 80 Disk',
|
||||
'value' => 'cpx21',
|
||||
],
|
||||
[
|
||||
'title' => 'CPX 31 - 4 Cores - 8 Memory - 160 Disk',
|
||||
'value' => 'cpx31',
|
||||
],
|
||||
[
|
||||
'title' => 'CPX 41 - 8 Cores - 16 Memory - 240 Disk',
|
||||
'value' => 'cpx41',
|
||||
],
|
||||
[
|
||||
'title' => 'CPX 51 - 16 Cores - 32 Memory - 360 Disk',
|
||||
'value' => 'cpx51',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX12 Dedicated CPU - 2 Cores - 8 Memory - 80 Disk',
|
||||
'value' => 'ccx12',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX22 Dedicated CPU - 4 Cores - 16 Memory - 160 Disk',
|
||||
'value' => 'ccx22',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX32 Dedicated CPU - 8 Cores - 32 Memory - 240 Disk',
|
||||
'value' => 'ccx32',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX42 Dedicated CPU - 16 Cores - 64 Memory - 360 Disk',
|
||||
'value' => 'ccx42',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX52 Dedicated CPU - 32 Cores - 128 Memory - 600 Disk',
|
||||
'value' => 'ccx52',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX62 Dedicated CPU - 48 Cores - 192 Memory - 960 Disk',
|
||||
'value' => 'ccx62',
|
||||
],
|
||||
[
|
||||
'title' => 'CAX11 - 2 Cores - 4 Memory - 40 Disk',
|
||||
'title' => 'CAX11 - 2 Cores (ARM64) - 4 Memory - 40 Disk',
|
||||
'value' => 'cax11',
|
||||
],
|
||||
[
|
||||
'title' => 'CAX21 - 4 Cores - 8 Memory - 80 Disk',
|
||||
'title' => 'CAX21 - 4 Cores (ARM64) - 8 Memory - 80 Disk',
|
||||
'value' => 'cax21',
|
||||
],
|
||||
[
|
||||
'title' => 'CAX31 - 8 Cores - 16 Memory - 160 Disk',
|
||||
'title' => 'CAX31 - 8 Cores (ARM64) - 16 Memory - 160 Disk',
|
||||
'value' => 'cax31',
|
||||
],
|
||||
[
|
||||
'title' => 'CAX41 - 16 Cores - 32 Memory - 320 Disk',
|
||||
'title' => 'CAX41 - 16 Cores (ARM64) - 32 Memory - 320 Disk',
|
||||
'value' => 'cax41',
|
||||
],
|
||||
|
||||
/* Dedicated vCPUs */
|
||||
[
|
||||
'title' => 'CCX13 Dedicated CPU - 2 Cores (AMD) - 8 Memory - 80 Disk',
|
||||
'value' => 'ccx13',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX23 Dedicated CPU - 4 Cores (AMD) - 16 Memory - 160 Disk',
|
||||
'value' => 'ccx23',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX33 Dedicated CPU - 8 Cores (AMD) - 32 Memory - 240 Disk',
|
||||
'value' => 'ccx33',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX43 Dedicated CPU - 16 Cores (AMD) - 64 Memory - 360 Disk',
|
||||
'value' => 'ccx43',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX53 Dedicated CPU - 32 Cores (AMD) - 128 Memory - 600 Disk',
|
||||
'value' => 'ccx53',
|
||||
],
|
||||
[
|
||||
'title' => 'CCX63 Dedicated CPU - 48 Cores (AMD) - 192 Memory - 960 Disk',
|
||||
'value' => 'ccx63',
|
||||
],
|
||||
],
|
||||
'regions' => [
|
||||
[
|
||||
@ -920,7 +942,6 @@
|
||||
],
|
||||
],
|
||||
'images' => [
|
||||
'ubuntu_18' => 'ubuntu-18.04',
|
||||
'ubuntu_20' => 'ubuntu-20.04',
|
||||
'ubuntu_22' => 'ubuntu-22.04',
|
||||
'ubuntu_24' => 'ubuntu-24.04',
|
||||
|
0
database/migrations/2014_10_12_000000_create_users_table.php
Executable file → Normal file
0
database/migrations/2014_10_12_000000_create_users_table.php
Executable file → Normal file
0
database/migrations/2014_10_12_100000_create_password_resets_table.php
Executable file → Normal file
0
database/migrations/2014_10_12_100000_create_password_resets_table.php
Executable file → Normal file
0
database/migrations/2021_06_23_192743_create_sessions_table.php
Executable file → Normal file
0
database/migrations/2021_06_23_192743_create_sessions_table.php
Executable file → Normal file
0
database/migrations/2021_06_23_211827_create_servers_table.php
Executable file → Normal file
0
database/migrations/2021_06_23_211827_create_servers_table.php
Executable file → Normal file
0
database/migrations/2021_06_23_214143_create_services_table.php
Executable file → Normal file
0
database/migrations/2021_06_23_214143_create_services_table.php
Executable file → Normal file
0
database/migrations/2021_06_25_102220_create_jobs_table.php
Executable file → Normal file
0
database/migrations/2021_06_25_102220_create_jobs_table.php
Executable file → Normal file
0
database/migrations/2021_06_25_124831_create_server_logs_table.php
Executable file → Normal file
0
database/migrations/2021_06_25_124831_create_server_logs_table.php
Executable file → Normal file
0
database/migrations/2021_06_26_211903_create_sites_table.php
Executable file → Normal file
0
database/migrations/2021_06_26_211903_create_sites_table.php
Executable file → Normal file
0
database/migrations/2021_06_28_085814_create_source_controls_table.php
Executable file → Normal file
0
database/migrations/2021_06_28_085814_create_source_controls_table.php
Executable file → Normal file
0
database/migrations/2021_07_02_065815_create_deployments_table.php
Executable file → Normal file
0
database/migrations/2021_07_02_065815_create_deployments_table.php
Executable file → Normal file
0
database/migrations/2021_07_03_133319_create_databases_table.php
Executable file → Normal file
0
database/migrations/2021_07_03_133319_create_databases_table.php
Executable file → Normal file
0
database/migrations/2021_07_03_133327_create_database_users_table.php
Executable file → Normal file
0
database/migrations/2021_07_03_133327_create_database_users_table.php
Executable file → Normal file
0
database/migrations/2021_07_15_090830_create_firewall_rules_table.php
Executable file → Normal file
0
database/migrations/2021_07_15_090830_create_firewall_rules_table.php
Executable file → Normal file
0
database/migrations/2021_07_30_204454_create_cron_jobs_table.php
Executable file → Normal file
0
database/migrations/2021_07_30_204454_create_cron_jobs_table.php
Executable file → Normal file
@ -0,0 +1,22 @@
|
||||
<?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('notification_channels', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('project_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('notification_channels', function (Blueprint $table) {
|
||||
$table->dropColumn('project_id');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
<?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('storage_providers', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('project_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('storage_providers', function (Blueprint $table) {
|
||||
$table->dropColumn('project_id');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
<?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('server_providers', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('project_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('server_providers', function (Blueprint $table) {
|
||||
$table->dropColumn('project_id');
|
||||
});
|
||||
}
|
||||
};
|
@ -5,6 +5,39 @@ NAME=${NAME:-"vito"}
|
||||
EMAIL=${EMAIL:-"vito@vitodeploy.com"}
|
||||
PASSWORD=${PASSWORD:-"password"}
|
||||
|
||||
# Function to check if a string is 32 characters long
|
||||
check_length() {
|
||||
local key=$1
|
||||
if [ ${#key} -ne 32 ]; then
|
||||
echo "Invalid APP_KEY"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if APP_KEY is set
|
||||
if [ -z "$APP_KEY" ]; then
|
||||
echo "APP_KEY is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if APP_KEY starts with 'base64:'
|
||||
if [[ $APP_KEY == base64:* ]]; then
|
||||
# Remove 'base64:' prefix and decode the base64 string
|
||||
decoded_key=$(echo "${APP_KEY:7}" | base64 --decode 2>/dev/null)
|
||||
|
||||
# Check if decoding was successful
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Invalid APP_KEY base64 encoding"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check the length of the decoded key
|
||||
check_length "$decoded_key"
|
||||
else
|
||||
# Check the length of the raw APP_KEY
|
||||
check_length "$APP_KEY"
|
||||
fi
|
||||
|
||||
# check if the flag file does not exist, indicating a first run
|
||||
if [ ! -f "$INIT_FLAG" ]; then
|
||||
echo "Initializing..."
|
||||
|
15
package-lock.json
generated
15
package-lock.json
generated
@ -4,6 +4,7 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vito",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
@ -694,12 +695,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -969,9 +970,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
|
@ -16,7 +16,7 @@ class="p-6"
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="script" :value="__('Script')" />
|
||||
<x-textarea id="script" name="script" class="mt-1 min-h-[400px] w-full">
|
||||
<x-textarea id="script" name="script" class="mt-1 min-h-[400px] w-full font-mono">
|
||||
{{ old("script", $site->deploymentScript?->content) }}
|
||||
</x-textarea>
|
||||
@error("script")
|
||||
@ -24,6 +24,27 @@ class="p-6"
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<div class="flex items-center">
|
||||
<x-input-label class="mr-1" :value="__('Available Variables')" />
|
||||
(
|
||||
<a
|
||||
href="https://vitodeploy.com/sites/application.html#deployment-script"
|
||||
target="_blank"
|
||||
class="text-primary-500"
|
||||
>
|
||||
{{ __("How to use?") }}
|
||||
</a>
|
||||
)
|
||||
</div>
|
||||
<div class="mt-1 rounded-lg bg-gray-100 p-4 dark:bg-gray-700">
|
||||
@foreach ($site->environmentVariables() as $key => $variable)
|
||||
{{ $key }}={{ $variable }}
|
||||
<br />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center justify-end">
|
||||
<x-secondary-button type="button" x-on:click="$dispatch('close')">
|
||||
{{ __("Cancel") }}
|
||||
|
@ -21,7 +21,7 @@ class="mt-6"
|
||||
>
|
||||
<x-input-label for="env" :value="__('.env')" />
|
||||
<div id="env-content">
|
||||
<x-textarea id="env" name="env" rows="10" class="mt-1 block min-h-[400px] w-full">
|
||||
<x-textarea id="env" name="env" rows="10" class="mt-1 block min-h-[400px] w-full font-mono">
|
||||
{{ old("env", session()->get("env") ?? "Loading...") }}
|
||||
</x-textarea>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div
|
||||
{{ $attributes->merge(["class" => "relative h-[500px] w-full overflow-auto whitespace-pre-line rounded-md border border-gray-200 bg-black p-5 text-gray-50 dark:border-gray-700"]) }}
|
||||
{{ $attributes->merge(["class" => "font-mono whitespace-pre relative h-[500px] w-full overflow-auto whitespace-pre-line rounded-md border border-gray-200 bg-black p-5 text-gray-50 dark:border-gray-700"]) }}
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
@ -35,7 +35,7 @@ class="p-6"
|
||||
<div class="mt-1 flex items-center">
|
||||
<x-select-input id="backup_storage" name="backup_storage" class="mt-1 w-full">
|
||||
<option value="" selected disabled>{{ __("Select") }}</option>
|
||||
@foreach (auth()->user()->storageProviders as $st)
|
||||
@foreach (\App\Models\StorageProvider::getByProjectId(auth()->user()->current_project_id)->get() as $st)
|
||||
<option value="{{ $st->id }}" @if(old('backup_storage') == $st->id) selected @endif>
|
||||
{{ $st->profile }} - {{ $st->provider }}
|
||||
</option>
|
||||
|
@ -37,16 +37,19 @@ class="cursor-pointer"
|
||||
>
|
||||
{{ __("Install Extension") }}
|
||||
</x-dropdown-link>
|
||||
<x-dropdown-link
|
||||
class="cursor-pointer"
|
||||
x-on:click="version = '{{ $php->version }}'; $dispatch('open-modal', 'update-php-ini'); document.getElementById('ini').value = 'Loading...';"
|
||||
hx-get="{{ route('servers.php.get-ini', ['server' => $server, 'version' => $php->version]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#update-php-ini-form"
|
||||
hx-select="#update-php-ini-form"
|
||||
>
|
||||
{{ __("Edit php.ini") }}
|
||||
</x-dropdown-link>
|
||||
@foreach ([\App\Enums\PHPIniType::FPM, \App\Enums\PHPIniType::CLI] as $type)
|
||||
<x-dropdown-link
|
||||
class="cursor-pointer"
|
||||
x-on:click="version = '{{ $php->version }}'; $dispatch('open-modal', 'update-php-ini-{{ $type }}'); document.getElementById('ini').value = 'Loading...';"
|
||||
hx-get="{{ route('servers.php.get-ini', ['server' => $server, 'version' => $php->version, 'type' => $type]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-target="#update-php-ini-{{ $type }}-form"
|
||||
hx-select="#update-php-ini-{{ $type }}-form"
|
||||
>
|
||||
{{ __("Edit php.ini (:type)", ["type" => $type]) }}
|
||||
</x-dropdown-link>
|
||||
@endforeach
|
||||
|
||||
<x-dropdown-link
|
||||
class="cursor-pointer"
|
||||
href="{{ route('servers.services.restart', ['server' => $server, 'service' => $php]) }}"
|
||||
@ -85,6 +88,7 @@ class="cursor-pointer"
|
||||
method="delete"
|
||||
x-bind:action="uninstallAction"
|
||||
/>
|
||||
@include("php.partials.update-php-ini")
|
||||
@include("php.partials.update-php-ini", ["type" => \App\Enums\PHPIniType::CLI])
|
||||
@include("php.partials.update-php-ini", ["type" => \App\Enums\PHPIniType::FPM])
|
||||
@include("php.partials.install-extension")
|
||||
</div>
|
||||
|
@ -1,20 +1,21 @@
|
||||
<x-modal name="update-php-ini">
|
||||
<x-modal name="update-php-ini-{{ $type }}">
|
||||
<form
|
||||
id="update-php-ini-form"
|
||||
id="update-php-ini-{{ $type }}-form"
|
||||
hx-post="{{ route("servers.php.update-ini", ["server" => $server]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#update-php-ini-form"
|
||||
hx-select="#update-php-ini-{{ $type }}-form"
|
||||
class="p-6"
|
||||
>
|
||||
<input type="hidden" name="type" value="{{ $type }}" />
|
||||
<input type="hidden" name="version" :value="version" />
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Update php.ini") }}
|
||||
{{ __("Update php.ini (:type)", ["type" => $type]) }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="ini" value="php.ini" />
|
||||
<x-textarea id="ini" name="ini" class="mt-1 w-full" rows="15">
|
||||
<x-textarea id="ini" name="ini" class="mt-1 w-full font-mono" rows="15">
|
||||
{{ old("ini", session()->get("ini")) }}
|
||||
</x-textarea>
|
||||
@error("ini")
|
||||
|
@ -46,6 +46,16 @@
|
||||
<div class="mt-5">
|
||||
{!! auth()->user()->twoFactorQrCodeSvg() !!}
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
{{ __("If you are unable to scan the QR code, please use the 2FA secret instead.") }}
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="inline-block rounded-md border border-gray-100 p-2 dark:border-gray-700">
|
||||
{{ decrypt(auth()->user()->two_factor_secret) }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Show 2FA Recovery Codes --}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<x-input-label for="content" :value="__('Content')" />
|
||||
<x-textarea id="content" name="content" class="mt-1 min-h-[400px] w-full">
|
||||
<x-textarea id="content" name="content" class="mt-1 min-h-[400px] w-full font-mono">
|
||||
{{ $value }}
|
||||
</x-textarea>
|
||||
@error("content")
|
||||
|
@ -100,6 +100,15 @@ class="mt-1 w-full"
|
||||
@enderror
|
||||
</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") }}
|
||||
|
@ -16,13 +16,29 @@
|
||||
@include("settings.notification-channels.partials.icons." . $channel->provider)
|
||||
</div>
|
||||
<div class="ml-3 flex flex-grow flex-col items-start justify-center">
|
||||
<span class="mb-1">{{ $channel->label }}</span>
|
||||
<div class="mb-1 flex items-center">
|
||||
{{ $channel->label }}
|
||||
@if (! $channel->project_id)
|
||||
<x-status status="disabled" class="ml-2">GLOBAL</x-status>
|
||||
@endif
|
||||
</div>
|
||||
<span class="text-sm text-gray-400">
|
||||
<x-datetime :value="$channel->created_at" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="inline">
|
||||
<x-icon-button
|
||||
id="edit-{{ $channel->id }}"
|
||||
hx-get="{{ route('settings.notification-channels', ['edit' => $channel->id]) }}"
|
||||
hx-replace-url="true"
|
||||
hx-select="#edit"
|
||||
hx-target="#edit"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#edit-{{ $channel->id }}"
|
||||
>
|
||||
<x-heroicon name="o-pencil" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('settings.notification-channels.delete', $channel->id) }}'; $dispatch('open-modal', 'delete-channel')"
|
||||
>
|
||||
@ -34,6 +50,12 @@
|
||||
@endforeach
|
||||
|
||||
@include("settings.notification-channels.partials.delete-channel")
|
||||
|
||||
<div id="edit">
|
||||
@if (isset($editChannel))
|
||||
@include("settings.notification-channels.partials.edit-notification-channel", ["notificationChannel" => $editChannel])
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<x-simple-card>
|
||||
<div class="text-center">
|
||||
|
@ -0,0 +1,59 @@
|
||||
<x-modal
|
||||
name="edit-notification-channel"
|
||||
:show="true"
|
||||
x-on:modal-edit-notification-channel-closed.window="window.history.pushState('', '', '{{ route('settings.notification-channels') }}');"
|
||||
>
|
||||
<form
|
||||
id="edit-notification-channel-form"
|
||||
hx-post="{{ route("settings.notification-channels.update", ["notificationChannel" => $notificationChannel->id]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#edit-notification-channel-form"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-edit-notification-channel"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Edit Channel") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="edit-label" value="Label" />
|
||||
<x-text-input
|
||||
value="{{ $notificationChannel->label }}"
|
||||
id="edit-label"
|
||||
name="label"
|
||||
type="text"
|
||||
class="mt-1 w-full"
|
||||
/>
|
||||
@error("label")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-checkbox
|
||||
id="edit-global"
|
||||
name="global"
|
||||
:checked="old('global', $notificationChannel->project_id === null ? 1 : null)"
|
||||
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") }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-primary-button id="btn-edit-notification-channel" class="ml-3">
|
||||
{{ __("Save") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
@ -83,6 +83,15 @@ class="mt-1 w-full"
|
||||
@enderror
|
||||
</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") }}
|
||||
|
@ -0,0 +1,59 @@
|
||||
<x-modal
|
||||
name="edit-server-provider"
|
||||
:show="true"
|
||||
x-on:modal-edit-server-provider-closed.window="window.history.pushState('', '', '{{ route('settings.server-providers') }}');"
|
||||
>
|
||||
<form
|
||||
id="edit-server-provider-form"
|
||||
hx-post="{{ route("settings.server-providers.update", ["serverProvider" => $serverProvider->id]) }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-select="#edit-server-provider-form"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#btn-edit-server-provider"
|
||||
class="p-6"
|
||||
>
|
||||
@csrf
|
||||
|
||||
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ __("Edit Provider") }}
|
||||
</h2>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-input-label for="edit-name" value="Name" />
|
||||
<x-text-input
|
||||
value="{{ $serverProvider->profile }}"
|
||||
id="edit-name"
|
||||
name="name"
|
||||
type="text"
|
||||
class="mt-1 w-full"
|
||||
/>
|
||||
@error("name")
|
||||
<x-input-error class="mt-2" :messages="$message" />
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-checkbox
|
||||
id="edit-global"
|
||||
name="global"
|
||||
:checked="old('global', $serverProvider->project_id === null ? 1 : null)"
|
||||
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") }}
|
||||
</x-secondary-button>
|
||||
|
||||
<x-primary-button id="btn-edit-server-provider" class="ml-3">
|
||||
{{ __("Save") }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-modal>
|
@ -18,13 +18,29 @@ class="h-10 w-10"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-3 flex flex-grow flex-col items-start justify-center">
|
||||
<span class="mb-1">{{ $provider->profile }}</span>
|
||||
<div class="mb-1 flex items-center">
|
||||
{{ $provider->profile }}
|
||||
@if (! $provider->project_id)
|
||||
<x-status status="disabled" class="ml-2">GLOBAL</x-status>
|
||||
@endif
|
||||
</div>
|
||||
<span class="text-sm text-gray-400">
|
||||
<x-datetime :value="$provider->created_at" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="inline">
|
||||
<x-icon-button
|
||||
id="edit-{{ $provider->id }}"
|
||||
hx-get="{{ route('settings.server-providers', ['edit' => $provider->id]) }}"
|
||||
hx-replace-url="true"
|
||||
hx-select="#edit"
|
||||
hx-target="#edit"
|
||||
hx-ext="disable-element"
|
||||
hx-disable-element="#edit-{{ $provider->id }}"
|
||||
>
|
||||
<x-heroicon name="o-pencil" class="h-5 w-5" />
|
||||
</x-icon-button>
|
||||
<x-icon-button
|
||||
x-on:click="deleteAction = '{{ route('settings.server-providers.delete', $provider->id) }}'; $dispatch('open-modal', 'delete-provider')"
|
||||
>
|
||||
@ -36,6 +52,12 @@ class="h-10 w-10"
|
||||
@endforeach
|
||||
|
||||
@include("settings.server-providers.partials.delete-provider")
|
||||
|
||||
<div id="edit">
|
||||
@if (isset($editProvider))
|
||||
@include("settings.server-providers.partials.edit-provider", ["serverProvider" => $editProvider])
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<x-simple-card>
|
||||
<div class="text-center">
|
||||
|
@ -109,7 +109,7 @@ class="text-primary-500"
|
||||
|
||||
<div class="mt-6">
|
||||
<x-checkbox
|
||||
id="global"
|
||||
id="edit-global"
|
||||
name="global"
|
||||
:checked="old('global', $sourceControl->project_id === null ? 1 : null)"
|
||||
value="1"
|
||||
|
@ -201,6 +201,15 @@ class="mt-1 w-full"
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<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