| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- <!DOCTYPE html>
- <html lang="fr" class="h-full bg-gray-50">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>{% block title %}Backup Manager{% endblock %} — {{ instance_name }}</title>
- <script src="https://cdn.tailwindcss.com"></script>
- <script type="text/tailwindcss">
- @layer components {
- /* Bouton principal (créer, lancer, enregistrer) */
- .btn-primary { @apply inline-flex items-center gap-1.5 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition; }
- /* Bouton secondaire (tester, éditer, sync, historique) */
- .btn-secondary { @apply inline-flex items-center gap-1.5 bg-white hover:bg-gray-50 text-gray-700 border border-gray-200 font-medium rounded transition; }
- /* Bouton icône / action légère */
- .btn-ghost { @apply inline-flex items-center gap-1 text-gray-500 hover:text-gray-800 hover:bg-gray-100 rounded transition; }
- /* Bouton destructeur */
- .btn-danger { @apply inline-flex items-center gap-1 text-red-400 hover:text-red-600 hover:bg-red-50 rounded transition; }
- /* Tailles */
- .btn-sm { @apply text-xs px-3 py-1.5; }
- .btn-md { @apply text-sm px-5 py-2; }
- .btn-icon-sm { @apply text-xs px-2 py-1; }
- }
- </script>
- </head>
- <body class="h-full flex flex-col">
- <nav class="bg-gray-900 text-white shadow-lg">
- <div class="max-w-7xl mx-auto px-6 py-3 flex items-center justify-between">
- <div class="flex items-center gap-3">
- <svg class="w-6 h-6 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
- d="M5 12h14M5 12l4-4m-4 4l4 4M19 12l-4-4m4 4l-4 4"/>
- </svg>
- <a href="{{ url_for('jobs.index') }}" class="text-lg font-bold tracking-tight">Backup Manager</a>
- <span class="bg-blue-600 text-xs font-medium px-2 py-0.5 rounded">{{ instance_name }}</span>
- </div>
- <div class="flex items-center gap-4 text-sm">
- <a href="{{ url_for('jobs.index') }}"
- class="text-gray-300 hover:text-white transition {% if request.endpoint and request.endpoint.startswith('jobs.') and request.endpoint != 'jobs.job_new' %}text-white font-semibold{% endif %}">
- Dashboard
- </a>
- <a href="{{ url_for('jobs.archives') }}"
- class="text-gray-300 hover:text-white transition {% if request.endpoint == 'jobs.archives' %}text-white font-semibold{% endif %}">
- Archives
- </a>
- <a href="{{ url_for('cfg.settings') }}"
- class="text-gray-300 hover:text-white transition {% if request.endpoint and request.endpoint.startswith('cfg.') %}text-white font-semibold{% endif %}">
- Paramètres
- </a>
- <a href="{{ url_for('jobs.job_new') }}"
- class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded font-medium transition">
- + Nouveau job
- </a>
- </div>
- </div>
- </nav>
- <!-- Barre d'activité sticky -->
- <div id="activity-bar"
- class="hidden sticky top-0 z-40 bg-gray-900 border-b border-blue-700 text-white text-xs px-6 py-2 flex items-center gap-3 shadow">
- <span class="inline-block w-2 h-2 rounded-full bg-blue-400 animate-pulse shrink-0"></span>
- <span class="text-blue-300 font-medium shrink-0">En cours</span>
- <div id="activity-list" class="flex gap-4 flex-wrap flex-1 text-gray-200"></div>
- </div>
- <main class="flex-1 max-w-7xl mx-auto w-full px-6 py-8">
- {% with messages = get_flashed_messages(with_categories=true) %}
- {% if messages %}
- <div class="mb-6 space-y-2">
- {% for category, message in messages %}
- <div class="px-4 py-3 rounded-lg text-sm font-medium
- {% if category == 'error' %}bg-red-50 text-red-800 border border-red-200
- {% elif category == 'info' %}bg-blue-50 text-blue-800 border border-blue-200
- {% else %}bg-green-50 text-green-800 border border-green-200{% endif %}">
- {{ message }}
- </div>
- {% endfor %}
- </div>
- {% endif %}
- {% endwith %}
- {% block content %}{% endblock %}
- </main>
- <footer class="text-center text-xs text-gray-400 py-4">
- Backup Manager — instance <strong>{{ instance_name }}</strong>
- </footer>
- <script>
- (function() {
- const bar = document.getElementById('activity-bar');
- const list = document.getElementById('activity-list');
- const LABELS = { backup: 'Sauvegarde', restore: 'Restauration', push: 'Push', pull: 'Pull' };
- function elapsed(isoStr) {
- const sec = Math.round((Date.now() - new Date(isoStr).getTime()) / 1000);
- if (sec < 60) return sec + 's';
- return Math.floor(sec / 60) + 'min' + (sec % 60 ? (sec % 60) + 's' : '');
- }
- function render(items) {
- if (!items || items.length === 0) {
- bar.classList.add('hidden');
- return;
- }
- bar.classList.remove('hidden');
- list.innerHTML = items.map(it => {
- const label = LABELS[it.kind] || it.kind;
- const since = it.started_at ? ' · ' + elapsed(it.started_at) : '';
- const size = it.size_human ? ' · ' + it.size_human : '';
- return `<span class="bg-gray-800 rounded px-2 py-0.5">${label} <strong>${it.name}</strong>${since}${size}</span>`;
- }).join('');
- }
- function poll() {
- fetch('{{ url_for("api.api_running") }}', {
- headers: { 'X-BackupManager-Key': '{{ api_token }}' }
- })
- .then(r => r.ok ? r.json() : null)
- .then(data => render(data && data.running ? data.running : []))
- .catch(() => {});
- }
- poll();
- setInterval(poll, 5000);
- })();
- </script>
- </body>
- </html>
|