mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-23 20:25:39 +00:00
feat(ui): consolidate resource heading actions
This commit is contained in:
@@ -1809,6 +1809,13 @@ html[data-theme="custom"] textarea:disabled {
|
||||
color: var(--color-fg);
|
||||
}
|
||||
|
||||
.application-heading-actions .button-highlighted,
|
||||
.dark .application-heading-actions .button-highlighted,
|
||||
.application-heading-actions .button-highlighted:hover:not(:disabled),
|
||||
.dark .application-heading-actions .button-highlighted:hover:not(:disabled) {
|
||||
@apply button-highlighted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Active primary tab styles.
|
||||
* The base rules above set background/color/box-shadow with higher specificity
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
<button type="button" class="button w-full justify-between" @click="open = !open"
|
||||
:aria-expanded="open" aria-haspopup="menu">
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<x-reicon name="play-circle" class="size-3.5 text-warning" />
|
||||
Actions
|
||||
</span>
|
||||
<span class="inline-flex transition-transform" :class="open && 'rotate-180'">
|
||||
@@ -186,37 +185,100 @@
|
||||
<div class="resource-heading-menus shrink-0">
|
||||
<x-applications.links :application="$application" />
|
||||
</div>
|
||||
<x-applications.deploy :application="$application" />
|
||||
<x-resource-heading-overflow id="application-desktop-actions">
|
||||
@if (! str($application->status)->startsWith('exited'))
|
||||
@if ($application->build_pack !== 'dockercompose' && ! $application->destination->server->isSwarm())
|
||||
<div id="application-desktop-actions" class="relative" x-data="{ open: false }"
|
||||
x-effect="$dispatch('resource-actions-toggled', { open })"
|
||||
@click.outside="open = false" @keydown.escape.window="open = false">
|
||||
<button type="button" class="button button-highlighted" @click="open = !open" :aria-expanded="open"
|
||||
aria-haspopup="menu">
|
||||
Actions
|
||||
<x-reicon name="chevron-down" class="size-3 opacity-55" />
|
||||
</button>
|
||||
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-60! min-w-0!" role="menu">
|
||||
@if (str($application->status)->startsWith('exited'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $application))
|
||||
wire:click="deploy" @click="open = false" role="menuitem">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Deploy
|
||||
</button>
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $application))
|
||||
wire:click="deploy(true)" @click="open = false" role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Deploy (without cache)
|
||||
</button>
|
||||
@endif
|
||||
@else
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
@can('deploy', $application)
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
wire:click="deploy" @click="open = false" role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Redeploy
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!" disabled>
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Redeploy
|
||||
</button>
|
||||
@endcan
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $application))
|
||||
wire:click="{{ str($application->status)->startsWith('running') ? 'force_deploy_without_cache' : 'deploy(true)' }}"
|
||||
@click="open = false" role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
{{ str($application->status)->startsWith('running') ? 'Redeploy (without cache)' : 'Deploy (without cache)' }}
|
||||
</button>
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
@if ($application->destination->server->isSwarm())
|
||||
@can('deploy', $application)
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
wire:click="deploy" @click="open = false" role="menuitem">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Update Service
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!" disabled>
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Update Service
|
||||
</button>
|
||||
@endcan
|
||||
@else
|
||||
@can('deploy', $application)
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('application-mobile-restart-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
Restart
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!" disabled>
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
Restart
|
||||
</button>
|
||||
@endcan
|
||||
@endif
|
||||
@endif
|
||||
@can('deploy', $application)
|
||||
<button type="button" class="button"
|
||||
@click="open = false; document.getElementById('application-mobile-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
Restart
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('application-mobile-stop-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="button" disabled>
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
Restart
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!" disabled>
|
||||
<x-reicon name="stop-circle" class="size-3.5 opacity-70" />
|
||||
Stop
|
||||
</button>
|
||||
@endcan
|
||||
@endif
|
||||
@can('deploy', $application)
|
||||
<button type="button" class="button"
|
||||
@click="open = false; document.getElementById('application-mobile-stop-trigger')?.click()">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="button" disabled>
|
||||
<x-reicon name="stop-circle" class="size-3.5 opacity-70" />
|
||||
Stop
|
||||
</button>
|
||||
@endcan
|
||||
@endif
|
||||
</x-resource-heading-overflow>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
<button type="button" class="button w-full justify-between" @click="open = !open"
|
||||
:aria-expanded="open" aria-haspopup="menu">
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<x-reicon name="play-circle" class="size-3.5 text-warning" />
|
||||
Actions
|
||||
</span>
|
||||
<span class="inline-flex transition-transform" :class="open && 'rotate-180'">
|
||||
@@ -138,28 +137,25 @@
|
||||
class="resource-heading-navbar application-heading-actions flex w-auto min-w-0 items-center justify-end gap-1 overflow-visible">
|
||||
<div class="resource-heading-actions flex shrink-0 items-center gap-0.5">
|
||||
@if ($database->destination->server->isFunctional())
|
||||
<x-resource-heading-overflow id="database-desktop-actions">
|
||||
<div id="database-desktop-actions" class="flex items-center gap-0.5">
|
||||
@if (! $databaseStatus->startsWith('exited'))
|
||||
<button type="button" class="button"
|
||||
<button type="button" class="button button-highlighted"
|
||||
@disabled(!auth()->user()->can('manage', $database))
|
||||
@click="open = false; document.getElementById('database-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
@click="document.getElementById('database-restart-trigger')?.click()">
|
||||
Restart
|
||||
</button>
|
||||
<button type="button" class="button"
|
||||
@disabled(!auth()->user()->can('manage', $database))
|
||||
@click="open = false; document.getElementById('database-stop-trigger')?.click()">
|
||||
<x-reicon name="stop" class="size-3.5 text-error" />
|
||||
@click="document.getElementById('database-stop-trigger')?.click()">
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<x-forms.button canGate="manage" :canResource="$database"
|
||||
<x-forms.button class="button-highlighted" canGate="manage" :canResource="$database"
|
||||
@click="$wire.dispatch('startEvent')">
|
||||
<x-reicon name="play-circle" class="size-4 opacity-70" />
|
||||
Start
|
||||
</x-forms.button>
|
||||
@endif
|
||||
</x-resource-heading-overflow>
|
||||
</div>
|
||||
@else
|
||||
<x-status-badge status="Server unavailable" type="error" />
|
||||
@endif
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
:aria-expanded="open" aria-haspopup="menu">
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<x-loading-on-button x-show="deploying" x-cloak />
|
||||
<x-reicon name="play-circle" class="size-3.5 text-warning" x-show="!deploying" />
|
||||
<span x-text="deploying ? 'Deploying…' : 'Actions'">Actions</span>
|
||||
</span>
|
||||
<span class="inline-flex transition-transform" :class="open && 'rotate-180'">
|
||||
@@ -184,25 +183,70 @@
|
||||
<div class="resource-heading-menus shrink-0">
|
||||
<x-services.links :service="$service" />
|
||||
</div>
|
||||
<x-services.advanced :service="$service" />
|
||||
<x-services.restart :service="$service" />
|
||||
<x-resource-heading-overflow id="service-desktop-actions">
|
||||
@if ($serviceStatus->contains('running') || $serviceStatus->contains('degraded'))
|
||||
<button type="button" class="button"
|
||||
@disabled(!auth()->user()->can('stop', $service))
|
||||
@click="open = false; document.getElementById('service-stop-trigger')?.click()">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
Stop
|
||||
<div id="service-desktop-actions" class="relative" x-data="{ open: false }"
|
||||
x-effect="$dispatch('resource-actions-toggled', { open })"
|
||||
@click.outside="open = false" @keydown.escape.window="open = false">
|
||||
<button type="button" class="button button-highlighted" @click="open = !open" :aria-expanded="open">
|
||||
Actions
|
||||
<x-reicon name="chevron-down" class="size-3 opacity-55" />
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="button"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="deploying = true; $wire.dispatch('startEvent'); open = false">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Deploy
|
||||
</button>
|
||||
@endif
|
||||
</x-resource-heading-overflow>
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-64! min-w-0!" role="menu">
|
||||
@if ($serviceStatus->contains('running') || $serviceStatus->contains('degraded'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="open = false; document.getElementById('service-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
Restart
|
||||
</button>
|
||||
@if ($serviceStatus->contains('running'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="$wire.dispatch('pullAndRestartEvent'); open = false">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Restart (pull latest)
|
||||
</button>
|
||||
@endif
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('stop', $service))
|
||||
@click="open = false; document.getElementById('service-stop-trigger')?.click()">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
Stop
|
||||
</button>
|
||||
@elseif (! $serviceStatus->contains('running'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="deploying = true; $wire.dispatch('startEvent'); open = false">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Deploy
|
||||
</button>
|
||||
@endif
|
||||
@if (! $serviceStatus->contains('running'))
|
||||
<div class="my-1 border-t border-coolgray-200 dark:border-coolgray-300" role="separator"></div>
|
||||
@endif
|
||||
@if ($serviceStatus->contains('degraded'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="$wire.dispatch('forceDeployEvent'); open = false">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force Restart
|
||||
</button>
|
||||
@elseif (! $serviceStatus->contains('running'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('deploy', $service))
|
||||
@click="$wire.dispatch('forceDeployEvent'); open = false">
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
Force Deploy
|
||||
</button>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@disabled(!auth()->user()->can('stop', $service))
|
||||
@click="$wire.dispatch('cleanupEvent'); open = false">
|
||||
<x-reicon name="trash" class="size-3.5 opacity-70" />
|
||||
Force Cleanup Containers
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<a href="{{ $environmentVariablesUrl }}" {{ wireNavigate() }}
|
||||
aria-label="Open required environment variables">
|
||||
|
||||
@@ -157,7 +157,6 @@
|
||||
<button type="button" class="button w-full justify-between" @click="open = !open"
|
||||
:aria-expanded="open" aria-haspopup="menu">
|
||||
<span class="inline-flex items-center gap-2">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
Actions
|
||||
</span>
|
||||
<span class="inline-flex transition-transform" :class="open && 'rotate-180'">
|
||||
@@ -286,33 +285,66 @@
|
||||
|
||||
@if ($server->proxySet())
|
||||
@can('manageProxy', $server)
|
||||
<x-server.advanced :server-ip="$serverIp" :traefik-dashboard-available="$traefikDashboardAvailable" />
|
||||
<x-resource-heading-overflow id="server-desktop-actions" class="resource-heading-actions">
|
||||
@if ($proxyCanBeStopped)
|
||||
<button type="button" class="button"
|
||||
@click="open = false; document.getElementById('server-mobile-restart-proxy-trigger')?.click()">
|
||||
<div id="server-desktop-actions" class="resource-heading-actions relative shrink-0"
|
||||
x-data="{ open: false }" x-effect="$dispatch('resource-actions-toggled', { open })"
|
||||
@click.outside="open = false"
|
||||
@keydown.escape.window="open = false">
|
||||
<button type="button" class="button button-highlighted" @click="open = !open" :aria-expanded="open"
|
||||
aria-haspopup="menu" wire:loading.attr="disabled" wire:loading.class="is-loading"
|
||||
wire:target="checkProxy,startProxy">
|
||||
Actions
|
||||
<x-reicon name="chevron-down" class="size-3 opacity-55" />
|
||||
</button>
|
||||
|
||||
<div x-cloak x-show="open" x-transition.origin.top.right
|
||||
class="listbox-panel top-full! right-0! left-auto! mt-1! w-60! min-w-0!" role="menu">
|
||||
@if ($proxyCanBeStopped)
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('server-mobile-restart-proxy-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
</span>
|
||||
Restart Proxy
|
||||
</button>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('server-mobile-stop-proxy-trigger')?.click()"
|
||||
role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
</span>
|
||||
Stop Proxy
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; $wire.dispatch('checkProxyEvent')" role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
</span>
|
||||
Start Proxy
|
||||
</button>
|
||||
@endif
|
||||
<div class="my-1 border-t border-coolgray-200 dark:border-coolgray-300"
|
||||
role="separator"></div>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
wire:click="checkProxyStatus" wire:loading.attr="disabled"
|
||||
@click="open = false" role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="restart" class="size-3.5 opacity-70" />
|
||||
<x-reicon name="refresh" class="size-3.5 opacity-70" />
|
||||
</span>
|
||||
Restart Proxy
|
||||
Refresh Proxy Status
|
||||
</button>
|
||||
<button type="button" class="button"
|
||||
@click="open = false; document.getElementById('server-mobile-stop-proxy-trigger')?.click()">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="stop-circle" class="size-3.5 text-error" />
|
||||
</span>
|
||||
Stop Proxy
|
||||
</button>
|
||||
@else
|
||||
<button type="button" class="button"
|
||||
@click="open = false; $wire.dispatch('checkProxyEvent')">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="play-circle" class="size-3.5 opacity-70" />
|
||||
</span>
|
||||
Start Proxy
|
||||
</button>
|
||||
@endif
|
||||
</x-resource-heading-overflow>
|
||||
@if ($traefikDashboardAvailable)
|
||||
<a class="listbox-option justify-start! gap-2.5!" target="_blank"
|
||||
href="http://{{ $serverIp }}:8080" @click="open = false" role="menuitem">
|
||||
<span class="flex size-4 shrink-0 items-center justify-center">
|
||||
<x-reicon name="external-link" class="size-3! opacity-70" />
|
||||
</span>
|
||||
Traefik Dashboard
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -124,25 +124,23 @@ it('keeps advanced operations in a dedicated Advanced dropdown', function () {
|
||||
$serviceDesktop = str($service)->after('resource-heading-actions flex')->toString();
|
||||
|
||||
expect($applicationDesktop)
|
||||
->toContain('<x-applications.deploy')
|
||||
->toContain('application-desktop-actions')
|
||||
->toContain('Deploy (without cache)')
|
||||
->not->toContain('<x-applications.advanced')
|
||||
->not->toContain('resource-heading-overflow-separator')
|
||||
->not->toContain('Force deploy without cache')
|
||||
->and($serviceDesktop)
|
||||
->toContain('<x-services.advanced')
|
||||
->toContain('<x-services.restart')
|
||||
->toContain('service-desktop-actions')
|
||||
->toContain('Force Deploy')
|
||||
->toContain('Force Cleanup Containers')
|
||||
->toContain('Restart (pull latest)')
|
||||
->not->toContain('resource-heading-overflow-separator')
|
||||
->not->toContain('Pull Latest Images & Restart');
|
||||
|
||||
expect(strpos($applicationDesktop, '<x-applications.links'))
|
||||
->toBeLessThan(strpos($applicationDesktop, '<x-applications.deploy'))
|
||||
->toBeLessThan(strpos($applicationDesktop, 'application-desktop-actions'));
|
||||
|
||||
expect(strpos($serviceDesktop, '<x-services.links'))
|
||||
->toBeLessThan(strpos($serviceDesktop, '<x-services.advanced'))
|
||||
->toBeLessThan(strpos($serviceDesktop, '<x-services.restart'))
|
||||
->toBeLessThan(strpos($serviceDesktop, 'service-desktop-actions'));
|
||||
|
||||
expect($applicationAdvanced)
|
||||
@@ -165,24 +163,20 @@ it('keeps advanced operations in a dedicated Advanced dropdown', function () {
|
||||
expect(substr_count($application, 'resource-heading-navbar'))->toBe(1);
|
||||
});
|
||||
|
||||
it('lists desktop application lifecycle controls inline and collapses them when space runs out', function () {
|
||||
it('groups desktop application lifecycle controls in one Actions dropdown', function () {
|
||||
$heading = file_get_contents(resource_path('views/livewire/project/application/heading.blade.php'));
|
||||
$overflow = file_get_contents(resource_path('views/components/resource-heading-overflow.blade.php'));
|
||||
$desktop = str($heading)->after('resource-heading-actions flex')->toString();
|
||||
|
||||
expect($desktop)
|
||||
->toContain('application-desktop-actions')
|
||||
->toContain('<x-resource-heading-overflow')
|
||||
->toContain('<x-applications.deploy')
|
||||
->toContain('Restart')
|
||||
->toContain('Stop')
|
||||
->not->toContain('listbox-option');
|
||||
|
||||
expect($overflow)
|
||||
->toContain('Actions')
|
||||
->toContain('listbox-panel top-full! right-0! left-auto!')
|
||||
->toContain('measureInlineWidth')
|
||||
->toContain('availableWidth');
|
||||
->toContain('listbox-option')
|
||||
->toContain('Deploy')
|
||||
->toContain('Restart')
|
||||
->toContain('Stop')
|
||||
->not->toContain('<x-resource-heading-overflow')
|
||||
->not->toContain('<x-applications.deploy');
|
||||
});
|
||||
|
||||
it('raises the desktop top bar while any resource dropdown is open', function () {
|
||||
@@ -208,19 +202,17 @@ it('raises the desktop top bar while any resource dropdown is open', function ()
|
||||
}
|
||||
});
|
||||
|
||||
it('groups service restart options in a Restart dropdown', function () {
|
||||
it('groups service restart options in the Actions dropdown', function () {
|
||||
$heading = file_get_contents(resource_path('views/livewire/project/service/heading.blade.php'));
|
||||
$restart = file_get_contents(resource_path('views/components/services/restart.blade.php'));
|
||||
$desktop = str($heading)->after('resource-heading-actions flex')->toString();
|
||||
|
||||
expect($desktop)
|
||||
->toContain('<x-services.restart')
|
||||
->not->toContain('Pull Latest Images & Restart')
|
||||
->and($restart)
|
||||
->toContain('Restart current version')
|
||||
->toContain('Pull latest and restart')
|
||||
->toContain('service-desktop-actions')
|
||||
->toContain('Restart')
|
||||
->toContain('Restart (pull latest)')
|
||||
->toContain("\$wire.dispatch('pullAndRestartEvent')")
|
||||
->toContain('service-restart-trigger');
|
||||
->not->toContain('Pull Latest Images & Restart')
|
||||
->not->toContain('<x-services.restart');
|
||||
|
||||
$mobile = str($heading)->before("@teleport('#resource-action-hud-slot')")->toString();
|
||||
|
||||
@@ -230,20 +222,31 @@ it('groups service restart options in a Restart dropdown', function () {
|
||||
->not->toContain('Pull Latest Images & Restart');
|
||||
});
|
||||
|
||||
it('groups application deploy options in a Deploy dropdown', function () {
|
||||
it('orders service restart actions before stop and advanced operations', function () {
|
||||
$heading = file_get_contents(resource_path('views/livewire/project/service/heading.blade.php'));
|
||||
$actions = str($heading)->after('id="service-desktop-actions"')->before('@else\n <a href')->toString();
|
||||
|
||||
expect(strpos($actions, 'Restart\n'))
|
||||
->toBeLessThan(strpos($actions, 'Restart (pull latest)'))
|
||||
->and(strpos($actions, 'Restart (pull latest)'))
|
||||
->toBeLessThan(strpos($actions, 'Stop'));
|
||||
});
|
||||
|
||||
it('groups application lifecycle options in an Actions dropdown', function () {
|
||||
$heading = file_get_contents(resource_path('views/livewire/project/application/heading.blade.php'));
|
||||
$deploy = file_get_contents(resource_path('views/components/applications/deploy.blade.php'));
|
||||
$desktop = str($heading)->after('resource-heading-actions flex')->toString();
|
||||
$trigger = str($desktop)->after('id="application-desktop-actions"')->before('<div x-cloak')->toString();
|
||||
|
||||
expect($desktop)
|
||||
->toContain('<x-applications.deploy')
|
||||
->toContain('id="application-desktop-actions"')
|
||||
->not->toContain('Force deploy without cache')
|
||||
->and($deploy)
|
||||
->toContain('Actions')
|
||||
->toContain('Deploy')
|
||||
->toContain('Deploy (without cache)')
|
||||
->toContain('force_deploy_without_cache')
|
||||
->toContain('deploy(true)');
|
||||
->toContain('deploy(true)')
|
||||
->and($trigger)
|
||||
->toContain('button button-highlighted')
|
||||
->not->toContain('play-circle');
|
||||
|
||||
$mobile = str($heading)->before("@teleport('#resource-action-hud-slot')")->toString();
|
||||
|
||||
@@ -252,6 +255,63 @@ it('groups application deploy options in a Deploy dropdown', function () {
|
||||
->not->toContain('Force deploy without cache');
|
||||
});
|
||||
|
||||
it('places the state-aware no-cache action immediately after deploy or redeploy', function () {
|
||||
$heading = file_get_contents(resource_path('views/livewire/project/application/heading.blade.php'));
|
||||
$actions = str($heading)->after('id="application-desktop-actions"')->before('@endteleport')->toString();
|
||||
|
||||
expect($actions)
|
||||
->toContain("str(\$application->status)->startsWith('running') ? 'Redeploy (without cache)' : 'Deploy (without cache)'")
|
||||
->toContain("str(\$application->status)->startsWith('running') ? 'force_deploy_without_cache' : 'deploy(true)'");
|
||||
|
||||
expect(strpos($actions, 'wire:click="deploy"'))
|
||||
->toBeLessThan(strpos($actions, 'Redeploy (without cache)'))
|
||||
->and(strpos($actions, 'Redeploy (without cache)'))
|
||||
->toBeLessThan(strpos($actions, 'Restart'));
|
||||
});
|
||||
|
||||
it('uses the shared Coollabs gradient for primary resource actions in the desktop header', function () {
|
||||
$css = file_get_contents(resource_path('css/app.css'));
|
||||
|
||||
expect($css)
|
||||
->toContain('.application-heading-actions .button-highlighted')
|
||||
->toContain('@apply button-highlighted;');
|
||||
});
|
||||
|
||||
it('uses iconless highlighted Actions dropdowns for service and proxy lifecycle controls', function () {
|
||||
foreach ([
|
||||
resource_path('views/livewire/project/service/heading.blade.php') => 'service-desktop-actions',
|
||||
resource_path('views/livewire/server/navbar.blade.php') => 'server-desktop-actions',
|
||||
] as $path => $id) {
|
||||
$heading = file_get_contents($path);
|
||||
$trigger = str($heading)->after("id=\"{$id}\"")->before('<div x-cloak')->toString();
|
||||
|
||||
expect($trigger)
|
||||
->toContain('button button-highlighted')
|
||||
->toContain('Actions')
|
||||
->not->toContain('play-circle');
|
||||
}
|
||||
});
|
||||
|
||||
it('places the Traefik dashboard last in the server Actions menu', function () {
|
||||
$navbar = file_get_contents(resource_path('views/livewire/server/navbar.blade.php'));
|
||||
$actions = str($navbar)->after('id="server-desktop-actions"')->before('@endcan')->toString();
|
||||
|
||||
expect(strpos($actions, 'Refresh Proxy Status'))
|
||||
->toBeLessThan(strpos($actions, 'Traefik Dashboard'));
|
||||
});
|
||||
|
||||
it('keeps database lifecycle controls direct and highlights the primary action', function () {
|
||||
$heading = file_get_contents(resource_path('views/livewire/project/database/heading.blade.php'));
|
||||
$desktop = str($heading)->after('resource-heading-actions flex')->before('@endteleport')->toString();
|
||||
|
||||
expect($desktop)
|
||||
->toContain('Restart')
|
||||
->toContain('Stop')
|
||||
->toContain('button button-highlighted')
|
||||
->not->toContain('<x-reicon')
|
||||
->not->toContain('<x-resource-heading-overflow');
|
||||
});
|
||||
|
||||
it('renders configuration warnings as navbar popovers instead of floating notifications', function () {
|
||||
$checker = file_get_contents(resource_path('views/livewire/project/shared/configuration-checker.blade.php'));
|
||||
$warning = file_get_contents(resource_path('views/components/configuration-warning.blade.php'));
|
||||
|
||||
Reference in New Issue
Block a user