Sync from main server - 2026-05-13 01:06:32

This commit is contained in:
root
2026-05-13 01:06:32 +02:00
parent 09bbe0403c
commit 6158b34613
8 changed files with 2159 additions and 129 deletions

View File

@@ -46,6 +46,10 @@
<div class="stat-card"><div class="stat-number" id="stat-users">{{ users|length }}</div><div class="stat-label">Linux Users</div></div>
<div class="stat-card"><div class="stat-number" id="stat-local-bk">{{ backups|length }}</div><div class="stat-label">Local Backups</div></div>
<div class="stat-card"><div class="stat-number" id="stat-vm-bk">{{ vm_backups|length }}</div><div class="stat-label">VM Backups</div></div>
<div class="stat-card" style="cursor:pointer;" onclick="window.location='/cloud'">
<div class="stat-number" id="stat-cloud-bk" style="color:var(--accent2);"></div>
<div class="stat-label">☁ Cloud Backups</div>
</div>
</div>
</div>
@@ -106,16 +110,26 @@
</div>
</div>
{% if not running_on_main %}
<script>
(function () {
// ── Helper: build a container row identical to the Jinja template ──
// ── Load cloud backup count async ──────────────────────────────────────────
async function loadCloudCount() {
try {
const res = await fetch('/api/cloud/r2/stats');
const data = await res.json();
const el = document.getElementById('stat-cloud-bk');
if (el) el.textContent = data.count ?? '—';
} catch(e) {}
}
loadCloudCount();
{% if not running_on_main %}
// ── Helper: build a container row ──────────────────────────────────────────
function buildRow(c) {
const isUp = c.status && c.status.includes('Up');
const badge = isUp
? `<span class="badge badge-run">Running</span>`
: `<span class="badge badge-stop">Stopped</span>`;
return `
<tr data-ctr="${c.name}">
<td class="ct-name">${c.name}</td>
@@ -136,45 +150,31 @@
<td class="col-extra app-extra ct-image" style="display:none;">${c.image || ''}</td>
<td class="col-extra app-extra ct-ports" style="display:none;">${c.ports || '—'}</td>
<td><div class="action-btns">
<button class="ctr-action-btn restart" title="Restart" onclick="ctrAction('${c.name}','restart',this)">
<i class="fas fa-rotate-right"></i>
</button>
<button class="ctr-action-btn stop" title="Stop" onclick="ctrAction('${c.name}','stop',this)">
<i class="fas fa-stop"></i>
</button>
<button class="ctr-action-btn start" title="Start" onclick="ctrAction('${c.name}','start',this)">
<i class="fas fa-play"></i>
</button>
<button class="ctr-action-btn restart" title="Restart" onclick="ctrAction('${c.name}','restart',this)"><i class="fas fa-rotate-right"></i></button>
<button class="ctr-action-btn stop" title="Stop" onclick="ctrAction('${c.name}','stop',this)"><i class="fas fa-stop"></i></button>
<button class="ctr-action-btn start" title="Start" onclick="ctrAction('${c.name}','start',this)"><i class="fas fa-play"></i></button>
</div></td>
</tr>`;
}
// ── Populate metrics cards ──
function applySystem(s) {
if (!s) return;
const cpu = parseFloat(s.cpu_pct) || 0;
document.getElementById('m-cpu').innerHTML = `${cpu.toFixed(1)}<span>%</span>`;
document.getElementById('m-mem').textContent = s.memory || '—';
document.getElementById('m-disk').textContent = s.disk || '—';
document.getElementById('m-load').textContent = s.load || '—';
document.getElementById('g-cpu').style.width = `${Math.min(cpu, 100)}%`;
document.getElementById('g-mem').style.width = `${Math.min(parseFloat(s.mem_pct) || 0, 100)}%`;
document.getElementById('g-disk').style.width = `${Math.min(parseFloat(s.disk_pct) || 0, 100)}%`;
if (s.docker_v) {
const meta = document.getElementById('overview-meta');
if (meta) meta.textContent = `Docker ${s.docker_v} · {{ main_server }}`;
}
}
// ── Populate overview stat numbers ──
function applyStats(data) {
const set = (id, val) => {
const el = document.getElementById(id);
if (el && val !== undefined && val !== null) el.textContent = val;
};
const set = (id, val) => { const el = document.getElementById(id); if (el && val !== undefined && val !== null) el.textContent = val; };
set('stat-total', data.containers ? data.containers.length : undefined);
set('stat-running', data.running_count);
set('stat-users', data.user_count);
@@ -182,52 +182,36 @@
set('stat-vm-bk', data.vm_backups);
}
// ── Populate the containers table ──
function applyContainers(containers) {
if (!containers || !containers.length) return;
const tbody = document.getElementById('app-containers-body');
if (!tbody) return;
tbody.innerHTML = containers.map(buildRow).join('');
// Re-apply column visibility in case "Show more" was already toggled
const extras = tbody.querySelectorAll('.app-extra');
const btn = document.getElementById('app-toggle-btn');
if (btn && btn.dataset.expanded === 'true') {
extras.forEach(el => el.style.display = '');
}
// Kick stats refresh if a global function exists (from base template)
if (typeof refreshContainerStats === 'function') {
refreshContainerStats();
}
if (btn && btn.dataset.expanded === 'true') extras.forEach(el => el.style.display = '');
if (typeof refreshContainerStats === 'function') refreshContainerStats();
}
// ── Main async loader ──
async function loadDashboardAsync() {
try {
const res = await fetch('/api/dashboard');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
applySystem(data.system);
applyStats(data);
applyContainers(data.containers);
} catch (err) {
console.error('[dashboard] async load failed:', err);
}
}
// Run immediately on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadDashboardAsync);
} else {
loadDashboardAsync();
}
{% endif %}
})();
</script>
{% endif %}
{% endblock %}