Ignore backups and generated telemetry

This commit is contained in:
2026-07-14 22:19:00 -04:00
parent 7795a1d8ad
commit 1265f37dd4
6 changed files with 3 additions and 841 deletions
+3
View File
@@ -0,0 +1,3 @@
*.backup
*.backup-*
config/mail-status.json
-121
View File
@@ -1,121 +0,0 @@
{
"status": [
{
"name": "Edge01",
"state": "online",
"label": "Online",
"value": "Reachable",
"detail": "DreamHost VPS"
},
{
"name": "Docker",
"state": "online",
"label": "Online",
"value": "Running",
"detail": "Container engine"
},
{
"name": "Portainer",
"state": "online",
"label": "Online",
"value": "Available",
"detail": "portainer.zerafa.com"
},
{
"name": "Gitea",
"state": "online",
"label": "Online",
"value": "Available",
"detail": "git.zerafa.com"
},
{
"name": "TLS",
"state": "online",
"label": "Valid",
"value": "Active",
"detail": "Caddy certificates"
},
{
"name": "Backups",
"state": "warning",
"label": "Review",
"value": "Pending",
"detail": "Restic + B2"
},
{
"name": "Tunnel",
"state": "warning",
"label": "Pending",
"value": "Not built",
"detail": "Home network access"
},
{
"name": "Monitoring",
"state": "warning",
"label": "Planned",
"value": "Next phase",
"detail": "Uptime Kuma"
}
],
"services": [
{
"name": "Portainer",
"icon": "P",
"description": "Docker containers, stacks, networks and volumes.",
"url": "https://portainer.zerafa.com",
"category": "Docker"
},
{
"name": "Gitea",
"icon": "G",
"description": "Infrastructure repositories and documentation.",
"url": "https://git.zerafa.com",
"category": "Git"
},
{
"name": "Edge01",
"icon": "E",
"description": "Primary VPS landing page and public edge host.",
"url": "https://edge01.zerafa.com",
"category": "Server"
}
],
"home": [
{
"name": "Home Assistant",
"icon": "HA",
"description": "Automation dashboards, devices and integrations.",
"category": "Automation"
},
{
"name": "Proxmox",
"icon": "PX",
"description": "Cluster, virtual machines and containers.",
"category": "Virtualization"
},
{
"name": "UniFi",
"icon": "U",
"description": "Gateways, switches, access points and clients.",
"category": "Network"
},
{
"name": "Pi-hole",
"icon": "PH",
"description": "DNS filtering and resolver statistics.",
"category": "DNS"
},
{
"name": "ADS-B",
"icon": "AD",
"description": "Aircraft tracking and feeder status.",
"category": "Radio"
},
{
"name": "NAS",
"icon": "NAS",
"description": "Storage, backups and private file services.",
"category": "Storage"
}
]
}
-336
View File
@@ -1,336 +0,0 @@
:root {
color-scheme: dark;
--bg: #090d14;
--sidebar: #0d131d;
--panel: #131b27;
--panel-hover: #192434;
--text: #f4f6f8;
--muted: #98a5b5;
--line: #263244;
--accent: #67d7c4;
--online: #45d483;
--warning: #e3b653;
--offline: #ff6b6b;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
a { color: inherit; }
.app-shell {
min-height: 100vh;
display: grid;
grid-template-columns: 250px 1fr;
}
.sidebar {
position: sticky;
top: 0;
height: 100vh;
padding: 24px 18px;
border-right: 1px solid var(--line);
background: var(--sidebar);
display: flex;
flex-direction: column;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
padding: 4px 6px 28px;
}
.brand-mark {
width: 42px;
height: 42px;
display: grid;
place-items: center;
border: 1px solid var(--line);
border-radius: 10px;
background: linear-gradient(145deg, rgba(103,215,196,.18), rgba(255,255,255,.03));
font-weight: 900;
color: var(--accent);
}
.brand-name {
font-size: .95rem;
font-weight: 900;
letter-spacing: .14em;
}
.brand-node {
margin-top: 3px;
color: var(--muted);
font-size: .82rem;
}
.nav {
display: grid;
gap: 6px;
}
.nav-link {
padding: 11px 12px;
border-radius: 9px;
color: var(--muted);
text-decoration: none;
font-size: .92rem;
}
.nav-link:hover,
.nav-link.active {
background: rgba(103,215,196,.08);
color: var(--text);
}
.sidebar-footer {
margin-top: auto;
padding: 12px;
color: var(--muted);
font-size: .8rem;
display: flex;
align-items: center;
gap: 8px;
}
.main {
width: min(1240px, calc(100% - 48px));
margin: 0 auto;
padding: 36px 0 48px;
}
.topbar {
display: flex;
justify-content: space-between;
gap: 24px;
align-items: end;
padding-bottom: 28px;
border-bottom: 1px solid var(--line);
}
.eyebrow,
.section-kicker {
margin: 0 0 8px;
color: var(--accent);
font-size: .72rem;
letter-spacing: .14em;
text-transform: uppercase;
font-weight: 800;
}
h1 {
margin: 0;
font-size: clamp(2.6rem, 6vw, 5.6rem);
line-height: .92;
letter-spacing: -.055em;
}
.node-label {
margin: 10px 0 0;
color: var(--muted);
font-size: 1.1rem;
}
.topbar-meta {
display: grid;
gap: 5px;
color: var(--muted);
text-align: right;
font-size: .86rem;
}
.section {
padding-top: 34px;
}
.section-heading {
display: flex;
justify-content: space-between;
align-items: end;
gap: 20px;
margin-bottom: 15px;
}
.section-heading h2 {
margin: 0;
font-size: 1.3rem;
}
.section-heading > p {
margin: 0;
color: var(--muted);
font-size: .86rem;
}
.status-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.status-card,
.service-card {
border: 1px solid var(--line);
border-radius: 14px;
background: var(--panel);
}
.status-card {
min-height: 130px;
padding: 18px;
}
.status-top {
display: flex;
justify-content: space-between;
gap: 12px;
}
.status-name {
font-weight: 750;
}
.status-state {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: .78rem;
font-weight: 750;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
display: inline-block;
}
.online { color: var(--online); }
.warning { color: var(--warning); }
.offline { color: var(--offline); }
.status-value {
margin-top: 28px;
font-size: 1.5rem;
font-weight: 800;
}
.status-detail {
margin-top: 6px;
color: var(--muted);
font-size: .8rem;
}
.service-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.service-card {
min-height: 160px;
padding: 18px;
position: relative;
text-decoration: none;
transition: transform .15s ease, border-color .15s ease, background .15s ease;
}
.service-card:hover {
transform: translateY(-3px);
border-color: var(--accent);
background: var(--panel-hover);
}
.service-card.disabled {
opacity: .5;
pointer-events: none;
}
.service-icon {
width: 38px;
height: 38px;
display: grid;
place-items: center;
border: 1px solid var(--line);
border-radius: 9px;
color: var(--accent);
font-weight: 900;
font-size: .82rem;
}
.service-card h3 {
margin: 18px 0 6px;
font-size: 1.05rem;
}
.service-card p {
margin: 0;
color: var(--muted);
font-size: .87rem;
line-height: 1.5;
}
.service-foot {
position: absolute;
left: 18px;
right: 18px;
bottom: 14px;
display: flex;
justify-content: space-between;
color: var(--muted);
font-size: .72rem;
text-transform: uppercase;
letter-spacing: .08em;
}
footer {
margin-top: 38px;
padding-top: 18px;
border-top: 1px solid var(--line);
display: flex;
justify-content: space-between;
gap: 16px;
color: var(--muted);
font-size: .8rem;
}
@media (max-width: 1050px) {
.status-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.service-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 760px) {
.app-shell { grid-template-columns: 1fr; }
.sidebar {
position: static;
width: auto;
height: auto;
border-right: 0;
border-bottom: 1px solid var(--line);
}
.nav {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.sidebar-footer { display: none; }
.main { width: min(100% - 24px, 1240px); padding-top: 24px; }
.topbar,
.section-heading { align-items: flex-start; flex-direction: column; }
.topbar-meta { text-align: left; }
}
@media (max-width: 560px) {
.status-grid,
.service-grid { grid-template-columns: 1fr; }
.nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
-238
View File
@@ -1,238 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zerafa Infrastructure Portal</title>
<meta name="description" content="Zerafa infrastructure and home lab portal.">
<style>
:root {
color-scheme: dark;
--bg: #0a0f1d;
--panel: rgba(20, 28, 48, 0.88);
--panel-hover: rgba(29, 40, 67, 0.96);
--text: #f4f7fb;
--muted: #9da9bd;
--accent: #79aaff;
--border: #283550;
--green: #45d483;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background:
radial-gradient(circle at 15% 0%, rgba(55, 92, 175, .28), transparent 35%),
radial-gradient(circle at 100% 10%, rgba(35, 120, 150, .16), transparent 30%),
var(--bg);
color: var(--text);
}
.wrap {
width: min(1120px, calc(100% - 32px));
margin: 0 auto;
padding: 56px 0 72px;
}
header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 24px;
margin-bottom: 44px;
}
.eyebrow {
margin: 0 0 10px;
color: var(--accent);
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
font-size: .78rem;
}
h1 {
margin: 0;
font-size: clamp(2.5rem, 7vw, 5.2rem);
line-height: .96;
letter-spacing: -.055em;
}
.subtitle {
margin: 18px 0 0;
color: var(--muted);
font-size: 1.08rem;
}
.status {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 11px 14px;
border: 1px solid var(--border);
border-radius: 999px;
background: rgba(14, 22, 39, .75);
color: var(--muted);
font-size: .92rem;
}
.dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--green);
box-shadow: 0 0 18px rgba(69, 212, 131, .75);
}
section { margin-top: 38px; }
.section-title {
margin: 0 0 15px;
font-size: .82rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .14em;
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.card {
position: relative;
display: block;
min-height: 150px;
padding: 22px;
border: 1px solid var(--border);
border-radius: 18px;
background: var(--panel);
color: inherit;
text-decoration: none;
backdrop-filter: blur(10px);
transition: transform .16s ease, border-color .16s ease, background .16s ease;
overflow: hidden;
}
.card::after {
content: "↗";
position: absolute;
right: 20px;
top: 18px;
color: var(--muted);
font-size: 1.05rem;
}
.card:hover {
transform: translateY(-3px);
border-color: var(--accent);
background: var(--panel-hover);
}
.card h2 {
margin: 0 0 9px;
font-size: 1.25rem;
}
.card p {
margin: 0;
color: var(--muted);
line-height: 1.5;
}
.tag {
display: inline-block;
margin-top: 22px;
color: var(--accent);
font-size: .78rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .08em;
}
.disabled {
opacity: .52;
pointer-events: none;
}
.disabled::after { content: "Soon"; font-size: .76rem; }
footer {
margin-top: 52px;
color: var(--muted);
font-size: .88rem;
}
@media (max-width: 820px) {
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
header { flex-direction: column; }
}
@media (max-width: 560px) {
.wrap { padding-top: 38px; }
.grid { grid-template-columns: 1fr; }
.card { min-height: 132px; }
}
</style>
</head>
<body>
<main class="wrap">
<header>
<div>
<p class="eyebrow">Operations Portal</p>
<h1>Zerafa<br>Infrastructure</h1>
<p class="subtitle">VPS services, home lab systems and project tools.</p>
</div>
<div class="status"><span class="dot"></span> Portal Online</div>
</header>
<section>
<p class="section-title">VPS Services</p>
<div class="grid">
<a class="card" href="https://portainer.zerafa.com">
<h2>Portainer</h2>
<p>Manage Docker containers, stacks, networks and volumes.</p>
<span class="tag">Docker</span>
</a>
<a class="card" href="https://git.zerafa.com">
<h2>Gitea</h2>
<p>Infrastructure repositories, configuration and documentation.</p>
<span class="tag">Git</span>
</a>
<a class="card" href="https://edge01.zerafa.com">
<h2>Edge01</h2>
<p>Primary DreamHost VPS landing page and infrastructure host.</p>
<span class="tag">Server</span>
</a>
</div>
</section>
<section>
<p class="section-title">Home Lab</p>
<div class="grid">
<div class="card disabled">
<h2>Home Assistant</h2>
<p>Home automation dashboards, devices and integrations.</p>
<span class="tag">Tunnel Pending</span>
</div>
<div class="card disabled">
<h2>Proxmox</h2>
<p>Virtual machines, containers and cluster management.</p>
<span class="tag">Tunnel Pending</span>
</div>
<div class="card disabled">
<h2>UniFi</h2>
<p>Network gateways, switches, access points and clients.</p>
<span class="tag">Tunnel Pending</span>
</div>
<div class="card disabled">
<h2>Pi-hole</h2>
<p>DNS filtering, query statistics and resolver status.</p>
<span class="tag">Tunnel Pending</span>
</div>
<div class="card disabled">
<h2>ADS-B</h2>
<p>Aircraft tracking, receiver statistics and feeder links.</p>
<span class="tag">Tunnel Pending</span>
</div>
<div class="card disabled">
<h2>Monitoring</h2>
<p>Service uptime, alerts and infrastructure health.</p>
<span class="tag">Coming Soon</span>
</div>
</div>
</section>
<footer>portal.zerafa.com</footer>
</main>
</body>
</html>
-93
View File
@@ -1,93 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>VOYAGER · Edge01</title>
<meta name="description" content="Voyager infrastructure console for Edge01 and connected systems.">
<link rel="stylesheet" href="css/voyager.css">
</head>
<body>
<div class="app-shell">
<aside class="sidebar">
<div class="brand">
<div class="brand-mark">V</div>
<div>
<div class="brand-name">VOYAGER</div>
<div class="brand-node">Edge01</div>
</div>
</div>
<nav class="nav">
<a class="nav-link active" href="#overview">Overview</a>
<a class="nav-link" href="#infrastructure">Infrastructure</a>
<a class="nav-link" href="#services">Services</a>
<a class="nav-link" href="#home-lab">Home Lab</a>
<a class="nav-link" href="#monitoring">Monitoring</a>
</nav>
<div class="sidebar-footer">
<span class="status-dot online"></span>
portal.zerafa.com
</div>
</aside>
<main class="main">
<header class="topbar">
<div>
<p class="eyebrow">Infrastructure Console</p>
<h1>VOYAGER</h1>
<p class="node-label">Edge01</p>
</div>
<div class="topbar-meta">
<span>DreamHost VPS</span>
<span>Debian 13</span>
</div>
</header>
<section id="overview" class="section">
<div class="section-heading">
<div>
<p class="section-kicker">Overview</p>
<h2>System sweep</h2>
</div>
<p>Each system reports independently.</p>
</div>
<div id="status-grid" class="status-grid"></div>
</section>
<section id="services" class="section">
<div class="section-heading">
<div>
<p class="section-kicker">Services</p>
<h2>Quick launch</h2>
</div>
<p>Public services hosted on Edge01.</p>
</div>
<div id="service-grid" class="service-grid"></div>
</section>
<section id="home-lab" class="section">
<div class="section-heading">
<div>
<p class="section-kicker">Private Network</p>
<h2>Home lab</h2>
</div>
<p>These activate after the secure tunnel is built.</p>
</div>
<div id="home-grid" class="service-grid"></div>
</section>
<footer>
<span>VOYAGER · Edge01</span>
<span>Infrastructure Console v0.2.0</span>
</footer>
</main>
</div>
<script src="js/voyager.js"></script>
</body>
</html>
-53
View File
@@ -1,53 +0,0 @@
async function loadConfig() {
const response = await fetch('config/services.json', { cache: 'no-store' });
if (!response.ok) throw new Error(`Config load failed: ${response.status}`);
return response.json();
}
function renderStatus(items) {
const grid = document.getElementById('status-grid');
grid.innerHTML = items.map(item => `
<article class="status-card">
<div class="status-top">
<span class="status-name">${item.name}</span>
<span class="status-state ${item.state}">
<span class="status-dot"></span>${item.label}
</span>
</div>
<div class="status-value">${item.value}</div>
<div class="status-detail">${item.detail}</div>
</article>
`).join('');
}
function renderServices(targetId, items, disabled = false) {
const grid = document.getElementById(targetId);
grid.innerHTML = items.map(item => {
const classes = `service-card${disabled ? ' disabled' : ''}`;
const tag = disabled ? 'div' : 'a';
const href = disabled ? '' : ` href="${item.url}"`;
return `
<${tag} class="${classes}"${href}>
<div class="service-icon">${item.icon}</div>
<h3>${item.name}</h3>
<p>${item.description}</p>
<div class="service-foot">
<span>${item.category}</span>
<span>${disabled ? 'Pending' : 'Open ↗'}</span>
</div>
</${tag}>
`;
}).join('');
}
loadConfig()
.then(config => {
renderStatus(config.status);
renderServices('service-grid', config.services);
renderServices('home-grid', config.home, true);
})
.catch(error => {
console.error(error);
document.getElementById('status-grid').innerHTML =
'<article class="status-card"><div class="status-name">Configuration</div><div class="status-value">Unavailable</div><div class="status-detail">Could not load services.json</div></article>';
});