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
7 changes: 0 additions & 7 deletions .claude/settings.local.json

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ venv/
.DS_Store
Thumbs.db

# Claude Code local settings (per-developer permissions, never shared)
.claude/

# Node
node_modules/
npm-debug.log*
Expand Down
153 changes: 0 additions & 153 deletions .handoff.md

This file was deleted.

62 changes: 0 additions & 62 deletions .pr-body.md

This file was deleted.

2 changes: 1 addition & 1 deletion dashboard/components/admin/admin-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function AdminView({
autoRefresh={false}
onAutoRefreshChange={() => {}}
onRefresh={() => keysData.refetch()}
lastUpdated={new Date(keysData.dataUpdatedAt || Date.now())}
lastUpdated={keysData.dataUpdatedAt ? new Date(keysData.dataUpdatedAt) : null}
showAutoRefresh={false}
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion dashboard/components/analytics/analytics-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export function AnalyticsView({
autoRefresh={autoRefresh}
onAutoRefreshChange={setAutoRefresh}
onRefresh={() => metrics.refetch()}
lastUpdated={new Date(metrics.dataUpdatedAt)}
lastUpdated={metrics.dataUpdatedAt ? new Date(metrics.dataUpdatedAt) : null}
/>
</div>
</div>
Expand Down
74 changes: 42 additions & 32 deletions dashboard/components/tables/recent-logs-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,9 @@ export function RecentLogsTable({
</div>
) : (
<>
{/* TODO(virtualizer-table): column widths drift from sticky header —
refactor to CSS grid (role="table" / "row" with shared
gridTemplateColumns) so virtualized absolutely-positioned rows
align with <thead>. */}
<div
ref={parentRef}
className="max-h-[600px] overflow-y-auto rounded border border-[var(--color-border)] relative"
className="max-h-[600px] overflow-y-auto rounded border border-[var(--color-border)]"
>
<Table>
<TableHead className="sticky top-0 bg-[var(--color-surface)] z-10">
Expand Down Expand Up @@ -263,32 +259,49 @@ export function RecentLogsTable({
<TableHeaderCell>User / Key</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
position: "relative",
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const d = sorted[virtualRow.index];
const tsFmt =
d.tsMs > 0 ? new Date(d.tsMs).toLocaleString() : d.ts || "—";
{/* Padding-rows virtualization (per @tanstack/react-virtual
table guidance). Browsers ignore `position: absolute` on
<tr>, so virtualized rows must stay in normal flow with
spacer <tr>s above and below the visible window. As a
bonus, real <tr>s inherit the <thead>'s column widths,
so the sticky header now aligns with body cells. */}
<TableBody>
{(() => {
const virtualItems = rowVirtualizer.getVirtualItems();
const totalSize = rowVirtualizer.getTotalSize();
const paddingTop = virtualItems.length > 0 ? virtualItems[0].start : 0;
const paddingBottom =
virtualItems.length > 0
? totalSize - virtualItems[virtualItems.length - 1].end
: 0;
return (
<DataRow
key={d.raw.request_id || virtualRow.index}
tsFmt={tsFmt}
derived={d}
onSelect={() => setSelectedRow(d.raw)}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
transform: `translateY(${virtualRow.start}px)`,
}}
/>
<>
{paddingTop > 0 ? (
<tr aria-hidden="true">
<td colSpan={8} style={{ height: paddingTop, padding: 0, border: 0 }} />
</tr>
) : null}
{virtualItems.map((virtualRow) => {
const d = sorted[virtualRow.index];
const tsFmt =
d.tsMs > 0 ? new Date(d.tsMs).toLocaleString() : d.ts || "—";
return (
<DataRow
key={d.raw.request_id || virtualRow.index}
tsFmt={tsFmt}
derived={d}
onSelect={() => setSelectedRow(d.raw)}
/>
);
})}
{paddingBottom > 0 ? (
<tr aria-hidden="true">
<td colSpan={8} style={{ height: paddingBottom, padding: 0, border: 0 }} />
</tr>
) : null}
</>
);
})}
})()}
</TableBody>
</Table>
</div>
Expand Down Expand Up @@ -343,12 +356,10 @@ function DataRow({
tsFmt,
derived: d,
onSelect,
style,
}: {
tsFmt: string;
derived: Derived;
onSelect: () => void;
style?: React.CSSProperties;
}) {
// Whole-row click opens the drawer for the visual hover affordance, but the
// accessible affordance is the explicit button in the first cell — that's
Expand All @@ -357,7 +368,6 @@ function DataRow({
return (
<TableRow
onClick={onSelect}
style={style}
className="cursor-pointer hover:bg-[var(--color-bg)]"
>
<TableCell>
Expand Down
9 changes: 6 additions & 3 deletions dashboard/hooks/use-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function useKeys(key: string, enabled: boolean) {
if (demo) return [] as LiteLLMKey[];

// Prefer the admin path: /key/list returns every key the caller can
// see, so we don't need the per-caller /v1/key/info round-trip when
// see, so we don't need the per-caller /key/info round-trip when
// the user has admin scope. On 401/403 we silently fall back — that
// means the caller is non-admin, which is expected for end-users.
// Other errors (5xx, 404, network) are surfaced rather than
Expand All @@ -55,8 +55,11 @@ export function useKeys(key: string, enabled: boolean) {
const isAuthError =
err instanceof ApiError && (err.status === 401 || err.status === 403);
if (!isAuthError) throw err;
// Non-admin path — fetch the caller's own key.
const infoResp = await gatewayFetch("/v1/key/info", { key });
// Non-admin path — fetch the caller's own key. Use /key/info to
// match the rest of this codebase's key-management endpoints; the
// /v1/key/* prefix is OpenAI-compat surface and isn't wired up
// for key-info in our gateway.
const infoResp = await gatewayFetch("/key/info", { key });
const info = await infoResp.json();
return [info.info] as LiteLLMKey[];
}
Expand Down
Loading