Replace service and delivery screenshots with live status

This commit is contained in:
Keith Casko
2026-10-02 11:05:45 -04:00
parent 62fc9fe0f4
commit 77d8cdd357
4 changed files with 154 additions and 21 deletions
+36 -21
View File
@@ -306,16 +306,19 @@
<div class="service-row"><strong>Uptime Kuma</strong><span>Availability</span><span>SSO</span><i>CONFIGURED</i></div>
</div>
<figure class="portal-shot">
<div class="portal-shot live-service-panel" aria-label="Live service health">
<figcaption>
<span>SERVICE PORTAL</span>
<small>Dashy / internal access layer</small>
<span>LIVE SERVICE HEALTH</span>
<small>SANITIZED ENDPOINT CHECKS</small>
</figcaption>
<a href="assets/images/portfolio-cloud-dashboard-overview.png" target="_blank">
<img src="assets/images/portfolio-cloud-dashboard-overview.png" alt="ByteGeist Dashy service portal snapshot" />
</a>
<p class="evidence-note">CAPTURED OCT 2026 · SERVICE PORTAL CONFIGURATION SNAPSHOT</p>
</figure>
<div class="service-health-grid">
<div><span>GITEA</span><strong id="health-gitea">CHECKING</strong><small id="gitea-version">VERSION --</small></div>
<div><span>WOODPECKER</span><strong id="health-woodpecker">CHECKING</strong><small>CI / DELIVERY</small></div>
<div><span>UPTIME KUMA</span><strong id="health-kuma">CHECKING</strong><small>AVAILABILITY</small></div>
<div><span>PROMETHEUS</span><strong id="health-prometheus">CHECKING</strong><small>METRICS</small></div>
</div>
<p class="evidence-note">LIVE HEALTH CHECKS · REFRESHED WITH /API/STATUS EVERY 15s</p>
</div>
</div>
</section>
@@ -340,19 +343,31 @@
<div><span>04</span><strong>DEPLOY</strong><small>Docker host</small></div>
</div>
<div class="pipeline-shots">
<figure>
<figcaption>GITEA / REPOSITORIES · OCT 2026 SNAPSHOT</figcaption>
<a href="assets/images/portfolio-gitea-dashboard.png" target="_blank">
<img src="assets/images/portfolio-gitea-dashboard.png" alt="Gitea repositories dashboard" />
</a>
</figure>
<figure>
<figcaption>WOODPECKER / PIPELINE · DEPLOYMENT EVIDENCE</figcaption>
<a href="assets/images/portfolio-woodpecker-pipeline.png" target="_blank">
<img src="assets/images/portfolio-woodpecker-pipeline.png" alt="Successful Woodpecker CI pipeline" />
</a>
</figure>
<div class="pipeline-shots live-pipeline-shots">
<div class="live-delivery-card">
<figcaption>GITEA / SOURCE CONTROL</figcaption>
<div class="delivery-state">
<span class="delivery-kicker">SERVICE STATE</span>
<strong id="pipeline-gitea-state">CHECKING</strong>
<small id="pipeline-gitea-version">VERSION --</small>
</div>
<div class="delivery-meta">
<span>ROLE</span><strong>REPOSITORY HOST</strong>
<span>TRANSPORT</span><strong>SSH / 2222</strong>
</div>
</div>
<div class="live-delivery-card">
<figcaption>WOODPECKER / DELIVERY</figcaption>
<div class="delivery-state">
<span class="delivery-kicker">SERVICE STATE</span>
<strong id="pipeline-woodpecker-state">CHECKING</strong>
<small>AUTOMATED DEPLOYMENT</small>
</div>
<div class="delivery-meta">
<span>DEPLOYED COMMIT</span><strong id="deployed-commit">-------</strong>
<span>TARGET</span><strong>BYTEGEIST.CASKO.DEV</strong>
</div>
</div>
</div>
</div>
</section>
+1
View File
@@ -0,0 +1 @@
.live-service-panel{min-width:0}.service-health-grid{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line);border:1px solid var(--line)}.service-health-grid div{min-height:104px;padding:15px;background:#080c12}.service-health-grid span,.delivery-kicker,.delivery-meta span{display:block;color:var(--dim);font:.54rem var(--mono);letter-spacing:.08em}.service-health-grid strong,.delivery-state strong{display:block;margin-top:14px;color:var(--green);font:500 1rem var(--mono)}.service-health-grid small,.delivery-state small{display:block;margin-top:8px;color:var(--muted);font:.52rem var(--mono)}.status-down{color:var(--amber)!important}.live-pipeline-shots{align-items:stretch}.live-delivery-card{border:1px solid var(--line);background:#080c12;min-height:250px}.live-delivery-card figcaption{padding:11px 14px;border-bottom:1px solid var(--line);color:var(--dim);font:.58rem var(--mono);letter-spacing:.06em}.delivery-state{padding:22px 18px;border-bottom:1px solid var(--line)}.delivery-state strong{font-size:1.35rem}.delivery-meta{display:grid;grid-template-columns:1fr auto;gap:11px 16px;padding:18px}.delivery-meta strong{color:var(--muted);font:500 .64rem var(--mono);text-align:right}@media(max-width:560px){.delivery-meta{grid-template-columns:1fr}.delivery-meta strong{text-align:left}}
+56
View File
@@ -1,3 +1,5 @@
const livePanelStyles=document.createElement("link");livePanelStyles.rel="stylesheet";livePanelStyles.href="live-panels.css";document.head.appendChild(livePanelStyles);
const timeElement = document.querySelector("#local-time");
const refreshElement = document.querySelector("#refresh-indicator");
@@ -13,6 +15,15 @@ const fields = {
opsUptime: document.querySelector("#ops-uptime-value"),
opsContainers: document.querySelector("#ops-containers-value"),
opsTargets: document.querySelector("#ops-targets-value"),
healthGitea: document.querySelector("#health-gitea"),
healthWoodpecker: document.querySelector("#health-woodpecker"),
healthKuma: document.querySelector("#health-kuma"),
healthPrometheus: document.querySelector("#health-prometheus"),
giteaVersion: document.querySelector("#gitea-version"),
pipelineGiteaState: document.querySelector("#pipeline-gitea-state"),
pipelineGiteaVersion: document.querySelector("#pipeline-gitea-version"),
pipelineWoodpeckerState: document.querySelector("#pipeline-woodpecker-state"),
deployedCommit: document.querySelector("#deployed-commit"),
};
const telemetryHistory = {
@@ -56,6 +67,22 @@ function setUnavailable() {
fields.opsUptime.textContent = "--";
fields.opsContainers.textContent = "--";
fields.opsTargets.textContent = "--";
for (const element of [
fields.healthGitea,
fields.healthWoodpecker,
fields.healthKuma,
fields.healthPrometheus,
fields.pipelineGiteaState,
fields.pipelineWoodpeckerState,
]) {
if (element) {
element.textContent = "UNAVAILABLE";
element.classList.add("status-down");
}
}
fields.giteaVersion.textContent = "VERSION --";
fields.pipelineGiteaVersion.textContent = "VERSION --";
fields.deployedCommit.textContent = "-------";
refreshElement.textContent = "TELEMETRY UNAVAILABLE";
refreshElement.classList.add("telemetry-error");
}
@@ -77,6 +104,17 @@ function pushHistory(series, value) {
if (series.length > 24) series.shift();
}
function serviceLabel(service) {
return service && service.up ? "ONLINE" : "UNAVAILABLE";
}
function setServiceState(element, service) {
if (!element) return;
const up = Boolean(service && service.up);
element.textContent = up ? "ONLINE" : "UNAVAILABLE";
element.classList.toggle("status-down", !up);
}
function renderStatus(data) {
const cpu = Number(data.host.cpu_percent);
const memory = Number(data.host.memory_percent);
@@ -97,6 +135,23 @@ function renderStatus(data) {
fields.opsContainers.textContent = containers;
fields.opsTargets.textContent = targets;
const services = data.services || {};
setServiceState(fields.healthGitea, services.gitea);
setServiceState(fields.healthWoodpecker, services.woodpecker);
setServiceState(fields.healthKuma, services.uptime_kuma);
setServiceState(fields.healthPrometheus, services.prometheus);
setServiceState(fields.pipelineGiteaState, services.gitea);
setServiceState(fields.pipelineWoodpeckerState, services.woodpecker);
const giteaVersion = services.gitea && services.gitea.version
? "VERSION " + services.gitea.version
: "VERSION --";
fields.giteaVersion.textContent = giteaVersion;
fields.pipelineGiteaVersion.textContent = giteaVersion;
fields.deployedCommit.textContent = data.deployment && data.deployment.commit
? data.deployment.commit
: "-------";
pushHistory(telemetryHistory.cpu, cpu);
pushHistory(telemetryHistory.memory, memory);
renderOpsBars("#ops-cpu-bars", telemetryHistory.cpu);
@@ -143,3 +198,4 @@ refreshTelemetry();
window.setInterval(updateClock, 1000);
window.setInterval(updateRefreshLabel, 1000);
window.setInterval(refreshTelemetry, 15000);
+61
View File
@@ -3,10 +3,12 @@ import os
import time
import urllib.parse
import urllib.request
from pathlib import Path
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
PROMETHEUS_URL = os.getenv("PROMETHEUS_URL", "http://host.docker.internal:9090").rstrip("/")
TIMEOUT = float(os.getenv("PROMETHEUS_TIMEOUT", "2.5"))
SITE_GIT_DIR = Path(os.getenv("SITE_GIT_DIR", "/site/.git"))
QUERIES = {
"cpu_percent": '100-(avg(rate(node_cpu_seconds_total{mode="idle"}[2m]))*100)',
@@ -15,11 +17,23 @@ QUERIES = {
"running_containers": 'count(count by (name) (container_last_seen{name!=""}))',
}
HTTP_CHECKS = {
"gitea": "http://host.docker.internal:3000/api/v1/version",
"woodpecker": "http://host.docker.internal:8010/",
"uptime_kuma": "http://host.docker.internal:3001/",
"prometheus": "http://host.docker.internal:9090/-/healthy",
}
def get_json(url):
request = urllib.request.Request(url, headers={"Accept": "application/json"})
with urllib.request.urlopen(request, timeout=TIMEOUT) as response:
return json.load(response)
def get_text(url):
request = urllib.request.Request(url, headers={"Accept": "*/*"})
with urllib.request.urlopen(request, timeout=TIMEOUT) as response:
return response.status, response.read(1024).decode("utf-8", "replace")
def prometheus_query(expression):
url = f"{PROMETHEUS_URL}/api/v1/query?{urllib.parse.urlencode({'query': expression})}"
payload = get_json(url)
@@ -43,9 +57,51 @@ def target_health():
"jobs": jobs,
}
def check_services():
services = {}
for name, url in HTTP_CHECKS.items():
try:
status, body = get_text(url)
services[name] = {"up": 200 <= status < 400, "status": status}
if name == "gitea":
try:
services[name]["version"] = json.loads(body).get("version")
except Exception:
pass
except Exception:
services[name] = {"up": False, "status": None}
return services
def deployed_commit():
try:
head = (SITE_GIT_DIR / "HEAD").read_text(encoding="utf-8").strip()
if head.startswith("ref: "):
ref = head[5:]
ref_path = SITE_GIT_DIR / ref
if ref_path.exists():
value = ref_path.read_text(encoding="utf-8").strip()
else:
value = None
packed = SITE_GIT_DIR / "packed-refs"
if packed.exists():
for line in packed.read_text(encoding="utf-8").splitlines():
if line and not line.startswith(("#", "^")):
sha, name = line.split(" ", 1)
if name == ref:
value = sha
break
if not value:
return None
else:
value = head
return value[:7]
except Exception:
return None
def build_status():
values = {name: prometheus_query(query) for name, query in QUERIES.items()}
targets = target_health()
services = check_services()
return {
"ok": True,
"source": "Prometheus / node_exporter / cAdvisor",
@@ -57,6 +113,11 @@ def build_status():
},
"containers": {"running": int(values["running_containers"])},
"prometheus": targets,
"services": services,
"deployment": {
"commit": deployed_commit(),
"site": "bytegeist.casko.dev",
},
}
class Handler(BaseHTTPRequestHandler):