72 lines
2.3 KiB
JavaScript
72 lines
2.3 KiB
JavaScript
/* Shared helpers: CSRF-aware fetch, confirm dialogs, formatting. */
|
|
"use strict";
|
|
|
|
const PB = (() => {
|
|
const csrf = document.querySelector('meta[name="csrf-token"]').content;
|
|
|
|
async function api(method, url, body) {
|
|
const opts = { method, headers: { "X-CSRF-Token": csrf }, credentials: "same-origin" };
|
|
if (body !== undefined) {
|
|
opts.headers["Content-Type"] = "application/json";
|
|
opts.body = JSON.stringify(body);
|
|
}
|
|
const res = await fetch(url, opts);
|
|
if (res.status === 401) {
|
|
location.href = "/login";
|
|
throw new Error("not logged in");
|
|
}
|
|
let data = {};
|
|
try { data = await res.json(); } catch (e) { /* non-JSON error page */ }
|
|
if (!res.ok) throw new Error(data.error || res.statusText);
|
|
return data;
|
|
}
|
|
|
|
function bytes(n) {
|
|
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
let i = 0;
|
|
n = Number(n) || 0;
|
|
while (Math.abs(n) >= 1000 && i < units.length - 1) { n /= 1000; i++; }
|
|
return (i === 0 ? n.toFixed(0) : n.toFixed(n < 10 ? 2 : n < 100 ? 1 : 0)) + " " + units[i];
|
|
}
|
|
|
|
function rate(bps) {
|
|
return bytes(bps) + "/s";
|
|
}
|
|
|
|
function ago(ts) {
|
|
if (!ts) return "never";
|
|
const s = Math.max(0, Date.now() / 1000 - ts);
|
|
if (s < 60) return "just now";
|
|
if (s < 3600) return Math.floor(s / 60) + " min ago";
|
|
if (s < 86400) return Math.floor(s / 3600) + " h ago";
|
|
return new Date(ts * 1000).toLocaleString();
|
|
}
|
|
|
|
function el(tag, attrs, ...children) {
|
|
const e = document.createElement(tag);
|
|
for (const [k, v] of Object.entries(attrs || {})) {
|
|
if (k === "class") e.className = v;
|
|
else if (k === "text") e.textContent = v;
|
|
else e.setAttribute(k, v);
|
|
}
|
|
for (const c of children) e.append(c);
|
|
return e;
|
|
}
|
|
|
|
// Inline handlers are blocked by the CSP, so confirmations are wired here.
|
|
document.addEventListener("submit", (ev) => {
|
|
const msg = ev.target.dataset && ev.target.dataset.confirm;
|
|
if (msg && !window.confirm(msg)) ev.preventDefault();
|
|
});
|
|
|
|
function fillTimestamps() {
|
|
document.querySelectorAll("[data-ts]").forEach((n) => {
|
|
const t = n.querySelector(".ts");
|
|
if (t) t.textContent = ago(Number(n.dataset.ts));
|
|
});
|
|
}
|
|
document.addEventListener("DOMContentLoaded", fillTimestamps);
|
|
|
|
return { api, bytes, rate, ago, el, csrf };
|
|
})();
|