mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 12:16:45 +00:00
feat(ui): add volume backup storage controls and grouped navigation
This commit is contained in:
1 parent
4ba6aa3500
commit
62b2fe2330
20 files changed
+373
-112
No files matched your search
@@ -10,7 +10,7 @@ test('highlighted buttons use the shared coollabs style in every color scheme',
|
||||
|
||||
expect($utilities)
|
||||
->toContain('@utility button-highlighted')
|
||||
->toContain('@apply border-2 text-coollabs-200 dark:text-white bg-coollabs-50 dark:bg-coollabs/20 border-coollabs dark:border-coollabs-100 hover:bg-coollabs hover:text-white dark:hover:bg-coollabs-100 dark:hover:text-white;')
|
||||
->toContain('@apply border-coollabs-200 bg-linear-to-b from-coollabs-100 to-coollabs-200 text-white! hover:from-coollabs-100 hover:to-coollabs hover:text-white!;')
|
||||
->and($appStyles)
|
||||
->toContain('button[isHighlighted]:not(:disabled)')
|
||||
->toContain('@apply button-highlighted;')
|
||||
|
||||
@@ -177,19 +177,20 @@ it('moves application terminal and logs from the top tabs into the settings side
|
||||
->not->toContain("'label' => 'Runtime'")
|
||||
->and($sidebar)
|
||||
->toContain("'label' => 'Terminal'")
|
||||
->toContain("'label' => 'Deployment'")
|
||||
->toContain("'label' => 'Runtime'")
|
||||
->toContain("'Logs' => ['Deployment', 'Runtime']")
|
||||
->toContain("'Operations' => ['Terminal', 'Rollback', 'Resource Limits'");
|
||||
->toContain("'label' => 'Deployment Logs'")
|
||||
->toContain("'label' => 'Runtime Logs'")
|
||||
->toContain("'Observe & troubleshoot' => ['Runtime Logs', 'Deployment Logs', 'Terminal', 'Metrics']")
|
||||
->toContain("'Operations' => ['Resource Operations', 'Resource Limits', 'Rollback'");
|
||||
});
|
||||
|
||||
it('groups application automation pages separately from build and deploy', function () {
|
||||
$sidebar = file_get_contents(resource_path('views/components/application/configuration-sidebar.blade.php'));
|
||||
|
||||
expect($sidebar)
|
||||
->toContain("'Build & deploy' => ['Git Source', 'Servers', 'Healthcheck']")
|
||||
->toContain("'Automation' => ['Scheduled Tasks', 'Webhooks', 'Preview Deployments']")
|
||||
->toContain("'Operations' => ['Terminal', 'Rollback', 'Resource Limits'");
|
||||
->toContain("'Settings' => ['General', 'Domains', 'Environment Variables', 'Persistent Storage', 'Advanced', 'Swarm', 'Healthcheck']")
|
||||
->toContain("'Deploy' => ['Git Source', 'Servers', 'Preview Deployments']")
|
||||
->toContain("'Automation' => ['Scheduled Tasks', 'Webhooks', 'Backups']")
|
||||
->toContain("'Operations' => ['Resource Operations', 'Resource Limits', 'Rollback'");
|
||||
});
|
||||
|
||||
it('centers the rollback image loading state across the card', function () {
|
||||
@@ -291,7 +292,7 @@ it('removes desktop top spacing from the deployment log viewer', function () {
|
||||
it('uses a distinct runtime log icon in the sidebar', function () {
|
||||
$sidebar = file_get_contents(resource_path('views/components/application/configuration-sidebar.blade.php'));
|
||||
|
||||
expect($sidebar)->toContain("'Runtime' => 'unordered-list'");
|
||||
expect($sidebar)->toContain("'Runtime Logs' => 'unordered-list'");
|
||||
});
|
||||
|
||||
it('shows deployment history above the selected deployment logs', function () {
|
||||
|
||||
@@ -8,17 +8,21 @@ test('view switchers use the shared coollabs selected state on every page', func
|
||||
resource_path('views/livewire/project/resource/index.blade.php'),
|
||||
resource_path('views/livewire/project/service/configuration.blade.php'),
|
||||
resource_path('views/livewire/tags/show.blade.php'),
|
||||
resource_path('views/livewire/shared/list-search-controls.blade.php'),
|
||||
resource_path('views/components/shared-variables/view-controls.blade.php'),
|
||||
])->map(fn (string $path): string => file_get_contents($path));
|
||||
$utilities = file_get_contents(resource_path('css/utilities.css'));
|
||||
|
||||
expect($views->every(fn (string $view): bool => str_contains($view, "viewMode === 'table'")
|
||||
expect($views->every(fn (string $view): bool => (str_contains($view, "viewMode === 'table'") || str_contains($view, "viewMode === 'list'"))
|
||||
&& str_contains($view, "viewMode === 'grid'")
|
||||
&& str_contains($view, 'control-selected')))
|
||||
->toBeTrue()
|
||||
->and($views->every(fn (string $view): bool => str_contains($view, 'flex h-9')
|
||||
&& str_contains($view, 'size-7.5')))->toBeTrue()
|
||||
->and($views->implode("\n"))->not->toContain('dark:bg-warning/15 dark:text-warning')
|
||||
->and($utilities)
|
||||
->toContain('@utility control-selected')
|
||||
->toContain('@apply bg-coollabs text-white dark:bg-coollabs dark:text-white;');
|
||||
->toContain('@apply bg-linear-to-b from-coollabs-100 to-coollabs-200 text-white;');
|
||||
});
|
||||
|
||||
test('server index does not expose server IP addresses', function () {
|
||||
|
||||
@@ -10,11 +10,11 @@ it('moves service and database page navigation into their sidebars', function ()
|
||||
->and($databaseHeading)->not->toContain('<x-resource-heading-tabs')
|
||||
->and($serviceConfiguration)
|
||||
->toContain("['label' => 'Backups'")
|
||||
->toContain("['label' => 'Runtime'")
|
||||
->toContain("['label' => 'Runtime Logs'")
|
||||
->toContain("['label' => 'Terminal'")
|
||||
->and($databaseSidebar)
|
||||
->toContain("['label' => 'Backups'")
|
||||
->toContain("['label' => 'Runtime'")
|
||||
->toContain("['label' => 'Runtime Logs'")
|
||||
->toContain("['label' => 'Terminal'");
|
||||
});
|
||||
|
||||
@@ -41,21 +41,38 @@ it('matches application action bar behavior for services and databases', functio
|
||||
->and($database)->toContain('id="database-desktop-actions"');
|
||||
});
|
||||
|
||||
it('keeps database and service sidebar sections in the application sequence', function () {
|
||||
it('groups database and service navigation by user workflow', function () {
|
||||
$database = file_get_contents(resource_path('views/components/database/configuration-sidebar.blade.php'));
|
||||
$service = file_get_contents(resource_path('views/livewire/project/service/configuration.blade.php'));
|
||||
$serviceSidebars = [
|
||||
file_get_contents(resource_path('views/components/service/configuration-sidebar.blade.php')),
|
||||
file_get_contents(resource_path('views/livewire/project/service/configuration.blade.php')),
|
||||
];
|
||||
|
||||
expect($database)
|
||||
->toContain("'Settings' => ['General', 'Environment Variables', 'Persistent Storage', 'Backups', 'Import Backup', 'Servers']")
|
||||
->toContain("'Automation' => ['Webhooks', 'Healthcheck']")
|
||||
->toContain("'Logs' => ['Runtime']")
|
||||
->toContain("'Operations' => ['Terminal', 'Resource Limits', 'Resource Operations', 'Metrics', 'Tags', 'Danger Zone']");
|
||||
->toContain("'Settings' => ['General', 'Environment Variables', 'Persistent Storage', 'Healthcheck']")
|
||||
->toContain("'Observe & troubleshoot' => ['Runtime Logs', 'Terminal', 'Metrics']")
|
||||
->toContain("'Deploy' => ['Servers']")
|
||||
->toContain("'Automation' => ['Webhooks', 'Backups', 'Import Backup']")
|
||||
->toContain("'Operations' => ['Resource Operations', 'Resource Limits', 'Tags', 'Danger Zone']");
|
||||
|
||||
expect($service)
|
||||
->toContain("'Settings' => ['General', 'Domains', 'Environment Variables', 'Persistent Storage', 'Backups']")
|
||||
->toContain("'Automation' => ['Scheduled Tasks', 'Webhooks']")
|
||||
->toContain("'Logs' => ['Runtime']")
|
||||
->toContain("'Operations' => ['Terminal', 'Resource Operations', 'Tags', 'Danger Zone']");
|
||||
foreach ($serviceSidebars as $serviceSidebar) {
|
||||
expect($serviceSidebar)
|
||||
->toContain("'Settings' => ['General', 'Domains', 'Environment Variables', 'Persistent Storage']")
|
||||
->toContain("'Observe & troubleshoot' => ['Runtime Logs', 'Terminal']")
|
||||
->toContain("'Automation' => ['Scheduled Tasks', 'Webhooks', 'Backups']")
|
||||
->toContain("'Operations' => ['Resource Operations', 'Tags', 'Danger Zone']");
|
||||
}
|
||||
});
|
||||
|
||||
it('groups application navigation by user workflow', function () {
|
||||
$application = file_get_contents(resource_path('views/components/application/configuration-sidebar.blade.php'));
|
||||
|
||||
expect($application)
|
||||
->toContain("'Settings' => ['General', 'Domains', 'Environment Variables', 'Persistent Storage', 'Advanced', 'Swarm', 'Healthcheck']")
|
||||
->toContain("'Observe & troubleshoot' => ['Runtime Logs', 'Deployment Logs', 'Terminal', 'Metrics']")
|
||||
->toContain("'Deploy' => ['Git Source', 'Servers', 'Preview Deployments']")
|
||||
->toContain("'Automation' => ['Scheduled Tasks', 'Webhooks', 'Backups']")
|
||||
->toContain("'Operations' => ['Resource Operations', 'Resource Limits', 'Rollback', 'Tags', 'Danger Zone']");
|
||||
});
|
||||
|
||||
it('shows the database sidebar on backup pages', function () {
|
||||
@@ -155,6 +172,6 @@ it('shows the service sidebar on runtime logs and terminal pages', function () {
|
||||
->toContain("in_array(\$type, ['application', 'database', 'service', 'server'], true)")
|
||||
->toContain('<x-service.configuration-sidebar :service="$resource" current-route="project.service.command"')
|
||||
->and($sidebar)
|
||||
->toContain("'Logs' => ['Runtime']")
|
||||
->toContain("'Operations' => ['Terminal', 'Resource Operations', 'Tags', 'Danger Zone']");
|
||||
->toContain("'Observe & troubleshoot' => ['Runtime Logs', 'Terminal']")
|
||||
->toContain("'Operations' => ['Resource Operations', 'Tags', 'Danger Zone']");
|
||||
});
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
use App\Livewire\Storage\Resources as StorageResources;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use App\Models\S3Storage;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledVolumeBackup;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
@@ -75,6 +77,57 @@ beforeEach(function () {
|
||||
});
|
||||
|
||||
describe('Storage/Resources team-scoped backup access', function () {
|
||||
test('lists and manages volume backup schedules using this storage', function () {
|
||||
$volume = LocalPersistentVolume::create([
|
||||
'name' => 'minio-volume-data',
|
||||
'mount_path' => '/data',
|
||||
'resource_id' => 999,
|
||||
'resource_type' => 'App\\Models\\Application',
|
||||
]);
|
||||
$backup = ScheduledVolumeBackup::create([
|
||||
'uuid' => fake()->uuid(),
|
||||
'backupable_type' => $volume->getMorphClass(),
|
||||
'backupable_id' => $volume->id,
|
||||
'team_id' => $this->teamA->id,
|
||||
's3_storage_id' => $this->storageA->id,
|
||||
'frequency' => 'daily',
|
||||
'enabled' => true,
|
||||
'save_s3' => true,
|
||||
]);
|
||||
$destination = S3Storage::unguarded(fn () => S3Storage::create([
|
||||
'uuid' => fake()->uuid(),
|
||||
'name' => 'volume-backup-destination',
|
||||
'region' => 'us-east-1',
|
||||
'key' => 'key-c',
|
||||
'secret' => 'secret-c',
|
||||
'bucket' => 'bucket-c',
|
||||
'endpoint' => 'https://s3.example.com',
|
||||
'team_id' => $this->teamA->id,
|
||||
'is_usable' => true,
|
||||
]));
|
||||
|
||||
Livewire::test(StorageResources::class, ['storage' => $this->storageA])
|
||||
->assertSee('minio-volume-data')
|
||||
->assertSee('Volume')
|
||||
->assertSeeHtml('class="listbox-trigger"')
|
||||
->assertSeeHtml('x-teleport="body"')
|
||||
->assertSeeHtml('requestAnimationFrame')
|
||||
->assertSeeHtml('-panel"')
|
||||
->assertSeeHtml("visibility: positioned ? 'visible' : 'hidden'")
|
||||
->assertSeeHtml('positionPanel($el)')
|
||||
->assertSeeHtml('class="flex items-center"')
|
||||
->set("selectedVolumeStorages.{$backup->id}", $destination->id)
|
||||
->call('moveVolumeBackup', $backup->id);
|
||||
|
||||
expect($backup->refresh()->s3_storage_id)->toBe($destination->id);
|
||||
|
||||
Livewire::test(StorageResources::class, ['storage' => $destination])
|
||||
->call('disableVolumeS3', $backup->id);
|
||||
|
||||
expect($backup->refresh()->save_s3)->toBeFalse()
|
||||
->and($backup->s3_storage_id)->toBeNull();
|
||||
});
|
||||
|
||||
test('disableS3 on other team backup throws and leaves row unchanged', function () {
|
||||
expect(fn () => Livewire::test(StorageResources::class, ['storage' => $this->storageA])
|
||||
->call('disableS3', $this->backupB->id))
|
||||
|
||||
Reference in new issue
Block a user