Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions dashboard/src/main.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";
import {
ActorFilter,
ProductMeta,
StatusBadge,
buildJobQuery,
groupSessionEvents,
Expand Down Expand Up @@ -48,6 +49,15 @@ describe("status badges", () => {
});
});

describe("product metadata", () => {
it("shows the bridge version and upstream repository link", () => {
render(<ProductMeta about={{ service: "github-agent-bridge-dashboard", version: "0.18.7", repository_url: "https://github.com/pilipilisbot/github-agent-bridge" }} />);

expect(screen.getByText("v0.18.7")).toBeInTheDocument();
expect(screen.getByRole("link", { name: /github/i })).toHaveAttribute("href", "https://github.com/pilipilisbot/github-agent-bridge");
});
});

describe("actor filter", () => {
it("filters actors, selects a suggestion, and clears the selection", async () => {
const user = userEvent.setup();
Expand Down
26 changes: 25 additions & 1 deletion dashboard/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type Percentiles = {
p99: number | null;
};

type About = {
service: string;
version: string;
repository_url: string;
};

type Job = {
id: number;
work_key: string;
Expand Down Expand Up @@ -478,6 +484,7 @@ function App() {
const selectedJobId = jobRouteId;
const metrics = useQuery({ queryKey: ["metrics"], queryFn: () => api<{ metrics: MetricsSummary }>("/api/metrics/summary"), enabled: !isJobDetailRoute });
const me = useQuery({ queryKey: ["me"], queryFn: () => api<{ user: UserProfile }>("/api/me"), refetchInterval: false });
const about = useQuery({ queryKey: ["about"], queryFn: () => api<About>("/api/about") });
const actorOptions = useQuery({ queryKey: ["job-actors"], queryFn: () => api<{ actors: JobActor[] }>("/api/jobs/actors"), enabled: !isJobDetailRoute });
const jobs = useQuery({ queryKey: ["jobs", filters, jobLimit], queryFn: () => api<{ jobs: Job[] }>(buildJobQuery(filters, jobLimit)), enabled: !isJobDetailRoute });
const processes = useQuery({ queryKey: ["processes"], queryFn: () => api<ProcessesResponse>("/api/processes"), enabled: !isJobDetailRoute });
Expand Down Expand Up @@ -558,7 +565,7 @@ function App() {
<div className="mx-auto flex w-full max-w-[1440px] items-center justify-between gap-3 px-4 py-4 md:px-6">
<div className="min-w-0">
<h1 className="truncate text-xl font-semibold">GitHub Agent Bridge</h1>
<p className="text-sm text-slate-300">Read-only operational dashboard</p>
<ProductMeta about={about.data} />
</div>
<UserMenu user={me.data?.user} loading={me.isLoading} />
</div>
Expand Down Expand Up @@ -631,6 +638,22 @@ function App() {
);
}

function ProductMeta({ about }: { about: About | undefined }) {
const version = about?.version ? `v${about.version}` : "version loading";
return (
<p className="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-slate-300">
<span>Read-only operational dashboard</span>
<span className="font-mono text-xs text-slate-400">{version}</span>
{about?.repository_url ? (
<a className="inline-flex items-center gap-1 text-xs font-semibold text-slate-200 hover:underline" href={safeExternalUrl(about.repository_url)} rel="noreferrer" target="_blank">
<ExternalLink className="h-3.5 w-3.5" aria-hidden />
GitHub
</a>
) : null}
</p>
);
}

function JobDetailPage({ jobId, detail, onRefresh }: { jobId: number; detail: React.ReactNode; onRefresh: () => void }) {
return (
<div className="grid min-w-0 gap-3 sm:gap-4">
Expand Down Expand Up @@ -1444,6 +1467,7 @@ function RefreshButton({ onClick, compactOnMobile = false }: { onClick: () => vo

export {
ActorFilter,
ProductMeta,
StatusBadge,
buildJobQuery,
groupSessionEvents,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/issue-50/version-link-mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 16 additions & 1 deletion src/github_agent_bridge/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
"""GitHub Agent Bridge."""

__version__ = "0.1.0"
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path
import tomllib


def _version_from_pyproject() -> str:
pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml"
if not pyproject.exists():
return "0.0.0"
return str(tomllib.loads(pyproject.read_text(encoding="utf-8")).get("project", {}).get("version", "0.0.0"))


try:
__version__ = version("github-agent-bridge")
except PackageNotFoundError:
__version__ = _version_from_pyproject()
10 changes: 10 additions & 0 deletions src/github_agent_bridge/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles

from . import __version__
from .cli import DEFAULT_DB
from .dashboard_data import (
get_job_detail,
Expand All @@ -43,6 +44,7 @@
GITHUB_AUTHORIZE_URL = "https://github.com/login/oauth/authorize"
GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"
GITHUB_USER_URL = "https://api.github.com/user"
PROJECT_REPOSITORY_URL = "https://github.com/pilipilisbot/github-agent-bridge"
SESSION_VERSION = 1


Expand Down Expand Up @@ -284,6 +286,14 @@ def dashboard_job(job_path: str, _: str = Depends(current_user)) -> FileResponse
def api_status(_: str = Depends(current_user)) -> dict[str, Any]:
return {"service": "github-agent-bridge-dashboard", "read_only": True, "metrics": inspect_db_read_only(config.db)}

@app.get("/api/about")
def api_about(_: str = Depends(current_user)) -> dict[str, Any]:
return {
"service": "github-agent-bridge-dashboard",
"version": __version__,
"repository_url": PROJECT_REPOSITORY_URL,
}

@app.get("/api/me")
def api_me(profile: dict[str, Any] = Depends(current_profile)) -> dict[str, Any]:
return {"user": profile}
Expand Down
110 changes: 0 additions & 110 deletions src/github_agent_bridge/dashboard_static/assets/index-Bh6F9yTT.js

This file was deleted.

110 changes: 110 additions & 0 deletions src/github_agent_bridge/dashboard_static/assets/index-CBKWFP7m.js

Large diffs are not rendered by default.

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/github_agent_bridge/dashboard_static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GitHub Agent Bridge Dashboard</title>
<script type="module" crossorigin src="/assets/index-Bh6F9yTT.js"></script>
<script type="module" crossorigin src="/assets/index-CBKWFP7m.js"></script>
<link rel="modulepreload" crossorigin href="/assets/charts-DRWoArYU.js">
<link rel="stylesheet" crossorigin href="/assets/index-DZ0faviD.css">
<link rel="stylesheet" crossorigin href="/assets/index-CDukmhDa.css">
</head>
<body>
<div id="root"></div>
Expand Down
16 changes: 16 additions & 0 deletions tests/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import pytest
from fastapi.testclient import TestClient

from github_agent_bridge import __version__
from github_agent_bridge.backend import DashboardConfig, _encode_session, _session_stream_events, _sign, create_app
from github_agent_bridge.dashboard_data import get_job_detail, job_session, job_session_events, job_session_transcript, list_job_actors, list_jobs, metrics_summary
from github_agent_bridge.monitor import MonitorReport
Expand Down Expand Up @@ -49,6 +50,21 @@ def test_dashboard_status_is_read_only_and_lists_recent_jobs(tmp_path):
assert jobs.json()["jobs"][0]["trigger_actor_avatar_url"] is None


def test_dashboard_about_exposes_package_version_and_repository(tmp_path):
db = tmp_path / "bridge.sqlite3"
JobQueue(db)
client = TestClient(create_app(DashboardConfig(db=db, require_auth=False)))

response = client.get("/api/about")

assert response.status_code == 200
assert response.json() == {
"service": "github-agent-bridge-dashboard",
"version": __version__,
"repository_url": "https://github.com/pilipilisbot/github-agent-bridge",
}


def test_dashboard_serves_built_react_ui_with_existing_auth(tmp_path):
db = tmp_path / "bridge.sqlite3"
static_dir = tmp_path / "static"
Expand Down
Loading