Explorar el Código

Crea snapshots

Celestino Rey hace 6 meses
padre
commit
7bbb7c49e2
Se han modificado 3 ficheros con 122 adiciones y 2 borrados
  1. 29 1
      app/main.py
  2. 92 0
      static/dashboard.js
  3. 1 1
      static/index.html

+ 29 - 1
app/main.py

@@ -491,4 +491,32 @@ async def get_vm_config(vm_name: str):
             "xml": xml_desc
             "xml": xml_desc
         }
         }
     except libvirt.libvirtError as e:
     except libvirt.libvirtError as e:
-        return {"error": f"Error de libvirt: {str(e)}"}
+        return {"error": f"Error de libvirt: {str(e)}"}
+
+@app.post("/vms/{vm_name}/snapshot")
+async def create_snapshot(vm_name: str, snapshot_name: str, description: str = ""):
+    """Create a snapshot of a VM"""
+    try:
+        conn = libvirt.open(None)
+        domain = conn.lookupByName(vm_name)
+        
+        # Create snapshot XML
+        snapshot_xml = f"""<domainsnapshot>
+            <name>{snapshot_name}</name>
+            <description>{description}</description>
+        </domainsnapshot>"""
+        
+        print(f"[SNAPSHOT] Creating snapshot '{snapshot_name}' for VM '{vm_name}'")
+        domain.snapshotCreateXML(snapshot_xml, 0)
+        conn.close()
+        
+        print(f"[SNAPSHOT] Snapshot '{snapshot_name}' created successfully")
+        return {"message": f"Snapshot '{snapshot_name}' creado exitosamente"}
+    except libvirt.libvirtError as e:
+        error_msg = str(e)
+        print(f"[SNAPSHOT] Error creating snapshot: {error_msg}")
+        return {"error": f"Error al crear snapshot: {error_msg}"}
+    except Exception as e:
+        error_msg = str(e)
+        print(f"[SNAPSHOT] Unexpected error: {error_msg}")
+        return {"error": f"Error: {error_msg}"}

+ 92 - 0
static/dashboard.js

@@ -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}`);
+    }
+}
+

+ 1 - 1
static/index.html

@@ -701,6 +701,6 @@
     </div>
     </div>
 
 
     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
-    <script src="/static/dashboard.js?v=2"></script>
+    <script src="/static/dashboard.js?v=4"></script>
 </body>
 </body>
 </html>
 </html>