|
|
|
@@ -1,227 +0,0 @@
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
<title>PixelRAG Status</title>
|
|
|
|
|
<meta name="description" content="Live availability of the PixelRAG search API, tile serving, agent backend, and website.">
|
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
|
|
|
<link href="https://fonts.googleapis.com/css2?family=Crimson+Pro:ital,wght@0,300;0,400;0,500;1,400&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
|
|
|
<style>
|
|
|
|
|
:root {
|
|
|
|
|
--bg: #faf9f7; --fg: #1a1816; --muted: #8a8078; --secondary: #3a3530;
|
|
|
|
|
--card: #ffffff; --border: #e8e2da; --accent: #8b6943;
|
|
|
|
|
--up: #5a8a5a; --down: #c44040; --degraded: #c08a3a;
|
|
|
|
|
--serif: "Crimson Pro", Georgia, serif;
|
|
|
|
|
--sans: "Inter", system-ui, sans-serif;
|
|
|
|
|
--mono: "JetBrains Mono", ui-monospace, monospace;
|
|
|
|
|
}
|
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
|
body {
|
|
|
|
|
background: var(--bg); color: var(--fg); font-family: var(--sans);
|
|
|
|
|
font-size: 14px; line-height: 1.6; min-height: 100vh;
|
|
|
|
|
position: relative; -webkit-font-smoothing: antialiased;
|
|
|
|
|
}
|
|
|
|
|
body::before {
|
|
|
|
|
content: ''; position: fixed; inset: 0; z-index: -2; pointer-events: none;
|
|
|
|
|
background:
|
|
|
|
|
radial-gradient(ellipse 80% 60% at 15% 0%, rgba(139,105,67,0.06), transparent 55%),
|
|
|
|
|
radial-gradient(ellipse 70% 50% at 85% 5%, rgba(150,170,140,0.05), transparent 50%);
|
|
|
|
|
}
|
|
|
|
|
body::after {
|
|
|
|
|
content: ''; position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: 0.4;
|
|
|
|
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='256' height='256' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E");
|
|
|
|
|
background-size: 200px 200px;
|
|
|
|
|
}
|
|
|
|
|
nav {
|
|
|
|
|
position: sticky; top: 0; z-index: 50; backdrop-filter: blur(12px);
|
|
|
|
|
background: rgba(250,249,247,0.7); border-bottom: 1px solid var(--border);
|
|
|
|
|
}
|
|
|
|
|
nav .inner {
|
|
|
|
|
max-width: 880px; margin: 0 auto; height: 64px; padding: 0 24px;
|
|
|
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
|
|
|
}
|
|
|
|
|
.logo { display: flex; align-items: center; gap: 10px; text-decoration: none; color: var(--fg); }
|
|
|
|
|
.logo-mark {
|
|
|
|
|
position: relative; width: 28px; height: 28px;
|
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
.logo-mark span:nth-child(1) { position: absolute; inset: 0; border-radius: 7px; background: rgba(139,105,67,0.15); }
|
|
|
|
|
.logo-mark span:nth-child(2) { position: absolute; inset: 3px; border-radius: 5px; border: 1px solid rgba(139,105,67,0.4); }
|
|
|
|
|
.logo-mark span:nth-child(3) { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
|
|
|
|
|
.logo-name { font-family: var(--serif); font-size: 20px; font-weight: 600; letter-spacing: -0.01em; }
|
|
|
|
|
.logo-name b { color: var(--accent); font-weight: 600; }
|
|
|
|
|
.nav-links { display: flex; gap: 24px; }
|
|
|
|
|
.nav-links a { color: var(--muted); text-decoration: none; font-size: 14px; transition: color .2s; }
|
|
|
|
|
.nav-links a:hover { color: var(--fg); }
|
|
|
|
|
|
|
|
|
|
main { max-width: 880px; margin: 0 auto; padding: 56px 24px 80px; }
|
|
|
|
|
.head { text-align: center; margin-bottom: 40px; }
|
|
|
|
|
.pill {
|
|
|
|
|
display: inline-flex; align-items: center; gap: 8px; font-family: var(--mono);
|
|
|
|
|
font-size: 10px; text-transform: uppercase; letter-spacing: 0.18em;
|
|
|
|
|
border: 1px solid var(--border); border-radius: 999px; padding: 6px 16px;
|
|
|
|
|
color: var(--muted); margin-bottom: 20px; background: rgba(255,255,255,0.5);
|
|
|
|
|
}
|
|
|
|
|
.dot { width: 8px; height: 8px; border-radius: 50%; position: relative; }
|
|
|
|
|
.dot.up { background: var(--up); }
|
|
|
|
|
.dot.down { background: var(--down); }
|
|
|
|
|
.dot.degraded { background: var(--degraded); }
|
|
|
|
|
.dot.up::after {
|
|
|
|
|
content: ''; position: absolute; inset: 0; border-radius: 50%;
|
|
|
|
|
background: var(--up); animation: ping 2s cubic-bezier(0,0,.2,1) infinite;
|
|
|
|
|
}
|
|
|
|
|
@keyframes ping { 75%,100% { transform: scale(2.4); opacity: 0; } }
|
|
|
|
|
h1 { font-family: var(--serif); font-weight: 300; font-size: 44px; line-height: 1.1; letter-spacing: -0.02em; }
|
|
|
|
|
.sub { color: var(--muted); margin-top: 12px; font-size: 15px; }
|
|
|
|
|
|
|
|
|
|
.banner {
|
|
|
|
|
display: flex; align-items: center; gap: 12px; justify-content: center;
|
|
|
|
|
border: 1px solid var(--border); border-radius: 16px; padding: 18px 24px;
|
|
|
|
|
background: var(--card); margin-bottom: 36px; font-size: 16px; font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
.banner .dot { width: 10px; height: 10px; }
|
|
|
|
|
.banner.up { border-color: rgba(90,138,90,0.3); }
|
|
|
|
|
.banner.down { border-color: rgba(196,64,64,0.3); }
|
|
|
|
|
.banner.degraded { border-color: rgba(192,138,58,0.3); }
|
|
|
|
|
|
|
|
|
|
.services { display: flex; flex-direction: column; gap: 14px; }
|
|
|
|
|
.service {
|
|
|
|
|
border: 1px solid var(--border); border-radius: 16px; padding: 20px 22px; background: var(--card);
|
|
|
|
|
}
|
|
|
|
|
.service-top { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
|
|
|
|
|
.service-name { font-family: var(--serif); font-size: 19px; font-weight: 500; }
|
|
|
|
|
.service-status { margin-left: auto; display: flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 500; }
|
|
|
|
|
.service-status.up { color: var(--up); }
|
|
|
|
|
.service-status.down { color: var(--down); }
|
|
|
|
|
.service-meta { display: flex; gap: 20px; color: var(--muted); font-size: 12.5px; margin-bottom: 14px; }
|
|
|
|
|
.service-meta b { color: var(--secondary); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
|
|
|
.bars { display: flex; gap: 2px; height: 32px; }
|
|
|
|
|
.bar { flex: 1; border-radius: 2px; min-width: 0; }
|
|
|
|
|
.bar.up { background: #cfe0cf; } .bar.up:hover { background: var(--up); }
|
|
|
|
|
.bar.warn { background: #ecd9b0; } .bar.warn:hover { background: var(--degraded); }
|
|
|
|
|
.bar.down { background: #e8b8b8; } .bar.down:hover { background: var(--down); }
|
|
|
|
|
.bar.none { background: var(--border); }
|
|
|
|
|
.bar { transition: background .15s; }
|
|
|
|
|
.bars-legend { display: flex; justify-content: space-between; color: var(--muted); font-family: var(--mono); font-size: 9px; margin-top: 6px; text-transform: uppercase; letter-spacing: .08em; }
|
|
|
|
|
|
|
|
|
|
.updated { text-align: center; color: var(--muted); font-size: 12px; margin-top: 32px; font-family: var(--mono); }
|
|
|
|
|
footer { text-align: center; color: var(--muted); font-size: 13px; margin-top: 16px; }
|
|
|
|
|
footer a { color: var(--accent); text-decoration: none; }
|
|
|
|
|
footer a:hover { text-decoration: underline; }
|
|
|
|
|
.loading, .error { text-align: center; color: var(--muted); padding: 60px 0; }
|
|
|
|
|
.error { color: var(--down); }
|
|
|
|
|
.skeleton { animation: pulse 1.4s ease-in-out infinite; }
|
|
|
|
|
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .5; } }
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<nav>
|
|
|
|
|
<div class="inner">
|
|
|
|
|
<a class="logo" href="https://pixelrag.ai">
|
|
|
|
|
<span class="logo-mark"><span></span><span></span><span></span></span>
|
|
|
|
|
<span class="logo-name">Pixel<b>RAG</b></span>
|
|
|
|
|
</a>
|
|
|
|
|
<div class="nav-links">
|
|
|
|
|
<a href="https://pixelrag.ai">Home</a>
|
|
|
|
|
<a href="https://github.com/StarTrail-org/PixelRAG" target="_blank" rel="noopener">GitHub</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<main>
|
|
|
|
|
<div class="head">
|
|
|
|
|
<div class="pill"><span class="dot up"></span> System Status</div>
|
|
|
|
|
<h1>PixelRAG Status</h1>
|
|
|
|
|
<p class="sub">Live availability of the search API, tile serving, agent backend, and website.</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="content">
|
|
|
|
|
<div class="loading skeleton">Loading status…</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="updated" id="updated"></div>
|
|
|
|
|
<footer>
|
|
|
|
|
Hosted independently on GitHub Pages ·
|
|
|
|
|
<a href="https://github.com/StarTrail-org/PixelRAG" target="_blank" rel="noopener">open source</a>
|
|
|
|
|
</footer>
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
const SUMMARY_URL = "https://raw.githubusercontent.com/StarTrail-org/PixelRAG/status/history/summary.json";
|
|
|
|
|
const DAYS = 90;
|
|
|
|
|
|
|
|
|
|
function fmtTime(ms) {
|
|
|
|
|
if (ms == null) return "—";
|
|
|
|
|
return ms < 1000 ? ms + " ms" : (ms / 1000).toFixed(2) + " s";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function dayBars(dailyMinutesDown) {
|
|
|
|
|
const bars = [];
|
|
|
|
|
const today = new Date();
|
|
|
|
|
for (let i = DAYS - 1; i >= 0; i--) {
|
|
|
|
|
const d = new Date(today);
|
|
|
|
|
d.setDate(d.getDate() - i);
|
|
|
|
|
const key = d.toISOString().slice(0, 10);
|
|
|
|
|
const down = (dailyMinutesDown && dailyMinutesDown[key]) || 0;
|
|
|
|
|
let cls = "up", label = "No downtime";
|
|
|
|
|
if (down > 0 && down < 30) { cls = "warn"; label = down + " min down"; }
|
|
|
|
|
else if (down >= 30) { cls = "down"; label = down + " min down"; }
|
|
|
|
|
bars.push(`<div class="bar ${cls}" title="${key}: ${label}"></div>`);
|
|
|
|
|
}
|
|
|
|
|
return bars.join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function render(services) {
|
|
|
|
|
const allUp = services.every(s => s.status === "up");
|
|
|
|
|
const anyDown = services.some(s => s.status === "down");
|
|
|
|
|
const overall = allUp ? "up" : anyDown ? "down" : "degraded";
|
|
|
|
|
const overallText = allUp ? "All systems operational"
|
|
|
|
|
: anyDown ? "Partial system outage" : "Degraded performance";
|
|
|
|
|
|
|
|
|
|
let html = `<div class="banner ${overall}"><span class="dot ${overall}"></span>${overallText}</div>`;
|
|
|
|
|
html += `<div class="services">`;
|
|
|
|
|
for (const s of services) {
|
|
|
|
|
const up = s.status === "up";
|
|
|
|
|
html += `
|
|
|
|
|
<div class="service">
|
|
|
|
|
<div class="service-top">
|
|
|
|
|
<span class="service-name">${s.name}</span>
|
|
|
|
|
<span class="service-status ${up ? "up" : "down"}">
|
|
|
|
|
<span class="dot ${up ? "up" : "down"}"></span>${up ? "Operational" : "Down"}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="service-meta">
|
|
|
|
|
<span>Uptime <b>${s.uptime || "—"}</b></span>
|
|
|
|
|
<span>Avg response <b>${fmtTime(s.time)}</b></span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="bars">${dayBars(s.dailyMinutesDown)}</div>
|
|
|
|
|
<div class="bars-legend"><span>${DAYS} days ago</span><span>today</span></div>
|
|
|
|
|
</div>`;
|
|
|
|
|
}
|
|
|
|
|
html += `</div>`;
|
|
|
|
|
document.getElementById("content").innerHTML = html;
|
|
|
|
|
const now = new Date();
|
|
|
|
|
document.getElementById("updated").textContent =
|
|
|
|
|
"Last checked " + now.toLocaleString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(SUMMARY_URL + "?t=" + Date.now(), { cache: "no-store" });
|
|
|
|
|
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
if (!Array.isArray(data) || data.length === 0) throw new Error("no data");
|
|
|
|
|
render(data);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
document.getElementById("content").innerHTML =
|
|
|
|
|
`<div class="error">Couldn't load status data (${e.message}).<br>The monitoring data may still be initializing.</div>`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
load();
|
|
|
|
|
setInterval(load, 60000); // refresh every minute from committed data (no GitHub API)
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|