diff --git a/index.html b/index.html index 5389379..0f99bd8 100644 --- a/index.html +++ b/index.html @@ -306,16 +306,19 @@
Uptime KumaAvailabilitySSOCONFIGURED
-
+
- SERVICE PORTAL - Dashy / internal access layer + LIVE SERVICE HEALTH + SANITIZED ENDPOINT CHECKS
- - ByteGeist Dashy service portal snapshot - -

CAPTURED OCT 2026 · SERVICE PORTAL CONFIGURATION SNAPSHOT

-
+
+
GITEACHECKINGVERSION --
+
WOODPECKERCHECKINGCI / DELIVERY
+
UPTIME KUMACHECKINGAVAILABILITY
+
PROMETHEUSCHECKINGMETRICS
+
+

LIVE HEALTH CHECKS · REFRESHED WITH /API/STATUS EVERY 15s

+ @@ -340,19 +343,31 @@
04DEPLOYDocker host
-
-
-
GITEA / REPOSITORIES · OCT 2026 SNAPSHOT
- - Gitea repositories dashboard - -
-
-
WOODPECKER / PIPELINE · DEPLOYMENT EVIDENCE
- - Successful Woodpecker CI pipeline - -
+
+
+
GITEA / SOURCE CONTROL
+
+ SERVICE STATE + CHECKING + VERSION -- +
+
+ ROLEREPOSITORY HOST + TRANSPORTSSH / 2222 +
+
+
+
WOODPECKER / DELIVERY
+
+ SERVICE STATE + CHECKING + AUTOMATED DEPLOYMENT +
+
+ DEPLOYED COMMIT------- + TARGETBYTEGEIST.CASKO.DEV +
+
diff --git a/live-panels.css b/live-panels.css new file mode 100644 index 0000000..f2ed3d8 --- /dev/null +++ b/live-panels.css @@ -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}} \ No newline at end of file diff --git a/script.js b/script.js index 46c8caa..a05b72a 100644 --- a/script.js +++ b/script.js @@ -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); + diff --git a/status-api/status_api.py b/status-api/status_api.py index b4fc861..498df3d 100644 --- a/status-api/status_api.py +++ b/status-api/status_api.py @@ -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):