101 lines
3.1 KiB
JavaScript
101 lines
3.1 KiB
JavaScript
/* @licstart Copyleft (🄯) 2025 James Osborne — AGPLv3+ @licend */
|
||
'use strict';
|
||
|
||
/* ===== Copy-to-clipboard handler ===== */
|
||
document.addEventListener('click', (e) => {
|
||
const el = e.target.closest('.copyable');
|
||
if (!el) return;
|
||
|
||
const text = el.dataset.copy ?? '';
|
||
const copyPromise =
|
||
(navigator.clipboard && window.isSecureContext)
|
||
? navigator.clipboard.writeText(text)
|
||
: (async () => {
|
||
const ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
ta.style.position = 'fixed';
|
||
ta.style.opacity = '0';
|
||
ta.style.left = '-9999px';
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
try { document.execCommand('copy'); } catch {}
|
||
ta.remove();
|
||
})();
|
||
|
||
Promise.resolve(copyPromise).then(() => {
|
||
el.dataset._orig ??= el.textContent || '';
|
||
el.textContent = `${el.dataset._orig} (copied!)`;
|
||
setTimeout(() => { el.textContent = el.dataset._orig; }, 1500);
|
||
});
|
||
});
|
||
|
||
/* ===== Stats loader ===== */
|
||
async function loadStats() {
|
||
const endpoints = [
|
||
'/relay-stats.json', // same-origin first (no CORS)
|
||
'https://relay-us-east.circlewithadot.net/relay-stats.json',
|
||
];
|
||
|
||
for (const url of endpoints) {
|
||
try {
|
||
const r = await fetch(url, { cache: 'no-store' });
|
||
if (!r.ok) continue;
|
||
|
||
const d = await r.json();
|
||
const set = (id, val) => {
|
||
const el = document.getElementById(id);
|
||
if (el) el.textContent = val;
|
||
};
|
||
|
||
set('instances', d.instances ?? '–');
|
||
// accept either jobs_5min (new) or jobs_per_min (old)
|
||
set('jobs', (d.jobs_5min ?? d.jobs_per_min ?? '–'));
|
||
set('updated', d.updated ? new Date(d.updated).toLocaleString() : '–');
|
||
return;
|
||
} catch (e) {
|
||
console.warn('[relay stats] fetch failed:', e);
|
||
}
|
||
}
|
||
|
||
const box = document.getElementById('relay-stats');
|
||
if (box) box.textContent = 'Stats unavailable';
|
||
}
|
||
|
||
loadStats();
|
||
setInterval(loadStats, 300000);
|
||
|
||
async function loadInstanceStatuses() {
|
||
const bodyEl = document.getElementById("instances-body");
|
||
const updatedEl = document.getElementById("instances-updated");
|
||
if (!bodyEl || !updatedEl) return;
|
||
|
||
try {
|
||
const res = await fetch("aprelay_instances.json", { cache: "no-store" });
|
||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||
|
||
const data = await res.json();
|
||
const instances = Array.isArray(data.instances) ? data.instances : [];
|
||
updatedEl.textContent = data.generated_at || "–";
|
||
|
||
if (!instances.length) {
|
||
bodyEl.innerHTML = `<div class="instances-row"><span class="mono">No data</span><span>–</span></div>`;
|
||
return;
|
||
}
|
||
|
||
bodyEl.innerHTML = instances.map(x => `
|
||
<div class="instances-row" role="row">
|
||
<span class="mono" role="cell">${x.domain}</span>
|
||
<span role="cell">${x.status}</span>
|
||
</div>
|
||
`).join("");
|
||
} catch (e) {
|
||
bodyEl.innerHTML = `<div class="instances-row"><span class="mono">Error loading list</span><span>–</span></div>`;
|
||
updatedEl.textContent = "–";
|
||
}
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
loadInstanceStatuses();
|
||
});
|
||
|