mirror of
https://github.com/coollabsio/coolify.git
synced 2026-09-26 09:20:54 -04:00
feat(ui): optimistic updates for notifications and resource actions
- Notification channels: flip enable/disable label + highlight instantly, roll back on error (all 6 channels via shared channel-actions) - Database heading: Start/Restart show instant Starting/Restarting state + spinner, disable during the request - Service heading: Restart now reuses the deploying state (spinner + Restarting label), matching the existing Deploy pattern
This commit is contained in:
@@ -13,12 +13,17 @@
|
||||
toggleMethod: @js($toggleMethod),
|
||||
testMethod: @js($testMethod),
|
||||
}">
|
||||
<x-forms.button type="button" :disabled="!$canUpdate" :isHighlighted="!$enabled"
|
||||
<x-forms.button type="button" :disabled="!$canUpdate"
|
||||
x-bind:class="{ 'button-highlighted': !enabled }"
|
||||
x-on:click="
|
||||
if (!enabled && !$el.closest('form').reportValidity()) return;
|
||||
$wire.$set(enabledProperty, !enabled).then(() => $wire.$call(toggleMethod));
|
||||
const next = !enabled;
|
||||
enabled = next;
|
||||
$wire.$set(enabledProperty, next)
|
||||
.then(() => $wire.$call(toggleMethod))
|
||||
.catch(() => { enabled = !next; });
|
||||
">
|
||||
{{ $enabled ? 'Disable' : 'Enable' }}
|
||||
<span x-text="enabled ? 'Disable' : 'Enable'">{{ $enabled ? 'Disable' : 'Enable' }}</span>
|
||||
</x-forms.button>
|
||||
<x-forms.button type="button" :disabled="!$enabled"
|
||||
x-on:click="if ($el.closest('form').reportValidity()) $wire.$call(testMethod)">
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
</x-slot:content>
|
||||
</x-process-dialog>
|
||||
|
||||
<div x-data>
|
||||
<div x-data="{ busy: false }" @database-busy.window="busy = true"
|
||||
@database-action-finished.window="busy = false">
|
||||
<div class="mb-3 w-full xl:hidden">
|
||||
<div class="flex min-w-0 flex-col items-start gap-2">
|
||||
<h1 class="min-w-0 max-w-full truncate text-[24px]! leading-7! font-semibold! tracking-tight! text-black dark:text-fg">
|
||||
@@ -77,9 +78,11 @@
|
||||
@can('manage', $database)
|
||||
<x-split-action id="database-mobile-actions" class="mb-3 flex w-full">
|
||||
@if (! $databaseStatus->startsWith('exited'))
|
||||
<x-slot:main @click="document.getElementById('database-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5" />
|
||||
Restart
|
||||
<x-slot:main x-bind:disabled="busy"
|
||||
@click="document.getElementById('database-restart-trigger')?.click()">
|
||||
<x-loading-on-button x-show="busy" x-cloak />
|
||||
<x-reicon name="restart" class="size-3.5" x-show="!busy" />
|
||||
<span x-text="busy ? 'Restarting…' : 'Restart'">Restart</span>
|
||||
</x-slot:main>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('database-stop-trigger')?.click()" role="menuitem">
|
||||
@@ -87,9 +90,10 @@
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<x-slot:main @click="$wire.dispatch('startEvent')">
|
||||
<x-reicon name="play-circle" class="size-3.5" />
|
||||
Start
|
||||
<x-slot:main x-bind:disabled="busy" @click="busy = true; $wire.dispatch('startEvent')">
|
||||
<x-loading-on-button x-show="busy" x-cloak />
|
||||
<x-reicon name="play-circle" class="size-3.5" x-show="!busy" />
|
||||
<span x-text="busy ? 'Starting…' : 'Start'">Start</span>
|
||||
</x-slot:main>
|
||||
@endif
|
||||
</x-split-action>
|
||||
@@ -107,9 +111,11 @@
|
||||
@can('manage', $database)
|
||||
<x-split-action id="database-desktop-actions">
|
||||
@if (! $databaseStatus->startsWith('exited'))
|
||||
<x-slot:main @click="document.getElementById('database-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5" />
|
||||
Restart
|
||||
<x-slot:main x-bind:disabled="busy"
|
||||
@click="document.getElementById('database-restart-trigger')?.click()">
|
||||
<x-loading-on-button x-show="busy" x-cloak />
|
||||
<x-reicon name="restart" class="size-3.5" x-show="!busy" />
|
||||
<span x-text="busy ? 'Restarting…' : 'Restart'">Restart</span>
|
||||
</x-slot:main>
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@click="open = false; document.getElementById('database-stop-trigger')?.click()" role="menuitem">
|
||||
@@ -117,9 +123,10 @@
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<x-slot:main @click="$wire.dispatch('startEvent')">
|
||||
<x-reicon name="play-circle" class="size-3.5" />
|
||||
Start
|
||||
<x-slot:main x-bind:disabled="busy" @click="busy = true; $wire.dispatch('startEvent')">
|
||||
<x-loading-on-button x-show="busy" x-cloak />
|
||||
<x-reicon name="play-circle" class="size-3.5" x-show="!busy" />
|
||||
<span x-text="busy ? 'Starting…' : 'Start'">Start</span>
|
||||
</x-slot:main>
|
||||
@endif
|
||||
</x-split-action>
|
||||
@@ -162,14 +169,23 @@
|
||||
|
||||
@script
|
||||
<script>
|
||||
$wire.$on('startEvent', () => {
|
||||
$wire.$on('startEvent', async () => {
|
||||
window.dispatchEvent(new CustomEvent('startdatabase'));
|
||||
$wire.$call('start');
|
||||
try {
|
||||
await $wire.$call('start');
|
||||
} finally {
|
||||
window.dispatchEvent(new CustomEvent('database-action-finished'));
|
||||
}
|
||||
});
|
||||
$wire.$on('restartEvent', () => {
|
||||
$wire.$on('restartEvent', async () => {
|
||||
window.dispatchEvent(new CustomEvent('database-busy'));
|
||||
$wire.$dispatch('info', 'Restarting database.');
|
||||
window.dispatchEvent(new CustomEvent('startdatabase'));
|
||||
$wire.$call('restart');
|
||||
try {
|
||||
await $wire.$call('restart');
|
||||
} finally {
|
||||
window.dispatchEvent(new CustomEvent('database-action-finished'));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endscript
|
||||
|
||||
@@ -65,7 +65,8 @@
|
||||
</x-slot:content>
|
||||
</x-process-dialog>
|
||||
|
||||
<div x-data="{ deploying: false }" @service-deploy-finished.window="deploying = false">
|
||||
<div x-data="{ deploying: false }" @service-restarting.window="deploying = true"
|
||||
@service-deploy-finished.window="deploying = false">
|
||||
<div class="mb-3 w-full xl:hidden">
|
||||
<div class="flex min-w-0 flex-col items-start gap-2">
|
||||
<h1 class="min-w-0 max-w-full truncate text-[24px]! leading-7! font-semibold! tracking-tight! text-black dark:text-fg">
|
||||
@@ -95,8 +96,9 @@
|
||||
@elseif ($serviceStatus->contains('running') || $serviceStatus->contains('degraded'))
|
||||
<x-slot:main x-bind:disabled="deploying"
|
||||
@click="document.getElementById('service-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5" />
|
||||
Restart
|
||||
<x-loading-on-button x-show="deploying" x-cloak />
|
||||
<x-reicon name="restart" class="size-3.5" x-show="!deploying" />
|
||||
<span x-text="deploying ? 'Restarting…' : 'Restart'">Restart</span>
|
||||
</x-slot:main>
|
||||
@if ($serviceStatus->contains('running'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@@ -185,8 +187,9 @@
|
||||
@elseif ($serviceStatus->contains('running') || $serviceStatus->contains('degraded'))
|
||||
<x-slot:main x-bind:disabled="deploying"
|
||||
@click="document.getElementById('service-restart-trigger')?.click()">
|
||||
<x-reicon name="restart" class="size-3.5" />
|
||||
Restart
|
||||
<x-loading-on-button x-show="deploying" x-cloak />
|
||||
<x-reicon name="restart" class="size-3.5" x-show="!deploying" />
|
||||
<span x-text="deploying ? 'Restarting…' : 'Restart'">Restart</span>
|
||||
</x-slot:main>
|
||||
@if ($serviceStatus->contains('running'))
|
||||
<button type="button" class="listbox-option justify-start! gap-2.5!"
|
||||
@@ -319,9 +322,14 @@
|
||||
return;
|
||||
}
|
||||
|
||||
window.dispatchEvent(new CustomEvent('service-restarting'));
|
||||
$wire.$dispatch('info',
|
||||
'Gracefully stopping service.<br/><br/>It could take a while depending on the service.');
|
||||
$wire.$call('restart');
|
||||
try {
|
||||
await $wire.$call('restart');
|
||||
} finally {
|
||||
window.dispatchEvent(new CustomEvent('service-deploy-finished'));
|
||||
}
|
||||
});
|
||||
$wire.$on('forceDeployEvent', () => $wire.$call('forceDeploy'));
|
||||
$wire.$on('pullAndRestartEvent', () => {
|
||||
|
||||
Reference in New Issue
Block a user