healthchecks_healthchecks/templates/front/dashboard.html
2022-12-22 16:40:16 +02:00

262 lines
7.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ site_name }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* Colors, dark theme */
.theme-dark {
background: #000;
color: #FFF;
}
.theme-dark #panel > div {
background: #AAA;
color: #000;
}
.theme-dark #panel > div.status-up {
color: #FFF;
background: #263026;
}
.theme-dark #panel > div.status-grace {
color: #000;
background: #FFB300;
}
.theme-dark #panel > div.status-down {
color: #000;
background: #ff3929;
}
.theme-dark .spinner:after {
border-color: rgba(255, 255, 255, 0.3) transparent rgba(255, 255, 255, 0.3) transparent;
}
/* Colors, light theme */
.theme-light {
background: #FFF;
color: #333;
}
.theme-light #panel > div {
color: #000;
background: #FFF;
border: 2px solid #DDD;
}
.theme-light #panel > div.status-grace {
color: #000;
background: #FFAB00;
border: none;
}
.theme-light #panel > div.status-down {
color: #FFF;
background: #D81818;
border: none;
}
.theme-light .spinner:after {
border-color: rgba(0, 0, 0, 0.2) transparent rgba(0, 0, 0, 0.2) transparent;
}
/* Spinner from https://loading.io/css/ */
.spinner {
display: none;
}
div.status-started .spinner {
position: absolute;
right: 8px;
top: 50%;
margin-top: -18px;
display: inline-block;
width: 36px;
height: 36px;
}
.spinner:after {
content: " ";
display: block;
width: 24px;
height: 24px;
margin: 1px;
border-radius: 50%;
border: 5px solid transparent;
animation: lds-dual-ring 1.25s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Layout and font */
html, body {
font-family: -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
height: 100%;
margin: 0;
}
#panel {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
grid-gap: 8px;
padding: 8px;
}
#panel > h1 {
grid-column-start: 1;
grid-column-end: -1;
margin: 12px 0;
font-size: 20px;
}
#panel > div {
padding: 8px 8px 32px 8px;
font-size: 18px;
position: relative;
}
#panel > div .name {
text-overflow: ellipsis;
overflow: hidden;
}
#panel > div .lp {
position: absolute;
font-size: 13px;
opacity: 0.8;
}
#panel > div .lp {
bottom: 8px;
left: 8px;
}
.check-template {
display: none;
}
</style>
</head>
<body class="theme-light">
<div id="panel"></div>
<div class="check-template">
<div class="name"></div>
<div class="lp"></div>
<div class="spinner"></div>
</div>
<script>
function fetch(key, callback) {
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
callback(JSON.parse(httpRequest.responseText));
}
}
};
httpRequest.open("GET", "/api/v2/checks/");
httpRequest.setRequestHeader("X-Api-Key", key);
httpRequest.send();
}
function timeSince(date) {
var v = Math.floor((new Date() - date) / 1000);
if (v < 60) { // v is seconds
return v + " second" + (v == 1 ? "" : "s");
}
v = Math.floor(v / 60); // v is now minutes
if (v < 60) {
return v + " minute" + (v == 1 ? "" : "s");
}
v = Math.floor(v / 60); // v is now hours
if (v < 24) {
return v + " hour" + (v == 1 ? "" : "s");
}
v = Math.floor(v / 24); // v is now days
return v + " day" + (v == 1 ? "" : "s");
};
var template = document.querySelector(".check-template");
function updatePanel(node) {
fetch(node.dataset.readonlyKey, function(doc) {
var tag = "TAG_" + node.dataset.readonlyKey.substr(0, 6);
// Sort returned checks by name:
var sorted = doc.checks.sort(function(a, b) {
return a.name.localeCompare(b.name)
});
var fragment = document.createDocumentFragment();
sorted.forEach(function(item) {
var div = template.cloneNode(true);
div.setAttribute("class", tag + " status-" + item.status + (item.started ? " status-started" : ""));
div.querySelector(".name").textContent = item.name || "unnamed";
if (item.last_ping) {
var s = timeSince(Date.parse(item.last_ping)) + " ago";
div.querySelector(".lp").textContent = s;
}
fragment.appendChild(div);
});
document.querySelectorAll('.' + tag).forEach(function(element) {
element.remove();
});
node.parentNode.insertBefore(fragment, node.nextSibling);
});
}
if (window.location.hash) {
var panel;
var pairs = window.location.hash.substr(1).split("&");
for (var i=0, pair; pair=pairs[i]; i++) {
if (pair.indexOf("theme=") != -1) {
document.body.setAttribute("class", pair.replace("=", "-"));
continue;
}
var parts = pair.split("=");
var h1 = document.createElement("H1");
h1.dataset.readonlyKey = parts[0];
if (parts[1]) {
h1.innerText = decodeURIComponent(parts[1]);
}
if (!panel) {
panel = document.getElementById("panel");
panel.innerHTML = "";
}
panel.appendChild(h1);
}
}
document.querySelectorAll("h1").forEach(updatePanel);
setInterval(function() {
document.querySelectorAll("h1").forEach(updatePanel);
}, 5000);
</script>
</body>
</html>