Change : UI/UX design
This commit is contained in:
@@ -836,26 +836,198 @@ a.sidebar-brand-link {
|
||||
.log-elapsed { font-size: 11px; font-family: var(--mono); color: var(--text3); margin-top: 6px; }
|
||||
|
||||
/* ── BACKUP LIST ───────────────────────────────────────────── */
|
||||
.backup-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.backup-item {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 11px 14px; border-radius: var(--radius);
|
||||
background: var(--surface2); gap: 10px; flex-wrap: wrap;
|
||||
/* ── BACKUP LIST ───────────────────────────────────────────── */
|
||||
.backup-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 22px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.backup-columns { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.backup-column { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
|
||||
|
||||
.backup-column-header {
|
||||
display: flex; flex-direction: column; gap: 8px;
|
||||
padding: 14px 16px;
|
||||
background: var(--surface2);
|
||||
border: 1px solid var(--border);
|
||||
transition: border-color var(--trans), background var(--trans), transform var(--trans);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.backup-column-header .section-title { margin: 0; font-size: 12px; }
|
||||
.backup-column-header .card-meta {
|
||||
align-self: flex-start;
|
||||
font-size: 10px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.backup-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
|
||||
.backup-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface2);
|
||||
border: 1px solid var(--border);
|
||||
transition: border-color var(--trans), background var(--trans), box-shadow var(--trans);
|
||||
}
|
||||
.backup-item:hover {
|
||||
border-color: rgba(79,140,255,0.25);
|
||||
border-color: rgba(79,140,255,0.28);
|
||||
background: var(--bg4);
|
||||
transform: translateX(2px);
|
||||
box-shadow: var(--glow-accent);
|
||||
}
|
||||
.backup-name { font-family: var(--mono); font-size: 12px; color: var(--text2); flex: 1; min-width: 0; word-break: break-all; }
|
||||
.backup-actions { display: flex; gap: 5px; align-items: center; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
|
||||
.btn-delete-backup { color: var(--red) !important; opacity: 0.7; transition: opacity 0.15s; }
|
||||
|
||||
.backup-item-main { min-width: 0; padding-bottom: 12px; }
|
||||
.backup-item:not(:has(.backup-actions)) .backup-item-main { padding-bottom: 0; }
|
||||
|
||||
.backup-name {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--text);
|
||||
word-break: break-all;
|
||||
display: block;
|
||||
}
|
||||
.backup-name i { margin-right: 6px; opacity: 0.85; }
|
||||
|
||||
.backup-meta {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 10px;
|
||||
color: var(--text3);
|
||||
font-family: var(--mono);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.backup-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.backup-actions .btn-sm { font-size: 11px; padding: 5px 10px; }
|
||||
.backup-actions .btn-sm:not(.btn-audit):not(.btn-delete-backup) { min-height: 30px; }
|
||||
|
||||
.btn-delete-backup { color: var(--red) !important; opacity: 0.75; transition: opacity 0.15s; }
|
||||
.btn-delete-backup:hover { opacity: 1; }
|
||||
.btn-audit { color: var(--cyan) !important; opacity: 0.85; }
|
||||
.btn-audit { color: var(--cyan) !important; opacity: 0.9; }
|
||||
.btn-audit:hover { opacity: 1; }
|
||||
|
||||
/* ── CLOUD PAGE ────────────────────────────────────────────── */
|
||||
.cloud-status-card .card-header { display: none; }
|
||||
|
||||
.cloud-status-hero {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.cloud-status-hero-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
.cloud-status-icon {
|
||||
width: 48px; height: 48px; border-radius: 14px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 20px; color: var(--accent2); flex-shrink: 0;
|
||||
background: rgba(79,140,255,0.1);
|
||||
border: 1px solid rgba(79,140,255,0.18);
|
||||
box-shadow: var(--glow-accent);
|
||||
}
|
||||
.cloud-status-title {
|
||||
font-size: 18px; font-weight: 700; letter-spacing: -0.02em;
|
||||
color: var(--text); line-height: 1.2;
|
||||
}
|
||||
.cloud-status-sub {
|
||||
font-size: 12px; color: var(--text3); margin-top: 4px; line-height: 1.4;
|
||||
}
|
||||
.cloud-status-hero-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.cloud-conn-badge {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 6px 12px; border-radius: 20px;
|
||||
font-family: var(--mono); font-size: 11px; font-weight: 600;
|
||||
background: rgba(79,140,255,0.1);
|
||||
border: 1px solid rgba(79,140,255,0.2);
|
||||
color: var(--accent2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cloud-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.cloud-stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.cloud-stats-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
.cloud-stats-grid .stat-card {
|
||||
text-align: left;
|
||||
padding: 16px 18px;
|
||||
background: var(--surface2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.cloud-stats-grid .stat-number {
|
||||
font-size: 26px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.cloud-stats-grid .stat-number.is-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.35;
|
||||
word-break: break-all;
|
||||
}
|
||||
.cloud-stats-grid .stat-label {
|
||||
text-align: left;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.cloud-conn-msg {
|
||||
color: var(--text3);
|
||||
font-size: 11px;
|
||||
font-family: var(--mono);
|
||||
margin-top: 14px;
|
||||
padding: 10px 14px;
|
||||
background: var(--surface2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cloud-upload-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
max-width: 560px;
|
||||
}
|
||||
.cloud-upload-form .form-group { width: 100%; }
|
||||
.cloud-upload-form .btn-primary { align-self: flex-start; }
|
||||
|
||||
.cloud-backup-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ── MODAL ─────────────────────────────────────────────────── */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; z-index: 1000;
|
||||
|
||||
@@ -403,7 +403,9 @@ function renderBackupList(items, id, source) {
|
||||
: '';
|
||||
return `
|
||||
<div class="backup-item" id="bk-item-${source}-${b.replace(/[^a-z0-9]/gi, '_')}">
|
||||
<span class="backup-name">${escapeHtml(b)}</span>
|
||||
<div class="backup-item-main">
|
||||
<span class="backup-name">${escapeHtml(b)}</span>
|
||||
</div>
|
||||
<div class="backup-actions">
|
||||
${detailsBtn}
|
||||
<button class="btn btn-ghost btn-sm btn-audit" onclick="auditBackup('${source}','${safe}',this)"><i class="fas fa-shield-check"></i> Audit</button>
|
||||
@@ -533,7 +535,13 @@ async function loadBackupLog() {
|
||||
node.innerHTML = '<div class="empty-state"><i class="fas fa-inbox"></i>No backup history yet</div>';
|
||||
return;
|
||||
}
|
||||
node.innerHTML = d.entries.map((e) => `<div class="backup-item"><span class="backup-name">${e.name}</span><span class="card-meta">${e.status}</span></div>`).join('');
|
||||
node.innerHTML = d.entries.map((e) => `
|
||||
<div class="backup-item">
|
||||
<div class="backup-item-main">
|
||||
<span class="backup-name">${escapeHtml(e.name)}</span>
|
||||
<span class="backup-meta">${escapeHtml(e.status)}</span>
|
||||
</div>
|
||||
</div>`).join('');
|
||||
} catch (e) {
|
||||
node.innerHTML = `<div class="empty-state">Error loading log: ${e}</div>`;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=Geist+Mono:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}?v=ui3">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}?v=ui4">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
|
||||
@@ -1,33 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
{# ── Extra styles scoped to this page ── #}
|
||||
{# ── Extra styles scoped to this page (modals only) ── #}
|
||||
<style>
|
||||
/* ── BACKUP ITEMS ──────────────────────────────────────────────── */
|
||||
.backup-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 14px;
|
||||
border-radius: 10px;
|
||||
background: var(--surface2);
|
||||
border: 1px solid var(--border);
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
}
|
||||
.backup-item:hover { border-color: var(--border2); background: var(--bg4); }
|
||||
|
||||
.backup-name {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--text2);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.backup-actions { display: flex; gap: 5px; align-items: center; flex-shrink: 0; }
|
||||
|
||||
/* ── ENHANCED MODAL ─────────────────────────────────────────── */
|
||||
/* page-specific modal styles — backup layout lives in style.css */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; z-index: 1000;
|
||||
background: rgba(0,0,0,0.72);
|
||||
@@ -298,18 +274,20 @@
|
||||
<button class="btn btn-ghost btn-sm" onclick="refreshBackupsList()"><i class="fas fa-sync-alt"></i> Refresh</button>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;">
|
||||
<div class="backup-columns">
|
||||
|
||||
{# LOCAL #}
|
||||
<div>
|
||||
<div class="section-header">
|
||||
<div class="section-title">🖥️ MAIN SERVER</div>
|
||||
<div class="backup-column">
|
||||
<div class="backup-column-header">
|
||||
<div class="section-title">🖥️ Main Server</div>
|
||||
<span class="card-meta">/root/backups/</span>
|
||||
</div>
|
||||
<div class="backup-list" id="local-backup-list">
|
||||
{% for b in backups %}
|
||||
<div class="backup-item">
|
||||
<span class="backup-name">{{ b }}</span>
|
||||
<div class="backup-item-main">
|
||||
<span class="backup-name">{{ b }}</span>
|
||||
</div>
|
||||
<div class="backup-actions">
|
||||
<button class="btn btn-ghost btn-sm" style="color:var(--text3);" onclick="showBackupDetails('local','{{ b }}',this)" title="Quick details"><i class="fas fa-circle-info"></i></button>
|
||||
<button class="btn btn-ghost btn-sm btn-audit" onclick="auditBackup('local','{{ b }}',this)"><i class="fas fa-shield-check"></i> Audit</button>
|
||||
@@ -323,15 +301,17 @@
|
||||
</div>
|
||||
|
||||
{# VM #}
|
||||
<div>
|
||||
<div class="section-header">
|
||||
<div class="section-title">💾 VM SERVER</div>
|
||||
<div class="backup-column">
|
||||
<div class="backup-column-header">
|
||||
<div class="section-title">💾 VM Server</div>
|
||||
<span class="card-meta">/backups/main-server/</span>
|
||||
</div>
|
||||
<div class="backup-list" id="vm-backup-list">
|
||||
{% for b in vm_backups %}
|
||||
<div class="backup-item">
|
||||
<span class="backup-name">{{ b }}</span>
|
||||
<div class="backup-item-main">
|
||||
<span class="backup-name">{{ b }}</span>
|
||||
</div>
|
||||
<div class="backup-actions">
|
||||
<button class="btn btn-ghost btn-sm" style="color:var(--text3);" onclick="showBackupDetails('vm','{{ b }}',this)" title="Quick details"><i class="fas fa-circle-info"></i></button>
|
||||
<button class="btn btn-ghost btn-sm btn-audit" onclick="auditBackup('vm','{{ b }}',this)"><i class="fas fa-shield-check"></i> Audit</button>
|
||||
@@ -344,9 +324,9 @@
|
||||
</div>
|
||||
|
||||
{# CLOUD R2 #}
|
||||
<div>
|
||||
<div class="section-header">
|
||||
<div class="section-title">☁️ CLOUD (R2)</div>
|
||||
<div class="backup-column">
|
||||
<div class="backup-column-header">
|
||||
<div class="section-title">☁️ Cloud (R2)</div>
|
||||
<span class="card-meta">navitrends-backups</span>
|
||||
</div>
|
||||
<div class="backup-list" id="cloud-backup-list">
|
||||
@@ -363,7 +343,7 @@
|
||||
<div class="card-title"><i class="fas fa-clock-rotate-left"></i> Backup History</div>
|
||||
<button class="btn btn-ghost btn-sm" onclick="loadBackupLog()"><i class="fas fa-sync-alt"></i> Refresh</button>
|
||||
</div>
|
||||
<div id="backup-history-list"><div class="empty-state"><i class="fas fa-spinner fa-spin"></i> Loading…</div></div>
|
||||
<div id="backup-history-list" class="cloud-backup-list"><div class="empty-state"><i class="fas fa-spinner fa-spin"></i> Loading…</div></div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -734,11 +714,9 @@ async function loadCloudBackupsColumn() {
|
||||
const archives = data.backups.filter(b => b.name.endsWith('.tar.gz'));
|
||||
list.innerHTML = archives.map(b => `
|
||||
<div class="backup-item">
|
||||
<div style="display:flex;flex-direction:column;gap:2px;min-width:0;">
|
||||
<span class="backup-name" style="font-size:11px;">
|
||||
<i class="fas fa-cloud" style="color:var(--accent2);font-size:10px;margin-right:4px;"></i>${b.name}
|
||||
</span>
|
||||
<span style="font-size:10px;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 btn-audit" onclick="auditCloudBackup('${b.key}', this)" title="Audit R2 integrity">
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user