feat: Add API endpoints for project backups and folder management
This commit is contained in:
parent
958d8ac994
commit
92f8ff8f62
|
@ -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
|
|
@ -3,6 +3,8 @@ API Routes - AutoBackups
|
||||||
Rutas de la API REST para operaciones AJAX
|
Rutas de la API REST para operaciones AJAX
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
from flask import request, jsonify
|
from flask import request, jsonify
|
||||||
|
|
||||||
|
|
||||||
|
@ -543,3 +545,134 @@ def register_api_routes(app, autobackups_instance):
|
||||||
f"Error in Everything-only discovery: {e}"
|
f"Error in Everything-only discovery: {e}"
|
||||||
)
|
)
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route("/api/projects/<project_id>/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/<project_id>/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
|
||||||
|
|
|
@ -475,26 +475,27 @@ body {
|
||||||
.directory-header {
|
.directory-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.25rem;
|
||||||
background-color: var(--hover-bg);
|
background-color: var(--hover-bg);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.observation-header {
|
.observation-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.375rem;
|
||||||
background-color: rgba(13, 110, 253, 0.1);
|
background-color: rgba(13, 110, 253, 0.1);
|
||||||
border: 2px solid var(--primary-color);
|
border: 2px solid var(--primary-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.observation-header:hover {
|
.observation-header:hover {
|
||||||
|
@ -505,15 +506,15 @@ body {
|
||||||
.collapsed-header {
|
.collapsed-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.2rem 0.4rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.2rem;
|
||||||
background-color: rgba(108, 117, 125, 0.1);
|
background-color: rgba(108, 117, 125, 0.1);
|
||||||
border: 1px dashed var(--text-muted);
|
border: 1px dashed var(--text-muted);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsed-header:hover {
|
.collapsed-header:hover {
|
||||||
|
@ -538,8 +539,8 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.directory-content {
|
.directory-content {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.25rem;
|
||||||
padding-left: 1rem;
|
padding-left: 0.75rem;
|
||||||
border-left: 2px solid var(--border-color);
|
border-left: 2px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,7 +115,6 @@
|
||||||
<thead class="table-dark">
|
<thead class="table-dark">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Proyecto</th>
|
<th>Proyecto</th>
|
||||||
<th>Ruta</th>
|
|
||||||
<th>Tipo</th>
|
<th>Tipo</th>
|
||||||
<th>Estado</th>
|
<th>Estado</th>
|
||||||
<th>Último Backup</th>
|
<th>Último Backup</th>
|
||||||
|
@ -133,12 +132,7 @@
|
||||||
<td>
|
<td>
|
||||||
<strong>{{ project.name or 'Sin nombre' }}</strong>
|
<strong>{{ project.name or 'Sin nombre' }}</strong>
|
||||||
<br>
|
<br>
|
||||||
<small class="text-muted">ID: {{ project.id }}</small>
|
<small class="text-muted">{{ project.path }}</small>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span title="{{ project.path }}">
|
|
||||||
{{ project.path[:50] }}{% if project.path|length > 50 %}...{% endif %}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-secondary">{{ project.type or 'N/A' }}</span>
|
<span class="badge bg-secondary">{{ project.type or 'N/A' }}</span>
|
||||||
|
@ -180,8 +174,10 @@
|
||||||
data-project-id="{{ project.id }}">
|
data-project-id="{{ project.id }}">
|
||||||
<i class="bi bi-gear"></i>
|
<i class="bi bi-gear"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-info view-project-btn"
|
<button class="btn btn-outline-info view-backups-btn"
|
||||||
data-project-id="{{ project.id }}">
|
data-project-id="{{ project.id }}"
|
||||||
|
data-project-name="{{ project.name or 'Sin nombre' }}"
|
||||||
|
data-project-path="{{ project.path }}">
|
||||||
<i class="bi bi-eye"></i>
|
<i class="bi bi-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -884,14 +880,17 @@ function renderProjectCard(project, level) {
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="project-card card mb-2" style="margin-left: ${level * 20}px;">
|
<div class="project-card card mb-2" style="margin-left: ${level * 20}px;">
|
||||||
<div class="card-body p-3">
|
<div class="card-body p-2">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h6 class="card-title mb-1">
|
<h6 class="card-title mb-1">
|
||||||
<i class="bi bi-file-earmark-code text-primary me-2"></i>
|
<i class="bi bi-file-earmark-code text-primary me-2"></i>
|
||||||
${projectName}
|
${projectName}
|
||||||
</h6>
|
</h6>
|
||||||
<small class="text-muted">ID: ${projectId}</small>
|
<small class="text-muted">
|
||||||
|
<i class="bi bi-folder me-1"></i>
|
||||||
|
${projectPath}
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<span class="badge bg-secondary">${projectType}</span>
|
<span class="badge bg-secondary">${projectType}</span>
|
||||||
|
@ -902,28 +901,25 @@ function renderProjectCard(project, level) {
|
||||||
<div class="col-md-2 text-end">
|
<div class="col-md-2 text-end">
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<button class="btn btn-outline-primary backup-project-btn"
|
<button class="btn btn-outline-primary backup-project-btn"
|
||||||
data-project-id="${projectId}">
|
data-project-id="${projectId}"
|
||||||
|
title="Hacer backup">
|
||||||
<i class="bi bi-archive"></i>
|
<i class="bi bi-archive"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-secondary config-project-btn"
|
<button class="btn btn-outline-secondary config-project-btn"
|
||||||
data-project-id="${projectId}">
|
data-project-id="${projectId}"
|
||||||
|
title="Configurar">
|
||||||
<i class="bi bi-gear"></i>
|
<i class="bi bi-gear"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-info view-project-btn"
|
<button class="btn btn-outline-info view-backups-btn"
|
||||||
data-project-id="${projectId}">
|
data-project-id="${projectId}"
|
||||||
|
data-project-name="${projectName}"
|
||||||
|
data-project-path="${projectPath}"
|
||||||
|
title="Ver backups y carpetas">
|
||||||
<i class="bi bi-eye"></i>
|
<i class="bi bi-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-2">
|
|
||||||
<div class="col-12">
|
|
||||||
<small class="text-muted">
|
|
||||||
<i class="bi bi-folder me-1"></i>
|
|
||||||
${projectPath}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -976,6 +972,46 @@ function displayFlatProjects(filteredProjects) {
|
||||||
|
|
||||||
row.style.display = (matchesSearch && matchesStatus) ? '' : 'none';
|
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() {
|
function bindHierarchicalEvents() {
|
||||||
|
@ -1012,6 +1048,16 @@ function bindHierarchicalEvents() {
|
||||||
openProjectConfig(projectId);
|
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
|
// 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 = `
|
||||||
|
<div class="modal fade" id="backupsModal" tabindex="-1" aria-labelledby="backupsModalTitle" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="backupsModalTitle">Backups del Proyecto</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong>Ruta del proyecto:</strong>
|
||||||
|
<p class="text-muted mb-2" id="backupsProjectPath"></p>
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<button type="button" class="btn btn-outline-primary btn-sm" id="openSourceFolderBtn">
|
||||||
|
<i class="bi bi-folder-fill me-1"></i>Abrir Carpeta Origen
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-info btn-sm" id="openBackupFolderBtn">
|
||||||
|
<i class="bi bi-archive-fill me-1"></i>Abrir Carpeta Backups
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div id="backupListContainer">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Cargando...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">Cargando lista de backups...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||||
|
Error: ${data.error}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.backup_count === 0) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
No se encontraron backups para este proyecto.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build backup list
|
||||||
|
let html = `
|
||||||
|
<h6>Backups encontrados (${data.backup_count})</h6>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-striped">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Fecha</th>
|
||||||
|
<th>Archivo</th>
|
||||||
|
<th>Tamaño</th>
|
||||||
|
<th>Creado</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
data.backups.forEach(backup => {
|
||||||
|
const createdDate = new Date(backup.created).toLocaleString('es-ES');
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td>${backup.date}</td>
|
||||||
|
<td>
|
||||||
|
<i class="bi bi-file-zip me-1"></i>
|
||||||
|
${backup.filename}
|
||||||
|
</td>
|
||||||
|
<td>${backup.size_mb} MB</td>
|
||||||
|
<td>${createdDate}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="bi bi-folder me-1"></i>
|
||||||
|
Ubicación: ${data.backup_destination}
|
||||||
|
</small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading backup list:', error);
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||||
|
Error cargando la lista de backups: ${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// Initialize on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
initializeProjectsView();
|
initializeProjectsView();
|
||||||
|
|
Loading…
Reference in New Issue