496 lines
18 KiB
HTML
496 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>HOMELAB COMMAND CENTER</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Press Start 2P', monospace;
|
|
background: #0a0a0a;
|
|
min-height: 100vh;
|
|
overflow-x: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
/* CRT scanline effect */
|
|
body::before {
|
|
content: "";
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: repeating-linear-gradient(
|
|
0deg,
|
|
rgba(0, 0, 0, 0.15),
|
|
rgba(0, 0, 0, 0.15) 1px,
|
|
transparent 1px,
|
|
transparent 2px
|
|
);
|
|
pointer-events: none;
|
|
z-index: 1000;
|
|
}
|
|
|
|
/* Animated starfield background */
|
|
.stars {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: -1;
|
|
}
|
|
|
|
.star {
|
|
position: absolute;
|
|
background: #fff;
|
|
border-radius: 50%;
|
|
animation: twinkle 2s infinite;
|
|
}
|
|
|
|
@keyframes twinkle {
|
|
0%, 100% { opacity: 0.3; }
|
|
50% { opacity: 1; }
|
|
}
|
|
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
header {
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
position: relative;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2rem;
|
|
color: #00ff00;
|
|
text-shadow:
|
|
0 0 10px #00ff00,
|
|
0 0 20px #00ff00,
|
|
0 0 40px #00ff00,
|
|
4px 4px 0 #003300;
|
|
animation: glow 1.5s ease-in-out infinite alternate;
|
|
letter-spacing: 4px;
|
|
}
|
|
|
|
@keyframes glow {
|
|
from { text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00, 0 0 40px #00ff00, 4px 4px 0 #003300; }
|
|
to { text-shadow: 0 0 20px #00ff00, 0 0 40px #00ff00, 0 0 60px #00ff00, 4px 4px 0 #003300; }
|
|
}
|
|
|
|
.subtitle {
|
|
color: #ff00ff;
|
|
font-size: 0.6rem;
|
|
margin-top: 15px;
|
|
text-shadow: 0 0 10px #ff00ff;
|
|
animation: blink 1s step-end infinite;
|
|
}
|
|
|
|
@keyframes blink {
|
|
50% { opacity: 0.7; }
|
|
}
|
|
|
|
.score-bar {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 40px;
|
|
margin-top: 20px;
|
|
font-size: 0.5rem;
|
|
}
|
|
|
|
.score-item {
|
|
color: #ffff00;
|
|
text-shadow: 0 0 5px #ffff00;
|
|
}
|
|
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 25px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.tile {
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
border: 4px solid;
|
|
border-radius: 8px;
|
|
padding: 25px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
display: block;
|
|
}
|
|
|
|
.tile::before {
|
|
content: "";
|
|
position: absolute;
|
|
top: 0;
|
|
left: -100%;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);
|
|
transition: left 0.5s;
|
|
}
|
|
|
|
.tile:hover::before {
|
|
left: 100%;
|
|
}
|
|
|
|
.tile:hover {
|
|
transform: translateY(-5px) scale(1.02);
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
/* Service-specific colors */
|
|
.tile.storage { border-color: #ff6b6b; }
|
|
.tile.storage .icon { color: #ff6b6b; text-shadow: 0 0 20px #ff6b6b; }
|
|
.tile.storage:hover { background: linear-gradient(135deg, #2a1a1a 0%, #1e1616 100%); }
|
|
|
|
.tile.database { border-color: #4ecdc4; }
|
|
.tile.database .icon { color: #4ecdc4; text-shadow: 0 0 20px #4ecdc4; }
|
|
.tile.database:hover { background: linear-gradient(135deg, #1a2a2a 0%, #162121 100%); }
|
|
|
|
.tile.git { border-color: #f7b731; }
|
|
.tile.git .icon { color: #f7b731; text-shadow: 0 0 20px #f7b731; }
|
|
.tile.git:hover { background: linear-gradient(135deg, #2a2a1a 0%, #212116 100%); }
|
|
|
|
.tile.ci { border-color: #a55eea; }
|
|
.tile.ci .icon { color: #a55eea; text-shadow: 0 0 20px #a55eea; }
|
|
.tile.ci:hover { background: linear-gradient(135deg, #2a1a2a 0%, #211621 100%); }
|
|
|
|
.tile.notebook { border-color: #26de81; }
|
|
.tile.notebook .icon { color: #26de81; text-shadow: 0 0 20px #26de81; }
|
|
.tile.notebook:hover { background: linear-gradient(135deg, #1a2a1a 0%, #162116 100%); }
|
|
|
|
.tile.research { border-color: #fd79a8; }
|
|
.tile.research .icon { color: #fd79a8; text-shadow: 0 0 20px #fd79a8; }
|
|
.tile.research:hover { background: linear-gradient(135deg, #2a1a22 0%, #21161a 100%); }
|
|
|
|
.tile.catalog { border-color: #00cec9; }
|
|
.tile.catalog .icon { color: #00cec9; text-shadow: 0 0 20px #00cec9; }
|
|
.tile.catalog:hover { background: linear-gradient(135deg, #1a2a2a 0%, #162121 100%); }
|
|
|
|
.tile.query { border-color: #6c5ce7; }
|
|
.tile.query .icon { color: #6c5ce7; text-shadow: 0 0 20px #6c5ce7; }
|
|
.tile.query:hover { background: linear-gradient(135deg, #1a1a2a 0%, #161621 100%); }
|
|
|
|
.tile.observability { border-color: #e17055; }
|
|
.tile.observability .icon { color: #e17055; text-shadow: 0 0 20px #e17055; }
|
|
.tile.observability:hover { background: linear-gradient(135deg, #2a1a1a 0%, #211616 100%); }
|
|
|
|
.tile.metrics { border-color: #fdcb6e; }
|
|
.tile.metrics .icon { color: #fdcb6e; text-shadow: 0 0 20px #fdcb6e; }
|
|
.tile.metrics:hover { background: linear-gradient(135deg, #2a2a1a 0%, #212116 100%); }
|
|
|
|
.tile.logs { border-color: #74b9ff; }
|
|
.tile.logs .icon { color: #74b9ff; text-shadow: 0 0 20px #74b9ff; }
|
|
.tile.logs:hover { background: linear-gradient(135deg, #1a1a2a 0%, #161621 100%); }
|
|
|
|
.tile.proxy { border-color: #55efc4; }
|
|
.tile.proxy .icon { color: #55efc4; text-shadow: 0 0 20px #55efc4; }
|
|
.tile.proxy:hover { background: linear-gradient(135deg, #1a2a2a 0%, #162121 100%); }
|
|
|
|
.icon {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 15px;
|
|
display: block;
|
|
}
|
|
|
|
.tile-title {
|
|
font-size: 0.8rem;
|
|
color: #fff;
|
|
margin-bottom: 10px;
|
|
text-shadow: 2px 2px 0 #000;
|
|
}
|
|
|
|
.tile-desc {
|
|
font-size: 0.5rem;
|
|
color: #888;
|
|
line-height: 1.8;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.tile-port {
|
|
font-size: 0.45rem;
|
|
color: #00ff00;
|
|
background: rgba(0, 255, 0, 0.1);
|
|
padding: 5px 10px;
|
|
border-radius: 4px;
|
|
display: inline-block;
|
|
border: 1px solid #00ff00;
|
|
}
|
|
|
|
.status {
|
|
position: absolute;
|
|
top: 15px;
|
|
right: 15px;
|
|
width: 12px;
|
|
height: 12px;
|
|
background: #00ff00;
|
|
border-radius: 50%;
|
|
animation: pulse 2s infinite;
|
|
box-shadow: 0 0 10px #00ff00;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.2); opacity: 0.7; }
|
|
}
|
|
|
|
.pixel-border {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
background: repeating-linear-gradient(
|
|
90deg,
|
|
currentColor 0px,
|
|
currentColor 4px,
|
|
transparent 4px,
|
|
transparent 8px
|
|
);
|
|
opacity: 0.5;
|
|
}
|
|
|
|
footer {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #444;
|
|
font-size: 0.4rem;
|
|
}
|
|
|
|
footer span {
|
|
color: #ff0000;
|
|
animation: heart 1s infinite;
|
|
display: inline-block;
|
|
}
|
|
|
|
@keyframes heart {
|
|
0%, 100% { transform: scale(1); }
|
|
50% { transform: scale(1.3); }
|
|
}
|
|
|
|
/* Power-up badges */
|
|
.badge {
|
|
position: absolute;
|
|
top: -8px;
|
|
left: 20px;
|
|
background: #ff0000;
|
|
color: #fff;
|
|
font-size: 0.4rem;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
text-transform: uppercase;
|
|
animation: bounce 0.5s ease infinite alternate;
|
|
}
|
|
|
|
@keyframes bounce {
|
|
from { transform: translateY(0); }
|
|
to { transform: translateY(-3px); }
|
|
}
|
|
|
|
.badge.new { background: #ff0000; }
|
|
.badge.gpu { background: #ff6b00; }
|
|
.badge.data { background: #0066ff; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="stars" id="stars"></div>
|
|
|
|
<div class="container">
|
|
<header>
|
|
<h1>HOMELAB</h1>
|
|
<p class="subtitle">[ PRESS START TO CONTINUE ]</p>
|
|
<div class="score-bar">
|
|
<span class="score-item">SERVICES: 13</span>
|
|
<span class="score-item">STATUS: ONLINE</span>
|
|
<span class="score-item">LEVEL: MAX</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="grid">
|
|
<a data-subdomain="traefik" target="_blank" class="tile proxy">
|
|
<span class="badge new">NEW</span>
|
|
<span class="status"></span>
|
|
<span class="icon">🔗</span>
|
|
<h2 class="tile-title">TRAEFIK</h2>
|
|
<p class="tile-desc">Cloud-native reverse proxy and load balancer</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #55efc4;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
|
<span class="status"></span>
|
|
<span class="icon">💾</span>
|
|
<h2 class="tile-title">RUSTFS</h2>
|
|
<p class="tile-desc">S3-compatible object storage for all your data needs</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #ff6b6b;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="gitea" target="_blank" class="tile git">
|
|
<span class="status"></span>
|
|
<span class="icon">🐙</span>
|
|
<h2 class="tile-title">GITEA</h2>
|
|
<p class="tile-desc">Self-hosted Git service with packages & CI</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #f7b731;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="ci" target="_blank" class="tile ci">
|
|
<span class="status"></span>
|
|
<span class="icon">🔨</span>
|
|
<h2 class="tile-title">WOODPECKER</h2>
|
|
<p class="tile-desc">Simple yet powerful CI/CD engine</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #a55eea;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="notebooks" target="_blank" class="tile notebook">
|
|
<span class="badge gpu">GPU</span>
|
|
<span class="status"></span>
|
|
<span class="icon">📑</span>
|
|
<h2 class="tile-title">NOTEBOOKS</h2>
|
|
<p class="tile-desc">Marimo reactive notebooks with CUDA</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #26de81;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="zotero" target="_blank" class="tile research">
|
|
<span class="badge gpu">GPU</span>
|
|
<span class="status"></span>
|
|
<span class="icon">📚</span>
|
|
<h2 class="tile-title">ZOTERO</h2>
|
|
<p class="tile-desc">Research reference manager in the cloud</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #fd79a8;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="nessie" data-path="/api/v2/config" target="_blank" class="tile catalog">
|
|
<span class="badge data">DATA</span>
|
|
<span class="status"></span>
|
|
<span class="icon">🌳</span>
|
|
<h2 class="tile-title">NESSIE</h2>
|
|
<p class="tile-desc">Git-like versioned data catalog for Iceberg</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #00cec9;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="trino" target="_blank" class="tile query">
|
|
<span class="badge data">DATA</span>
|
|
<span class="status"></span>
|
|
<span class="icon">🔍</span>
|
|
<h2 class="tile-title">TRINO</h2>
|
|
<p class="tile-desc">Distributed SQL query engine for the lakehouse</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #6c5ce7;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="polaris" target="_blank" class="tile catalog">
|
|
<span class="badge new">NEW</span>
|
|
<span class="status"></span>
|
|
<span class="icon">⭐</span>
|
|
<h2 class="tile-title">POLARIS</h2>
|
|
<p class="tile-desc">Apache Iceberg catalog with governance & Unity-style access</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #00cec9;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
|
<span class="status"></span>
|
|
<span class="icon">📊</span>
|
|
<h2 class="tile-title">GRAFANA</h2>
|
|
<p class="tile-desc">Observability dashboards for logs and traces</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #e17055;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="jaeger" target="_blank" class="tile metrics">
|
|
<span class="status"></span>
|
|
<span class="icon">👁</span>
|
|
<h2 class="tile-title">JAEGER</h2>
|
|
<p class="tile-desc">Distributed tracing for microservices</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #fdcb6e;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
|
<span class="status"></span>
|
|
<span class="icon">📈</span>
|
|
<h2 class="tile-title">PROMETHEUS</h2>
|
|
<p class="tile-desc">Metrics collection and alerting</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #74b9ff;"></div>
|
|
</a>
|
|
|
|
<a data-subdomain="loki" data-path="/ready" target="_blank" class="tile database">
|
|
<span class="status"></span>
|
|
<span class="icon">📋</span>
|
|
<h2 class="tile-title">LOKI</h2>
|
|
<p class="tile-desc">Log aggregation system by Grafana</p>
|
|
<span class="tile-port"></span>
|
|
<div class="pixel-border" style="color: #4ecdc4;"></div>
|
|
</a>
|
|
</div>
|
|
|
|
<footer>
|
|
MADE WITH <span>♥</span> FOR THE HOMELAB
|
|
</footer>
|
|
</div>
|
|
|
|
<script>
|
|
// ── Domain resolution: derive base domain from current hostname ──
|
|
// dashboard.homelab.fhirworx.io → homelab.fhirworx.io
|
|
// homelab.fhirworx.io → homelab.fhirworx.io
|
|
(function() {
|
|
var base = location.hostname.replace(/^dashboard\./, '');
|
|
document.querySelectorAll('[data-subdomain]').forEach(function(el) {
|
|
var sub = el.dataset.subdomain;
|
|
var path = el.dataset.path || '';
|
|
var fqdn = sub + '.' + base;
|
|
el.href = location.protocol + '//' + fqdn + path;
|
|
var port = el.querySelector('.tile-port');
|
|
if (port) port.textContent = fqdn;
|
|
});
|
|
})();
|
|
|
|
// Generate starfield
|
|
var starsContainer = document.getElementById('stars');
|
|
for (var i = 0; i < 100; i++) {
|
|
var star = document.createElement('div');
|
|
star.className = 'star';
|
|
star.style.left = Math.random() * 100 + '%';
|
|
star.style.top = Math.random() * 100 + '%';
|
|
star.style.width = Math.random() * 3 + 1 + 'px';
|
|
star.style.height = star.style.width;
|
|
star.style.animationDelay = Math.random() * 2 + 's';
|
|
starsContainer.appendChild(star);
|
|
}
|
|
|
|
// Add click sound effect (visual feedback)
|
|
document.querySelectorAll('.tile').forEach(function(tile) {
|
|
tile.addEventListener('click', function(e) {
|
|
this.style.transform = 'scale(0.95)';
|
|
var self = this;
|
|
setTimeout(function() { self.style.transform = ''; }, 100);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|