Spaces:
Sleeping
Sleeping
File size: 4,831 Bytes
aa66c2a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
const tbody = document.getElementById("statusTbody");
const lastRefresh = document.getElementById("lastRefresh");
const checkNowBtn = document.getElementById("checkNowBtn");
const addSiteBtn = document.getElementById("addSiteBtn");
const addDialog = document.getElementById("addDialog");
const addForm = document.getElementById("addForm");
const cancelAdd = document.getElementById("cancelAdd");
const siteName = document.getElementById("siteName");
const siteUrl = document.getElementById("siteUrl");
const incidentPane = document.getElementById("incidentPane");
const incidentTitle = document.getElementById("incidentTitle");
const incidentsList = document.getElementById("incidentsList");
const closeIncidents = document.getElementById("closeIncidents");
function fmtTs(s){
if(!s) return "β";
const d = new Date(s);
return d.toLocaleString();
}
function fmtPct(v){
if(v === null || v === undefined) return "β";
return `${v.toFixed ? v.toFixed(2) : v}%`;
}
function dot(ok){
return `<span class="dot ${ok ? 'green':'red'}" title="${ok?'UP':'DOWN'}"></span>`;
}
async function fetchStatus(){
const res = await fetch("/api/status");
const data = await res.json();
tbody.innerHTML = "";
data.forEach(item => {
const last = item.last || {};
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${dot(last.ok)}</td>
<td>${item.name}</td>
<td><a class="url" href="${item.url}" target="_blank" rel="noopener">${item.url}</a></td>
<td>${fmtTs(last.ts)}</td>
<td>${last.ms ?? "β"}</td>
<td>${last.status_code ?? "β"}</td>
<td><span class="badge">${fmtPct(item.uptime24h)}</span></td>
<td><span class="badge">${fmtPct(item.uptime7d)}</span></td>
<td class="row-actions">
<button class="ghost" data-action="incidents" data-url="${item.url}" data-name="${item.name}">Incidents</button>
<button class="ghost" data-action="delete" data-url="${item.url}">Delete</button>
</td>
`;
tbody.appendChild(tr);
});
lastRefresh.textContent = `Last refresh: ${new Date().toLocaleTimeString()}`;
}
async function checkNow(){
checkNowBtn.disabled = true;
try{
await fetch("/api/check-now", {method:"POST"});
await fetchStatus();
} finally {
checkNowBtn.disabled = false;
}
}
function openAdd(){
siteName.value = "";
siteUrl.value = "";
addDialog.showModal();
}
function closeAdd(){
addDialog.close();
}
addForm.addEventListener("submit", async (e) => {
e.preventDefault();
const body = { name: siteName.value || siteUrl.value, url: siteUrl.value };
if(!body.url) return;
const res = await fetch("/api/sites", {
method:"POST",
headers: { "Content-Type":"application/json" },
body: JSON.stringify(body)
});
if (res.ok){
closeAdd();
await fetchStatus();
} else {
const msg = await res.text(); // <-- show backend detail
alert("Failed to add site:\n" + msg);
}
});
cancelAdd.addEventListener("click", (e)=>{ e.preventDefault(); closeAdd(); });
tbody.addEventListener("click", async (e) => {
const btn = e.target.closest("button");
if(!btn) return;
const action = btn.dataset.action;
const url = btn.dataset.url;
if(action === "delete"){
if(confirm(`Delete monitor for:\n${url}?`)){
await fetch(`/api/sites?url=${encodeURIComponent(url)}`, { method: "DELETE" });
await fetchStatus();
}
}
if(action === "incidents"){
await loadIncidents(url, btn.dataset.name || url);
}
});
async function loadIncidents(url, name){
const res = await fetch(`/api/incidents?url=${encodeURIComponent(url)}`);
const data = await res.json();
incidentPane.classList.remove("hidden");
incidentTitle.textContent = `Incidents β ${name}`;
if(!data.length){
incidentsList.innerHTML = `<div class="muted" style="padding:8px 2px">No incidents recorded.</div>`;
return;
}
incidentsList.innerHTML = "";
data.forEach(x => {
const end = x.end_ts ? new Date(x.end_ts) : null;
const start = new Date(x.start_ts);
const durationMin = end ? Math.max(0, Math.round((end - start)/60000)) : null;
const div = document.createElement("div");
div.className = "incident";
div.innerHTML = `
<div>
<div><strong class="down">DOWN</strong> ${start.toLocaleString()}</div>
${end ? `<div><strong class="ok">UP</strong> ${end.toLocaleString()}</div>` : `<div class="muted">ongoing...</div>`}
</div>
<div class="muted">${durationMin !== null ? durationMin + " min" : ""}</div>
`;
incidentsList.appendChild(div);
});
}
closeIncidents.addEventListener("click", ()=> incidentPane.classList.add("hidden"));
checkNowBtn.addEventListener("click", checkNow);
addSiteBtn.addEventListener("click", openAdd);
fetchStatus();
setInterval(fetchStatus, 30000);
|