From 3c50e2c947af8837db99eb6af8faed20a106fe99 Mon Sep 17 00:00:00 2001 From: Saeed Vaziry Date: Sun, 6 Oct 2024 00:04:57 +0200 Subject: [PATCH] - 2.x - sites settings - tags - source-control soft deletes --- app/Actions/Site/CreateSite.php | 4 +- app/Actions/Site/Deploy.php | 6 +- app/Actions/Site/UpdateAliases.php | 9 +- app/Actions/Site/UpdatePHPVersion.php | 26 +++ app/Actions/Site/UpdateSourceControl.php | 13 +- app/Actions/Tag/AttachTag.php | 3 + app/Actions/Tag/CreateTag.php | 9 +- app/Actions/Tag/DetachTag.php | 3 + app/Actions/Tag/EditTag.php | 12 +- app/Actions/Tag/SyncTags.php | 40 ++++ app/Models/Deployment.php | 7 + app/Models/NotificationChannel.php | 5 +- app/Models/ServerProvider.php | 3 +- app/Models/Site.php | 40 +--- app/Models/SourceControl.php | 7 +- app/Models/StorageProvider.php | 5 +- app/Models/Tag.php | 8 +- app/Policies/TagPolicy.php | 37 ++++ app/Providers/WebServiceProvider.php | 2 +- .../Webserver/scripts/nginx/update-vhost.sh | 8 +- app/SiteTypes/AbstractSiteType.php | 2 +- app/Support/helpers.php | 2 +- .../Databases/Widgets/DatabaseUsersList.php | 3 - .../Databases/Widgets/DatabasesList.php | 3 - app/Web/Pages/Servers/Sites/Page.php | 10 + app/Web/Pages/Servers/Sites/Settings.php | 77 ++++++++ app/Web/Pages/Servers/Sites/View.php | 8 + .../Servers/Sites/Widgets/DeploymentsList.php | 84 ++++++++ .../Servers/Sites/Widgets/SiteDetails.php | 182 ++++++++++++++++++ .../Pages/Servers/Sites/Widgets/SitesList.php | 12 +- .../Pages/Servers/Widgets/ServerDetails.php | 16 +- app/Web/Pages/Servers/Widgets/ServersList.php | 12 +- .../Settings/SourceControls/Actions/Edit.php | 36 +++- .../Widgets/SourceControlsList.php | 3 +- .../Pages/Settings/Tags/Actions/Create.php | 49 +++++ app/Web/Pages/Settings/Tags/Actions/Edit.php | 31 +++ .../Pages/Settings/Tags/Actions/EditTags.php | 61 ++++++ app/Web/Pages/Settings/Tags/Index.php | 59 ++++++ .../Pages/Settings/Tags/Widgets/TagsList.php | 78 ++++++++ composer.json | 1 + composer.lock | 73 ++++++- ...50_add_soft_deletes_to_source_controls.php | 22 +++ resources/css/filament/app/tailwind.config.js | 18 ++ .../partials/update-source-control.blade.php | 2 +- 44 files changed, 972 insertions(+), 119 deletions(-) create mode 100644 app/Actions/Site/UpdatePHPVersion.php create mode 100644 app/Actions/Tag/SyncTags.php create mode 100644 app/Policies/TagPolicy.php create mode 100644 app/Web/Pages/Servers/Sites/Settings.php create mode 100644 app/Web/Pages/Servers/Sites/Widgets/DeploymentsList.php create mode 100644 app/Web/Pages/Servers/Sites/Widgets/SiteDetails.php create mode 100644 app/Web/Pages/Settings/Tags/Actions/Create.php create mode 100644 app/Web/Pages/Settings/Tags/Actions/Edit.php create mode 100644 app/Web/Pages/Settings/Tags/Actions/EditTags.php create mode 100644 app/Web/Pages/Settings/Tags/Index.php create mode 100644 app/Web/Pages/Settings/Tags/Widgets/TagsList.php create mode 100644 database/migrations/2024_10_05_094650_add_soft_deletes_to_source_controls.php diff --git a/app/Actions/Site/CreateSite.php b/app/Actions/Site/CreateSite.php index 71a556c..f8b9e07 100755 --- a/app/Actions/Site/CreateSite.php +++ b/app/Actions/Site/CreateSite.php @@ -40,8 +40,8 @@ public function create(Server $server, array $input): Site // check has access to repository try { - if ($site->sourceControl()) { - $site->sourceControl()->getRepo($site->repository); + if ($site->sourceControl) { + $site->sourceControl?->getRepo($site->repository); } } catch (SourceControlIsNotConnected) { throw ValidationException::withMessages([ diff --git a/app/Actions/Site/Deploy.php b/app/Actions/Site/Deploy.php index 773c998..7a86686 100644 --- a/app/Actions/Site/Deploy.php +++ b/app/Actions/Site/Deploy.php @@ -17,8 +17,8 @@ class Deploy */ public function run(Site $site): Deployment { - if ($site->sourceControl()) { - $site->sourceControl()->getRepo($site->repository); + if ($site->sourceControl) { + $site->sourceControl->getRepo($site->repository); } if (! $site->deploymentScript?->content) { @@ -30,7 +30,7 @@ public function run(Site $site): Deployment 'deployment_script_id' => $site->deploymentScript->id, 'status' => DeploymentStatus::DEPLOYING, ]); - $lastCommit = $site->sourceControl()?->provider()?->getLastCommit($site->repository, $site->branch); + $lastCommit = $site->sourceControl?->provider()?->getLastCommit($site->repository, $site->branch); if ($lastCommit) { $deployment->commit_id = $lastCommit['commit_id']; $deployment->commit_data = $lastCommit['commit_data']; diff --git a/app/Actions/Site/UpdateAliases.php b/app/Actions/Site/UpdateAliases.php index 4756509..2d2f397 100644 --- a/app/Actions/Site/UpdateAliases.php +++ b/app/Actions/Site/UpdateAliases.php @@ -5,14 +5,11 @@ use App\Models\Site; use App\SSH\Services\Webserver\Webserver; use App\ValidationRules\DomainRule; -use Illuminate\Support\Facades\Validator; class UpdateAliases { public function update(Site $site, array $input): void { - $this->validate($input); - $site->aliases = $input['aliases'] ?? []; /** @var Webserver $webserver */ @@ -22,12 +19,12 @@ public function update(Site $site, array $input): void $site->save(); } - private function validate(array $input): void + public static function rules(): array { - Validator::make($input, [ + return [ 'aliases.*' => [ new DomainRule, ], - ])->validate(); + ]; } } diff --git a/app/Actions/Site/UpdatePHPVersion.php b/app/Actions/Site/UpdatePHPVersion.php new file mode 100644 index 0000000..1b41483 --- /dev/null +++ b/app/Actions/Site/UpdatePHPVersion.php @@ -0,0 +1,26 @@ + [ + 'required', + Rule::exists('services', 'version') + ->where('server_id', $site->server_id) + ->where('type', 'php'), + ], + ]; + } + + public function update(Site $site, array $input): void + { + $site->changePHPVersion($input['version']); + } +} diff --git a/app/Actions/Site/UpdateSourceControl.php b/app/Actions/Site/UpdateSourceControl.php index 02edbab..1506ebe 100644 --- a/app/Actions/Site/UpdateSourceControl.php +++ b/app/Actions/Site/UpdateSourceControl.php @@ -6,7 +6,6 @@ use App\Exceptions\RepositoryPermissionDenied; use App\Exceptions\SourceControlIsNotConnected; use App\Models\Site; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -14,12 +13,10 @@ class UpdateSourceControl { public function update(Site $site, array $input): void { - $this->validate($input); - $site->source_control_id = $input['source_control']; try { - if ($site->sourceControl()) { - $site->sourceControl()->getRepo($site->repository); + if ($site->sourceControl) { + $site->sourceControl?->getRepo($site->repository); } } catch (SourceControlIsNotConnected) { throw ValidationException::withMessages([ @@ -37,13 +34,13 @@ public function update(Site $site, array $input): void $site->save(); } - private function validate(array $input): void + public static function rules(): array { - Validator::make($input, [ + return [ 'source_control' => [ 'required', Rule::exists('source_controls', 'id'), ], - ])->validate(); + ]; } } diff --git a/app/Actions/Tag/AttachTag.php b/app/Actions/Tag/AttachTag.php index 661e853..e41fb59 100644 --- a/app/Actions/Tag/AttachTag.php +++ b/app/Actions/Tag/AttachTag.php @@ -9,6 +9,9 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; +/** + * @deprecated + */ class AttachTag { public function attach(User $user, array $input): Tag diff --git a/app/Actions/Tag/CreateTag.php b/app/Actions/Tag/CreateTag.php index 2d69a0b..097a9a1 100644 --- a/app/Actions/Tag/CreateTag.php +++ b/app/Actions/Tag/CreateTag.php @@ -4,7 +4,6 @@ use App\Models\Tag; use App\Models\User; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -12,8 +11,6 @@ class CreateTag { public function create(User $user, array $input): Tag { - $this->validate($input); - $tag = Tag::query() ->where('project_id', $user->current_project_id) ->where('name', $input['name']) @@ -34,9 +31,9 @@ public function create(User $user, array $input): Tag return $tag; } - private function validate(array $input): void + public static function rules(): array { - Validator::make($input, [ + return [ 'name' => [ 'required', ], @@ -44,6 +41,6 @@ private function validate(array $input): void 'required', Rule::in(config('core.tag_colors')), ], - ])->validate(); + ]; } } diff --git a/app/Actions/Tag/DetachTag.php b/app/Actions/Tag/DetachTag.php index 9b5c3e0..789ad40 100644 --- a/app/Actions/Tag/DetachTag.php +++ b/app/Actions/Tag/DetachTag.php @@ -8,6 +8,9 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; +/** + * @deprecated + */ class DetachTag { public function detach(Tag $tag, array $input): void diff --git a/app/Actions/Tag/EditTag.php b/app/Actions/Tag/EditTag.php index d237029..13c56cb 100644 --- a/app/Actions/Tag/EditTag.php +++ b/app/Actions/Tag/EditTag.php @@ -3,28 +3,21 @@ namespace App\Actions\Tag; use App\Models\Tag; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; -use Illuminate\Validation\ValidationException; class EditTag { public function edit(Tag $tag, array $input): void { - $this->validate($input); - $tag->name = $input['name']; $tag->color = $input['color']; $tag->save(); } - /** - * @throws ValidationException - */ - private function validate(array $input): void + public static function rules(): array { - $rules = [ + return [ 'name' => [ 'required', ], @@ -33,6 +26,5 @@ private function validate(array $input): void Rule::in(config('core.tag_colors')), ], ]; - Validator::make($input, $rules)->validate(); } } diff --git a/app/Actions/Tag/SyncTags.php b/app/Actions/Tag/SyncTags.php new file mode 100644 index 0000000..f66d7f3 --- /dev/null +++ b/app/Actions/Tag/SyncTags.php @@ -0,0 +1,40 @@ +whereIn('id', $input['tags'])->get(); + + $taggable->tags()->sync($tags->pluck('id')); + } + + public static function rules(int $projectId): array + { + return [ + 'tags.*' => [ + 'required', + Rule::exists('tags', 'id')->where('project_id', $projectId), + ], + 'taggable_id' => [ + 'required', + 'integer', + ], + 'taggable_type' => [ + 'required', + Rule::in(config('core.taggable_types')), + ], + ]; + } +} diff --git a/app/Models/Deployment.php b/app/Models/Deployment.php index 4ded05f..41d6493 100755 --- a/app/Models/Deployment.php +++ b/app/Models/Deployment.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Enums\DeploymentStatus; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -37,6 +38,12 @@ class Deployment extends AbstractModel 'commit_data' => 'json', ]; + public static array $statusColors = [ + DeploymentStatus::DEPLOYING => 'warning', + DeploymentStatus::FINISHED => 'success', + DeploymentStatus::FAILED => 'danger', + ]; + public function site(): BelongsTo { return $this->belongsTo(Site::class); diff --git a/app/Models/NotificationChannel.php b/app/Models/NotificationChannel.php index df4259d..7305402 100644 --- a/app/Models/NotificationChannel.php +++ b/app/Models/NotificationChannel.php @@ -60,7 +60,8 @@ public function project(): BelongsTo public static function getByProjectId(int $projectId): Builder { return self::query() - ->where('project_id', $projectId) - ->orWhereNull('project_id'); + ->where(function (Builder $query) use ($projectId) { + $query->where('project_id', $projectId)->orWhereNull('project_id'); + }); } } diff --git a/app/Models/ServerProvider.php b/app/Models/ServerProvider.php index 9dc3057..f7ba4d7 100644 --- a/app/Models/ServerProvider.php +++ b/app/Models/ServerProvider.php @@ -71,8 +71,7 @@ public static function getByProjectId(int $projectId): Builder { return self::query() ->where(function (Builder $query) use ($projectId) { - $query->where('project_id', $projectId) - ->orWhereNull('project_id'); + $query->where('project_id', $projectId)->orWhereNull('project_id'); }); } diff --git a/app/Models/Site.php b/app/Models/Site.php index af47786..2d61acc 100755 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -9,7 +9,6 @@ use App\SSH\Services\Webserver\Webserver; use App\Traits\HasProjectThroughServer; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; @@ -42,6 +41,7 @@ * @property Ssl[] $ssls * @property ?Ssl $activeSsl * @property string $ssh_key_name + * @property ?SourceControl $sourceControl */ class Site extends AbstractModel { @@ -157,38 +157,14 @@ public function tags(): MorphToMany return $this->morphToMany(Tag::class, 'taggable'); } - /** - * @throws SourceControlIsNotConnected - */ - public function sourceControl(): SourceControl|HasOne|null|Model + public function sourceControl(): BelongsTo { - $sourceControl = null; - - if (! $this->source_control && ! $this->source_control_id) { - return null; - } - - if ($this->source_control) { - $sourceControl = SourceControl::query()->where('provider', $this->source_control)->first(); - } - - if ($this->source_control_id) { - $sourceControl = SourceControl::query()->find($this->source_control_id); - } - - if (! $sourceControl) { - throw new SourceControlIsNotConnected($this->source_control); - } - - return $sourceControl; + return $this->belongsTo(SourceControl::class)->withTrashed(); } - /** - * @throws SourceControlIsNotConnected - */ - public function getFullRepositoryUrl() + public function getFullRepositoryUrl(): ?string { - return $this->sourceControl()->provider()->fullRepoUrl($this->repository, $this->getSshKeyName()); + return $this->sourceControl?->provider()?->fullRepoUrl($this->repository, $this->getSshKeyName()); } public function getAliasesString(): string @@ -259,13 +235,13 @@ public function enableAutoDeployment(): void return; } - if (! $this->sourceControl()?->getRepo($this->repository)) { + if (! $this->sourceControl?->getRepo($this->repository)) { throw new SourceControlIsNotConnected($this->source_control); } $gitHook = new GitHook([ 'site_id' => $this->id, - 'source_control_id' => $this->sourceControl()->id, + 'source_control_id' => $this->source_control_id, 'secret' => Str::uuid()->toString(), 'actions' => ['deploy'], 'events' => ['push'], @@ -279,7 +255,7 @@ public function enableAutoDeployment(): void */ public function disableAutoDeployment(): void { - if (! $this->sourceControl()?->getRepo($this->repository)) { + if (! $this->sourceControl?->getRepo($this->repository)) { throw new SourceControlIsNotConnected($this->source_control); } diff --git a/app/Models/SourceControl.php b/app/Models/SourceControl.php index f8c17c3..a6b7904 100755 --- a/app/Models/SourceControl.php +++ b/app/Models/SourceControl.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\SoftDeletes; /** * @property string $provider @@ -20,6 +21,7 @@ class SourceControl extends AbstractModel { use HasFactory; + use SoftDeletes; protected $fillable = [ 'provider', @@ -61,8 +63,9 @@ public function project(): BelongsTo public static function getByProjectId(int $projectId): Builder { return self::query() - ->where('project_id', $projectId) - ->orWhereNull('project_id'); + ->where(function (Builder $query) use ($projectId) { + $query->where('project_id', $projectId)->orWhereNull('project_id'); + }); } public function getImageUrlAttribute(): string diff --git a/app/Models/StorageProvider.php b/app/Models/StorageProvider.php index e4ccfdf..9b8f280 100644 --- a/app/Models/StorageProvider.php +++ b/app/Models/StorageProvider.php @@ -59,8 +59,9 @@ public function project(): BelongsTo public static function getByProjectId(int $projectId): Builder { return self::query() - ->where('project_id', $projectId) - ->orWhereNull('project_id'); + ->where(function (Builder $query) use ($projectId) { + $query->where('project_id', $projectId)->orWhereNull('project_id'); + }); } public function getImageUrlAttribute(): string diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 8c23487..2a0ce61 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -5,7 +5,6 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphToMany; @@ -17,7 +16,7 @@ * @property Carbon $created_at * @property Carbon $updated_at */ -class Tag extends Model +class Tag extends AbstractModel { use HasFactory; @@ -49,7 +48,8 @@ public function sites(): MorphToMany public static function getByProjectId(int $projectId): Builder { return self::query() - ->where('project_id', $projectId) - ->orWhereNull('project_id'); + ->where(function (Builder $query) use ($projectId) { + $query->where('project_id', $projectId)->orWhereNull('project_id'); + }); } } diff --git a/app/Policies/TagPolicy.php b/app/Policies/TagPolicy.php new file mode 100644 index 0000000..5cdcebc --- /dev/null +++ b/app/Policies/TagPolicy.php @@ -0,0 +1,37 @@ +isAdmin(); + } + + public function view(User $user, Tag $tag): bool + { + return $user->isAdmin(); + } + + public function create(User $user): bool + { + return $user->isAdmin(); + } + + public function update(User $user, Tag $tag): bool + { + return $user->isAdmin(); + } + + public function delete(User $user, Tag $tag): bool + { + return $user->isAdmin(); + } +} diff --git a/app/Providers/WebServiceProvider.php b/app/Providers/WebServiceProvider.php index 1336d40..ca260d0 100644 --- a/app/Providers/WebServiceProvider.php +++ b/app/Providers/WebServiceProvider.php @@ -51,7 +51,7 @@ public function boot(): void ]); FilamentColor::register([ 'slate' => Color::Slate, - 'gray' => Color::Zinc, + 'gray' => Color::Gray, 'red' => Color::Red, 'orange' => Color::Orange, 'amber' => Color::Amber, diff --git a/app/SSH/Services/Webserver/scripts/nginx/update-vhost.sh b/app/SSH/Services/Webserver/scripts/nginx/update-vhost.sh index 97ce3e0..3bf6094 100755 --- a/app/SSH/Services/Webserver/scripts/nginx/update-vhost.sh +++ b/app/SSH/Services/Webserver/scripts/nginx/update-vhost.sh @@ -1,7 +1,3 @@ -if ! echo '__vhost__' | sudo tee /etc/nginx/sites-available/__domain__; then - echo 'VITO_SSH_ERROR' && exit 1 -fi +echo '__vhost__' | sudo tee /etc/nginx/sites-available/__domain__ -if ! sudo service nginx restart; then - echo 'VITO_SSH_ERROR' && exit 1 -fi +sudo service nginx restart diff --git a/app/SiteTypes/AbstractSiteType.php b/app/SiteTypes/AbstractSiteType.php index 38998dc..d93580a 100755 --- a/app/SiteTypes/AbstractSiteType.php +++ b/app/SiteTypes/AbstractSiteType.php @@ -29,7 +29,7 @@ protected function deployKey(): void $os->generateSSHKey($this->site->getSshKeyName()); $this->site->ssh_key = $os->readSSHKey($this->site->getSshKeyName()); $this->site->save(); - $this->site->sourceControl()->provider()->deployKey( + $this->site->sourceControl?->provider()?->deployKey( $this->site->domain.'-key-'.$this->site->id, $this->site->repository, $this->site->ssh_key diff --git a/app/Support/helpers.php b/app/Support/helpers.php index 458d042..884724b 100755 --- a/app/Support/helpers.php +++ b/app/Support/helpers.php @@ -66,7 +66,7 @@ function run_action(object $static, Closure $callback): void Notification::make() ->danger() ->title($e->getMessage()) - ->body($e->getLog()?->getContent(10)) + ->body($e->getLog()?->getContent(30)) ->send(); if (method_exists($static, 'halt')) { diff --git a/app/Web/Pages/Servers/Databases/Widgets/DatabaseUsersList.php b/app/Web/Pages/Servers/Databases/Widgets/DatabaseUsersList.php index 113350a..990c0fc 100644 --- a/app/Web/Pages/Servers/Databases/Widgets/DatabaseUsersList.php +++ b/app/Web/Pages/Servers/Databases/Widgets/DatabaseUsersList.php @@ -33,9 +33,6 @@ protected function getTableQuery(): Builder protected function getTableColumns(): array { return [ - TextColumn::make('id') - ->searchable() - ->sortable(), TextColumn::make('username') ->searchable(), TextColumn::make('status') diff --git a/app/Web/Pages/Servers/Databases/Widgets/DatabasesList.php b/app/Web/Pages/Servers/Databases/Widgets/DatabasesList.php index 561ab6f..08de517 100644 --- a/app/Web/Pages/Servers/Databases/Widgets/DatabasesList.php +++ b/app/Web/Pages/Servers/Databases/Widgets/DatabasesList.php @@ -29,9 +29,6 @@ protected function getTableQuery(): Builder protected function getTableColumns(): array { return [ - TextColumn::make('id') - ->searchable() - ->sortable(), TextColumn::make('name') ->searchable(), TextColumn::make('status') diff --git a/app/Web/Pages/Servers/Sites/Page.php b/app/Web/Pages/Servers/Sites/Page.php index 541f4a5..54b6ccf 100644 --- a/app/Web/Pages/Servers/Sites/Page.php +++ b/app/Web/Pages/Servers/Sites/Page.php @@ -49,6 +49,16 @@ public function getSecondSubNavigation(): array ])); } + if (Settings::canAccess()) { + $items[] = NavigationItem::make(Settings::getNavigationLabel()) + ->icon('heroicon-o-wrench-screwdriver') + ->isActiveWhen(fn () => request()->routeIs(Settings::getRouteName())) + ->url(Settings::getUrl(parameters: [ + 'server' => $this->server, + 'site' => $this->site, + ])); + } + return [ NavigationGroup::make() ->items($items), diff --git a/app/Web/Pages/Servers/Sites/Settings.php b/app/Web/Pages/Servers/Sites/Settings.php new file mode 100644 index 0000000..f9ea36c --- /dev/null +++ b/app/Web/Pages/Servers/Sites/Settings.php @@ -0,0 +1,77 @@ +user()?->can('update', [static::getSiteFromRoute(), static::getServerFromRoute()]) ?? false; + } + + public function getWidgets(): array + { + return [ + [Widgets\SiteDetails::class, ['site' => $this->site]], + ]; + } + + protected function getHeaderActions(): array + { + return [ + $this->vhostAction(), + $this->deleteAction(), + ]; + } + + private function deleteAction(): Action + { + return DeleteAction::make() + ->icon('heroicon-o-trash') + ->record($this->server) + ->modalHeading('Delete Site') + ->modalDescription('Once your site is deleted, all of its resources and data will be permanently deleted and can\'t be restored'); + } + + private function vhostAction(): Action + { + return Action::make('vhost') + ->color('gray') + ->icon('si-nginx') + ->label('VHost') + ->modalSubmitActionLabel('Save') + ->form([ + CodeEditorField::make('vhost') + ->formatStateUsing(function () { + /** @var Webserver $handler */ + $handler = $this->server->webserver()->handler(); + + return $handler->getVhost($this->site); + }) + ->rules(['required']), + ]) + ->action(function (array $data) { + run_action($this, function () use ($data) { + /** @var Webserver $handler */ + $handler = $this->server->webserver()->handler(); + $handler->updateVHost($this->site, false, $data['vhost']); + Notification::make() + ->success() + ->title('VHost updated!') + ->send(); + }); + }); + } +} diff --git a/app/Web/Pages/Servers/Sites/View.php b/app/Web/Pages/Servers/Sites/View.php index 064be1a..e00b5b0 100644 --- a/app/Web/Pages/Servers/Sites/View.php +++ b/app/Web/Pages/Servers/Sites/View.php @@ -68,6 +68,12 @@ public function getWidgets(): array } } + if ($this->site->isReady()) { + if (in_array(SiteFeature::DEPLOYMENT, $this->site->type()->supportedFeatures())) { + $widgets[] = [Widgets\DeploymentsList::class, ['site' => $this->site]]; + } + } + return $widgets; } @@ -118,6 +124,8 @@ private function deployAction(): Action ->success() ->title('Deployment started!') ->send(); + + $this->dispatch('$refresh'); }); }); } diff --git a/app/Web/Pages/Servers/Sites/Widgets/DeploymentsList.php b/app/Web/Pages/Servers/Sites/Widgets/DeploymentsList.php new file mode 100644 index 0000000..b5dcae3 --- /dev/null +++ b/app/Web/Pages/Servers/Sites/Widgets/DeploymentsList.php @@ -0,0 +1,84 @@ +where('site_id', $this->site->id); + } + + protected function applySortingToTableQuery(Builder $query): Builder + { + return $query->latest('created_at'); + } + + protected function getTableColumns(): array + { + return [ + TextColumn::make('commit_data') + ->label('Commit') + ->url(fn (Deployment $record) => $record->commit_data['url'] ?? '#') + ->openUrlInNewTab() + ->formatStateUsing(fn (Deployment $record) => $record->commit_data['message'] ?? 'No message') + ->tooltip(fn (Deployment $record) => $record->commit_data['message'] ?? 'No message') + ->limit(50) + ->searchable() + ->sortable(), + TextColumn::make('created_at') + ->formatStateUsing(fn (Deployment $record) => $record->created_at_by_timezone) + ->sortable(), + TextColumn::make('status') + ->label('Status') + ->badge() + ->color(fn (Deployment $record) => Deployment::$statusColors[$record->status]) + ->searchable() + ->sortable(), + ]; + } + + public function table(Table $table): Table + { + return $table + ->query($this->getTableQuery()) + ->columns($this->getTableColumns()) + ->heading('Deployments') + ->actions([ + Action::make('view') + ->hiddenLabel() + ->tooltip('View') + ->icon('heroicon-o-eye') + ->authorize(fn ($record) => auth()->user()->can('view', $record->log)) + ->modalHeading('View Log') + ->modalContent(function (Deployment $record) { + return view('components.console-view', [ + 'slot' => $record->log?->getContent(), + 'attributes' => new ComponentAttributeBag, + ]); + }) + ->modalSubmitAction(false) + ->modalCancelActionLabel('Close'), + Action::make('download') + ->hiddenLabel() + ->tooltip('Download') + ->color('gray') + ->icon('heroicon-o-archive-box-arrow-down') + ->authorize(fn ($record) => auth()->user()->can('view', $record->log)) + ->action(fn (Deployment $record) => $record->log?->download()), + ]); + } +} diff --git a/app/Web/Pages/Servers/Sites/Widgets/SiteDetails.php b/app/Web/Pages/Servers/Sites/Widgets/SiteDetails.php new file mode 100644 index 0000000..5d8906b --- /dev/null +++ b/app/Web/Pages/Servers/Sites/Widgets/SiteDetails.php @@ -0,0 +1,182 @@ +schema([ + Section::make() + ->heading('Site Details') + ->description('More details about your site') + ->columns(1) + ->schema([ + TextEntry::make('id') + ->label('ID') + ->inlineLabel() + ->hintIcon('heroicon-o-information-circle') + ->hintIconTooltip('Site unique identifier to use in the API'), + TextEntry::make('created_at') + ->label('Created At') + ->formatStateUsing(fn ($record) => $record->created_at_by_timezone) + ->inlineLabel(), + TextEntry::make('type') + ->extraAttributes(['class' => 'capitalize']) + ->inlineLabel(), + TextEntry::make('tags.*') + ->default('No tags') + ->formatStateUsing(fn ($state) => is_object($state) ? $state->name : $state) + ->inlineLabel() + ->badge() + ->color(fn ($state) => is_object($state) ? $state->color : 'gray') + ->icon(fn ($state) => is_object($state) ? 'heroicon-o-tag' : '') + ->suffixAction( + EditTags::infolist($this->site) + ), + TextEntry::make('php_version') + ->label('PHP Version') + ->inlineLabel() + ->suffixAction( + Action::make('edit_php_version') + ->icon('heroicon-o-pencil-square') + ->tooltip('Change') + ->modalSubmitActionLabel('Save') + ->modalHeading('Update PHP Version') + ->modalWidth(MaxWidth::Medium) + ->form([ + Select::make('version') + ->label('Version') + ->selectablePlaceholder(false) + ->rules(UpdatePHPVersion::rules($this->site)['version']) + ->default($this->site->php_version) + ->options( + collect($this->site->server->installedPHPVersions()) + ->mapWithKeys(fn ($version) => [$version => $version]) + ), + + ]) + ->action(function (array $data) { + run_action($this, function () use ($data) { + app(UpdatePHPVersion::class)->update($this->site, $data); + + Notification::make() + ->success() + ->title('PHP version updated!') + ->send(); + }); + }) + ), + TextEntry::make('aliases.*') + ->inlineLabel() + ->badge() + ->default('No aliases') + ->color(fn ($state) => $state == 'No aliases' ? 'gray' : 'primary') + ->suffixAction( + Action::make('edit_aliases') + ->icon('heroicon-o-pencil-square') + ->tooltip('Change') + ->modalSubmitActionLabel('Save') + ->modalHeading('Update Aliases') + ->modalWidth(MaxWidth::Medium) + ->form([ + TagsInput::make('aliases') + ->splitKeys(['Enter', 'Tab', ' ', ',']) + ->placeholder('Type and press enter to add an alias') + ->default($this->site->aliases) + ->nestedRecursiveRules(UpdateAliases::rules()['aliases.*']), + + ]) + ->action(function (array $data) { + run_action($this, function () use ($data) { + app(UpdateAliases::class)->update($this->site, $data); + + Notification::make() + ->success() + ->title('Aliases updated!') + ->send(); + }); + }) + ), + TextEntry::make('source_control_id') + ->label('Source Control') + ->formatStateUsing(fn (Site $record) => $record->sourceControl?->profile) + ->inlineLabel() + ->suffixAction( + Action::make('edit_source_control') + ->icon('heroicon-o-pencil-square') + ->tooltip('Change') + ->modalSubmitActionLabel('Save') + ->modalHeading('Update Source Control') + ->modalWidth(MaxWidth::Medium) + ->form([ + Select::make('source_control') + ->label('Source Control') + ->rules(UpdateSourceControl::rules()['source_control']) + ->options( + SourceControl::getByProjectId(auth()->user()->current_project_id) + ->pluck('profile', 'id') + ) + ->default($this->site->source_control_id) + ->suffixAction( + \Filament\Forms\Components\Actions\Action::make('connect') + ->form(Create::form()) + ->modalHeading('Connect to a source control') + ->modalSubmitActionLabel('Connect') + ->icon('heroicon-o-wifi') + ->tooltip('Connect to a source control') + ->modalWidth(MaxWidth::Large) + ->authorize(fn () => auth()->user()->can('create', SourceControl::class)) + ->action(fn (array $data) => Create::action($data)) + ) + ->placeholder('Select source control'), + ]) + ->action(function (array $data) { + run_action($this, function () use ($data) { + app(UpdateSourceControl::class)->update($this->site, $data); + + Notification::make() + ->success() + ->title('Source control updated!') + ->send(); + }); + }) + ), + ]), + ]) + ->record($this->site); + } +} diff --git a/app/Web/Pages/Servers/Sites/Widgets/SitesList.php b/app/Web/Pages/Servers/Sites/Widgets/SitesList.php index cb24d7c..68d74d1 100644 --- a/app/Web/Pages/Servers/Sites/Widgets/SitesList.php +++ b/app/Web/Pages/Servers/Sites/Widgets/SitesList.php @@ -4,6 +4,7 @@ use App\Models\Server; use App\Models\Site; +use App\Web\Pages\Servers\Sites\Settings; use App\Web\Pages\Servers\Sites\View; use Filament\Tables\Actions\Action; use Filament\Tables\Columns\TextColumn; @@ -25,10 +26,15 @@ protected function getTableQuery(): Builder protected function getTableColumns(): array { return [ - TextColumn::make('id') + TextColumn::make('domain') ->searchable() ->sortable(), - TextColumn::make('domain') + TextColumn::make('tags') + ->label('Tags') + ->badge() + ->icon('heroicon-o-tag') + ->formatStateUsing(fn ($state) => $state->name) + ->color(fn ($state) => $state->color) ->searchable() ->sortable(), TextColumn::make('created_at') @@ -52,7 +58,7 @@ public function getTable(): Table ->label('Settings') ->icon('heroicon-o-cog-6-tooth') ->authorize(fn (Site $record) => auth()->user()->can('update', [$record, $this->server])) - ->url(fn (Site $record) => '/'), + ->url(fn (Site $record) => Settings::getUrl(parameters: ['server' => $this->server, 'site' => $record])), ]); } } diff --git a/app/Web/Pages/Servers/Widgets/ServerDetails.php b/app/Web/Pages/Servers/Widgets/ServerDetails.php index f4c1a69..25ade22 100644 --- a/app/Web/Pages/Servers/Widgets/ServerDetails.php +++ b/app/Web/Pages/Servers/Widgets/ServerDetails.php @@ -4,6 +4,7 @@ use App\Actions\Server\Update; use App\Models\Server; +use App\Web\Pages\Settings\Tags\Actions\EditTags; use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Contracts\HasForms; use Filament\Infolists\Components\Actions\Action; @@ -88,16 +89,15 @@ public function infolist(Infolist $infolist): Infolist TextEntry::make('provider') ->label('Provider') ->inlineLabel(), - TextEntry::make('tags') - ->label('Tags') + TextEntry::make('tags.*') + ->default('No tags') + ->formatStateUsing(fn ($state) => is_object($state) ? $state->name : $state) ->inlineLabel() - ->state(fn (Server $record) => view('web.components.tags', ['tags' => $record->tags])) + ->badge() + ->color(fn ($state) => is_object($state) ? $state->color : 'gray') + ->icon(fn ($state) => is_object($state) ? 'heroicon-o-tag' : '') ->suffixAction( - Action::make('edit-tags') - ->icon('heroicon-o-pencil') - ->tooltip('Edit Tags') - ->action(fn (Server $record) => $this->dispatch('$editTags', $record)) - ->tooltip('Edit Tags') + EditTags::infolist($this->server) ), ]), diff --git a/app/Web/Pages/Servers/Widgets/ServersList.php b/app/Web/Pages/Servers/Widgets/ServersList.php index 0dfc04a..e839f06 100644 --- a/app/Web/Pages/Servers/Widgets/ServersList.php +++ b/app/Web/Pages/Servers/Widgets/ServersList.php @@ -7,7 +7,6 @@ use App\Web\Pages\Servers\View; use Filament\Tables\Actions\Action; use Filament\Tables\Columns\TextColumn; -use Filament\Tables\Columns\ViewColumn; use Filament\Tables\Table; use Filament\Widgets\TableWidget as Widget; use Illuminate\Database\Eloquent\Builder; @@ -24,16 +23,15 @@ protected function getTableQuery(): Builder protected function getTableColumns(): array { return [ - TextColumn::make('id') - ->searchable() - ->sortable(), TextColumn::make('name') ->searchable() ->sortable(), - ViewColumn::make('tags.name') + TextColumn::make('tags') ->label('Tags') - ->view('web.components.tags') - ->extraCellAttributes(['class' => 'px-3']) + ->badge() + ->icon('heroicon-o-tag') + ->formatStateUsing(fn ($state) => $state->name) + ->color(fn ($state) => $state->color) ->searchable() ->sortable(), TextColumn::make('status') diff --git a/app/Web/Pages/Settings/SourceControls/Actions/Edit.php b/app/Web/Pages/Settings/SourceControls/Actions/Edit.php index 7591b29..5d9b06e 100644 --- a/app/Web/Pages/Settings/SourceControls/Actions/Edit.php +++ b/app/Web/Pages/Settings/SourceControls/Actions/Edit.php @@ -2,25 +2,53 @@ namespace App\Web\Pages\Settings\SourceControls\Actions; +use App\Actions\SourceControl\ConnectSourceControl; use App\Actions\SourceControl\EditSourceControl; -use App\Models\SourceControl; +use App\Enums\SourceControl; use Exception; +use Filament\Forms\Components\Checkbox; +use Filament\Forms\Components\TextInput; +use Filament\Forms\Get; use Filament\Notifications\Notification; class Edit { public static function form(): array { - return Create::form(); + return [ + TextInput::make('name') + ->rules(fn (Get $get) => ConnectSourceControl::rules($get())['name']), + TextInput::make('token') + ->label('API Key') + ->validationAttribute('API Key') + ->visible(fn ($get) => in_array($get('provider'), [ + SourceControl::GITHUB, + SourceControl::GITLAB, + ])) + ->rules(fn (Get $get) => ConnectSourceControl::rules($get())['token']), + TextInput::make('url') + ->label('URL (optional)') + ->visible(fn ($get) => $get('provider') == SourceControl::GITLAB) + ->rules(fn (Get $get) => ConnectSourceControl::rules($get())['url']) + ->helperText('If you run a self-managed gitlab enter the url here, leave empty to use gitlab.com'), + TextInput::make('username') + ->visible(fn ($get) => $get('provider') == SourceControl::BITBUCKET) + ->rules(fn (Get $get) => ConnectSourceControl::rules($get())['username']), + TextInput::make('password') + ->visible(fn ($get) => $get('provider') == SourceControl::BITBUCKET) + ->rules(fn (Get $get) => ConnectSourceControl::rules($get())['password']), + Checkbox::make('global') + ->label('Is Global (Accessible in all projects)'), + ]; } /** * @throws Exception */ - public static function action(SourceControl $provider, array $data): void + public static function action(\App\Models\SourceControl $sourceControl, array $data): void { try { - app(EditSourceControl::class)->edit($provider, auth()->user(), $data); + app(EditSourceControl::class)->edit($sourceControl, auth()->user(), $data); } catch (Exception $e) { Notification::make() ->title($e->getMessage()) diff --git a/app/Web/Pages/Settings/SourceControls/Widgets/SourceControlsList.php b/app/Web/Pages/Settings/SourceControls/Widgets/SourceControlsList.php index ac5afb4..3c6e3ec 100644 --- a/app/Web/Pages/Settings/SourceControls/Widgets/SourceControlsList.php +++ b/app/Web/Pages/Settings/SourceControls/Widgets/SourceControlsList.php @@ -57,8 +57,9 @@ public function getTable(): Table EditAction::make('edit') ->label('Edit') ->modalHeading('Edit Source Control') - ->mutateRecordDataUsing(function (array $data, SourceControl $record) { + ->fillForm(function (array $data, SourceControl $record) { return [ + 'provider' => $record->provider, 'name' => $record->profile, 'token' => $record->provider_data['token'] ?? null, 'username' => $record->provider_data['username'] ?? null, diff --git a/app/Web/Pages/Settings/Tags/Actions/Create.php b/app/Web/Pages/Settings/Tags/Actions/Create.php new file mode 100644 index 0000000..d19af65 --- /dev/null +++ b/app/Web/Pages/Settings/Tags/Actions/Create.php @@ -0,0 +1,49 @@ +rules(fn ($get) => CreateTag::rules()['name']), + Select::make('color') + ->prefixIcon('heroicon-s-tag') + ->prefixIconColor(fn (Get $get) => $get('color')) + ->searchable() + ->options( + collect(config('core.tag_colors')) + ->mapWithKeys(fn ($color) => [$color => $color]) + ) + ->reactive() + ->rules(fn ($get) => CreateTag::rules()['color']), + ]; + } + + /** + * @throws Exception + */ + public static function action(array $data): Tag + { + try { + return app(CreateTag::class)->create(auth()->user(), $data); + } catch (Exception $e) { + Notification::make() + ->title($e->getMessage()) + ->danger() + ->send(); + + throw $e; + } + } +} diff --git a/app/Web/Pages/Settings/Tags/Actions/Edit.php b/app/Web/Pages/Settings/Tags/Actions/Edit.php new file mode 100644 index 0000000..20c35ea --- /dev/null +++ b/app/Web/Pages/Settings/Tags/Actions/Edit.php @@ -0,0 +1,31 @@ +rules(EditTag::rules()['name']), + Select::make('color') + ->searchable() + ->options( + collect(config('core.tag_colors')) + ->mapWithKeys(fn ($color) => [$color => $color]) + ) + ->rules(fn ($get) => EditTag::rules()['color']), + ]; + } + + public static function action(Tag $tag, array $data): void + { + app(EditTag::class)->edit($tag, $data); + } +} diff --git a/app/Web/Pages/Settings/Tags/Actions/EditTags.php b/app/Web/Pages/Settings/Tags/Actions/EditTags.php new file mode 100644 index 0000000..b79600e --- /dev/null +++ b/app/Web/Pages/Settings/Tags/Actions/EditTags.php @@ -0,0 +1,61 @@ +icon('heroicon-o-pencil-square') + ->tooltip('Edit Tags') + ->hiddenLabel() + ->modalSubmitActionLabel('Save') + ->modalHeading('Edit Tags') + ->modalWidth(MaxWidth::Medium) + ->form([ + Select::make('tags') + ->default($taggable->tags()->pluck('tags.id')->toArray()) + ->options(function () { + return auth()->user()->currentProject->tags()->pluck('name', 'id')->toArray(); + }) + ->nestedRecursiveRules(SyncTags::rules(auth()->user()->currentProject->id)['tags.*']) + ->suffixAction( + \Filament\Forms\Components\Actions\Action::make('create_tag') + ->icon('heroicon-o-plus') + ->tooltip('Create a new tag') + ->modalSubmitActionLabel('Create') + ->modalHeading('Create Tag') + ->modalWidth(MaxWidth::Medium) + ->form(Create::form()) + ->action(function (array $data) { + Create::action($data); + }), + ) + ->multiple(), + ]) + ->action(function (array $data) use ($taggable) { + app(SyncTags::class)->sync(auth()->user(), [ + 'taggable_id' => $taggable->id, + 'taggable_type' => get_class($taggable), + 'tags' => $data['tags'], + ]); + + Notification::make() + ->success() + ->title('Tags updated!') + ->send(); + }); + } +} diff --git a/app/Web/Pages/Settings/Tags/Index.php b/app/Web/Pages/Settings/Tags/Index.php new file mode 100644 index 0000000..431201e --- /dev/null +++ b/app/Web/Pages/Settings/Tags/Index.php @@ -0,0 +1,59 @@ +user()?->can('viewAny', Tag::class) ?? false; + } + + public function getWidgets(): array + { + return [ + [Widgets\TagsList::class], + ]; + } + + protected function getHeaderActions(): array + { + return [ + CreateAction::make() + ->label('Create') + ->icon('heroicon-o-plus') + ->modalHeading('Create a Tag') + ->modalSubmitActionLabel('Save') + ->createAnother(false) + ->form(Actions\Create::form()) + ->authorize('create', Tag::class) + ->modalWidth(MaxWidth::ExtraLarge) + ->using(function (array $data) { + Actions\Create::action($data); + + $this->dispatch('$refresh'); + + Notification::make() + ->success() + ->title('Tag created!') + ->send(); + }), + ]; + } +} diff --git a/app/Web/Pages/Settings/Tags/Widgets/TagsList.php b/app/Web/Pages/Settings/Tags/Widgets/TagsList.php new file mode 100644 index 0000000..65c1f64 --- /dev/null +++ b/app/Web/Pages/Settings/Tags/Widgets/TagsList.php @@ -0,0 +1,78 @@ +user()->current_project_id); + } + + protected function getTableColumns(): array + { + return [ + ColorColumn::make('color'), + TextColumn::make('name') + ->searchable() + ->sortable(), + TextColumn::make('created_at') + ->label('Created At') + ->formatStateUsing(fn (Tag $record) => $record->created_at_by_timezone) + ->searchable() + ->sortable(), + ]; + } + + public function table(Table $table): Table + { + return $table + ->heading('') + ->query($this->getTableQuery()) + ->columns($this->getTableColumns()) + ->actions([ + $this->editAction(), + $this->deleteAction(), + ]); + } + + private function editAction(): Action + { + return EditAction::make('edit') + ->fillForm(function (Tag $record) { + return [ + 'name' => $record->name, + 'color' => $record->color, + 'global' => $record->project_id === null, + ]; + }) + ->form(Edit::form()) + ->authorize(fn (Tag $record) => auth()->user()->can('update', $record)) + ->using(fn (array $data, Tag $record) => Edit::action($record, $data)) + ->modalWidth(MaxWidth::Medium); + } + + private function deleteAction(): Action + { + return DeleteAction::make('delete') + ->authorize(fn (Tag $record) => auth()->user()->can('delete', $record)) + ->using(function (Tag $record) { + app(DeleteTag::class)->delete($record); + }); + } +} diff --git a/composer.json b/composer.json index 1f1faac..84d580a 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "php": "^8.2", "ext-ftp": "*", "aws/aws-sdk-php": "^3.158", + "codeat3/blade-simple-icons": "^5.10", "filament/filament": "^3.2", "laravel/fortify": "^1.17", "laravel/framework": "^11.0", diff --git a/composer.lock b/composer.lock index 6705c29..28a05a1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "616d8813a66d4a67234d11ebe6f90d67", + "content-hash": "12a4dd2d7ef0bd63bdd94c2ab1ba72e1", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -557,6 +557,77 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "codeat3/blade-simple-icons", + "version": "5.10.0", + "source": { + "type": "git", + "url": "https://github.com/codeat3/blade-simple-icons.git", + "reference": "2e6c78bca0200b008e23c60cd481ab750267ed9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codeat3/blade-simple-icons/zipball/2e6c78bca0200b008e23c60cd481ab750267ed9a", + "reference": "2e6c78bca0200b008e23c60cd481ab750267ed9a", + "shasum": "" + }, + "require": { + "blade-ui-kit/blade-icons": "^1.1", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "codeat3/blade-icon-generation-helpers": "^0.8", + "codeat3/phpcs-styles": "^1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.0|^10.5|^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Codeat3\\BladeSimpleIcons\\BladeSimpleIconsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Codeat3\\BladeSimpleIcons\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Swapnil Sarwe", + "homepage": "https://swapnilsarwe.com" + }, + { + "name": "Dries Vints", + "homepage": "https://driesvints.com" + } + ], + "description": "A package to easily make use of \"Simple Icons\" in your Laravel Blade views. ", + "homepage": "https://github.com/codeat3/blade-simple-icons", + "keywords": [ + "blade", + "laravel", + "simpleicons" + ], + "support": { + "issues": "https://github.com/codeat3/blade-simple-icons/issues", + "source": "https://github.com/codeat3/blade-simple-icons/tree/5.10.0" + }, + "funding": [ + { + "url": "https://github.com/swapnilsarwe", + "type": "github" + } + ], + "time": "2024-09-21T14:06:07+00:00" + }, { "name": "danharrin/date-format-converter", "version": "v0.3.1", diff --git a/database/migrations/2024_10_05_094650_add_soft_deletes_to_source_controls.php b/database/migrations/2024_10_05_094650_add_soft_deletes_to_source_controls.php new file mode 100644 index 0000000..7d01aaf --- /dev/null +++ b/database/migrations/2024_10_05_094650_add_soft_deletes_to_source_controls.php @@ -0,0 +1,22 @@ +softDeletes(); + }); + } + + public function down(): void + { + Schema::table('source_controls', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + } +}; diff --git a/resources/css/filament/app/tailwind.config.js b/resources/css/filament/app/tailwind.config.js index 7d6b83a..5f3707e 100644 --- a/resources/css/filament/app/tailwind.config.js +++ b/resources/css/filament/app/tailwind.config.js @@ -2,6 +2,24 @@ import preset from "../../../../vendor/filament/filament/tailwind.config.preset" export default { presets: [preset], + safelist: [ + // Safelist all colors for text, background, border, etc. + { + pattern: + /text-(red|green|blue|yellow|indigo|purple|pink|gray|white|black|orange|lime|emerald|teal|cyan|sky|violet|rose|fuchsia|amber|slate|zinc|neutral|stone)-(50|100|200|300|400|500|600|700|800|900)/, + variants: ["dark"], // Ensure dark mode variants are also included + }, + { + pattern: + /bg-(red|green|blue|yellow|indigo|purple|pink|gray|white|black|orange|lime|emerald|teal|cyan|sky|violet|rose|fuchsia|amber|slate|zinc|neutral|stone)-(50|100|200|300|400|500|600|700|800|900)/, + variants: ["dark"], + }, + { + pattern: + /border-(red|green|blue|yellow|indigo|purple|pink|gray|white|black|orange|lime|emerald|teal|cyan|sky|violet|rose|fuchsia|amber|slate|zinc|neutral|stone)-(50|100|200|300|400|500|600|700|800|900)/, + variants: ["dark"], + }, + ], content: [ "./app/Web/**/*.php", "./resources/views/web/**/*.blade.php", diff --git a/resources/views/site-settings/partials/update-source-control.blade.php b/resources/views/site-settings/partials/update-source-control.blade.php index 9bba670..6b4f3fd 100644 --- a/resources/views/site-settings/partials/update-source-control.blade.php +++ b/resources/views/site-settings/partials/update-source-control.blade.php @@ -18,7 +18,7 @@ class="space-y-6" "sites.partials.create.fields.source-control", [ "sourceControls" => \App\Models\SourceControl::query() - ->where("provider", $site->sourceControl()?->provider) + ->where("provider", $site->sourceControl?->provider) ->get(), ] )