105 lines
3.8 KiB
HTML
105 lines
3.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MysticOS</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
</head>
|
|
<body>
|
|
|
|
<h1>MYSTIC<span>OS</span></h1>
|
|
|
|
<div class="dock">
|
|
<a href="https://git.bray.io" class="icon-link">
|
|
<div class="icon-box"><i data-lucide="git-branch"></i></div>
|
|
<div class="label">Forgejo</div>
|
|
</a>
|
|
<a href="https://vault.bray.io" class="icon-link">
|
|
<div class="icon-box"><i data-lucide="shield-check"></i></div>
|
|
<div class="label">Vault</div>
|
|
</a>
|
|
<a href="https://dns.bray.io" class="icon-link">
|
|
<div class="icon-box"><i data-lucide="globe"></i></div>
|
|
<div class="label">Pi-hole</div>
|
|
</a>
|
|
<a href="https://grafana.bray.io" class="icon-link">
|
|
<div class="icon-box"><i data-lucide="line-chart"></i></div>
|
|
<div class="label">Grafana</div>
|
|
</a>
|
|
<a href="https://prometheus.bray.io" class="icon-link">
|
|
<div class="icon-box"><i data-lucide="activity"></i></div>
|
|
<div class="label">Prometheus</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div class="search-container">
|
|
<form action="https://www.google.com/search" method="GET">
|
|
<div class="search-wrapper">
|
|
<i data-lucide="search" class="search-icon"></i>
|
|
<input type="text" name="q" placeholder="Search the web..." autofocus autocomplete="off">
|
|
<button type="submit" style="display:none;"></button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
// Configuration
|
|
const services = [
|
|
{ name: 'Forgejo', url: 'https://git.bray.io' },
|
|
{ name: 'Vault', url: 'https://vault.bray.io' },
|
|
{ name: 'Pi-hole', url: 'https://dns.bray.io' },
|
|
{ name: 'Grafana', url: 'https://grafana.bray.io' },
|
|
{ name: 'Prometheus', url: 'https://prometheus.bray.io' }
|
|
];
|
|
|
|
function updateStatus(name, isUp) {
|
|
const links = document.querySelectorAll('.icon-link');
|
|
links.forEach(link => {
|
|
if (link.querySelector('.label').textContent === name) {
|
|
const box = link.querySelector('.icon-box');
|
|
box.style.borderColor = isUp ? '#3fb950' : '#f85149';
|
|
box.style.boxShadow = isUp ? '0 0 10px rgba(63, 185, 80, 0.2)' : '0 0 10px rgba(248, 81, 73, 0.2)';
|
|
}
|
|
});
|
|
}
|
|
|
|
async function checkHealth() {
|
|
services.forEach(service => {
|
|
const img = new Image();
|
|
let hasResponded = false;
|
|
|
|
// Success or security errors both indicate the service is "up"
|
|
img.onload = () => {
|
|
hasResponded = true;
|
|
updateStatus(service.name, true);
|
|
};
|
|
|
|
img.onerror = () => {
|
|
hasResponded = true;
|
|
updateStatus(service.name, true);
|
|
};
|
|
|
|
// Trigger the request with a cache-buster
|
|
img.src = `${service.url}/favicon.ico?t=${Date.now()}`;
|
|
|
|
// If no response at all after 5s, mark as down
|
|
setTimeout(() => {
|
|
if (!hasResponded) {
|
|
updateStatus(service.name, false);
|
|
}
|
|
}, 5000);
|
|
});
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
lucide.createIcons();
|
|
checkHealth();
|
|
// Refresh every 30 seconds
|
|
setInterval(checkHealth, 30000);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |