From 017014586f67f8acfed06d78ebf80bced3d590af Mon Sep 17 00:00:00 2001 From: Bobban Rydh Date: Sat, 26 Sep 2026 03:20:43 +0200 Subject: [PATCH] Show when each Gitea repository was last updated The API already returned Gitea's updated_at for every repo but the page never displayed it. The Gitea table now has a sortable "Last updated" column with a relative age ("3 d ago") over the exact date/time (in the app's configured date format), and the CSV export includes it. Gitea only exposes a single updated_at per repository (there is no separate last-push time), so this is Gitea's own notion of "updated". Co-Authored-By: Claude Sonnet 5 --- web/src/pages/Gitea.tsx | 18 ++++++++++++++++-- web/src/utils/duration.ts | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/web/src/pages/Gitea.tsx b/web/src/pages/Gitea.tsx index 13e316f..7b56309 100644 --- a/web/src/pages/Gitea.tsx +++ b/web/src/pages/Gitea.tsx @@ -6,6 +6,8 @@ import SortableTh from "../components/SortableTh"; import { usePagination } from "../hooks/usePagination"; import Pagination from "../components/Pagination"; import { downloadCsv } from "../utils/csv"; +import { formatDateTime } from "../utils/date"; +import { formatAgo } from "../utils/duration"; function runStatusBadge(repo: GiteaRepo) { const run = repo.latestRun; @@ -97,10 +99,11 @@ export default function Gitea({ user }: { user: CurrentUser }) { if (!sorted) return; downloadCsv( "gitea-repos.csv", - ["Repository", "Private", "Last run", "Branch", "Status"], + ["Repository", "Private", "Last updated", "Last run", "Branch", "Status"], sorted.map((r) => [ r.fullName, r.private ? "yes" : "no", + r.updatedAt ? formatDateTime(new Date(r.updatedAt)) : "", r.latestRun ? `#${r.latestRun.runNumber} ${r.latestRun.displayTitle}` : "", r.latestRun?.headBranch ?? "", r.latestRun ? r.latestRun.conclusion || r.latestRun.status : "", @@ -167,6 +170,7 @@ export default function Gitea({ user }: { user: CurrentUser }) { label="Repository" sortKeyName="fullName" activeKey={sortKey} direction={sortDir} onSort={requestSort} /> + label="Last updated" sortKeyName="updatedAt" activeKey={sortKey} direction={sortDir} onSort={requestSort} /> Last run Branch Status @@ -182,6 +186,16 @@ export default function Gitea({ user }: { user: CurrentUser }) { {r.private && Private} + + {r.updatedAt ? ( + <> +
{formatAgo(r.updatedAt)}
+
{formatDateTime(new Date(r.updatedAt))}
+ + ) : ( + — + )} + {r.latestRun ? ( @@ -212,7 +226,7 @@ export default function Gitea({ user }: { user: CurrentUser }) { ))} {sorted?.length === 0 && ( - + No repositories visible to this token. diff --git a/web/src/utils/duration.ts b/web/src/utils/duration.ts index f1fe145..25d7a5d 100644 --- a/web/src/utils/duration.ts +++ b/web/src/utils/duration.ts @@ -7,3 +7,18 @@ export function formatRemaining(endsAtIso: string, now: number = Date.now()): st const days = Math.floor(hours / 24); return hours % 24 ? `${days} d ${hours % 24} h` : `${days} d`; } + +/** "just now", "5 min ago", "3 h ago", "12 d ago", "5 mo ago", "2 y ago" for an ISO timestamp (a future one reads as "just now"). */ +export function formatAgo(iso: string, now: number = Date.now()): string { + const t = new Date(iso).getTime(); + if (!Number.isFinite(t)) return ""; + const minutes = Math.floor(Math.max(0, now - t) / 60_000); + if (minutes < 1) return "just now"; + if (minutes < 60) return `${minutes} min ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours} h ago`; + const days = Math.floor(hours / 24); + if (days < 60) return `${days} d ago`; + if (days < 730) return `${Math.floor(days / 30)} mo ago`; + return `${Math.floor(days / 365)} y ago`; +}