|
|
@@ -201,6 +201,9 @@ function displayVMDetails(vm, vmName) {
|
|
|
<button class="btn btn-sm btn-warning btn-action" onclick="openSnapshotModal('${vmName}')">
|
|
|
<i class="bi bi-camera"></i> Crear Snapshot
|
|
|
</button>
|
|
|
+ <button class="btn btn-sm btn-secondary btn-action" onclick="openSnapshotsListModal('${vmName}')">
|
|
|
+ <i class="bi bi-collection"></i> Ver Snapshots
|
|
|
+ </button>
|
|
|
<button class="btn btn-sm btn-secondary btn-action" onclick="downloadVMConfig('${vmName}')">
|
|
|
<i class="bi bi-download"></i> Descargar XML
|
|
|
</button>
|
|
|
@@ -821,3 +824,136 @@ async function createSnapshot(vmName) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// Open snapshots list modal
|
|
|
+function openSnapshotsListModal(vmName) {
|
|
|
+ const modal = document.createElement('div');
|
|
|
+ modal.classList.add('modal', 'fade');
|
|
|
+ modal.innerHTML = `
|
|
|
+ <div class="modal-dialog modal-lg">
|
|
|
+ <div class="modal-content">
|
|
|
+ <div class="modal-header">
|
|
|
+ <h5 class="modal-title">Snapshots - ${vmName}</h5>
|
|
|
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
|
+ </div>
|
|
|
+ <div class="modal-body">
|
|
|
+ <div id="snapshotsList" class="text-center">
|
|
|
+ <p class="text-muted">Cargando snapshots...</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="modal-footer">
|
|
|
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ document.body.appendChild(modal);
|
|
|
+ const bsModal = new bootstrap.Modal(modal);
|
|
|
+ bsModal.show();
|
|
|
+
|
|
|
+ // Limpiar modal al cerrarlo
|
|
|
+ modal.addEventListener('hidden.bs.modal', () => {
|
|
|
+ modal.remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ // Cargar snapshots
|
|
|
+ loadSnapshotsList(vmName);
|
|
|
+}
|
|
|
+
|
|
|
+// Load and display snapshots list
|
|
|
+async function loadSnapshotsList(vmName) {
|
|
|
+ try {
|
|
|
+ const response = await fetch(`${API_BASE}/vms/${vmName}/snapshots`);
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ if (data.error) {
|
|
|
+ document.getElementById('snapshotsList').innerHTML = `<p class="text-danger">Error: ${data.error}</p>`;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const snapshots = data.snapshots;
|
|
|
+
|
|
|
+ if (!snapshots || snapshots.length === 0) {
|
|
|
+ document.getElementById('snapshotsList').innerHTML = '<p class="text-muted">No hay snapshots disponibles</p>';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let html = `
|
|
|
+ <div class="table-responsive">
|
|
|
+ <table class="table table-sm table-hover">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>Nombre</th>
|
|
|
+ <th>Creado</th>
|
|
|
+ <th>Descripción</th>
|
|
|
+ <th>Estado</th>
|
|
|
+ <th>Acciones</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ `;
|
|
|
+
|
|
|
+ snapshots.forEach(snap => {
|
|
|
+ let created = 'Desconocida';
|
|
|
+ if (snap.created && snap.created > 0) {
|
|
|
+ created = new Date(snap.created * 1000).toLocaleString('es-ES');
|
|
|
+ }
|
|
|
+ html += `
|
|
|
+ <tr>
|
|
|
+ <td><strong>${snap.name}</strong></td>
|
|
|
+ <td>${created}</td>
|
|
|
+ <td>${snap.description || '-'}</td>
|
|
|
+ <td><span class="badge bg-info">${snap.state}</span></td>
|
|
|
+ <td>
|
|
|
+ <button class="btn btn-sm btn-danger" onclick="deleteSnapshotConfirm('${vmName}', '${snap.name}')">
|
|
|
+ <i class="bi bi-trash"></i> Eliminar
|
|
|
+ </button>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ `;
|
|
|
+ });
|
|
|
+
|
|
|
+ html += `
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ document.getElementById('snapshotsList').innerHTML = html;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error:', error);
|
|
|
+ document.getElementById('snapshotsList').innerHTML = `<p class="text-danger">Error: ${error.message}</p>`;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Delete snapshot with confirmation
|
|
|
+function deleteSnapshotConfirm(vmName, snapshotName) {
|
|
|
+ if (confirm(`¿Estás seguro de que deseas eliminar el snapshot "${snapshotName}"?`)) {
|
|
|
+ deleteSnapshot(vmName, snapshotName);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Delete snapshot
|
|
|
+async function deleteSnapshot(vmName, snapshotName) {
|
|
|
+ try {
|
|
|
+ const encodedSnapshotName = encodeURIComponent(snapshotName);
|
|
|
+ const response = await fetch(`${API_BASE}/vms/${vmName}/snapshots/${encodedSnapshotName}`, {
|
|
|
+ method: 'DELETE'
|
|
|
+ });
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ if (data.error) {
|
|
|
+ alert(`Error: ${data.error}`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ alert(`✓ Snapshot "${snapshotName}" eliminado exitosamente`);
|
|
|
+
|
|
|
+ // Recargar lista de snapshots
|
|
|
+ loadSnapshotsList(vmName);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error:', error);
|
|
|
+ alert(`Error al eliminar snapshot: ${error.message}`);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|