infra/master/mystic-home/www/index.html
2026-04-01 11:43:26 -04:00

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>