2024-11-05 15:58:00 +01:00
|
|
|
|
// animation speeds
|
2024-11-05 08:18:36 +01:00
|
|
|
|
const TYPING_SPEED = 50;
|
|
|
|
|
const ERASE_SPEED = 70;
|
2024-11-04 15:58:35 +01:00
|
|
|
|
const STATUS_UPDATE_INTERVAL = 60000; // 1min updates
|
|
|
|
|
const TIME_UPDATE_INTERVAL = 1; // 1ms updates (yeah its bad)
|
2024-11-04 15:42:18 +01:00
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// status warning stuff
|
2024-11-05 08:19:07 +01:00
|
|
|
|
const LATENCY_WARNING_THRESHOLD = 300000;
|
2024-11-04 15:42:18 +01:00
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// lazy group name mapping
|
2024-11-04 15:42:18 +01:00
|
|
|
|
const GROUP_NAMES = {
|
|
|
|
|
4: "INFRASTRUCTURE",
|
|
|
|
|
5: "SERVICES",
|
|
|
|
|
6: "API",
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// does the fancy typing animation for nav labels
|
|
|
|
|
// its way overcomplicated but whatever
|
2024-11-04 12:11:42 +01:00
|
|
|
|
function writeoutnavlabel(label) {
|
|
|
|
|
if (label.getAttribute("data-animating") === "true") return;
|
|
|
|
|
|
|
|
|
|
const text = label.getAttribute("data-text") || label.textContent;
|
|
|
|
|
label.setAttribute("data-text", text);
|
2024-11-03 23:17:56 +01:00
|
|
|
|
label.textContent = "";
|
|
|
|
|
label.style.opacity = "1";
|
2024-11-04 12:11:42 +01:00
|
|
|
|
label.setAttribute("data-animating", "true");
|
2024-11-03 23:17:56 +01:00
|
|
|
|
|
|
|
|
|
let i = 0;
|
2024-11-04 12:11:42 +01:00
|
|
|
|
let animationInterval = setInterval(() => {
|
2024-11-03 23:17:56 +01:00
|
|
|
|
if (i < text.length) {
|
|
|
|
|
label.textContent += text.charAt(i);
|
|
|
|
|
i++;
|
2024-11-04 12:11:42 +01:00
|
|
|
|
} else {
|
|
|
|
|
clearInterval(animationInterval);
|
|
|
|
|
label.setAttribute("data-animating", "false");
|
|
|
|
|
const icon = label.closest(".nav-icon");
|
|
|
|
|
if (icon) {
|
|
|
|
|
icon.isAnimating = false;
|
|
|
|
|
icon.hoverAnimationDone = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-04 15:42:18 +01:00
|
|
|
|
}, TYPING_SPEED);
|
2024-11-04 12:11:42 +01:00
|
|
|
|
|
|
|
|
|
label.setAttribute("data-animation-interval", animationInterval);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// erases the nav labels
|
|
|
|
|
// just as terrible as the typing but matches
|
2024-11-04 12:11:42 +01:00
|
|
|
|
function eraselabel(label) {
|
|
|
|
|
if (label.getAttribute("data-animating") === "true") {
|
|
|
|
|
const existingInterval = label.getAttribute("data-animation-interval");
|
2024-11-04 15:42:18 +01:00
|
|
|
|
if (existingInterval) clearInterval(existingInterval);
|
2024-11-03 23:17:56 +01:00
|
|
|
|
}
|
2024-11-04 12:11:42 +01:00
|
|
|
|
|
|
|
|
|
label.setAttribute("data-animating", "true");
|
|
|
|
|
|
|
|
|
|
let eraseInterval = setInterval(() => {
|
|
|
|
|
const currentText = label.textContent;
|
|
|
|
|
if (currentText.length > 0) {
|
|
|
|
|
label.textContent = currentText.slice(0, -1);
|
|
|
|
|
} else {
|
|
|
|
|
clearInterval(eraseInterval);
|
|
|
|
|
label.setAttribute("data-animating", "false");
|
|
|
|
|
label.style.opacity = "0";
|
|
|
|
|
}
|
2024-11-04 15:42:18 +01:00
|
|
|
|
}, ERASE_SPEED);
|
2024-11-04 12:11:42 +01:00
|
|
|
|
|
|
|
|
|
label.setAttribute("data-animation-interval", eraseInterval);
|
2024-11-03 23:17:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-11-04 23:37:38 +01:00
|
|
|
|
// we get the client info from the headers
|
|
|
|
|
// unclean but it works
|
|
|
|
|
function updateClientInfo() {
|
2024-11-05 00:02:01 +01:00
|
|
|
|
const locationElement = document.querySelector(".location");
|
|
|
|
|
if (!locationElement) {
|
|
|
|
|
console.warn("Location element not found in DOM");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
locationElement.classList.add("loading");
|
2024-11-04 23:37:38 +01:00
|
|
|
|
fetch(window.location.href)
|
|
|
|
|
.then((response) => {
|
|
|
|
|
const clientInfo = response.headers.get("X-Client-Info");
|
2024-11-05 00:02:01 +01:00
|
|
|
|
if (!clientInfo) {
|
|
|
|
|
throw new Error("X-Client-Info header not present");
|
2024-11-04 23:37:38 +01:00
|
|
|
|
}
|
2024-11-05 15:58:00 +01:00
|
|
|
|
// this solves the weird encoding issue with µ
|
2024-11-05 00:02:01 +01:00
|
|
|
|
const cleanClientInfo = clientInfo
|
2024-11-05 01:44:42 +01:00
|
|
|
|
.replace(/\u00C2\u00B5s/g, "µs")
|
|
|
|
|
.replace(/µs/g, "µs")
|
2024-11-05 00:02:01 +01:00
|
|
|
|
.trim();
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
locationElement.textContent = cleanClientInfo;
|
|
|
|
|
locationElement.classList.remove("loading");
|
|
|
|
|
locationElement.classList.add("loaded");
|
|
|
|
|
});
|
2024-11-04 23:37:38 +01:00
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
2024-11-05 00:02:01 +01:00
|
|
|
|
console.error("Failed to update client info:", error);
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
locationElement.textContent = "CLIENT INFO UNAVAILABLE";
|
|
|
|
|
locationElement.classList.remove("loading");
|
|
|
|
|
locationElement.classList.add("loaded", "error");
|
|
|
|
|
});
|
2024-11-04 23:37:38 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// updates the time display
|
|
|
|
|
// probably updates too much but eh
|
2024-11-03 23:17:56 +01:00
|
|
|
|
function updateTime() {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const timeString = now
|
|
|
|
|
.toLocaleString("en-US", {
|
|
|
|
|
month: "2-digit",
|
|
|
|
|
day: "2-digit",
|
|
|
|
|
year: "numeric",
|
|
|
|
|
hour: "2-digit",
|
|
|
|
|
minute: "2-digit",
|
|
|
|
|
second: "2-digit",
|
|
|
|
|
fractionalSecondDigits: 3,
|
|
|
|
|
hour12: true,
|
|
|
|
|
})
|
|
|
|
|
.toUpperCase();
|
|
|
|
|
document.getElementById("current-time").textContent = timeString;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// bunch of status helper functions for formatting and stuff
|
2024-11-03 23:17:56 +01:00
|
|
|
|
function calculateAverageUptime(services) {
|
|
|
|
|
if (!services || services.length === 0) return 0;
|
|
|
|
|
const totalUptime = services.reduce(
|
|
|
|
|
(sum, service) => sum + service.online_24_hours,
|
|
|
|
|
0,
|
|
|
|
|
);
|
|
|
|
|
return (totalUptime / services.length).toFixed(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatLatency(latency) {
|
|
|
|
|
return (Math.round((latency / 1000) * 10) / 10).toFixed(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatUptime(uptime) {
|
|
|
|
|
return uptime.toFixed(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getStatusClass(service) {
|
|
|
|
|
if (!service.online) return "status-offline";
|
2024-11-04 15:42:18 +01:00
|
|
|
|
if (service.latency > LATENCY_WARNING_THRESHOLD) return "status-warning";
|
2024-11-03 23:17:56 +01:00
|
|
|
|
return "status-online";
|
|
|
|
|
}
|
2024-11-04 15:42:18 +01:00
|
|
|
|
|
2024-11-03 23:17:56 +01:00
|
|
|
|
function getGroupName(groupId) {
|
2024-11-04 15:42:18 +01:00
|
|
|
|
return GROUP_NAMES[groupId] || `GROUP ${groupId}`;
|
2024-11-03 23:17:56 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// builds all the html for status page
|
|
|
|
|
// groups everything by group_id cause organization
|
2024-11-03 23:17:56 +01:00
|
|
|
|
function createStatusHTML(services) {
|
|
|
|
|
const groups = services.reduce((acc, service) => {
|
|
|
|
|
const groupId = service.group_id;
|
|
|
|
|
if (!acc[groupId]) acc[groupId] = [];
|
|
|
|
|
acc[groupId].push(service);
|
|
|
|
|
return acc;
|
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
|
|
return Object.entries(groups)
|
|
|
|
|
.map(
|
|
|
|
|
([groupId, groupServices]) => `
|
2024-11-04 15:58:35 +01:00
|
|
|
|
<div class="status-group">
|
|
|
|
|
<div class="subsection-title">${getGroupName(parseInt(groupId))}</div>
|
|
|
|
|
${groupServices
|
|
|
|
|
.map(
|
|
|
|
|
(service) => `
|
|
|
|
|
<div class="status-item">
|
|
|
|
|
<div class="entry" data-service="${service.permalink}">
|
|
|
|
|
<span class="status-indicator ${getStatusClass(service)}"></span>
|
|
|
|
|
<span class="service-name">${service.name.toUpperCase()}</span>
|
|
|
|
|
<span class="service-status">${service.online ? "OPERATIONAL" : "OFFLINE"}</span>
|
2024-11-04 15:42:18 +01:00
|
|
|
|
</div>
|
2024-11-04 15:58:35 +01:00
|
|
|
|
<div class="status-details">
|
|
|
|
|
<div class="status-metric">Latency: ${formatLatency(service.latency)}ms</div>
|
|
|
|
|
<div class="status-metric">Uptime 24h: ${formatUptime(service.online_24_hours)}%</div>
|
|
|
|
|
<div class="status-metric">7-day Uptime: ${formatUptime(service.online_7_days)}%</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`,
|
|
|
|
|
)
|
|
|
|
|
.join("")}
|
|
|
|
|
</div>
|
|
|
|
|
`,
|
2024-11-03 23:17:56 +01:00
|
|
|
|
)
|
|
|
|
|
.join("");
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// main status updater
|
|
|
|
|
// fetches and displays all service statuses
|
2024-11-03 23:17:56 +01:00
|
|
|
|
function updateStatus() {
|
|
|
|
|
const statusContainer = document.getElementById("status-container");
|
|
|
|
|
const averageUptimeElement = document.getElementById("average-uptime");
|
|
|
|
|
|
|
|
|
|
fetch("https://status.elia.network/api/services")
|
|
|
|
|
.then((response) => response.json())
|
|
|
|
|
.then((data) => {
|
|
|
|
|
statusContainer.innerHTML = createStatusHTML(data);
|
|
|
|
|
const averageUptime = calculateAverageUptime(data);
|
|
|
|
|
averageUptimeElement.textContent = `UPTIME ${averageUptime}%`;
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll(".status-item").forEach((item) => {
|
|
|
|
|
item.addEventListener("click", function () {
|
|
|
|
|
const details = this.querySelector(".status-details");
|
|
|
|
|
details.classList.toggle("expanded");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
statusContainer.innerHTML = `
|
2024-11-04 15:42:18 +01:00
|
|
|
|
<div class="error-message">
|
2024-11-04 15:58:35 +01:00
|
|
|
|
UNABLE TO FETCH STATUS DATA
|
2024-11-04 15:42:18 +01:00
|
|
|
|
<div style="font-size: 0.8em; margin-top: 5px;">
|
|
|
|
|
${error.message}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2024-11-03 23:17:56 +01:00
|
|
|
|
averageUptimeElement.textContent = "UPTIME ERROR";
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// handles expanding/collapsing project items
|
|
|
|
|
// closes others when opening new one
|
2024-11-04 15:42:18 +01:00
|
|
|
|
document.querySelectorAll(".project-item").forEach((item) => {
|
|
|
|
|
item.addEventListener("click", function () {
|
|
|
|
|
const content = this.querySelector(".project-content");
|
|
|
|
|
const icon = this.querySelector(".expand-icon");
|
2024-11-03 23:17:56 +01:00
|
|
|
|
|
2024-11-04 15:42:18 +01:00
|
|
|
|
document.querySelectorAll(".project-item").forEach((otherItem) => {
|
|
|
|
|
if (otherItem !== item) {
|
|
|
|
|
otherItem
|
|
|
|
|
.querySelector(".project-content")
|
|
|
|
|
.classList.remove("expanded");
|
|
|
|
|
otherItem.querySelector(".expand-icon").textContent = "+";
|
|
|
|
|
}
|
2024-11-03 23:17:56 +01:00
|
|
|
|
});
|
2024-11-04 15:42:18 +01:00
|
|
|
|
|
|
|
|
|
content.classList.toggle("expanded");
|
|
|
|
|
icon.textContent = content.classList.contains("expanded") ? "−" : "+";
|
|
|
|
|
});
|
2024-11-03 23:17:56 +01:00
|
|
|
|
});
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// smooth scrolls to sections when clicking nav
|
|
|
|
|
// better than jumping around
|
2024-11-03 23:17:56 +01:00
|
|
|
|
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
|
|
|
|
anchor.addEventListener("click", function (e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
document.querySelector(this.getAttribute("href")).scrollIntoView({
|
|
|
|
|
behavior: "smooth",
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// handles active nav icon updates on scroll
|
|
|
|
|
// shows which section youre looking at
|
2024-11-03 23:17:56 +01:00
|
|
|
|
window.addEventListener("scroll", () => {
|
|
|
|
|
let current = "";
|
|
|
|
|
document.querySelectorAll("section").forEach((section) => {
|
|
|
|
|
const sectionTop = section.offsetTop;
|
|
|
|
|
if (scrollY >= sectionTop - 60) {
|
|
|
|
|
current = section.getAttribute("id");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll(".nav-icon").forEach((icon) => {
|
2024-11-04 12:11:42 +01:00
|
|
|
|
const label = icon.querySelector(".nav-label");
|
|
|
|
|
const isCurrentSection = icon.getAttribute("href") === `#${current}`;
|
|
|
|
|
const wasActive = icon.classList.contains("active");
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
if (isCurrentSection && !wasActive) {
|
|
|
|
|
icon.classList.add("active");
|
|
|
|
|
label.style.opacity = "1";
|
|
|
|
|
if (!icon.isAnimating && !icon.hoverAnimationDone) {
|
|
|
|
|
writeoutnavlabel(label);
|
2024-11-04 12:11:42 +01:00
|
|
|
|
icon.hoverAnimationDone = true;
|
|
|
|
|
}
|
2024-11-04 15:58:35 +01:00
|
|
|
|
} else if (!isCurrentSection && wasActive) {
|
|
|
|
|
icon.classList.remove("active");
|
|
|
|
|
if (!icon.isAnimating) {
|
2024-11-04 12:11:42 +01:00
|
|
|
|
eraselabel(label);
|
|
|
|
|
icon.isAnimating = false;
|
2024-11-04 15:58:35 +01:00
|
|
|
|
icon.hoverAnimationDone = false;
|
2024-11-04 12:11:42 +01:00
|
|
|
|
}
|
2024-11-03 23:17:56 +01:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// nav icon hover stuff
|
|
|
|
|
// does the typing animation when hovering
|
2024-11-04 12:11:42 +01:00
|
|
|
|
document.querySelectorAll(".nav-icon").forEach((icon) => {
|
|
|
|
|
icon.hoverAnimationDone = false;
|
|
|
|
|
icon.isAnimating = false;
|
|
|
|
|
|
|
|
|
|
icon.addEventListener("mouseenter", () => {
|
|
|
|
|
if (!icon.classList.contains("active") && !icon.isAnimating) {
|
|
|
|
|
const label = icon.querySelector(".nav-label");
|
|
|
|
|
writeoutnavlabel(label);
|
|
|
|
|
icon.isAnimating = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
icon.addEventListener("mouseleave", () => {
|
|
|
|
|
if (!icon.classList.contains("active")) {
|
|
|
|
|
const label = icon.querySelector(".nav-label");
|
|
|
|
|
eraselabel(label);
|
|
|
|
|
icon.isAnimating = false;
|
|
|
|
|
icon.hoverAnimationDone = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-04 15:42:18 +01:00
|
|
|
|
icon.addEventListener("click", () => {
|
2024-11-04 12:11:42 +01:00
|
|
|
|
const label = icon.querySelector(".nav-label");
|
|
|
|
|
label.style.opacity = "1";
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// sets everything up when page loads
|
|
|
|
|
// starts all the intervals and stuff
|
2024-11-03 23:17:56 +01:00
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
2024-11-05 08:20:40 +01:00
|
|
|
|
updateClientInfo();
|
|
|
|
|
|
2024-11-04 15:42:18 +01:00
|
|
|
|
updateTime();
|
|
|
|
|
setInterval(updateTime, TIME_UPDATE_INTERVAL);
|
|
|
|
|
|
|
|
|
|
updateStatus();
|
|
|
|
|
setInterval(updateStatus, STATUS_UPDATE_INTERVAL);
|
|
|
|
|
|
2024-11-04 12:11:42 +01:00
|
|
|
|
const activeNavIcon = document.querySelector(".nav-icon.active");
|
|
|
|
|
if (activeNavIcon) {
|
|
|
|
|
const label = activeNavIcon.querySelector(".nav-label");
|
|
|
|
|
if (label) writeoutnavlabel(label);
|
|
|
|
|
}
|
2024-11-05 08:20:40 +01:00
|
|
|
|
console.log("Finished init");
|
2024-11-04 15:58:35 +01:00
|
|
|
|
// fancy console log for style points
|
2024-11-03 23:17:56 +01:00
|
|
|
|
console.log(
|
|
|
|
|
"%cELIA.NETWORK",
|
2024-11-04 15:42:18 +01:00
|
|
|
|
"color: #2a5a5a; font-size: 24px; font-weight: bold; text-shadow: 2px 2px 4px rgba(42, 90, 90, 0.3);",
|
2024-11-03 23:17:56 +01:00
|
|
|
|
);
|
|
|
|
|
});
|