feat(ui): polish domains, storage, env vars and resource nav

Improve project resource UIs: sort domains by DNS failure, stop re-adding www pairs on refresh, lazy-load storage tabs with counts, tighten env-var tables, keep application tabs active across Livewire polls, unify database type labels, and update related CSS/JS and tests.
This commit is contained in:
Andras Bacsai committed 2026-08-05 13:50:11 +02:00
1 parent dbc6f5e08c
commit a7a06aa6c8
75 files changed
+3909 -1173

No files matched your search

+111 -18
View File
@@ -37,6 +37,14 @@ class Storage extends Component
public string $file_storage_directory_destination = '';
public string $activeTab = 'volumes';
public int $cachedVolumeCount = 0;
public int $cachedFileCount = 0;
public int $cachedDirectoryCount = 0;
public function getListeners()
{
$teamId = auth()->user()->currentTeam()->id;
@@ -57,12 +65,18 @@ class Storage extends Component
}
if ($this->resource->getMorphClass() === Application::class) {
if ($this->resource->destination->server->isSwarm()) {
$this->resource->loadMissing('destination.server', 'environment.project');
if ($this->resource->destination?->server?->isSwarm()) {
$this->isSwarm = true;
}
}
$this->refreshStorages();
// Counts only on mount — child All (volumes) / file list load their own payloads.
$this->loadVolumeCount();
$this->loadFileStorageMetaCounts();
$this->activeTab = $this->resolveDefaultTab();
$this->fileStorage = collect();
$this->loadFileStorageForActiveTab();
}
public function refreshStoragesFromEvent()
@@ -73,37 +87,110 @@ class Storage extends Component
public function refreshStorages()
{
$this->fileStorage = $this->resource->fileStorages()->get()->each(function (LocalFileVolume $fs) {
// Avoid loading full volume models onto this parent (child All owns that snapshot).
$this->resource->unsetRelation('persistentStorages');
$this->loadVolumeCount();
$this->loadFileStorageMetaCounts();
$this->loadFileStorageForActiveTab();
}
public function setActiveTab(string $tab): void
{
if (! in_array($tab, ['volumes', 'files', 'directories'], true)) {
return;
}
$this->activeTab = $tab;
$this->loadFileStorageForActiveTab();
}
private function resolveDefaultTab(): string
{
if ($this->volumeCount > 0) {
return 'volumes';
}
if ($this->fileCount > 0) {
return 'files';
}
if ($this->directoryCount > 0) {
return 'directories';
}
return 'volumes';
}
private function loadVolumeCount(): void
{
$this->cachedVolumeCount = $this->resource->persistentStorages()->count();
}
/**
* Counts only — avoids loading file contents into the Livewire snapshot on the volumes tab.
*/
private function loadFileStorageMetaCounts(): void
{
$this->cachedFileCount = $this->resource->fileStorages()->where('is_directory', false)->count();
$this->cachedDirectoryCount = $this->resource->fileStorages()->where('is_directory', true)->count();
}
/**
* Load full file/directory mounts only for the active tab (content only on files).
*/
private function loadFileStorageForActiveTab(): void
{
if ($this->activeTab === 'volumes') {
// Keep snapshot small while the volumes tab is shown.
$this->fileStorage = collect();
return;
}
$query = $this->resource->fileStorages();
if ($this->activeTab === 'files') {
$query->where('is_directory', false);
} else {
$query->where('is_directory', true);
}
$this->fileStorage = $query->get()->each(function (LocalFileVolume $fs): void {
if ($this->activeTab !== 'files') {
$fs->content = null;
return;
}
if (strlen((string) $fs->content) > LocalFileVolume::MAX_CONTENT_SIZE) {
$fs->content = LocalFileVolume::TOO_LARGE_PLACEHOLDER;
}
});
$this->resource->load('persistentStorages.resource');
}
public function getFilesProperty()
{
return $this->fileStorage->where('is_directory', false);
return collect($this->fileStorage)->where('is_directory', false);
}
public function getDirectoriesProperty()
{
return $this->fileStorage->where('is_directory', true);
return collect($this->fileStorage)->where('is_directory', true);
}
public function getVolumeCountProperty()
{
return $this->resource->persistentStorages()->count();
return $this->cachedVolumeCount;
}
public function getFileCountProperty()
{
return $this->files->count();
return $this->cachedFileCount;
}
public function getDirectoryCountProperty()
{
return $this->directories->count();
return $this->cachedDirectoryCount;
}
public function submitPersistentVolume()
@@ -130,12 +217,12 @@ class Storage extends Component
'resource_id' => $this->resource->id,
'resource_type' => $this->resource->getMorphClass(),
]);
$this->resource->refresh();
$this->clearForm();
$this->activeTab = 'volumes';
$this->refreshStorages();
$this->dispatch('configurationChanged');
$this->dispatch('success', 'Volume added successfully');
$this->dispatch('closeStorageModal', 'volume');
$this->clearForm();
$this->refreshStorages();
$this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -165,11 +252,13 @@ class Storage extends Component
'resource_type' => get_class($this->resource),
]);
$this->clearForm();
$this->activeTab = 'files';
$this->refreshStorages();
$this->dispatch('configurationChanged');
$this->dispatch('success', 'File mount added successfully');
$this->dispatch('closeStorageModal', 'file');
$this->clearForm();
$this->refreshStorages();
$this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -198,11 +287,13 @@ class Storage extends Component
'resource_type' => get_class($this->resource),
]);
$this->clearForm();
$this->activeTab = 'files';
$this->refreshStorages();
$this->dispatch('configurationChanged');
$this->dispatch('success', 'Host file mount added successfully');
$this->dispatch('closeStorageModal', 'host-file');
$this->clearForm();
$this->refreshStorages();
$this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -235,11 +326,13 @@ class Storage extends Component
'resource_type' => get_class($this->resource),
]);
$this->clearForm();
$this->activeTab = 'directories';
$this->refreshStorages();
$this->dispatch('configurationChanged');
$this->dispatch('success', 'Directory mount added successfully');
$this->dispatch('closeStorageModal', 'directory');
$this->clearForm();
$this->refreshStorages();
$this->dispatch('refreshStorages');
} catch (\Throwable $e) {
return handleError($e, $this);
}