134 lines
4.8 KiB
JavaScript
134 lines
4.8 KiB
JavaScript
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 = `<div class="item-card"><p class="item-content">No news yet.</p></div>`;
|
|
return;
|
|
}
|
|
el.innerHTML = posts.map(post => `
|
|
<div class="item-card">
|
|
<p class="item-title">${escapeHtml(post.title)}<span class="item-timestamp">${timeAgo(post.created_at)}</span></p>
|
|
<p class="item-content">${escapeHtml(post.content)}</p>
|
|
</div>
|
|
`).join("");
|
|
} catch (err) {
|
|
console.error("Failed to load news:", err);
|
|
el.innerHTML = `<div class="item-card"><p class="item-content">Couldn't load news right now.</p></div>`;
|
|
}
|
|
}
|
|
|
|
// ---------- Media ----------
|
|
async function loadMedia() {
|
|
const el = document.getElementById("media-grid");
|
|
try {
|
|
const items = await getJSON("/media?limit=8");
|
|
if (items.length === 0) {
|
|
el.innerHTML = `<div class="media-item"><p class="media-title">No media yet.</p></div>`;
|
|
return;
|
|
}
|
|
el.innerHTML = items.map(item => `
|
|
<div class="media-item">
|
|
<p class="media-title">${escapeHtml(item.title)}</p>
|
|
${item.media_type === "video"
|
|
? `<video class="img-embed" src="${escapeHtml(item.url)}" controls style="width:100%;height:auto;background:#000;"></video>`
|
|
: `<img class="img-embed" src="${escapeHtml(item.url)}" alt="${escapeHtml(item.title)}" style="width:100%;height:auto;object-fit:cover;">`
|
|
}
|
|
</div>
|
|
`).join("");
|
|
} catch (err) {
|
|
console.error("Failed to load media:", err);
|
|
el.innerHTML = `<div class="media-item"><p class="media-title">Couldn't load media right now.</p></div>`;
|
|
}
|
|
}
|
|
|
|
// ---------- 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 => `
|
|
<div class="stat-row"><span>${escapeHtml(u.username)}</span><span class="val">now</span></div>
|
|
`).join("")
|
|
: `<div class="stat-row"><span>No one online</span><span class="val"></span></div>`;
|
|
|
|
activityEl.innerHTML = data.recentActivity.length
|
|
? data.recentActivity.map(a => `
|
|
<div class="stat-row"><span>${escapeHtml(a.description)}</span><span class="val">${timeAgo(a.created_at)}</span></div>
|
|
`).join("")
|
|
: `<div class="stat-row"><span>No recent activity</span><span class="val"></span></div>`;
|
|
|
|
const { users, reports, build } = data.general;
|
|
generalEl.innerHTML = `
|
|
<div class="stat-card">
|
|
<h4>Users</h4>
|
|
<p>Total: ${users.total}<br>Recent: ${users.mostRecent ? escapeHtml(users.mostRecent.username) : "—"}</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h4>Reports</h4>
|
|
<p>Named: ${reports.named}<br>Associated: ${reports.associated}<br>Logged: ${reports.logged} time(s)</p>
|
|
</div>
|
|
<div class="stat-card">
|
|
<h4>Build</h4>
|
|
<p>Pipeline: ${build.pipeline}<br>Build: ${build.buildNumber}<br>Version: ${build.version}</p>
|
|
</div>
|
|
`;
|
|
} catch (err) {
|
|
console.error("Failed to load overview stats:", err);
|
|
usersEl.innerHTML = `<div class="stat-row"><span>no active users!</span><span class="val"></span></div>`;
|
|
activityEl.innerHTML = `<div class="stat-row"><span>no activity!</span><span class="val"></span></div>`;
|
|
generalEl.innerHTML = `<div class="stat-card"><h4>no statistics!</h4></div>`;
|
|
}
|
|
}
|
|
|
|
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); |