Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files
web_demo/templates/admin.html
CHANGED
|
@@ -264,6 +264,10 @@
|
|
| 264 |
<h2>User Ratings</h2>
|
| 265 |
<div class="chart-wrap"><canvas id="chartRatings"></canvas></div>
|
| 266 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
</div>
|
| 268 |
</div>
|
| 269 |
|
|
@@ -628,13 +632,13 @@
|
|
| 628 |
if (storageStats === null) return { label: "Storage used", value: "…", sub: "" };
|
| 629 |
if (!storageStats.available) return { label: "Storage used", value: "n/a", sub: "persistence off" };
|
| 630 |
const s = storageStats;
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
const
|
| 635 |
-
|
| 636 |
const subCls = (s.used_fraction != null && s.used_fraction > 0.8) ? "delta-down" : "";
|
| 637 |
-
return { label: "Storage used", value:
|
| 638 |
};
|
| 639 |
const fmtDay = (iso) => {
|
| 640 |
if (!iso) return "—";
|
|
@@ -791,6 +795,37 @@
|
|
| 791 |
});
|
| 792 |
};
|
| 793 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 794 |
const loadStats = async () => {
|
| 795 |
dashStatusLabel.textContent = "Loading...";
|
| 796 |
const days = windowSelect.value;
|
|
@@ -820,6 +855,7 @@
|
|
| 820 |
if (resp.status === 401) return;
|
| 821 |
storageStats = await resp.json();
|
| 822 |
if (lastStats) renderStatCards(lastStats); // re-render so the card fills in
|
|
|
|
| 823 |
} catch (e) {
|
| 824 |
console.error("Failed to load storage", e);
|
| 825 |
}
|
|
|
|
| 264 |
<h2>User Ratings</h2>
|
| 265 |
<div class="chart-wrap"><canvas id="chartRatings"></canvas></div>
|
| 266 |
</div>
|
| 267 |
+
<div class="chart-card">
|
| 268 |
+
<h2>Storage Breakdown</h2>
|
| 269 |
+
<div class="chart-wrap"><canvas id="chartStorage"></canvas></div>
|
| 270 |
+
</div>
|
| 271 |
</div>
|
| 272 |
</div>
|
| 273 |
|
|
|
|
| 632 |
if (storageStats === null) return { label: "Storage used", value: "…", sub: "" };
|
| 633 |
if (!storageStats.available) return { label: "Storage used", value: "n/a", sub: "persistence off" };
|
| 634 |
const s = storageStats;
|
| 635 |
+
// Card is the at-a-glance headline only: % of quota + the quota it's
|
| 636 |
+
// measured against. The per-prefix breakdown lives in the Storage
|
| 637 |
+
// Breakdown doughnut below.
|
| 638 |
+
const pct = s.used_fraction != null ? (s.used_fraction * 100).toFixed(0) + "%" : fmtBytes(s.total_bytes);
|
| 639 |
+
const sub = `of ${fmtBytes(s.quota_bytes)}`;
|
| 640 |
const subCls = (s.used_fraction != null && s.used_fraction > 0.8) ? "delta-down" : "";
|
| 641 |
+
return { label: "Storage used", value: pct, sub, subCls };
|
| 642 |
};
|
| 643 |
const fmtDay = (iso) => {
|
| 644 |
if (!iso) return "—";
|
|
|
|
| 795 |
});
|
| 796 |
};
|
| 797 |
|
| 798 |
+
// Storage breakdown doughnut — fed by the separate /api/admin/storage
|
| 799 |
+
// fetch (not the stats payload), so it has its own render path. Shows the
|
| 800 |
+
// per-prefix split plus a Free slice for quota context.
|
| 801 |
+
const renderStorageChart = () => {
|
| 802 |
+
const canvas = document.getElementById("chartStorage");
|
| 803 |
+
if (!canvas || !storageStats || !storageStats.available) return;
|
| 804 |
+
const s = storageStats;
|
| 805 |
+
const p = s.prefixes || {};
|
| 806 |
+
const segs = [
|
| 807 |
+
{ label: "Photos", bytes: (p.photos || {}).bytes || 0, color: "#7a8b3d" },
|
| 808 |
+
{ label: "Results", bytes: (p.results || {}).bytes || 0, color: "#d99c4f" },
|
| 809 |
+
{ label: "Feedback", bytes: (p.feedback || {}).bytes || 0, color: "#3d6b8b" },
|
| 810 |
+
].filter((seg) => seg.bytes > 0);
|
| 811 |
+
const free = Math.max(0, (s.quota_bytes || 0) - (s.total_bytes || 0));
|
| 812 |
+
if (free > 0) segs.push({ label: "Free", bytes: free, color: "rgba(45,33,33,0.10)" });
|
| 813 |
+
upsertChart("storage", canvas, {
|
| 814 |
+
type: "doughnut",
|
| 815 |
+
data: {
|
| 816 |
+
labels: segs.map((seg) => seg.label),
|
| 817 |
+
datasets: [{ data: segs.map((seg) => seg.bytes), backgroundColor: segs.map((seg) => seg.color) }],
|
| 818 |
+
},
|
| 819 |
+
options: {
|
| 820 |
+
responsive: true, maintainAspectRatio: false,
|
| 821 |
+
plugins: {
|
| 822 |
+
legend: { position: "right", labels: { boxWidth: 12, font: { size: 11 } } },
|
| 823 |
+
tooltip: { callbacks: { label: (ctx) => `${ctx.label}: ${fmtBytes(ctx.parsed)}` } },
|
| 824 |
+
},
|
| 825 |
+
},
|
| 826 |
+
});
|
| 827 |
+
};
|
| 828 |
+
|
| 829 |
const loadStats = async () => {
|
| 830 |
dashStatusLabel.textContent = "Loading...";
|
| 831 |
const days = windowSelect.value;
|
|
|
|
| 855 |
if (resp.status === 401) return;
|
| 856 |
storageStats = await resp.json();
|
| 857 |
if (lastStats) renderStatCards(lastStats); // re-render so the card fills in
|
| 858 |
+
renderStorageChart();
|
| 859 |
} catch (e) {
|
| 860 |
console.error("Failed to load storage", e);
|
| 861 |
}
|