This commit is contained in:
@@ -317,6 +317,9 @@ async function loadSnapshots() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 ml-4">
|
||||
<button onclick="restoreSnapshot('${snap.name}', '${snap.dataset}')" class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm">
|
||||
Restore
|
||||
</button>
|
||||
<button onclick="deleteSnapshot('${snap.name}')" class="px-3 py-1.5 bg-red-600 hover:bg-red-700 text-white rounded text-sm">
|
||||
Delete
|
||||
</button>
|
||||
@@ -393,6 +396,48 @@ async function createSnapshot(e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Restore snapshot function - must be in global scope
|
||||
window.restoreSnapshot = async function(snapshotName, datasetName) {
|
||||
const warning = `WARNING: This will rollback dataset "${datasetName}" to snapshot "${snapshotName}".\n\n` +
|
||||
`This action will:\n` +
|
||||
`- Discard all changes made after this snapshot\n` +
|
||||
`- Cannot be undone\n\n` +
|
||||
`Are you sure you want to continue?`;
|
||||
|
||||
if (!confirm(warning)) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/v1/snapshots/${encodeURIComponent(snapshotName)}/restore`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ force: false })
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
// Reload both snapshot lists
|
||||
if (typeof loadSnapshots === 'function') loadSnapshots();
|
||||
if (typeof loadVolumeSnapshots === 'function') loadVolumeSnapshots();
|
||||
alert('Snapshot restored successfully');
|
||||
} else {
|
||||
const data = await res.json();
|
||||
let errMsg = 'Failed to restore snapshot';
|
||||
if (data) {
|
||||
if (data.message) {
|
||||
errMsg = data.message;
|
||||
if (data.details) {
|
||||
errMsg += ': ' + data.details;
|
||||
}
|
||||
} else if (data.error) {
|
||||
errMsg = data.error;
|
||||
}
|
||||
}
|
||||
alert(`Error: ${errMsg}`);
|
||||
}
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteSnapshot(name) {
|
||||
if (!confirm(`Are you sure you want to delete snapshot "${name}"?`)) return;
|
||||
|
||||
@@ -613,6 +658,9 @@ async function loadVolumeSnapshots() {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 ml-4">
|
||||
<button onclick="restoreSnapshot('${snap.name}', '${snap.dataset}')" class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm">
|
||||
Restore
|
||||
</button>
|
||||
<button onclick="deleteSnapshot('${snap.name}')" class="px-3 py-1.5 bg-red-600 hover:bg-red-700 text-white rounded text-sm">
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -630,10 +630,16 @@ async function loadDisks() {
|
||||
<div class="flex flex-col items-center justify-center pt-2">
|
||||
<div class="w-20 h-20 rounded-xl bg-gradient-to-br ${isAvailable ? 'from-slate-600 to-slate-700' : 'from-slate-700 to-slate-800'} flex items-center justify-center mb-3 border-2 border-slate-500/50 ${isAvailable ? 'group-hover:border-blue-500/70 group-hover:from-blue-600/30 group-hover:to-slate-700' : ''} transition-all shadow-inner">
|
||||
<!-- Disk Icon -->
|
||||
<svg class="w-12 h-12 ${isAvailable ? 'text-slate-200 group-hover:text-blue-400' : 'text-slate-400'} transition-colors" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M4 3a2 2 0 100 4h12a2 2 0 100-4H4z"></path>
|
||||
<path fill-rule="evenodd" d="M3 8h14v7a2 2 0 01-2 2H5a2 2 0 01-2-2V8zm5 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
${isAvailable ? `
|
||||
<svg class="w-12 h-12 text-slate-200 group-hover:text-blue-400 transition-colors" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M4 3a2 2 0 100 4h12a2 2 0 100-4H4z"></path>
|
||||
<path fill-rule="evenodd" d="M3 8h14v7a2 2 0 01-2 2H5a2 2 0 01-2-2V8zm5 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
` : `
|
||||
<svg class="w-12 h-12 text-red-400 transition-colors" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.368zm1.414-1.414L6.524 5.11a6 6 0 018.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<!-- Device Name -->
|
||||
@@ -641,6 +647,15 @@ async function loadDisks() {
|
||||
<div class="${isAvailable ? 'text-slate-100 group-hover:text-blue-400' : 'text-slate-400'} font-bold text-base mb-1 transition-colors font-mono">
|
||||
${disk.name}
|
||||
</div>
|
||||
${disk.health_status ? `
|
||||
<div class="mb-1">
|
||||
<span class="px-1.5 py-0.5 rounded text-xs font-medium ${
|
||||
disk.health_status === 'healthy' ? 'bg-green-600 text-white' :
|
||||
disk.health_status === 'failed' ? 'bg-red-600 text-white' :
|
||||
'bg-yellow-600 text-white'
|
||||
}">${disk.health_status === 'healthy' ? '✓ Healthy' : disk.health_status === 'failed' ? '✗ Failed' : '? Unknown'}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="${isAvailable ? 'text-slate-300' : 'text-slate-500'} text-xs font-semibold mb-1">
|
||||
${disk.size || 'N/A'}
|
||||
</div>
|
||||
@@ -680,11 +695,17 @@ async function loadDisks() {
|
||||
</div>
|
||||
|
||||
<!-- Disk Icon -->
|
||||
<div class="w-12 h-12 rounded-lg bg-slate-700 border border-slate-600 flex items-center justify-center flex-shrink-0">
|
||||
<svg class="w-6 h-6 text-slate-300" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M4 3a2 2 0 100 4h12a2 2 0 100-4H4z"></path>
|
||||
<path fill-rule="evenodd" d="M3 8h14v7a2 2 0 01-2 2H5a2 2 0 01-2-2V8zm5 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<div class="w-12 h-12 rounded-lg ${isAvailable ? 'bg-slate-700 border-slate-600' : 'bg-slate-800 border-slate-700 opacity-60'} border flex items-center justify-center flex-shrink-0">
|
||||
${isAvailable ? `
|
||||
<svg class="w-6 h-6 text-slate-300" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M4 3a2 2 0 100 4h12a2 2 0 100-4H4z"></path>
|
||||
<path fill-rule="evenodd" d="M3 8h14v7a2 2 0 01-2 2H5a2 2 0 01-2-2V8zm5 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
` : `
|
||||
<svg class="w-6 h-6 text-red-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.368zm1.414-1.414L6.524 5.11a6 6 0 018.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<!-- Disk Info -->
|
||||
@@ -711,14 +732,36 @@ async function loadDisks() {
|
||||
<span class="text-slate-400">Slot:</span>
|
||||
<span class="text-white ml-2 font-semibold">#${slotNumber}</span>
|
||||
</div>
|
||||
${disk.health_status ? `
|
||||
<div>
|
||||
<span class="text-slate-400">Health:</span>
|
||||
<span class="ml-2 px-2 py-0.5 rounded text-xs font-medium ${
|
||||
disk.health_status === 'healthy' ? 'bg-green-600 text-white' :
|
||||
disk.health_status === 'failed' ? 'bg-red-600 text-white' :
|
||||
'bg-yellow-600 text-white'
|
||||
}">${disk.health_status === 'healthy' ? 'Healthy' : disk.health_status === 'failed' ? 'Failed' : 'Unknown'}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${disk.health_temperature ? `
|
||||
<div>
|
||||
<span class="text-slate-400">Temp:</span>
|
||||
<span class="text-white ml-2">${disk.health_temperature}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${disk.health_power_on_hours ? `
|
||||
<div>
|
||||
<span class="text-slate-400">Power On:</span>
|
||||
<span class="text-white ml-2">${parseInt(disk.health_power_on_hours).toLocaleString()}h</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex gap-2">
|
||||
<button onclick="useDiskForPool('${diskPath}')"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm transition-colors">
|
||||
<button ${isAvailable ? `onclick="useDiskForPool('${diskPath}')"` : 'disabled'}
|
||||
class="px-3 py-1.5 ${isAvailable ? 'bg-blue-600 hover:bg-blue-700 text-white cursor-pointer' : 'bg-slate-600 text-slate-400 cursor-not-allowed'} rounded text-sm transition-colors">
|
||||
Use for Pool
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user