mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-03 07:12:33 +00:00
feat(project): add icons and improve persistent volume management
Add project icon storage and serving with local/S3 support, prevent deletion of compose-managed volumes, and optimize volume backups. Refine project and backup UI components with consistent styling and coverage.
This commit is contained in:
1 parent
a02d02d19b
commit
fd5eb3e0cd
43 files changed
+728
-60
No files matched your search
@@ -3,13 +3,16 @@
|
||||
namespace App\Livewire\Project;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Services\ProjectIconStorageService;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class Edit extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
use WithFileUploads;
|
||||
|
||||
public Project $project;
|
||||
|
||||
@@ -17,6 +20,40 @@ class Edit extends Component
|
||||
|
||||
public ?string $description = null;
|
||||
|
||||
public $icon;
|
||||
|
||||
public function uploadIcon(ProjectIconStorageService $iconStorage): bool
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->project);
|
||||
$this->validate([
|
||||
'icon' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:5120', 'dimensions:max_width=6000,max_height=6000'],
|
||||
]);
|
||||
$iconStorage->storeProject($this->project, $this->icon);
|
||||
$this->reset('icon');
|
||||
$this->project->refresh();
|
||||
$this->dispatch('success', 'Project icon updated.');
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function removeIcon(ProjectIconStorageService $iconStorage): void
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->project);
|
||||
$iconStorage->deleteProject($this->project);
|
||||
$this->project->refresh();
|
||||
$this->dispatch('success', 'Project icon removed.');
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -53,6 +53,10 @@ class Index extends Component
|
||||
'uuid' => $project->uuid,
|
||||
'name' => $project->name,
|
||||
'description' => $project->description,
|
||||
'iconUrl' => $project->icon_path ? route('project.icon', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'v' => $project->updated_at->timestamp,
|
||||
]) : null,
|
||||
'href' => $project->navigateTo(),
|
||||
'environmentCount' => $project->environments->count(),
|
||||
'resourceCount' => $resourceCount,
|
||||
|
||||
@@ -21,7 +21,7 @@ class All extends Component
|
||||
/**
|
||||
* Editable form state keyed by storage id.
|
||||
*
|
||||
* @var array<int|string, array{name: string, mountPath: string, hostPath: ?string, isPreviewSuffixEnabled: bool, isReadOnly: bool}>
|
||||
* @var array<int|string, array{name: string, mountPath: string, hostPath: ?string, isPreviewSuffixEnabled: bool, isReadOnly: bool, canDeleteStale: bool}>
|
||||
*/
|
||||
public array $forms = [];
|
||||
|
||||
@@ -42,13 +42,16 @@ class All extends Component
|
||||
|
||||
public bool $canUpdate = false;
|
||||
|
||||
public bool $deleteDockerVolume = false;
|
||||
|
||||
protected $listeners = ['refreshStorages' => 'refreshList', 'refreshVolumeBackups' => 'refreshList'];
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->canUpdate = (bool) auth()->user()?->can('update', $this->resource);
|
||||
$this->supportsPreviewSuffix = $this->resource instanceof Application
|
||||
&& $this->resource->git_based();
|
||||
&& $this->resource->git_based()
|
||||
&& filled($this->resource->git_repository);
|
||||
$this->showActionsColumn = $this->canUpdate;
|
||||
$this->showBackupAction = $this->resource instanceof Application
|
||||
|| $this->resource instanceof ServiceApplication
|
||||
@@ -129,12 +132,35 @@ class All extends Component
|
||||
|
||||
$storage = $this->findStorageOrFail($storageId);
|
||||
|
||||
if ($this->isComposeOrService && $storage->isDeclaredInCompose()) {
|
||||
$this->dispatch('error', 'This volume is managed by the current Docker Compose file.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($storage->scheduledBackups()->exists()) {
|
||||
$this->dispatch('error', 'Delete this volume backup schedule and its archives before deleting the volume.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->deleteDockerVolume = in_array('deleteDockerVolume', $selectedActions, true);
|
||||
if ($this->deleteDockerVolume) {
|
||||
$server = $this->resource instanceof Application
|
||||
? $this->resource->destination->server
|
||||
: $this->resource->service->server;
|
||||
|
||||
try {
|
||||
instant_remote_process([
|
||||
'docker volume rm -f '.escapeshellarg($storage->name),
|
||||
], $server);
|
||||
} catch (\Throwable $exception) {
|
||||
$this->dispatch('error', 'Failed to delete the Docker volume: '.$exception->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$storage->delete();
|
||||
$this->refreshList();
|
||||
$this->dispatch('refreshStorages');
|
||||
@@ -169,6 +195,9 @@ class All extends Component
|
||||
'hostPath' => $storage->host_path,
|
||||
'isPreviewSuffixEnabled' => (bool) ($storage->is_preview_suffix_enabled ?? true),
|
||||
'isReadOnly' => $storage->shouldBeReadOnlyInUI() || ! $this->canUpdate,
|
||||
'canDeleteStale' => $this->canUpdate
|
||||
&& ($storage->isServiceResource() || $storage->isDockerComposeResource())
|
||||
&& ! $storage->isDeclaredInCompose(),
|
||||
];
|
||||
}
|
||||
$this->forms = $forms;
|
||||
|
||||
@@ -105,6 +105,7 @@ class Show extends Component
|
||||
// PR deployment volume suffixes only apply to git-based applications.
|
||||
$this->supportsPreviewSuffix = $this->resource instanceof Application
|
||||
&& $this->resource->git_based()
|
||||
&& filled($this->resource->git_repository)
|
||||
&& ! $this->isService;
|
||||
// Parent All batches badge/url; isolated embeds still hydrate themselves.
|
||||
if (! $this->backupMetaHydrated) {
|
||||
|
||||
Reference in new issue
Block a user