This commit is contained in:
Saeed Vaziry
2024-10-13 12:33:12 +02:00
parent 386d8e73a7
commit 224e0ac2b0
49 changed files with 3668 additions and 766 deletions

View File

@ -43,7 +43,7 @@ public static function rules(Server $server): array
{
return [
'name' => [
'string',
'required',
'max:255',
Rule::unique('servers')->where('project_id', $server->project_id)->ignore($server->id),
],

View File

@ -2,7 +2,6 @@
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
@ -17,16 +16,17 @@ public function handle(): void
$privateKeyPath = storage_path('ssh-private.pem');
$publicKeyPath = storage_path('ssh-public.key');
if (File::exists($privateKeyPath) && File::exists($publicKeyPath) && !$this->option('force')) {
if (File::exists($privateKeyPath) && File::exists($publicKeyPath) && ! $this->option('force')) {
$this->error('Keys already exist. Use --force to overwrite.');
return;
}
exec('openssl genpkey -algorithm RSA -out ' . $privateKeyPath);
exec('chmod 600 ' . $privateKeyPath);
exec('ssh-keygen -y -f ' . $privateKeyPath . ' > ' . $publicKeyPath);
exec('chown -R ' . get_current_user() . ':' . get_current_user() . ' ' . $privateKeyPath);
exec('chown -R ' . get_current_user() . ':' . get_current_user() . ' ' . $publicKeyPath);
exec('openssl genpkey -algorithm RSA -out '.$privateKeyPath);
exec('chmod 600 '.$privateKeyPath);
exec('ssh-keygen -y -f '.$privateKeyPath.' > '.$publicKeyPath);
exec('chown -R '.get_current_user().':'.get_current_user().' '.$privateKeyPath);
exec('chown -R '.get_current_user().':'.get_current_user().' '.$publicKeyPath);
$this->info('Keys generated successfully.');
}

View File

@ -57,6 +57,15 @@ class Queue extends AbstractModel
QueueStatus::STOPPED => 'gray',
];
public static function boot(): void
{
parent::boot();
static::deleting(function (Queue $queue) {
$queue->server->processManager()->handler()->delete($queue->id, $queue->site_id);
});
}
public function getServerIdAttribute(int $value): int
{
if (! $value) {

View File

@ -18,6 +18,7 @@
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
@ -117,26 +118,33 @@ public static function boot(): void
parent::boot();
static::deleting(function (Server $server) {
$server->sites()->each(function (Site $site) {
$site->delete();
});
$server->provider()->delete();
$server->logs()->each(function (ServerLog $log) {
$log->delete();
});
$server->services()->delete();
$server->databases()->delete();
$server->databaseUsers()->delete();
$server->firewallRules()->delete();
$server->cronJobs()->delete();
$server->queues()->delete();
$server->daemons()->delete();
$server->sshKeys()->detach();
if (File::exists($server->sshKey()['public_key_path'])) {
File::delete($server->sshKey()['public_key_path']);
}
if (File::exists($server->sshKey()['private_key_path'])) {
File::delete($server->sshKey()['private_key_path']);
DB::beginTransaction();
try {
$server->sites()->each(function (Site $site) {
$site->delete();
});
$server->logs()->each(function (ServerLog $log) {
$log->delete();
});
$server->services()->delete();
$server->databases()->delete();
$server->databaseUsers()->delete();
$server->firewallRules()->delete();
$server->cronJobs()->delete();
$server->queues()->delete();
$server->daemons()->delete();
$server->sshKeys()->detach();
if (File::exists($server->sshKey()['public_key_path'])) {
File::delete($server->sshKey()['public_key_path']);
}
if (File::exists($server->sshKey()['private_key_path'])) {
File::delete($server->sshKey()['private_key_path']);
}
$server->provider()->delete();
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
throw $e;
}
});
}

View File

@ -5,7 +5,11 @@
use App\Actions\Service\Manage;
use App\Enums\ServiceStatus;
use App\Exceptions\ServiceInstallationFailed;
use App\SSH\Services\Database\Database as DatabaseAlias;
use App\SSH\Services\PHP\PHP as PHPAlias;
use App\SSH\Services\ProcessManager\ProcessManager;
use App\SSH\Services\ServiceInterface;
use App\SSH\Services\WebServer\WebServer as WebServerAlias;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
@ -76,6 +80,9 @@ public function server(): BelongsTo
return $this->belongsTo(Server::class);
}
/**
* @return ProcessManager|DatabaseAlias|PHPAlias|WebServerAlias
*/
public function handler(): ServiceInterface
{
$handler = config('core.service_handlers')[$this->name];

View File

@ -88,7 +88,7 @@ public static function boot(): void
parent::boot();
static::deleting(function (Site $site) {
$site->queues()->delete();
$site->queues()->each(fn (Queue $queue) => $queue->delete());
$site->ssls()->delete();
$site->deployments()->delete();
$site->deploymentScript()->delete();

View File

@ -2,6 +2,7 @@
namespace App\Policies;
use App\Enums\SiteFeature;
use App\Models\Queue;
use App\Models\Server;
use App\Models\Site;
@ -16,6 +17,7 @@ public function viewAny(User $user, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$site->isReady();
}
@ -25,6 +27,7 @@ public function view(User $user, Queue $queue, Site $site, Server $server): bool
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$queue->site_id === $site->id;
}
@ -32,6 +35,7 @@ public function create(User $user, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$site->isReady();
}
@ -41,6 +45,7 @@ public function update(User $user, Queue $queue, Site $site, Server $server): bo
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$queue->site_id === $site->id;
}
@ -50,6 +55,7 @@ public function delete(User $user, Queue $queue, Site $site, Server $server): bo
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$site->hasFeature(SiteFeature::QUEUES) &&
$queue->site_id === $site->id;
}
}

View File

@ -2,6 +2,7 @@
namespace App\Policies;
use App\Enums\SiteFeature;
use App\Models\Server;
use App\Models\Site;
use App\Models\Ssl;
@ -16,6 +17,7 @@ public function viewAny(User $user, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$site->isReady();
}
@ -25,6 +27,7 @@ public function view(User $user, Ssl $ssl, Site $site, Server $server): bool
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$ssl->site_id === $site->id;
}
@ -32,6 +35,7 @@ public function create(User $user, Site $site, Server $server): bool
{
return ($user->isAdmin() || $server->project->users->contains($user)) &&
$server->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$site->isReady();
}
@ -41,6 +45,7 @@ public function update(User $user, Ssl $ssl, Site $site, Server $server): bool
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$ssl->site_id === $site->id;
}
@ -50,6 +55,7 @@ public function delete(User $user, Ssl $ssl, Site $site, Server $server): bool
$site->server_id === $server->id &&
$server->isReady() &&
$site->isReady() &&
$site->hasFeature(SiteFeature::SSL) &&
$ssl->site_id === $site->id;
}
}

View File

@ -2,6 +2,8 @@
namespace App\SiteTypes;
use App\Enums\SiteFeature;
use App\SSH\Services\Webserver\Webserver;
use Illuminate\Validation\Rule;
class PHPMyAdmin extends PHPSite
@ -9,7 +11,7 @@ class PHPMyAdmin extends PHPSite
public function supportedFeatures(): array
{
return [
//
SiteFeature::SSL,
];
}
@ -20,7 +22,7 @@ public function createRules(array $input): array
'required',
Rule::in($this->site->server->installedPHPVersions()),
],
'version' => 'required|string',
'version' => 'required',
];
}

View File

@ -35,7 +35,10 @@ public function createRules(array $input): array
'title' => 'required',
'username' => 'required',
'password' => 'required',
'email' => 'required|email',
'email' => [
'required',
'email',
],
'database' => [
'required',
Rule::unique('databases', 'name')->where(function ($query) {

View File

@ -26,9 +26,9 @@ public function getTitle(): string|Htmlable
return $this->script->name.' - Executions';
}
public static function canAccess(): bool
public function mount(): void
{
return auth()->user()?->can('view', get_from_route(Script::class, 'script')) ?? false;
$this->authorize('view', $this->script);
}
public function getWidgets(): array

View File

@ -60,9 +60,9 @@ public function getTable(): Table
return $this->table
->heading('')
->actions([
Action::make('view')
Action::make('logs')
->hiddenLabel()
->tooltip('View')
->tooltip('Logs')
->icon('heroicon-o-eye')
->authorize(fn (ScriptExecution $record) => auth()->user()->can('view', $record->serverLog))
->modalHeading('View Log')

View File

@ -48,7 +48,8 @@ protected function getTableColumns(): array
->sortable(),
TextColumn::make('created_at')
->label('Installed At')
->formatStateUsing(fn ($record) => $record->created_at_by_timezone),
->formatStateUsing(fn ($record) => $record->created_at_by_timezone)
->sortable(),
];
}

View File

@ -7,7 +7,6 @@
use App\Web\Pages\Servers\Widgets\ServerDetails;
use App\Web\Pages\Servers\Widgets\UpdateServerInfo;
use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Notifications\Notification;
class Settings extends Page
@ -40,11 +39,29 @@ public function getWidgets(): array
protected function getHeaderActions(): array
{
return [
DeleteAction::make()
Action::make('delete')
->icon('heroicon-o-trash')
->record($this->server)
->color('danger')
->requiresConfirmation()
->modalHeading('Delete Server')
->modalDescription('Once your server is deleted, all of its resources and data will be permanently deleted and can\'t be restored'),
->modalDescription('Once your server is deleted, all of its resources and data will be permanently deleted and can\'t be restored')
->action(function () {
try {
$this->server->delete();
Notification::make()
->success()
->title('Server deleted')
->send();
$this->redirect(Index::getUrl());
} catch (\Throwable $e) {
Notification::make()
->danger()
->title($e->getMessage())
->send();
}
}),
Action::make('reboot')
->color('gray')
->icon('heroicon-o-arrow-path')

View File

@ -9,6 +9,8 @@
use App\Web\Pages\Settings\SourceControls\Actions\Create;
use Filament\Actions\Action;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
@ -53,6 +55,7 @@ protected function getHeaderActions(): array
->slideOver()
->form([
Select::make('type')
->label('Site Type')
->options(
collect(config('core.site_types'))->mapWithKeys(fn ($type) => [$type => $type])
)
@ -119,6 +122,17 @@ protected function getHeaderActions(): array
->label('Run `composer install --no-dev`')
->default(false)
->visible(fn (Get $get) => isset(CreateSite::rules($this->server, $get())['composer'])),
// PHPMyAdmin
Select::make('version')
->label('PHPMyAdmin Version')
->validationAttribute('PHPMyAdmin Version')
->options([
'5.2.1' => '5.2.1',
])
->visible(fn (Get $get) => $get('type') === SiteType::PHPMYADMIN)
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['version']),
// WordPress
$this->wordpressFields(),
])
->action(function (array $data) {
$this->authorize('create', [Site::class, $this->server]);
@ -128,7 +142,7 @@ protected function getHeaderActions(): array
try {
$site = app(CreateSite::class)->create($this->server, $data);
$this->redirect(\App\Web\Pages\Servers\Sites\View::getUrl([
$this->redirect(View::getUrl([
'server' => $this->server,
'site' => $site,
]));
@ -142,4 +156,35 @@ protected function getHeaderActions(): array
->modalSubmitActionLabel('Create Site'),
];
}
private function wordpressFields(): Component
{
return Grid::make()
->columns(3)
->visible(fn (Get $get) => $get('type') === SiteType::WORDPRESS)
->schema([
TextInput::make('title')
->label('Site Title')
->columnSpan(3)
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['title']),
TextInput::make('email')
->label('WP Admin Email')
->default(auth()->user()?->email)
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['email']),
TextInput::make('username')
->label('WP Admin Username')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['username']),
TextInput::make('password')
->label('WP Admin Password')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['password']),
TextInput::make('database')
->helperText('It will create a database with this name')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['database']),
TextInput::make('database_user')
->helperText('It will create a db user with this username')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['database']),
TextInput::make('database_password')
->rules(fn (Get $get) => CreateSite::rules($this->server, $get())['database']),
]);
}
}

View File

@ -24,7 +24,7 @@ public function getSecondSubNavigation(): array
if ($user->can('view', [$this->site, $this->server])) {
$items[] = NavigationItem::make(View::getNavigationLabel())
->icon('heroicon-o-globe-alt')
->icon('icon-'.$this->site->type)
->isActiveWhen(fn () => request()->routeIs(View::getRouteName()))
->url(View::getUrl(parameters: [
'server' => $this->server,

View File

@ -54,15 +54,6 @@ public function getWidgets(): array
if ($this->site->isInstalling()) {
$widgets[] = [Widgets\Installing::class, ['site' => $this->site]];
if (auth()->user()->can('viewAny', [ServerLog::class, $this->server])) {
$widgets[] = [
LogsList::class, [
'server' => $this->server,
'site' => $this->site,
'label' => 'Logs',
],
];
}
}
if ($this->site->isReady()) {
@ -71,6 +62,16 @@ public function getWidgets(): array
}
}
if (auth()->user()->can('viewAny', [ServerLog::class, $this->server])) {
$widgets[] = [
LogsList::class, [
'server' => $this->server,
'site' => $this->site,
'label' => 'Logs',
],
];
}
return $widgets;
}
@ -96,7 +97,9 @@ public function getHeaderActions(): array
$actionsGroup[] = $this->dotEnvAction();
}
$actionsGroup[] = $this->branchAction();
if ($this->site->sourceControl) {
$actionsGroup[] = $this->branchAction();
}
$actions[] = ActionGroup::make($actionsGroup)
->button()

View File

@ -56,6 +56,7 @@ public function infolist(Infolist $infolist): Infolist
->inlineLabel(),
TextEntry::make('type')
->extraAttributes(['class' => 'capitalize'])
->icon(fn ($state) => 'icon-'.$state)
->inlineLabel(),
TextEntry::make('tags.*')
->default('No tags')
@ -133,6 +134,7 @@ public function infolist(Infolist $infolist): Infolist
),
TextEntry::make('source_control_id')
->label('Source Control')
->visible(fn (Site $record) => $record->sourceControl)
->formatStateUsing(fn (Site $record) => $record->sourceControl?->profile)
->inlineLabel()
->suffixAction(

View File

@ -50,7 +50,7 @@ public function infolist(Infolist $infolist): Infolist
TextEntry::make('last_updated_check')
->label('Last Updated Check')
->inlineLabel()
->state(fn (Server $record) => $record->last_update_check?->ago())
->state(fn (Server $record) => $record->last_update_check ? $record->last_update_check->ago() : '-')
->suffixAction(
Action::make('check-update')
->icon('heroicon-o-arrow-path')
@ -63,11 +63,11 @@ public function infolist(Infolist $infolist): Infolist
Notification::make()
->info()
->title('Available updates:')
->body($record->available_updates)
->body($record->updates)
->send();
})
),
TextEntry::make('available_updates')
TextEntry::make('updates')
->label('Available Updates')
->inlineLabel()
->suffixAction(

View File

@ -32,6 +32,7 @@ class ServerSummary extends Widget implements HasForms, HasInfolists
public function infolist(Infolist $infolist): Infolist
{
return $infolist
->name('server-summary')
->schema([
Fieldset::make('info')
->label('Server Summary')

View File

@ -5,6 +5,8 @@
use App\Actions\ServerProvider\DeleteServerProvider;
use App\Models\ServerProvider;
use App\Web\Pages\Settings\ServerProviders\Actions\Edit;
use Exception;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction;
@ -70,7 +72,14 @@ public function getTable(): Table
->modalHeading('Delete Server Provider')
->authorize(fn (ServerProvider $record) => auth()->user()->can('delete', $record))
->using(function (array $data, ServerProvider $record) {
app(DeleteServerProvider::class)->delete($record);
try {
app(DeleteServerProvider::class)->delete($record);
} catch (Exception $e) {
Notification::make()
->danger()
->title($e->getMessage())
->send();
}
}),
]);
}

View File

@ -5,8 +5,10 @@
use App\Actions\SourceControl\DeleteSourceControl;
use App\Models\SourceControl;
use App\Web\Pages\Settings\SourceControls\Actions\Edit;
use Exception;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
@ -71,14 +73,24 @@ public function getTable(): Table
->authorize(fn (SourceControl $record) => auth()->user()->can('update', $record))
->using(fn (array $data, SourceControl $record) => Edit::action($record, $data))
->modalWidth(MaxWidth::Medium),
DeleteAction::make('delete')
Action::make('delete')
->label('Delete')
->icon('heroicon-o-trash')
->color('danger')
->requiresConfirmation()
->modalHeading('Delete Source Control')
->authorize(fn (SourceControl $record) => auth()->user()->can('delete', $record))
->using(function (array $data, SourceControl $record) {
app(DeleteSourceControl::class)->delete($record);
->action(function (array $data, SourceControl $record) {
try {
app(DeleteSourceControl::class)->delete($record);
$this->dispatch('$refresh');
$this->dispatch('$refresh');
} catch (Exception $e) {
Notification::make()
->danger()
->title($e->getMessage())
->send();
}
}),
]);
}

View File

@ -4,7 +4,7 @@
use App\Enums\StorageProvider;
use App\Web\Components\Page;
use Filament\Actions\CreateAction;
use Filament\Actions\Action;
use Filament\Support\Enums\MaxWidth;
class Index extends Page
@ -34,16 +34,19 @@ public function getWidgets(): array
protected function getHeaderActions(): array
{
return [
CreateAction::make()
Action::make('connect')
->label('Connect')
->icon('heroicon-o-wifi')
->modalHeading('Connect to a Storage Provider')
->modalSubmitActionLabel('Connect')
->createAnother(false)
->form(Actions\Create::form())
->authorize('create', StorageProvider::class)
->modalWidth(MaxWidth::ExtraLarge)
->using(fn (array $data) => Actions\Create::action($data)),
->action(function (array $data) {
Actions\Create::action($data);
$this->dispatch('$refresh');
}),
];
}
}

View File

@ -5,6 +5,7 @@
use App\Actions\StorageProvider\DeleteStorageProvider;
use App\Models\StorageProvider;
use App\Web\Pages\Settings\StorageProviders\Actions\Edit;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\EditAction;
@ -71,7 +72,14 @@ public function getTable(): Table
->modalHeading('Delete Storage Provider')
->authorize(fn (StorageProvider $record) => auth()->user()->can('delete', $record))
->using(function (array $data, StorageProvider $record) {
app(DeleteStorageProvider::class)->delete($record);
try {
app(DeleteStorageProvider::class)->delete($record);
} catch (\Exception $e) {
Notification::make()
->danger()
->title($e->getMessage())
->send();
}
}),
]);
}