window.addEventListener('DOMContentLoaded', () => {
bootstrapAdmin();
});
const API_BASE = "http://10.0.0.66:7455/api";
// Small helper: fetch JSON, throw on non-2xx so callers can catch() it.
async function getJSON(path) {
const res = await fetch(`${API_BASE}${path}`);
if (!res.ok) throw new Error(`${path} responded ${res.status}`);
return res.json();
}
function timeAgo(isoString) {
const diffMs = Date.now() - new Date(isoString).getTime();
const mins = Math.floor(diffMs / 60000);
if (mins < 60) return `${mins}m ago`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h ago`;
return `${Math.floor(hours / 24)}d ago`;
}
function escapeHtml(str) {
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
// ---------- News ----------
async function loadNews() {
const el = document.getElementById("news-list");
try {
const posts = await getJSON("/news?limit=10");
if (posts.length === 0) {
el.innerHTML = `
`;
return;
}
el.innerHTML = posts.map(post => `
${escapeHtml(post.title)}${timeAgo(post.created_at)}
${escapeHtml(post.content)}
`).join("");
} catch (err) {
console.error("Failed to load news:", err);
el.innerHTML = `Couldn't load news right now.
`;
}
}
// ---------- Media ----------
async function loadMedia() {
const el = document.getElementById("media-grid");
try {
const items = await getJSON("/media?limit=8");
if (items.length === 0) {
el.innerHTML = ``;
return;
}
el.innerHTML = items.map(item => `
`).join("");
} catch (err) {
console.error("Failed to load media:", err);
el.innerHTML = ``;
}
}
// ---------- Overview (current users / recent activity / general stats) ----------
async function loadOverview() {
const usersEl = document.getElementById("current-users");
const activityEl = document.getElementById("recent-activity");
const generalEl = document.getElementById("general-stats");
try {
const data = await getJSON("/stats/overview");
usersEl.innerHTML = data.currentUsers.length
? data.currentUsers.map(u => `
${escapeHtml(u.username)}now
`).join("")
: `No one online
`;
activityEl.innerHTML = data.recentActivity.length
? data.recentActivity.map(a => `
${escapeHtml(a.description)}${timeAgo(a.created_at)}
`).join("")
: `No recent activity
`;
const { users, reports, build } = data.general;
generalEl.innerHTML = `
Users
Total: ${users.total}
Recent: ${users.mostRecent ? escapeHtml(users.mostRecent.username) : "—"}
Reports
Named: ${reports.named}
Associated: ${reports.associated}
Logged: ${reports.logged} time(s)
Build
Pipeline: ${build.pipeline}
Build: ${build.buildNumber}
Version: ${build.version}
`;
} catch (err) {
console.error("Failed to load overview stats:", err);
usersEl.innerHTML = `no active users!
`;
activityEl.innerHTML = `no activity!
`;
generalEl.innerHTML = `no statistics!
`;
}
}
function loadAll() {
loadNews();
loadMedia();
loadOverview();
}
async function bootstrapAdmin() {
try {
await loadAll();
} finally {
document.body.classList.remove('is-loading');
}
setInterval(loadOverview, 30000);
}
// Keep "current users" / "recent activity" feeling live without a full reload.
setInterval(loadOverview, 30000);