Replace service and delivery screenshots with live status
This commit is contained in:
+36
-21
@@ -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>
|
||||
|
||||
@@ -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}}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user