diff --git a/debug_project_id.py b/debug_project_id.py new file mode 100644 index 0000000..84b58f8 --- /dev/null +++ b/debug_project_id.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +"""Debug script to check project IDs""" + +import sys +from pathlib import Path + +# Add src directory to path +current_dir = Path(__file__).parent +src_dir = current_dir / "src" +sys.path.insert(0, str(src_dir)) + +from models.config_model import Config +from models.project_model import ProjectManager +from services.project_discovery_service import ProjectDiscoveryService + +# Initialize services +config = Config() +config.load_config() +project_manager = ProjectManager() +project_discovery_service = ProjectDiscoveryService(config, project_manager) + +# Discover projects +projects = project_discovery_service.discover_all_projects() + +print("Available project IDs:") +found = False +for project_id, project in project_manager.projects.items(): + if "Ssae0452 Last Version Walter" in project.name: + print(f"Found project:") + print(f" ID: {project_id}") + print(f" Name: {project.name}") + print(f" Path: {project.path}") + found = True + break + +if not found: + print('Project "Ssae0452 Last Version Walter" not found!') + print('Available projects containing "Ssae":') + count = 0 + for project_id, project in project_manager.projects.items(): + if "Ssae" in project.name: + print(f" ID: {project_id}, Name: {project.name}") + count += 1 + if count >= 10: # Limit output + break diff --git a/src/routes/api_routes.py b/src/routes/api_routes.py index 243ac1f..c393ec7 100644 --- a/src/routes/api_routes.py +++ b/src/routes/api_routes.py @@ -3,6 +3,8 @@ API Routes - AutoBackups Rutas de la API REST para operaciones AJAX """ +import os +from datetime import datetime from flask import request, jsonify @@ -543,3 +545,134 @@ def register_api_routes(app, autobackups_instance): f"Error in Everything-only discovery: {e}" ) return jsonify({"error": str(e)}), 500 + + @app.route("/api/projects//backups") + def get_project_backups(project_id): + """Get list of backup files for a specific project""" + try: + # Get the project + project = autobackups_instance.project_manager.get_project(project_id) + if not project: + return jsonify({"error": f"Project {project_id} not found"}), 404 + + # Get backup destination path for this project + backup_destination = autobackups_instance.config.backup_destination + backup_path = project.backup_path + + full_backup_path = os.path.join(backup_destination, backup_path) + + backups = [] + + if os.path.exists(full_backup_path): + # List date directories (format: YYYY-MM-DD) + try: + date_dirs = [ + d + for d in os.listdir(full_backup_path) + if os.path.isdir(os.path.join(full_backup_path, d)) + and len(d) == 10 + and d.count("-") == 2 + ] + + for date_dir in sorted(date_dirs, reverse=True): + date_path = os.path.join(full_backup_path, date_dir) + + # List backup files in date directory + try: + backup_files = [ + f for f in os.listdir(date_path) if f.endswith(".zip") + ] + + for backup_file in sorted(backup_files, reverse=True): + backup_file_path = os.path.join(date_path, backup_file) + try: + stat = os.stat(backup_file_path) + backups.append( + { + "filename": backup_file, + "date": date_dir, + "size": stat.st_size, + "size_mb": round( + stat.st_size / (1024 * 1024), 2 + ), + "created": datetime.fromtimestamp( + stat.st_ctime + ).isoformat(), + "modified": datetime.fromtimestamp( + stat.st_mtime + ).isoformat(), + "full_path": backup_file_path, + } + ) + except OSError: + continue + except OSError: + continue + except OSError: + pass + + return jsonify( + { + "project_id": project_id, + "project_name": project.name, + "project_path": project.path, + "backup_destination": full_backup_path, + "backup_count": len(backups), + "backups": backups, + } + ) + + except Exception as e: + autobackups_instance.logger.error( + f"Error getting backups for project {project_id}: {e}" + ) + return jsonify({"error": str(e)}), 500 + + @app.route("/api/projects//open-folder", methods=["POST"]) + def open_project_folder(project_id): + """Open project source or backup folder in Windows Explorer""" + try: + data = request.get_json() + folder_type = data.get("type", "source") # 'source' or 'backup' + + # Get the project + project = autobackups_instance.project_manager.get_project(project_id) + if not project: + return jsonify({"error": f"Project {project_id} not found"}), 404 + + if folder_type == "source": + folder_path = project.path + if not os.path.exists(folder_path): + return jsonify({"error": "Source folder does not exist"}), 404 + elif folder_type == "backup": + backup_destination = autobackups_instance.config.backup_destination + backup_path = project.backup_path + folder_path = os.path.join(backup_destination, backup_path) + if not os.path.exists(folder_path): + return jsonify({"error": "Backup folder does not exist"}), 404 + else: + return ( + jsonify( + {"error": "Invalid folder type. Must be 'source' or 'backup'"} + ), + 400, + ) + + # Open folder in Windows Explorer + import subprocess + + subprocess.run(["explorer", folder_path], shell=True) + + return jsonify( + { + "success": True, + "folder_path": folder_path, + "folder_type": folder_type, + } + ) + + except Exception as e: + autobackups_instance.logger.error( + f"Error opening folder for project {project_id}: {e}" + ) + return jsonify({"error": str(e)}), 500 diff --git a/static/css/styles.css b/static/css/styles.css index 3306199..ad41415 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -475,26 +475,27 @@ body { .directory-header { display: flex; align-items: center; - padding: 0.5rem; - border-radius: 0.375rem; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; background-color: var(--hover-bg); border: 1px solid var(--border-color); cursor: pointer; transition: background-color 0.2s ease; + margin-bottom: 0.25rem; } .observation-header { display: flex; align-items: center; - padding: 0.75rem; - border-radius: 0.5rem; + padding: 0.5rem 0.75rem; + border-radius: 0.375rem; background-color: rgba(13, 110, 253, 0.1); border: 2px solid var(--primary-color); cursor: pointer; transition: all 0.2s ease; font-weight: 600; color: var(--primary-color); - margin-bottom: 0.75rem; + margin-bottom: 0.5rem; } .observation-header:hover { @@ -505,15 +506,15 @@ body { .collapsed-header { display: flex; align-items: center; - padding: 0.25rem 0.5rem; - border-radius: 0.25rem; + padding: 0.2rem 0.4rem; + border-radius: 0.2rem; background-color: rgba(108, 117, 125, 0.1); border: 1px dashed var(--text-muted); cursor: pointer; transition: background-color 0.2s ease; font-style: italic; color: var(--text-muted); - margin-bottom: 0.25rem; + margin-bottom: 0.2rem; } .collapsed-header:hover { @@ -538,8 +539,8 @@ body { } .directory-content { - margin-top: 0.5rem; - padding-left: 1rem; + margin-top: 0.25rem; + padding-left: 0.75rem; border-left: 2px solid var(--border-color); } diff --git a/templates/dashboard.html b/templates/dashboard.html index aeced51..43d1aea 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -115,7 +115,6 @@ Proyecto - Ruta Tipo Estado Último Backup @@ -133,12 +132,7 @@ {{ project.name or 'Sin nombre' }}
- ID: {{ project.id }} - - - - {{ project.path[:50] }}{% if project.path|length > 50 %}...{% endif %} - + {{ project.path }} {{ project.type or 'N/A' }} @@ -180,8 +174,10 @@ data-project-id="{{ project.id }}"> - @@ -884,14 +880,17 @@ function renderProjectCard(project, level) { return `
-
+
${projectName}
- ID: ${projectId} + + + ${projectPath} +
${projectType} @@ -902,28 +901,25 @@ function renderProjectCard(project, level) {
-
-
-
- - - ${projectPath} - -
-
`; @@ -976,6 +972,46 @@ function displayFlatProjects(filteredProjects) { row.style.display = (matchesSearch && matchesStatus) ? '' : 'none'; }); + + // Bind events for flat view buttons + bindFlatViewEvents(); +} + +function bindFlatViewEvents() { + // Bind backup buttons + document.querySelectorAll('.backup-project-btn').forEach(btn => { + btn.replaceWith(btn.cloneNode(true)); // Remove existing listeners + }); + document.querySelectorAll('.backup-project-btn').forEach(btn => { + btn.addEventListener('click', function() { + const projectId = this.dataset.projectId; + backupProject(projectId); + }); + }); + + // Bind config buttons + document.querySelectorAll('.config-project-btn').forEach(btn => { + btn.replaceWith(btn.cloneNode(true)); // Remove existing listeners + }); + document.querySelectorAll('.config-project-btn').forEach(btn => { + btn.addEventListener('click', function() { + const projectId = this.dataset.projectId; + openProjectConfig(projectId); + }); + }); + + // Bind view backups buttons + document.querySelectorAll('.view-backups-btn').forEach(btn => { + btn.replaceWith(btn.cloneNode(true)); // Remove existing listeners + }); + document.querySelectorAll('.view-backups-btn').forEach(btn => { + btn.addEventListener('click', function() { + const projectId = this.dataset.projectId; + const projectName = this.dataset.projectName; + const projectPath = this.dataset.projectPath; + showBackupsModal(projectId, projectName, projectPath); + }); + }); } function bindHierarchicalEvents() { @@ -1012,6 +1048,16 @@ function bindHierarchicalEvents() { openProjectConfig(projectId); }); }); + + // Re-bind view backups buttons + document.querySelectorAll('.view-backups-btn').forEach(btn => { + btn.addEventListener('click', function() { + const projectId = this.dataset.projectId; + const projectName = this.dataset.projectName; + const projectPath = this.dataset.projectPath; + showBackupsModal(projectId, projectName, projectPath); + }); + }); } // Global expand/collapse functionality @@ -1052,6 +1098,182 @@ function initializeExpandCollapseButton() { } } +// Show backups modal with backup list and folder actions +function showBackupsModal(projectId, projectName, projectPath) { + // Create modal if it doesn't exist + let modal = document.getElementById('backupsModal'); + if (!modal) { + modal = createBackupsModal(); + document.body.appendChild(modal); + } + + // Update modal content + document.getElementById('backupsModalTitle').textContent = `Backups de ${projectName}`; + document.getElementById('backupsProjectPath').textContent = projectPath; + + // Set up folder buttons + const openSourceBtn = document.getElementById('openSourceFolderBtn'); + const openBackupBtn = document.getElementById('openBackupFolderBtn'); + + openSourceBtn.onclick = () => openProjectFolder(projectId, 'source'); + openBackupBtn.onclick = () => openProjectFolder(projectId, 'backup'); + + // Load backup list + loadBackupList(projectId); + + // Show modal + const bsModal = new bootstrap.Modal(modal); + bsModal.show(); +} + +function createBackupsModal() { + const modalHTML = ` + + `; + + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = modalHTML; + return tempDiv.firstElementChild; +} + +function loadBackupList(projectId) { + const container = document.getElementById('backupListContainer'); + + fetch(`/api/projects/${projectId}/backups`) + .then(response => response.json()) + .then(data => { + if (data.error) { + container.innerHTML = ` +
+ + Error: ${data.error} +
+ `; + return; + } + + if (data.backup_count === 0) { + container.innerHTML = ` +
+ + No se encontraron backups para este proyecto. +
+ `; + return; + } + + // Build backup list + let html = ` +
Backups encontrados (${data.backup_count})
+
+ + + + + + + + + + + `; + + data.backups.forEach(backup => { + const createdDate = new Date(backup.created).toLocaleString('es-ES'); + html += ` + + + + + + + `; + }); + + html += ` + +
FechaArchivoTamañoCreado
${backup.date} + + ${backup.filename} + ${backup.size_mb} MB${createdDate}
+
+ + + Ubicación: ${data.backup_destination} + + `; + + container.innerHTML = html; + }) + .catch(error => { + console.error('Error loading backup list:', error); + container.innerHTML = ` +
+ + Error cargando la lista de backups: ${error.message} +
+ `; + }); +} + +function openProjectFolder(projectId, folderType) { + fetch(`/api/projects/${projectId}/open-folder`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ type: folderType }) + }) + .then(response => response.json()) + .then(data => { + if (data.error) { + showNotification(`Error: ${data.error}`, 'error'); + } else { + const folderName = folderType === 'source' ? 'origen' : 'backups'; + showNotification(`Carpeta ${folderName} abierta en el explorador`, 'success'); + } + }) + .catch(error => { + console.error('Error opening folder:', error); + showNotification(`Error abriendo la carpeta: ${error.message}`, 'error'); + }); +} + // Initialize on page load document.addEventListener('DOMContentLoaded', function() { initializeProjectsView();