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
18 changes: 13 additions & 5 deletions apiforgepy/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@
function mapReleases(releases) {
return (releases || []).map(r => ({
tag: r.release_tag,
ts: r.release_ts,
summary: `${r.routes_affected || 0} route${r.routes_affected !== 1 ? 's' : ''} recorded`,
age: formatAge(r.release_ts),
by: 'local',
Expand Down Expand Up @@ -748,11 +749,18 @@
const globalCalls = chartData?.calls || Array(fallbackPts).fill(0);
const xLabelsFinal = xLabels.length > 0 ? xLabels : Array.from({length:globalP90.length}, (_,i) => `${i}`);

const releaseMarkers = (RELEASES || []).slice(0,2).map((r, i) => ({
idx: Math.min(Math.floor(globalP90.length * (0.45 + i * 0.35)), globalP90.length - 1),
label: r.tag,
color: i === 0 ? '#b91c1c' : '#15803d',
}));
const MARKER_COLORS = ['#b91c1c','#15803d','#2563eb','#b45309','#7c3aed'];
const nowTs = Date.now() / 1000;
const releaseMarkers = globalTs && globalTs.length > 0
? [...(RELEASES || [])]
.filter(r => r.ts != null && r.ts >= nowTs - hours * 3600)
.reverse()
.map((r, i) => {
const idx = globalTs.reduce((best, b, j) =>
Math.abs(b.bucket_ts - r.ts) < Math.abs(globalTs[best].bucket_ts - r.ts) ? j : best, 0);
return { idx, label: r.tag, color: MARKER_COLORS[i % MARKER_COLORS.length] };
})
: [];

const topSlow = [...ENDPOINTS].filter(e => !e.untracked && e.base_p90 > 0).sort((a,b) => b.base_p90-a.base_p90).slice(0,5);
const topCalled = [...ENDPOINTS].sort((a,b) => b.calls24h-a.calls24h).slice(0,5);
Expand Down
28 changes: 28 additions & 0 deletions apiforgepy/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ def upsert_known_routes(self, routes: list[dict]):
INSERT INTO known_routes (route, method) VALUES (?, ?)
ON CONFLICT (route, method) DO NOTHING
""", [(r["route"], r["method"]) for r in routes])
if routes:
keys = [f"{r['route']}|{r['method']}" for r in routes]
ph = ",".join("?" * len(keys))
self._conn.execute(f"""
DELETE FROM known_routes
WHERE route || '|' || method NOT IN ({ph})
AND NOT EXISTS (
SELECT 1 FROM api_metrics m
WHERE m.route = known_routes.route AND m.method = known_routes.method
)
""", keys)
self._conn.commit()

def get_summary(self) -> dict:
Expand Down Expand Up @@ -226,6 +237,23 @@ def get_untracked_routes(self) -> list[dict]:
""").fetchall()
return [dict(r) for r in rows]

def get_releases(self) -> list[dict]:
rows = self._conn.execute("""
WITH release_times AS (
SELECT release_tag, MIN(bucket_ts) AS release_ts
FROM api_metrics
WHERE release_tag IS NOT NULL AND release_tag != ''
GROUP BY release_tag
)
SELECT rt.release_tag,
rt.release_ts,
(SELECT COUNT(*) FROM known_routes WHERE first_seen <= rt.release_ts + 60) AS routes_affected
FROM release_times rt
ORDER BY rt.release_ts DESC
LIMIT 20
""").fetchall()
return [dict(r) for r in rows]

def get_global_time_series(self, hours: int = 24) -> list[dict]:
since = _now_sec() - hours * 3600
rows = self._conn.execute("""
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ build-backend = "setuptools.build_meta"

[project]
name = "apiforgepy"
version = "1.0.1"
version = "1.0.2"

description = "API observability & intelligence for FastAPI/Starlette — local-first, privacy-first"
readme = "README.md"
license = { text = "MIT" }
Expand Down
Loading