|
@@ -198,6 +198,9 @@ function displayVMDetails(vm, vmName) {
|
|
|
<button class="btn btn-sm btn-info btn-action" onclick="openAddDiskModal('${vmName}')" ${vm.state !== 'shut off' ? 'disabled' : ''}>
|
|
<button class="btn btn-sm btn-info btn-action" onclick="openAddDiskModal('${vmName}')" ${vm.state !== 'shut off' ? 'disabled' : ''}>
|
|
|
<i class="bi bi-plus-circle"></i> Agregar Disco
|
|
<i class="bi bi-plus-circle"></i> Agregar Disco
|
|
|
</button>
|
|
</button>
|
|
|
|
|
+ <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="downloadVMConfig('${vmName}')">
|
|
<button class="btn btn-sm btn-secondary btn-action" onclick="downloadVMConfig('${vmName}')">
|
|
|
<i class="bi bi-download"></i> Descargar XML
|
|
<i class="bi bi-download"></i> Descargar XML
|
|
|
</button>
|
|
</button>
|
|
@@ -729,3 +732,92 @@ async function downloadVMConfig(vmName) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// Open snapshot modal
|
|
|
|
|
+function openSnapshotModal(vmName) {
|
|
|
|
|
+ const modal = document.createElement('div');
|
|
|
|
|
+ modal.classList.add('modal', 'fade');
|
|
|
|
|
+ modal.innerHTML = `
|
|
|
|
|
+ <div class="modal-dialog">
|
|
|
|
|
+ <div class="modal-content">
|
|
|
|
|
+ <div class="modal-header">
|
|
|
|
|
+ <h5 class="modal-title">Crear Snapshot - ${vmName}</h5>
|
|
|
|
|
+ <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="modal-body">
|
|
|
|
|
+ <div class="mb-3">
|
|
|
|
|
+ <label for="snapshotName" class="form-label">Nombre del Snapshot</label>
|
|
|
|
|
+ <input type="text" class="form-control" id="snapshotName" placeholder="ej: snapshot-2026-01-18" required>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="mb-3">
|
|
|
|
|
+ <label for="snapshotDesc" class="form-label">Descripción (opcional)</label>
|
|
|
|
|
+ <textarea class="form-control" id="snapshotDesc" rows="3" placeholder="Describe el propósito de este snapshot"></textarea>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="modal-footer">
|
|
|
|
|
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
|
|
|
+ <button type="button" class="btn btn-warning" onclick="createSnapshot('${vmName}')">
|
|
|
|
|
+ <i class="bi bi-camera"></i> Crear Snapshot
|
|
|
|
|
+ </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();
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Create snapshot
|
|
|
|
|
+async function createSnapshot(vmName) {
|
|
|
|
|
+ const snapshotName = document.getElementById('snapshotName').value.trim();
|
|
|
|
|
+ const snapshotDesc = document.getElementById('snapshotDesc').value.trim();
|
|
|
|
|
+
|
|
|
|
|
+ if (!snapshotName) {
|
|
|
|
|
+ alert('El nombre del snapshot es obligatorio');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Validar que el nombre sea válido (sin espacios ni caracteres especiales)
|
|
|
|
|
+ if (!/^[a-zA-Z0-9._-]+$/.test(snapshotName)) {
|
|
|
|
|
+ alert('El nombre del snapshot solo puede contener letras, números, puntos, guiones y guiones bajos');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const url = new URL(`${API_BASE}/vms/${vmName}/snapshot`);
|
|
|
|
|
+ url.searchParams.append('snapshot_name', snapshotName);
|
|
|
|
|
+ if (snapshotDesc) {
|
|
|
|
|
+ url.searchParams.append('description', snapshotDesc);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const response = await fetch(url, { method: 'POST' });
|
|
|
|
|
+ const data = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (data.error) {
|
|
|
|
|
+ alert(`Error: ${data.error}`);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ alert(`✓ Snapshot "${snapshotName}" creado exitosamente`);
|
|
|
|
|
+
|
|
|
|
|
+ // Cerrar modal - buscar el modal visible
|
|
|
|
|
+ const visibleModal = document.querySelector('.modal.show');
|
|
|
|
|
+ if (visibleModal) {
|
|
|
|
|
+ const modalInstance = bootstrap.Modal.getInstance(visibleModal);
|
|
|
|
|
+ if (modalInstance) {
|
|
|
|
|
+ modalInstance.hide();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Error:', error);
|
|
|
|
|
+ alert(`Error al crear snapshot: ${error.message}`);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|