384 lines
16 KiB
HTML
384 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Dashboard - AutoBackups{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<!-- Statistics Cards -->
|
|
<div class="col-12">
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card text-center bg-primary text-white">
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<i class="bi bi-folder2-open"></i> Total Proyectos
|
|
</h5>
|
|
<h2 class="card-text">{{ stats.total_projects }}</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center bg-success text-white">
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<i class="bi bi-check-circle"></i> Habilitados
|
|
</h5>
|
|
<h2 class="card-text">{{ stats.enabled_projects }}</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center bg-info text-white">
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<i class="bi bi-hdd"></i> Espacio Libre
|
|
</h5>
|
|
<h2 class="card-text">{{ "%.1f"|format(stats.free_space_mb) }} MB</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card text-center bg-warning text-white">
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<i class="bi bi-hdd-stack"></i> Espacio Total
|
|
</h5>
|
|
<h2 class="card-text">{{ "%.1f"|format(stats.total_space_mb) }} MB</h2>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Projects Table -->
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-list-task"></i> Proyectos
|
|
</h5>
|
|
<div>
|
|
<button class="btn btn-outline-primary btn-sm" id="refresh-projects-btn">
|
|
<i class="bi bi-arrow-clockwise"></i> Actualizar
|
|
</button>
|
|
<button class="btn btn-success btn-sm" id="backup-all-btn">
|
|
<i class="bi bi-archive"></i> Backup Todos
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if projects %}
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th>Proyecto</th>
|
|
<th>Ruta</th>
|
|
<th>Tipo</th>
|
|
<th>Estado</th>
|
|
<th>Último Backup</th>
|
|
<th>Próximo Backup</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for project in projects %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ project.name or 'Sin nombre' }}</strong>
|
|
<br>
|
|
<small class="text-muted">ID: {{ project.id }}</small>
|
|
</td>
|
|
<td>
|
|
<span title="{{ project.path }}">
|
|
{{ project.path[:50] }}{% if project.path|length > 50 %}...{% endif %}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary">{{ project.type or 'N/A' }}</span>
|
|
</td>
|
|
<td>
|
|
{% if project.current_status.value == 'ready' %}
|
|
<span class="badge bg-success">Listo</span>
|
|
{% elif project.current_status.value == 'backing_up' %}
|
|
<span class="badge bg-warning">En progreso</span>
|
|
{% elif project.current_status.value == 'error' %}
|
|
<span class="badge bg-danger">Error</span>
|
|
{% elif project.current_status.value == 'files_in_use' %}
|
|
<span class="badge bg-warning">Archivos en uso</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{{ project.current_status.value }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if project.last_backup_date %}
|
|
{{ project.last_backup_date[:19] }}
|
|
{% else %}
|
|
<span class="text-muted">Nunca</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if project.next_scheduled_backup %}
|
|
{{ project.next_scheduled_backup[:19] }}
|
|
{% else %}
|
|
<span class="text-muted">No programado</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button class="btn btn-outline-primary backup-project-btn"
|
|
data-project-id="{{ project.id }}">
|
|
<i class="bi bi-archive"></i>
|
|
</button>
|
|
<button class="btn btn-outline-secondary config-project-btn"
|
|
data-project-id="{{ project.id }}">
|
|
<i class="bi bi-gear"></i>
|
|
</button>
|
|
<button class="btn btn-outline-info view-project-btn"
|
|
data-project-id="{{ project.id }}">
|
|
<i class="bi bi-eye"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-folder-x display-1 text-muted"></i>
|
|
<h4 class="text-muted mt-3">No hay proyectos disponibles</h4>
|
|
<p class="text-muted">Ejecuta un escaneo para buscar proyectos en los directorios configurados.</p>
|
|
<button class="btn btn-primary" id="first-scan-btn">
|
|
<i class="bi bi-search"></i> Escanear Proyectos
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block modals %}
|
|
<!-- Project Configuration Modal -->
|
|
<div class="modal fade" id="projectConfigModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Configuración del Proyecto</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="projectConfigForm">
|
|
<input type="hidden" id="modal-project-id">
|
|
|
|
<div class="mb-3">
|
|
<label for="modal-project-schedule" class="form-label">Frecuencia de Backup</label>
|
|
<select class="form-select" id="modal-project-schedule">
|
|
<option value="manual">Manual</option>
|
|
<option value="daily">Diario</option>
|
|
<option value="hourly">Cada hora</option>
|
|
<option value="3-hour">Cada 3 horas</option>
|
|
<option value="7-hour">Cada 7 horas</option>
|
|
<option value="startup">Al iniciar</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="modal-project-time" class="form-label">Hora de Backup (para programación diaria)</label>
|
|
<input type="time" class="form-control" id="modal-project-time">
|
|
</div>
|
|
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="modal-project-enabled">
|
|
<label class="form-check-label" for="modal-project-enabled">
|
|
Backup habilitado
|
|
</label>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
<button type="button" class="btn btn-primary" id="save-project-config-btn">Guardar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- System Status Modal -->
|
|
<div class="modal fade" id="systemStatusModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Estado del Sistema</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body" id="system-status-content">
|
|
<div class="text-center">
|
|
<div class="spinner-border" role="status">
|
|
<span class="visually-hidden">Cargando...</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Event listeners for project actions
|
|
document.querySelectorAll('.backup-project-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const projectId = this.dataset.projectId;
|
|
backupProject(projectId);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.config-project-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const projectId = this.dataset.projectId;
|
|
openProjectConfig(projectId);
|
|
});
|
|
});
|
|
|
|
// Other event listeners
|
|
const refreshBtn = document.getElementById('refresh-projects-btn');
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', () => location.reload());
|
|
}
|
|
|
|
const scanBtn = document.getElementById('scan-projects-btn');
|
|
if (scanBtn) {
|
|
scanBtn.addEventListener('click', scanProjects);
|
|
}
|
|
|
|
const firstScanBtn = document.getElementById('first-scan-btn');
|
|
if (firstScanBtn) {
|
|
firstScanBtn.addEventListener('click', scanProjects);
|
|
}
|
|
|
|
const systemStatusBtn = document.getElementById('system-status-btn');
|
|
if (systemStatusBtn) {
|
|
systemStatusBtn.addEventListener('click', showSystemStatus);
|
|
}
|
|
});
|
|
|
|
function backupProject(projectId) {
|
|
showAlert('info', `Iniciando backup del proyecto ${projectId}...`);
|
|
|
|
fetch(`/api/projects/${projectId}/backup`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showAlert('danger', `Error: ${data.error}`);
|
|
} else {
|
|
showAlert('success', data.message || 'Backup iniciado correctamente');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showAlert('danger', `Error de conexión: ${error.message}`);
|
|
});
|
|
}
|
|
|
|
function openProjectConfig(projectId) {
|
|
document.getElementById('modal-project-id').value = projectId;
|
|
const modal = new bootstrap.Modal(document.getElementById('projectConfigModal'));
|
|
modal.show();
|
|
}
|
|
|
|
function scanProjects() {
|
|
showAlert('info', 'Escaneando directorios en busca de proyectos...');
|
|
|
|
fetch('/api/scan', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showAlert('danger', `Error: ${data.error}`);
|
|
} else {
|
|
showAlert('success', `Escaneo completado. ${data.projects_found} proyectos encontrados.`);
|
|
setTimeout(() => location.reload(), 2000);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showAlert('danger', `Error de conexión: ${error.message}`);
|
|
});
|
|
}
|
|
|
|
function showSystemStatus() {
|
|
const modal = new bootstrap.Modal(document.getElementById('systemStatusModal'));
|
|
|
|
fetch('/api/system/status')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
document.getElementById('system-status-content').innerHTML =
|
|
`<div class="alert alert-danger">Error: ${data.error}</div>`;
|
|
} else {
|
|
document.getElementById('system-status-content').innerHTML = `
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Espacio en Disco</h6>
|
|
<ul class="list-group list-group-flush">
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span>Total:</span>
|
|
<span>${data.disk_space.total_mb.toFixed(1)} MB</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span>Usado:</span>
|
|
<span>${data.disk_space.used_mb.toFixed(1)} MB</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span>Libre:</span>
|
|
<span>${data.disk_space.free_mb.toFixed(1)} MB</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Proyectos</h6>
|
|
<ul class="list-group list-group-flush">
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span>Total:</span>
|
|
<span>${data.projects.total}</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span>Habilitados:</span>
|
|
<span>${data.projects.enabled}</span>
|
|
</li>
|
|
<li class="list-group-item d-flex justify-content-between">
|
|
<span>Everything API:</span>
|
|
<span class="badge ${data.everything_api.available ? 'bg-success' : 'bg-warning'}">
|
|
${data.everything_api.available ? 'Disponible' : 'No disponible'}
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
document.getElementById('system-status-content').innerHTML =
|
|
`<div class="alert alert-danger">Error de conexión: ${error.message}</div>`;
|
|
});
|
|
|
|
modal.show();
|
|
}
|
|
</script>
|
|
{% endblock %}
|