219 lines
7.3 KiB
JavaScript
219 lines
7.3 KiB
JavaScript
|
// this is slop. i know. i'm sorry.
|
|||
|
// atleast i'm not a reactfag.
|
|||
|
|
|||
|
// we add some fancy text typing animation blah blah blah
|
|||
|
function writeoutnavlabel() {
|
|||
|
const firstNavIcon = document.querySelector('.nav-icon[href="#about"]');
|
|||
|
const label = firstNavIcon.querySelector(".nav-label");
|
|||
|
const text = label.textContent;
|
|||
|
label.textContent = "";
|
|||
|
label.style.opacity = "1";
|
|||
|
|
|||
|
let i = 0;
|
|||
|
function type() {
|
|||
|
if (i < text.length) {
|
|||
|
label.textContent += text.charAt(i);
|
|||
|
i++;
|
|||
|
setTimeout(type, 100);
|
|||
|
}
|
|||
|
}
|
|||
|
// why am i using a function inside a function?
|
|||
|
// i don't know. i'm sorry.
|
|||
|
type();
|
|||
|
}
|
|||
|
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
updateTime();
|
|||
|
// this is a terrible way to update the time
|
|||
|
setInterval(updateTime, 1);
|
|||
|
|
|||
|
function calculateAverageUptime(services) {
|
|||
|
if (!services || services.length === 0) return 0;
|
|||
|
// did i ever mention that i hate math?
|
|||
|
const totalUptime = services.reduce(
|
|||
|
(sum, service) => sum + service.online_24_hours,
|
|||
|
0,
|
|||
|
);
|
|||
|
return (totalUptime / services.length).toFixed(2);
|
|||
|
}
|
|||
|
|
|||
|
// projects
|
|||
|
const projectItems = document.querySelectorAll(".project-item");
|
|||
|
projectItems.forEach((item) => {
|
|||
|
item.addEventListener("click", function () {
|
|||
|
const content = this.querySelector(".project-content");
|
|||
|
const icon = this.querySelector(".expand-icon");
|
|||
|
|
|||
|
// close other expanded items
|
|||
|
projectItems.forEach((otherItem) => {
|
|||
|
if (otherItem !== item) {
|
|||
|
otherItem
|
|||
|
.querySelector(".project-content")
|
|||
|
.classList.remove("expanded");
|
|||
|
otherItem.querySelector(".expand-icon").textContent = "+";
|
|||
|
}
|
|||
|
});
|
|||
|
content.classList.toggle("expanded");
|
|||
|
icon.textContent = content.classList.contains("expanded") ? "−" : "+";
|
|||
|
});
|
|||
|
});
|
|||
|
|
|||
|
// status updater
|
|||
|
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";
|
|||
|
if (service.latency > 500000) return "status-warning";
|
|||
|
return "status-online";
|
|||
|
}
|
|||
|
|
|||
|
function getGroupName(groupId) {
|
|||
|
const groupNames = {
|
|||
|
4: "INFRASTRUCTURE",
|
|||
|
5: "SERVICES",
|
|||
|
6: "API",
|
|||
|
};
|
|||
|
return groupNames[groupId] || `GROUP ${groupId}`;
|
|||
|
}
|
|||
|
|
|||
|
function createStatusHTML(services) {
|
|||
|
// Group services by group_id
|
|||
|
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]) => `
|
|||
|
<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>
|
|||
|
</div>
|
|||
|
<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>
|
|||
|
`,
|
|||
|
)
|
|||
|
.join("");
|
|||
|
}
|
|||
|
|
|||
|
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 = `
|
|||
|
<div class="error-message">
|
|||
|
UNABLE TO FETCH STATUS DATA
|
|||
|
<div style="font-size: 0.8em; margin-top: 5px;">
|
|||
|
${error.message}
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
`;
|
|||
|
averageUptimeElement.textContent = "UPTIME ERROR";
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
// initial update
|
|||
|
updateStatus();
|
|||
|
|
|||
|
// status details toggle
|
|||
|
document.querySelectorAll(".status-item .entry").forEach((item) => {
|
|||
|
const details = item.nextElementSibling;
|
|||
|
if (details && details.classList.contains("status-details")) {
|
|||
|
item.addEventListener("click", function () {
|
|||
|
details.classList.toggle("expanded");
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// navigation
|
|||
|
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
|||
|
anchor.addEventListener("click", function (e) {
|
|||
|
e.preventDefault();
|
|||
|
document.querySelector(this.getAttribute("href")).scrollIntoView({
|
|||
|
behavior: "smooth",
|
|||
|
});
|
|||
|
});
|
|||
|
});
|
|||
|
|
|||
|
// update active nav icon
|
|||
|
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) => {
|
|||
|
icon.classList.remove("active");
|
|||
|
if (icon.getAttribute("href") === `#${current}`) {
|
|||
|
icon.classList.add("active");
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
|
|||
|
// dom content event listener
|
|||
|
document.addEventListener("DOMContentLoaded", () => {
|
|||
|
writeoutnavlabel();
|
|||
|
console.log(
|
|||
|
"%cELIA.NETWORK",
|
|||
|
"color: #2a5a5a; font-size: 24px; font-weight: bold; text-shadow: 2px 2px 4px rgba(42, 90, 90, 0.4), 4px 4px 8px rgba(42, 90, 90, 0.2), 6px 6px 12px rgba(42, 90, 90, 0.1), 0 0 20px rgba(42, 90, 90, 0.3); transform: perspective(1000px) rotateX(20deg) rotateY(-10deg) translateZ(50px); animation: console3d 8s infinite cubic-bezier(0.4, 0, 0.2, 1), glow 2s infinite alternate; @keyframes console3d { 0% { transform: perspective(1000px) rotateX(20deg) rotateY(-10deg) translateZ(50px); } 25% { transform: perspective(1000px) rotateX(-15deg) rotateY(15deg) translateZ(75px); } 50% { transform: perspective(1000px) rotateX(10deg) rotateY(20deg) translateZ(100px); } 75% { transform: perspective(1000px) rotateX(-20deg) rotateY(-15deg) translateZ(75px); } 100% { transform: perspective(1000px) rotateX(20deg) rotateY(-10deg) translateZ(50px); } } @keyframes glow { from { text-shadow: 0 0 20px rgba(42, 90, 90, 0.3), 0 0 40px rgba(42, 90, 90, 0.2); } to { text-shadow: 0 0 40px rgba(42, 90, 90, 0.5), 0 0 80px rgba(42, 90, 90, 0.3); } }",
|
|||
|
);
|
|||
|
});
|