feat: add confirmation modals for detaching and deleting drives to enhance user experience
This commit is contained in:
@@ -109,6 +109,18 @@ class Storage extends Component
|
||||
public ?string $movingDriveKey = null;
|
||||
public string $moveDriveTargetBus = 'scsi';
|
||||
|
||||
/**
|
||||
* Form state for detach confirmation modal.
|
||||
*/
|
||||
public bool $showDetachModal = false;
|
||||
public ?string $detachingDriveKey = null;
|
||||
|
||||
/**
|
||||
* Form state for delete confirmation modal.
|
||||
*/
|
||||
public bool $showDeleteModal = false;
|
||||
public ?string $deletingDriveKey = null;
|
||||
|
||||
/**
|
||||
* Storage name for cephpool.
|
||||
*/
|
||||
@@ -619,15 +631,40 @@ class Storage extends Component
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach a drive (move to unused).
|
||||
* Open the detach confirmation modal.
|
||||
*/
|
||||
public function detachDrive(string $driveKey): void
|
||||
public function openDetachModal(string $driveKey): void
|
||||
{
|
||||
if (!isset($this->drives[$driveKey])) {
|
||||
$this->notify('Drive not found.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->detachingDriveKey = $driveKey;
|
||||
$this->showDetachModal = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the detach confirmation modal.
|
||||
*/
|
||||
public function closeDetachModal(): void
|
||||
{
|
||||
$this->showDetachModal = false;
|
||||
$this->detachingDriveKey = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach a drive (move to unused).
|
||||
*/
|
||||
public function detachDrive(): void
|
||||
{
|
||||
if (!$this->detachingDriveKey || !isset($this->drives[$this->detachingDriveKey])) {
|
||||
$this->notify('Drive not found.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$driveKey = $this->detachingDriveKey;
|
||||
|
||||
// Check VM status - must be stopped to detach
|
||||
if ($this->status && $this->status->status !== 'stopped') {
|
||||
$this->notify('VM must be stopped to detach drives.', 'error');
|
||||
@@ -647,6 +684,7 @@ class Storage extends Component
|
||||
);
|
||||
|
||||
$this->notify("Drive {$driveKey} detached successfully.", 'success');
|
||||
$this->closeDetachModal();
|
||||
$this->loadStorageConfiguration(false);
|
||||
} catch (\Exception $e) {
|
||||
$this->notify('Failed to detach drive: ' . $e->getMessage(), 'error');
|
||||
@@ -704,15 +742,40 @@ class Storage extends Component
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a drive permanently.
|
||||
* Open the delete confirmation modal.
|
||||
*/
|
||||
public function deleteDrive(string $driveKey): void
|
||||
public function openDeleteModal(string $driveKey): void
|
||||
{
|
||||
if (!isset($this->drives[$driveKey])) {
|
||||
$this->notify('Drive not found.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deletingDriveKey = $driveKey;
|
||||
$this->showDeleteModal = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the delete confirmation modal.
|
||||
*/
|
||||
public function closeDeleteModal(): void
|
||||
{
|
||||
$this->showDeleteModal = false;
|
||||
$this->deletingDriveKey = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a drive permanently.
|
||||
*/
|
||||
public function deleteDrive(): void
|
||||
{
|
||||
if (!$this->deletingDriveKey || !isset($this->drives[$this->deletingDriveKey])) {
|
||||
$this->notify('Drive not found.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
$driveKey = $this->deletingDriveKey;
|
||||
|
||||
// Check VM status
|
||||
if ($this->status && $this->status->status !== 'stopped') {
|
||||
$this->notify('VM must be stopped to delete drives.', 'error');
|
||||
@@ -752,6 +815,7 @@ class Storage extends Component
|
||||
}
|
||||
|
||||
$this->notify("Drive {$driveKey} deleted successfully.", 'success');
|
||||
$this->closeDeleteModal();
|
||||
$this->loadStorageConfiguration(false);
|
||||
} catch (\Exception $e) {
|
||||
$this->notify('Failed to delete drive: ' . $e->getMessage(), 'error');
|
||||
|
||||
@@ -135,8 +135,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SCSI Controller Configuration -->
|
||||
<div class="bg-background-secondary border border-neutral rounded-lg p-5 space-y-4">
|
||||
<!-- SCSI Controller Configuration (Advanced Only) -->
|
||||
<div class="relative bg-background-secondary border border-neutral rounded-lg p-5 space-y-4"
|
||||
x-data="{
|
||||
isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced'
|
||||
}"
|
||||
x-init="
|
||||
window.addEventListener('proxmox-view-mode-changed', (e) => {
|
||||
isAdvancedView = e.detail.isAdvancedView;
|
||||
});
|
||||
"
|
||||
:class="{ 'opacity-50 pointer-events-none': !isAdvancedView }">
|
||||
<template x-if="!isAdvancedView">
|
||||
<div class="absolute inset-0 flex items-center justify-center z-10 bg-background-secondary/80 rounded-lg">
|
||||
<span class="text-sm text-base/60 font-medium">Enable Advanced View to configure SCSI controller</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -243,7 +257,15 @@
|
||||
@else
|
||||
<div class="divide-y divide-neutral">
|
||||
@foreach($drives as $driveKey => $drive)
|
||||
<div class="p-4 hover:bg-background/50 transition-colors">
|
||||
<div class="p-4 hover:bg-background/50 transition-colors"
|
||||
x-data="{
|
||||
isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced'
|
||||
}"
|
||||
x-init="
|
||||
window.addEventListener('proxmox-view-mode-changed', (e) => {
|
||||
isAdvancedView = e.detail.isAdvancedView;
|
||||
});
|
||||
">
|
||||
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 flex-shrink-0">
|
||||
@@ -253,43 +275,55 @@
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<h4 class="font-semibold text-base">{{ $drive['label'] }}</h4>
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded bg-neutral/30 text-base/70">{{ $driveKey }}</span>
|
||||
@if($drive['ssd'])
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded bg-success/20 text-success">SSD</span>
|
||||
@endif
|
||||
@if($drive['iothread'])
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded bg-info/20 text-info">IO Thread</span>
|
||||
@endif
|
||||
<template x-if="isAdvancedView">
|
||||
<span>
|
||||
@if($drive['ssd'])
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded bg-success/20 text-success">SSD</span>
|
||||
@endif
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="isAdvancedView">
|
||||
<span>
|
||||
@if($drive['iothread'])
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded bg-info/20 text-info">IO Thread</span>
|
||||
@endif
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1 mt-1 text-xs text-base/60">
|
||||
<span>{{ number_format($drive['size'], 1) }} GB</span>
|
||||
<span>Cache: {{ ucfirst($drive['cache']) }}</span>
|
||||
<span>AIO: {{ $drive['aio'] }}</span>
|
||||
@if($drive['discard'])
|
||||
<span class="text-success">Discard enabled</span>
|
||||
@endif
|
||||
<span x-show="isAdvancedView">Cache: {{ ucfirst($drive['cache']) }}</span>
|
||||
<span x-show="isAdvancedView">AIO: {{ $drive['aio'] }}</span>
|
||||
<template x-if="isAdvancedView">
|
||||
<span>
|
||||
@if($drive['discard'])
|
||||
<span class="text-success">Discard enabled</span>
|
||||
@endif
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<button wire:click="openEditDriveModal('{{ $driveKey }}')"
|
||||
x-show="isAdvancedView"
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-neutral bg-background hover:bg-background-secondary transition-colors">
|
||||
<x-ri-settings-3-line class="size-3.5" />
|
||||
Settings
|
||||
</button>
|
||||
<button wire:click="openMoveDriveModal('{{ $driveKey }}')"
|
||||
x-show="isAdvancedView"
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-neutral bg-background hover:bg-background-secondary transition-colors">
|
||||
<x-ri-arrow-left-right-line class="size-3.5" />
|
||||
Move
|
||||
</button>
|
||||
<button wire:click="detachDrive('{{ $driveKey }}')"
|
||||
wire:confirm="Are you sure you want to detach this drive? The data will be preserved but the drive will no longer be accessible."
|
||||
<button wire:click="openDetachModal('{{ $driveKey }}')"
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-warning/50 bg-warning/10 text-warning hover:bg-warning/20 transition-colors">
|
||||
<x-ri-eject-line class="size-3.5" />
|
||||
Detach
|
||||
</button>
|
||||
<button wire:click="deleteDrive('{{ $driveKey }}')"
|
||||
wire:confirm="Are you sure you want to DELETE this drive? This action cannot be undone and all data will be permanently lost!"
|
||||
<button wire:click="openDeleteModal('{{ $driveKey }}')"
|
||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-danger/50 bg-danger/10 text-danger hover:bg-danger/20 transition-colors">
|
||||
<x-ri-delete-bin-line class="size-3.5" />
|
||||
Delete
|
||||
@@ -591,4 +625,124 @@
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Detach Drive Confirmation Modal -->
|
||||
@if($showDetachModal && $detachingDriveKey && isset($drives[$detachingDriveKey]))
|
||||
@php $detachDrive = $drives[$detachingDriveKey]; @endphp
|
||||
<div class="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm" wire:click="closeDetachModal"></div>
|
||||
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||
<div class="relative w-full max-w-md bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl">
|
||||
<div class="flex items-center justify-between p-5 border-b border-neutral">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-warning/10">
|
||||
<x-ri-eject-line class="size-5 text-warning" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold">Detach Drive</h3>
|
||||
</div>
|
||||
<button wire:click="closeDetachModal" class="text-base/50 hover:text-base transition-colors">
|
||||
<x-ri-close-line class="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4">
|
||||
<div class="p-3 rounded-lg bg-neutral/20 text-sm">
|
||||
<p><span class="text-base/50">Drive:</span> {{ $detachDrive['label'] }} ({{ $detachingDriveKey }})</p>
|
||||
<p class="mt-1"><span class="text-base/50">Size:</span> {{ number_format($detachDrive['size'], 1) }} GB</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-2 p-3 rounded-lg bg-warning/10 border border-warning/30 text-warning text-sm">
|
||||
<x-ri-alert-line class="size-4 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p class="font-medium">Are you sure you want to detach this drive?</p>
|
||||
<p class="mt-1 text-warning/80">The data will be preserved but the drive will no longer be accessible to the VM until reattached.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($status && $status->status !== 'stopped')
|
||||
<div class="flex items-center gap-2 p-3 rounded-lg bg-danger/10 border border-danger/30 text-danger text-sm">
|
||||
<x-ri-error-warning-line class="size-4 flex-shrink-0" />
|
||||
<span>VM must be stopped to detach drives.</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 p-5 border-t border-neutral">
|
||||
<button wire:click="closeDetachModal"
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg border border-neutral bg-background hover:bg-background-secondary transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
<button wire:click="detachDrive"
|
||||
wire:loading.attr="disabled"
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg bg-warning text-white hover:bg-warning/90 disabled:opacity-50 transition-colors"
|
||||
@if($status && $status->status !== 'stopped') disabled @endif>
|
||||
<x-loading target="detachDrive" />
|
||||
<span wire:loading.remove wire:target="detachDrive">Detach Drive</span>
|
||||
<span wire:loading wire:target="detachDrive">Detaching...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Delete Drive Confirmation Modal -->
|
||||
@if($showDeleteModal && $deletingDriveKey && isset($drives[$deletingDriveKey]))
|
||||
@php $deleteDrive = $drives[$deletingDriveKey]; @endphp
|
||||
<div class="fixed inset-0 z-50 overflow-y-auto">
|
||||
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm" wire:click="closeDeleteModal"></div>
|
||||
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||
<div class="relative w-full max-w-md bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl">
|
||||
<div class="flex items-center justify-between p-5 border-b border-neutral">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-danger/10">
|
||||
<x-ri-delete-bin-line class="size-5 text-danger" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold">Delete Drive</h3>
|
||||
</div>
|
||||
<button wire:click="closeDeleteModal" class="text-base/50 hover:text-base transition-colors">
|
||||
<x-ri-close-line class="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4">
|
||||
<div class="p-3 rounded-lg bg-neutral/20 text-sm">
|
||||
<p><span class="text-base/50">Drive:</span> {{ $deleteDrive['label'] }} ({{ $deletingDriveKey }})</p>
|
||||
<p class="mt-1"><span class="text-base/50">Size:</span> {{ number_format($deleteDrive['size'], 1) }} GB</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-start gap-2 p-3 rounded-lg bg-danger/10 border border-danger/30 text-danger text-sm">
|
||||
<x-ri-error-warning-line class="size-4 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p class="font-medium">Are you sure you want to DELETE this drive?</p>
|
||||
<p class="mt-1 text-danger/80">This action cannot be undone. All data on this drive will be permanently lost!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($status && $status->status !== 'stopped')
|
||||
<div class="flex items-center gap-2 p-3 rounded-lg bg-danger/10 border border-danger/30 text-danger text-sm">
|
||||
<x-ri-error-warning-line class="size-4 flex-shrink-0" />
|
||||
<span>VM must be stopped to delete drives.</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3 p-5 border-t border-neutral">
|
||||
<button wire:click="closeDeleteModal"
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg border border-neutral bg-background hover:bg-background-secondary transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
<button wire:click="deleteDrive"
|
||||
wire:loading.attr="disabled"
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg bg-danger text-white hover:bg-danger/90 disabled:opacity-50 transition-colors"
|
||||
@if($status && $status->status !== 'stopped') disabled @endif>
|
||||
<x-loading target="deleteDrive" />
|
||||
<span wire:loading.remove wire:target="deleteDrive">Delete Drive</span>
|
||||
<span wire:loading wire:target="deleteDrive">Deleting...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</x-proxmox::layout>
|
||||
|
||||
Reference in New Issue
Block a user