mirror of
https://github.com/vitodeploy/vito.git
synced 2025-04-20 18:31:36 +00:00
- 2.x - sites settings
- tags - source-control soft deletes
This commit is contained in:
parent
d1f2add699
commit
3c50e2c947
@ -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([
|
||||
|
@ -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'];
|
||||
|
@ -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();
|
||||
];
|
||||
}
|
||||
}
|
||||
|
26
app/Actions/Site/UpdatePHPVersion.php
Normal file
26
app/Actions/Site/UpdatePHPVersion.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Site;
|
||||
|
||||
use App\Models\Site;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdatePHPVersion
|
||||
{
|
||||
public static function rules(Site $site): array
|
||||
{
|
||||
return [
|
||||
'version' => [
|
||||
'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']);
|
||||
}
|
||||
}
|
@ -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();
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,9 @@
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
class AttachTag
|
||||
{
|
||||
public function attach(User $user, array $input): Tag
|
||||
|
@ -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();
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,9 @@
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
class DetachTag
|
||||
{
|
||||
public function detach(Tag $tag, array $input): void
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
40
app/Actions/Tag/SyncTags.php
Normal file
40
app/Actions/Tag/SyncTags.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Tag;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SyncTags
|
||||
{
|
||||
public function sync(User $user, array $input): void
|
||||
{
|
||||
/** @var Server|Site $taggable */
|
||||
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);
|
||||
|
||||
$tags = Tag::query()->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')),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
return $this->belongsTo(SourceControl::class)->withTrashed();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
37
app/Policies/TagPolicy.php
Normal file
37
app/Policies/TagPolicy.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Tag;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class TagPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->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();
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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')) {
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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),
|
||||
|
77
app/Web/Pages/Servers/Sites/Settings.php
Normal file
77
app/Web/Pages/Servers/Sites/Settings.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Sites;
|
||||
|
||||
use App\SSH\Services\Webserver\Webserver;
|
||||
use App\Web\Fields\CodeEditorField;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class Settings extends Page
|
||||
{
|
||||
protected static ?string $slug = 'servers/{server}/sites/{site}/settings';
|
||||
|
||||
protected static ?string $title = 'Settings';
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
84
app/Web/Pages/Servers/Sites/Widgets/DeploymentsList.php
Normal file
84
app/Web/Pages/Servers/Sites/Widgets/DeploymentsList.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Sites\Widgets;
|
||||
|
||||
use App\Models\Deployment;
|
||||
use App\Models\Site;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\View\ComponentAttributeBag;
|
||||
|
||||
class DeploymentsList extends Widget
|
||||
{
|
||||
public Site $site;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return Deployment::query()->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()),
|
||||
]);
|
||||
}
|
||||
}
|
182
app/Web/Pages/Servers/Sites/Widgets/SiteDetails.php
Normal file
182
app/Web/Pages/Servers/Sites/Widgets/SiteDetails.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Servers\Sites\Widgets;
|
||||
|
||||
use App\Actions\Site\UpdateAliases;
|
||||
use App\Actions\Site\UpdatePHPVersion;
|
||||
use App\Actions\Site\UpdateSourceControl;
|
||||
use App\Models\Site;
|
||||
use App\Models\SourceControl;
|
||||
use App\Web\Pages\Settings\SourceControls\Actions\Create;
|
||||
use App\Web\Pages\Settings\Tags\Actions\EditTags;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Infolists\Components\Actions\Action;
|
||||
use Filament\Infolists\Components\Section;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Infolists\Concerns\InteractsWithInfolists;
|
||||
use Filament\Infolists\Contracts\HasInfolists;
|
||||
use Filament\Infolists\Infolist;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Widgets\Widget;
|
||||
|
||||
class SiteDetails extends Widget implements HasForms, HasInfolists
|
||||
{
|
||||
use InteractsWithForms;
|
||||
use InteractsWithInfolists;
|
||||
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected static bool $isLazy = false;
|
||||
|
||||
protected static string $view = 'web.components.infolist';
|
||||
|
||||
public Site $site;
|
||||
|
||||
public function infolist(Infolist $infolist): Infolist
|
||||
{
|
||||
return $infolist
|
||||
->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);
|
||||
}
|
||||
}
|
@ -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])),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
),
|
||||
|
||||
]),
|
||||
|
@ -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')
|
||||
|
@ -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())
|
||||
|
@ -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,
|
||||
|
49
app/Web/Pages/Settings/Tags/Actions/Create.php
Normal file
49
app/Web/Pages/Settings/Tags/Actions/Create.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Settings\Tags\Actions;
|
||||
|
||||
use App\Actions\Tag\CreateTag;
|
||||
use App\Models\Tag;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class Create
|
||||
{
|
||||
public static function form(): array
|
||||
{
|
||||
return [
|
||||
TextInput::make('name')
|
||||
->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;
|
||||
}
|
||||
}
|
||||
}
|
31
app/Web/Pages/Settings/Tags/Actions/Edit.php
Normal file
31
app/Web/Pages/Settings/Tags/Actions/Edit.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Settings\Tags\Actions;
|
||||
|
||||
use App\Actions\Tag\EditTag;
|
||||
use App\Models\Tag;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
|
||||
class Edit
|
||||
{
|
||||
public static function form(): array
|
||||
{
|
||||
return [
|
||||
TextInput::make('name')
|
||||
->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);
|
||||
}
|
||||
}
|
61
app/Web/Pages/Settings/Tags/Actions/EditTags.php
Normal file
61
app/Web/Pages/Settings/Tags/Actions/EditTags.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Settings\Tags\Actions;
|
||||
|
||||
use App\Actions\Tag\SyncTags;
|
||||
use App\Models\Server;
|
||||
use App\Models\Site;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Infolists\Components\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class EditTags
|
||||
{
|
||||
/**
|
||||
* @param Site|Server $taggable
|
||||
*/
|
||||
public static function infolist(mixed $taggable): Action
|
||||
{
|
||||
return Action::make('edit_tags')
|
||||
->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();
|
||||
});
|
||||
}
|
||||
}
|
59
app/Web/Pages/Settings/Tags/Index.php
Normal file
59
app/Web/Pages/Settings/Tags/Index.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Settings\Tags;
|
||||
|
||||
use App\Models\Tag;
|
||||
use App\Web\Components\Page;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
|
||||
class Index extends Page
|
||||
{
|
||||
protected static ?string $navigationGroup = 'Settings';
|
||||
|
||||
protected static ?string $slug = 'settings/tags';
|
||||
|
||||
protected static ?string $title = 'Tags';
|
||||
|
||||
protected static ?string $navigationIcon = 'heroicon-o-tag';
|
||||
|
||||
protected static ?int $navigationSort = 7;
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->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();
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
78
app/Web/Pages/Settings/Tags/Widgets/TagsList.php
Normal file
78
app/Web/Pages/Settings/Tags/Widgets/TagsList.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Web\Pages\Settings\Tags\Widgets;
|
||||
|
||||
use App\Actions\Tag\DeleteTag;
|
||||
use App\Models\Tag;
|
||||
use App\Web\Pages\Settings\Tags\Actions\Edit;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\ColorColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as Widget;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class TagsList extends Widget
|
||||
{
|
||||
protected $listeners = ['$refresh'];
|
||||
|
||||
protected function getTableQuery(): Builder
|
||||
{
|
||||
return Tag::getByProjectId(auth()->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);
|
||||
});
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
73
composer.lock
generated
73
composer.lock
generated
@ -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",
|
||||
|
@ -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('source_controls', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('source_controls', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
};
|
@ -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",
|
||||
|
@ -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(),
|
||||
]
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user