deepagent-bot
ci: sync dashboard from 3607a75
0d7206c
Raw
History Blame Contribute Delete
5.83 kB
"""Streamlit entrypoint — multi-page, multi-tenant admin dashboard."""
from __future__ import annotations
import datetime as _dt
import os
import time
from collections import Counter
import streamlit as st
from gh_deepagent.dashboard.api import APIError, parse_prometheus, sum_by, total
from gh_deepagent.dashboard.auth_ui import (
is_standalone,
render_user_badge,
require_login,
)
st.set_page_config(
page_title="gh-deepagent",
page_icon="🤖",
layout="wide",
initial_sidebar_state="expanded",
)
# ---------- sidebar (drawn before login so the user can change the API URL) ----------
with st.sidebar:
st.title("🤖 gh-deepagent")
st.caption("Admin dashboard")
base = st.text_input(
"API base URL",
value=st.session_state.get("api_base") or os.getenv("DEEPAGENT_API_URL", "http://localhost:8080"),
key="api_base_input",
help="The gh-deepagent webhook server.",
)
if base != st.session_state.get("api_base"):
st.session_state["api_base"] = base
st.session_state.pop("_backend_probed", None)
st.session_state.setdefault("autorefresh", True)
st.session_state["autorefresh"] = st.toggle(
"Auto-refresh (10s)", value=st.session_state["autorefresh"]
)
# ---------- login gate ----------
api, user = require_login()
# Sidebar widgets that depend on being logged in.
with st.sidebar:
st.divider()
if is_standalone():
st.warning("🌐 Standalone mode (no backend)")
else:
try:
h = api.healthz()
if h.get("status") == "ok":
st.success(
f"✅ Healthy · queue {h.get('queue_depth', '?')} · "
f"DLQ {h.get('dead_letter', '?')}"
)
else:
st.warning(f"⚠️ Degraded · {h}")
except APIError as e:
st.error(f"❌ Unreachable: {e}")
render_user_badge()
# ---------- helpers ----------
def _fmt_ts(ts):
if not ts:
return "—"
return _dt.datetime.fromtimestamp(float(ts)).strftime("%Y-%m-%d %H:%M:%S")
# ---------- HOME page ----------
st.title("Overview")
if user.get("is_admin"):
_scope = "admin (sees all)"
else:
_n = len(user.get("installation_ids") or [])
_scope = f"{_n} installation(s)"
st.caption(f"Signed in as **{user['login']}** — {_scope}.")
if is_standalone():
st.info(
"🌐 **Standalone mode** — you're signed in directly against GitHub. "
"Job / queue / cost data lives in a backend that isn't reachable from "
"this Space. Configure `DEEPAGENT_API_URL` in the sidebar to point at "
"your webhook, or deploy the all-in-one demo Space for a self-contained "
"experience."
)
st.subheader("Your GitHub App installations")
iids = user.get("installation_ids") or []
if iids:
for iid in iids[:50]:
st.markdown(f"- installation `#{iid}`")
else:
st.caption(
"You don't have access to any installation of a configured "
"gh-deepagent GitHub App. Install one to see your scoped data here."
)
elif user.get("is_admin"):
try:
metrics = parse_prometheus(api.metrics_raw())
except APIError as e:
st.error(f"Failed to fetch /metrics: {e}")
st.stop()
def _val(name: str) -> float:
return total(metrics.get(name, []))
k1, k2, k3, k4, k5 = st.columns(5)
k1.metric("Queue depth", int(_val("deepagent_queue_depth")))
dlq = int(_val("deepagent_dlq_size"))
k2.metric("DLQ", dlq, delta="⚠️" if dlq else None,
delta_color="inverse" if dlq else "off")
k3.metric("In-progress", int(_val("deepagent_jobs_in_progress")))
k4.metric("Jobs total", int(_val("deepagent_jobs_total")))
k5.metric("LLM spend (USD)", f"${_val('deepagent_llm_cost_usd_total'):.2f}")
st.divider()
c1, c2 = st.columns(2)
with c1:
st.subheader("Jobs by status")
by_status = sum_by(metrics.get("deepagent_jobs_total", []), "status")
if by_status:
st.bar_chart(by_status, horizontal=True)
else:
st.info("No job activity yet.")
with c2:
st.subheader("Sub-agent invocations")
by_sub = sum_by(metrics.get("deepagent_subagent_calls_total", []), "subagent")
if by_sub:
st.bar_chart(by_sub, horizontal=True)
else:
st.info("No sub-agent calls yet.")
else:
try:
my_jobs = api.list_jobs(limit_per_install=50)
except APIError as e:
st.error(f"Failed to list jobs: {e}")
st.stop()
statuses = Counter(j["status"] for j in my_jobs)
k1, k2, k3, k4 = st.columns(4)
k1.metric("Recent jobs", len(my_jobs))
k2.metric("Running", statuses.get("running", 0) + statuses.get("pending", 0))
k3.metric("Failed", statuses.get("failed", 0) + statuses.get("dead", 0))
k4.metric("Succeeded", statuses.get("succeeded", 0))
st.divider()
st.subheader("Your recent jobs")
if not my_jobs:
st.info("No jobs yet for your installations. Trigger one by labeling an "
"issue with `deepagent` or commenting `/deepagent <instruction>`.")
else:
import pandas as _pd
df = _pd.DataFrame([{
"When": _fmt_ts(j.get("created_at")),
"Repo": j["repo"],
"Event": j["event"],
"Status": j["status"],
"ID": j["id"],
} for j in my_jobs[:50]])
st.dataframe(df, use_container_width=True, hide_index=True)
st.caption("Open a job by copying its ID into the **Jobs** page.")
# Auto-refresh — but not in standalone mode (nothing changes, just wastes cycles).
if not is_standalone() and st.session_state.get("autorefresh"):
time.sleep(10)
st.rerun()