mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 15:39:59 +00:00
Add a /dashboard/canvas page that renders every team server with its resources and their running or queued deployments, backed by a new ServerCanvasController::dashboard endpoint and a per-server usage endpoint for zone headers. The dashboard remembers the list/canvas choice and each section (projects, servers) its own table/grid view. - Show Deploying/Queued pills on canvas cards and a foldable recent deployments panel; add shared CanvasControls component - Allow backup downloads from the server host via the S3 download storage (PrepareFileDownloadJob accepts a null container) - Extract backup toggle/run actions into ManagesDatabaseBackup and surface them in backup execution headers; merge S3 and retention sections into the general backup settings - Move deployments indicator to the top bar and add a main nav resource menu with submenus; simplify breadcrumbs and settings layouts - Add shared variables overview canvas and scheduled task modals - Add feature tests for dashboard canvas, view modes, navbar submenus, backup S3 downloads and scheduled task modals
134 lines
4.6 KiB
PHP
134 lines
4.6 KiB
PHP
<?php
|
|
|
|
use App\Models\InstanceSettings;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\Request;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
InstanceSettings::forceCreate(['id' => 0]);
|
|
$this->team = Team::factory()->create();
|
|
$this->user = User::factory()->create();
|
|
});
|
|
|
|
function renderNavbarAt(string $path, bool $mobile = false): string
|
|
{
|
|
app()->instance('request', Request::create($path));
|
|
|
|
return (string) test()->blade($mobile ? '<x-navbar />' : '<x-navbar desktop />');
|
|
}
|
|
|
|
function loginNavbarUser(string $role): void
|
|
{
|
|
test()->team->members()->attach(test()->user->id, ['role' => $role]);
|
|
test()->actingAs(test()->user);
|
|
session(['currentTeam' => test()->team]);
|
|
}
|
|
|
|
test('a submenu page opens its submenu with the current page active', function () {
|
|
loginNavbarUser('owner');
|
|
|
|
$html = renderNavbarAt('/metrics');
|
|
|
|
expect($html)
|
|
->toContain('Back to the main menu')
|
|
->toMatch('#aria-current="page"[^>]*>\s*<svg[^>]*>.*?</svg>\s*<span[^>]*>Metrics</span>#s');
|
|
});
|
|
|
|
test('a page outside every submenu starts on the main menu', function () {
|
|
loginNavbarUser('owner');
|
|
|
|
expect(renderNavbarAt('/servers'))->not->toContain('data-active-submenu="');
|
|
});
|
|
|
|
test('a submenu entry links to its first visible page', function () {
|
|
loginNavbarUser('owner');
|
|
|
|
expect(renderNavbarAt('/servers'))->toContain('href="'.route('analytics').'"');
|
|
});
|
|
|
|
test('the registries page is hidden from members in the integrations submenu', function () {
|
|
loginNavbarUser('member');
|
|
|
|
expect(renderNavbarAt('/sources'))
|
|
->toContain('Destinations')
|
|
->not->toContain('href="'.route('registries.index').'"');
|
|
});
|
|
|
|
test('the team submenu lists the team pages by role', function () {
|
|
loginNavbarUser('owner');
|
|
|
|
expect(renderNavbarAt('/team/members'))
|
|
->toContain('href="'.route('team.member.index').'"')
|
|
->toContain('href="'.route('team.audit-log').'"')
|
|
->toContain('href="'.route('team.danger-zone').'"');
|
|
});
|
|
|
|
test('members do not see the audit log or danger zone in the team submenu', function () {
|
|
loginNavbarUser('member');
|
|
|
|
expect(renderNavbarAt('/team'))
|
|
->toContain('href="'.route('team.member.index').'"')
|
|
->not->toContain('href="'.route('team.audit-log').'"')
|
|
->not->toContain('href="'.route('team.danger-zone').'"');
|
|
});
|
|
|
|
test('notifications and keys & tokens open their own submenus', function () {
|
|
loginNavbarUser('owner');
|
|
|
|
expect(renderNavbarAt('/notifications/discord'))
|
|
->toContain('href="'.route('notifications.slack').'"')
|
|
->toMatch('#aria-current="page"[^>]*>.*?discord\.svg#s');
|
|
|
|
expect(renderNavbarAt('/security/api-tokens'))
|
|
->toContain('href="'.route('security.private-key.index').'"')
|
|
->toContain('Back to the main menu');
|
|
});
|
|
|
|
test('the admin link belongs to the main menu, not to submenus', function () {
|
|
config()->set('app.env', 'local');
|
|
$root = User::factory()->create(['id' => 0]);
|
|
test()->team->members()->attach($root->id, ['role' => 'owner']);
|
|
test()->actingAs($root);
|
|
session(['currentTeam' => test()->team]);
|
|
|
|
expect(renderNavbarAt('/team'))
|
|
->toMatch('#<li\s+x-show="!submenu"\s+x-cloak[^>]*>\s*<a title="Admin"#');
|
|
});
|
|
|
|
test('shared variables open their own submenu', function () {
|
|
loginNavbarUser('owner');
|
|
|
|
expect(renderNavbarAt('/shared-variables/servers'))
|
|
->toContain('Back to the main menu')
|
|
->toContain('href="'.route('shared-variables.team.index').'"')
|
|
->toMatch('#aria-current="page"[^>]*>\s*<svg[^>]*>.*?</svg>\s*<span[^>]*>Servers</span>#s');
|
|
});
|
|
|
|
test('instance settings open their own submenu for instance admins', function () {
|
|
config()->set('app.env', 'local');
|
|
$root = User::factory()->create(['id' => 0]);
|
|
test()->team->members()->attach($root->id, ['role' => 'owner']);
|
|
test()->actingAs($root);
|
|
session(['currentTeam' => test()->team]);
|
|
|
|
expect(renderNavbarAt('/settings/email'))
|
|
->toContain('Back to the main menu')
|
|
->toContain('href="'.route('settings.oauth').'"')
|
|
->toMatch('#aria-current="page"[^>]*>\s*<svg[^>]*>.*?</svg>\s*<span[^>]*>Email</span>#s');
|
|
});
|
|
|
|
test('a parent entry opens its submenu in place on mobile and desktop', function () {
|
|
loginNavbarUser('owner');
|
|
|
|
foreach ([renderNavbarAt('/servers', mobile: true), renderNavbarAt('/servers')] as $html) {
|
|
expect($html)
|
|
->toContain('x-show="submenu === \'Monitoring\'"')
|
|
->toMatch('#<button type="button" title="Monitoring" @click="submenu = \'Monitoring\'#')
|
|
->not->toMatch('#<a title="Monitoring"#');
|
|
}
|
|
});
|