uptimer2 / static /app.js
ntdservices's picture
Upload 6 files
aa66c2a verified
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);