Skip to content

feat: show restarts and resource limits on the app detail page without metrics-server - #164

Merged
jbouder merged 2 commits into
mainfrom
feat/app-detail-health-metrics
Oct 21, 2026
Merged

jbouder merged 2 commits into
mainfrom
feat/app-detail-health-metrics

Conversation

@jbouder

@jbouder jbouder commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Summary

The app detail page's CPU and Memory cards said "Metrics unavailable" on any cluster without metrics-server. They are replaced by cards that always show something, built from pod status and spec.

API (GET /apps/{ns}/{name}/metrics, still behind authorized_app, no new route)

  • New Store.pod_health lists the app's pods once by the app label and returns, for the app container: restarts, lastTermination ({reason, exitCode, finishedAt} or null), startedAt (when it started running, null while it is not), and limits / requests ({cpu, memory} as "500m" / "512Mi", parsed like usage).
  • The route joins usage onto those pods by name. available still means "metrics-server answered"; the pod data comes back either way. Finished pods (Succeeded/Failed, e.g. evicted) are skipped, and usage for a pod no longer listed is dropped.
  • Backwards compatible: fields added to PodMetric, none renamed. One behaviour change: with available: false, pods is no longer [] (it lists the live pods with cpu/memory set to "").
  • Restarts scope: app container only. The authz sidecar's restarts are left out, so the count, last termination, uptime and limits all describe the same container (the user's code). /analytics/metrics still sums every container, as before.
  • Untrusted text: the termination message (written by the container) is never read. reason is kept only when it is a plain identifier ([A-Za-z][A-Za-z0-9]{0,63}), so no free text reaches MCP callers and the MCP tool needs no fence. The get_app_metrics docstring describes the new fields.

UI

  • Restarts: large tabular count, "Up 3d" (or "Not running"), "Last: OOMKilled · 12 min ago" (reuses relativeTime, with <time datetime>), and for an OOMKilled a short hint that the memory limit may be too low (warning-token icon, muted text).
  • Resources: the limits ("CPU 500m · Memory 512 Mi", or "per pod" with several replicas) always; with metrics-server, usage against the limit ("12 / 500m", "34 / 512 Mi") over the existing Sparkline; without it, a muted "Live usage needs metrics-server".
  • Same skeleton / FADE_IN reveal and 4-column xl grid. Failed apps now keep the cards, since their crash-looping pods are what the cards explain. Stopped and idle apps still get the explanatory line, now "Restarts and resources are shown once the app is running."
  • New lib/pod-health.ts rolls the pods up; metricValue moved there as quantity; shortDuration was added to lib/time.ts.
  • Bundle: total JS 240.1 → 240.9 kB gzip (budget 245, not raised); initial unchanged at 192.1 kB.

Docs: api-reference, mcp, launching-apps (the detail page's cards), troubleshooting, runbook (metrics-server missing), getting-started.

Test plan

  • api: uv lock --check, pytest --cov (312 passed, 91.5%), ruff check, ruff format --check, mypy. New tests/test_pod_health.py covers the store parsing: a healthy pod, an OOMKilled pod (only the app container's restarts count), crash-looping (no startedAt), a non-identifier reason with a message (both dropped), skipped finished pods, a pod with no status or limits, and 404. test_apps.py covers the route with no metrics-server, joining usage, and dropping usage for pods that are gone. pod_health was added to the request-timeout check.
  • mcp: pytest --cov (60 passed), ruff, mypy. get_app_metrics returns pod health with and without metrics-server.
  • ui: npm run check:ci, npm run build, npm run size, npm run test:coverage (278 passed), npm run test:e2e (150 passed: Playwright plus axe in light and dark, desktop and mobile, including the failed app's detail page with the OOMKilled card).
  • docs: bun test test, bun run build.
  • On a cluster with and without metrics-server: an app that is OOMKilled shows the reason and hint, and limits show either way. Not run; the change needs no new RBAC (apps-api already lists pods in app namespaces).

🤖 Generated with Claude Code

jbouder and others added 2 commits October 21, 2026 15:29
GET /apps/{ns}/{name}/metrics now joins metrics.k8s.io usage with data
read from the app's pods (one list call by the app label): the `app`
container's restarts, last termination (reason, exit code, finish time),
start time, and CPU/memory limits and requests. The pod data comes back
even when `available` is false (no metrics-server). Fields are added,
none renamed. The termination reason is kept only when it is a plain
identifier, and the container's termination message is never read.

The MCP get_app_metrics docstring describes the new fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app detail page's CPU and Memory cards said "Metrics unavailable" on
clusters without metrics-server. Restarts shows the app container's
restart count, uptime and how its last run ended (with a memory-limit
hint after an OOMKilled); Resources shows the limits always and live
usage against them, with the sparkline history, when metrics-server is
there. Failed apps keep the cards, since their crash-looping pods are
what the cards explain; stopped and idle apps still explain their absence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

📄 Docs preview for feat/app-detail-health-metrics:
https://feat-app-detail-health-metri.apps-pack.pages.dev

@jbouder
jbouder merged commit 61fd965 into main Oct 21, 2026
15 checks passed
@jbouder
jbouder deleted the feat/app-detail-health-metrics branch October 21, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant