Change : UI/UX design

This commit is contained in:
2026-06-21 19:16:07 +01:00
parent ae267a6d9b
commit 32feb9d4eb
5 changed files with 266 additions and 96 deletions

View File

@@ -1,12 +1,18 @@
{% extends "base.html" %}
{% block content %}
{# ── Connection status banner ─────────────────────────────────────────────── #}
<div class="card" id="r2-status-card">
<div class="card-header">
<div class="card-title"><i class="fas fa-cloud"></i> Cloudflare R2 — Cloud Storage</div>
<div style="display:flex;align-items:center;gap:10px;">
<span id="r2-conn-badge" class="badge" style="background:rgba(59,130,246,0.12);color:var(--accent2);">
{# ── Connection status ─────────────────────────────────────────────────────── #}
<div class="card cloud-status-card" id="r2-status-card">
<div class="cloud-status-hero">
<div class="cloud-status-hero-left">
<div class="cloud-status-icon"><i class="fas fa-cloud"></i></div>
<div>
<div class="cloud-status-title">Cloudflare R2</div>
<div class="cloud-status-sub">Cold storage for off-site backup archives</div>
</div>
</div>
<div class="cloud-status-hero-right">
<span id="r2-conn-badge" class="cloud-conn-badge">
<i class="fas fa-spinner fa-spin"></i> Checking…
</span>
<button class="btn btn-ghost btn-sm" onclick="testR2Connection()">
@@ -14,7 +20,8 @@
</button>
</div>
</div>
<div class="stat-row" id="r2-stats-row">
<div class="cloud-stats-grid" id="r2-stats-row">
<div class="stat-card">
<div class="stat-number" id="r2-stat-objects"></div>
<div class="stat-label">Cloud Backups</div>
@@ -24,15 +31,16 @@
<div class="stat-label">Total Cloud Size</div>
</div>
<div class="stat-card">
<div class="stat-number" style="font-size:13px;color:var(--text2);" id="r2-stat-bucket">{{ r2_bucket }}</div>
<div class="stat-number is-text" id="r2-stat-bucket">{{ r2_bucket }}</div>
<div class="stat-label">Bucket Name</div>
</div>
<div class="stat-card">
<div class="stat-number" style="font-size:11px;color:var(--text3);">R2</div>
<div class="stat-number is-text" style="color:var(--text2);">Cloudflare R2</div>
<div class="stat-label">Provider</div>
</div>
</div>
<div id="r2-conn-msg" style="color:var(--text3);font-size:12px;margin-top:4px;font-family:var(--mono);padding:0 4px;"></div>
<div id="r2-conn-msg" class="cloud-conn-msg" style="display:none;"></div>
</div>
{# ── Upload panel ─────────────────────────────────────────────────────────── #}
@@ -40,11 +48,11 @@
<div class="card-header">
<div class="card-title"><i class="fas fa-cloud-upload-alt"></i> Upload Backup to R2</div>
</div>
<p style="color:var(--text2);font-size:13px;margin-bottom:14px;">
<p style="color:var(--text2);font-size:13px;margin-bottom:18px;line-height:1.55;">
Select a local or VM backup to push to Cloudflare R2 cold storage.
</p>
<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end;max-width:640px;">
<div class="form-group" style="flex:1;min-width:260px;">
<div class="cloud-upload-form">
<div class="form-group">
<label class="form-label">SOURCE BACKUP</label>
<select id="upload-backup-select" class="form-input">
{% if local_backups %}
@@ -69,24 +77,24 @@
</div>
{% if not r2_configured %}
<div style="margin-top:14px;padding:12px 14px;background:rgba(245,158,11,0.08);border:1px solid rgba(245,158,11,0.25);border-radius:8px;font-size:12px;color:var(--yellow);">
<div style="margin-top:16px;padding:12px 14px;background:rgba(245,158,11,0.08);border:1px solid rgba(245,158,11,0.25);border-radius:10px;font-size:12px;color:var(--yellow);line-height:1.5;">
<i class="fas fa-triangle-exclamation"></i>
<strong>R2 credentials not configured.</strong>
Set <code>R2_ACCESS_KEY_ID</code> and <code>R2_SECRET_ACCESS_KEY</code> — see Setup Guide below.
</div>
{% endif %}
<div id="r2-upload-wrapper" style="display:none;margin-top:16px;">
<div id="r2-upload-wrapper" style="display:none;margin-top:20px;padding-top:20px;border-top:1px solid var(--border);">
<div class="card-header" style="margin-bottom:8px;">
<div class="card-title" style="font-size:13px;"><i class="fas fa-terminal"></i> Upload Log</div>
<span class="badge" id="r2-upload-badge" style="background:rgba(59,130,246,0.15);color:var(--accent2);">Running…</span>
</div>
<div id="r2-upload-log" class="log-console" style="max-height:200px;"></div>
<div style="margin-top:8px;">
<div style="height:6px;background:var(--border);border-radius:4px;overflow:hidden;">
<div id="r2-progress-bar" style="height:100%;background:linear-gradient(90deg,var(--accent2),var(--cyan));width:0%;transition:width 0.4s ease;border-radius:4px;"></div>
<div style="margin-top:10px;">
<div style="height:6px;background:var(--border);border-radius:99px;overflow:hidden;">
<div id="r2-progress-bar" style="height:100%;background:linear-gradient(90deg,var(--accent2),var(--cyan));width:0%;transition:width 0.4s ease;border-radius:99px;"></div>
</div>
<div style="display:flex;justify-content:space-between;margin-top:4px;">
<div style="display:flex;justify-content:space-between;margin-top:6px;">
<span style="font-size:11px;color:var(--text3);font-family:var(--mono);" id="r2-upload-elapsed"></span>
<span style="font-size:11px;color:var(--accent2);font-family:var(--mono);" id="r2-progress-pct">0%</span>
</div>
@@ -100,7 +108,7 @@
<div class="card-title"><i class="fas fa-database"></i> Cloud Backups (R2)</div>
<button class="btn btn-ghost btn-sm" onclick="loadR2Backups()"><i class="fas fa-sync-alt"></i> Refresh</button>
</div>
<div id="r2-backup-list">
<div id="r2-backup-list" class="cloud-backup-list">
<div class="empty-state"><i class="fas fa-spinner fa-spin"></i> Loading…</div>
</div>
</div>
@@ -168,22 +176,30 @@ function toggleSetupGuide() {
async function testR2Connection() {
const badge = document.getElementById('r2-conn-badge');
const msg = document.getElementById('r2-conn-msg');
badge.className = 'cloud-conn-badge';
badge.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Testing…';
badge.style.cssText = 'background:rgba(59,130,246,0.12);color:var(--accent2);';
badge.style.cssText = '';
try {
const res = await fetch('/api/cloud/r2/test');
const data = await res.json();
if (data.success) {
badge.innerHTML = ' Connected';
badge.style.cssText = 'background:rgba(34,197,94,0.12);color:var(--green);';
badge.innerHTML = '<i class="fas fa-circle-check"></i> Connected';
badge.style.cssText = 'background:rgba(34,197,94,0.1);border-color:rgba(34,197,94,0.25);color:var(--green);';
} else {
badge.innerHTML = ' Disconnected';
badge.style.cssText = 'background:rgba(239,68,68,0.12);color:#f87171;';
badge.innerHTML = '<i class="fas fa-circle-xmark"></i> Disconnected';
badge.style.cssText = 'background:rgba(239,68,68,0.1);border-color:rgba(239,68,68,0.25);color:var(--red);';
}
if (data.message) {
msg.textContent = data.message;
msg.style.display = '';
} else {
msg.style.display = 'none';
}
msg.textContent = data.message || '';
} catch(e) {
badge.innerHTML = ' Error';
badge.innerHTML = '<i class="fas fa-circle-xmark"></i> Error';
badge.style.cssText = 'background:rgba(239,68,68,0.1);border-color:rgba(239,68,68,0.25);color:var(--red);';
msg.textContent = e.message;
msg.style.display = '';
}
}
@@ -208,16 +224,12 @@ async function loadR2Backups() {
}
list.innerHTML = data.backups.map(b => `
<div class="backup-item">
<div style="display:flex;flex-direction:column;gap:2px;">
<span class="backup-name">
<i class="fas fa-cloud" style="color:var(--accent2);font-size:11px;margin-right:6px;"></i>${b.name}
</span>
<span style="font-size:11px;color:var(--text3);font-family:var(--mono);">
${b.size_human} · ${b.last_modified_str}
</span>
<div class="backup-item-main">
<span class="backup-name"><i class="fas fa-cloud" style="color:var(--accent2);"></i>${b.name}</span>
<span class="backup-meta">${b.size_human} · ${b.last_modified_str}</span>
</div>
<div class="backup-actions">
<button class="btn btn-ghost btn-sm" onclick="auditR2Backup('${b.key}', this)" title="Audit integrity">
<button class="btn btn-ghost btn-sm btn-audit" onclick="auditR2Backup('${b.key}', this)" title="Audit integrity">
<i class="fas fa-shield-check"></i> Audit
</button>
<button class="btn btn-ghost btn-sm" onclick="restoreFromR2('${b.key}','${b.name}')">
@@ -230,7 +242,7 @@ async function loadR2Backups() {
</div>
`).join('');
} catch(e) {
list.innerHTML = `<div class="empty-state" style="color:#f87171;"><i class="fas fa-circle-exclamation"></i> ${e.message}</div>`;
list.innerHTML = `<div class="empty-state" style="color:var(--red);"><i class="fas fa-circle-exclamation"></i> ${e.message}</div>`;
}
}
@@ -411,4 +423,4 @@ document.addEventListener('DOMContentLoaded', () => {
loadR2Backups();
});
</script>
{% endblock %}
{% endblock %}