Show SPA e621 request times in the status pill
- New e621 metrics store tracks request count, cumulative time and the last request (path + duration) for the session - The e621 client measures each request around fetch and JSON parsing - Status pill appends a teal 'e621: <total>' segment with a tooltip showing request count and the last request; a server-side e621 time is shown separately if the backend ever reports one
This commit is contained in:
@@ -2,6 +2,7 @@ import { Laptop, Monitor, Terminal, type LucideIcon } from "lucide-react";
|
|||||||
|
|
||||||
import { cn } from "@/lib/cn";
|
import { cn } from "@/lib/cn";
|
||||||
import type { SystemStatus } from "@/lib/types";
|
import type { SystemStatus } from "@/lib/types";
|
||||||
|
import { useE621Metrics } from "@/store/e621Metrics";
|
||||||
|
|
||||||
const osIcons: Record<string, LucideIcon> = {
|
const osIcons: Record<string, LucideIcon> = {
|
||||||
linux: Terminal,
|
linux: Terminal,
|
||||||
@@ -10,10 +11,16 @@ const osIcons: Record<string, LucideIcon> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function formatMs(value: number): string {
|
function formatMs(value: number): string {
|
||||||
return value >= 100 ? `${Math.round(value)}ms` : `${value.toFixed(1)}ms`;
|
if (value >= 1000) return `${(value / 1000).toFixed(1)}s`;
|
||||||
|
if (value >= 100) return `${Math.round(value)}ms`;
|
||||||
|
return `${value.toFixed(1)}ms`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StatusPill({ status }: { status?: SystemStatus }) {
|
export function StatusPill({ status }: { status?: SystemStatus }) {
|
||||||
|
const requestCount = useE621Metrics((state) => state.requestCount);
|
||||||
|
const totalMs = useE621Metrics((state) => state.totalMs);
|
||||||
|
const last = useE621Metrics((state) => state.last);
|
||||||
|
|
||||||
if (!status) return null;
|
if (!status) return null;
|
||||||
const OsIcon = osIcons[status.os.icon] ?? Monitor;
|
const OsIcon = osIcons[status.os.icon] ?? Monitor;
|
||||||
|
|
||||||
@@ -37,10 +44,25 @@ export function StatusPill({ status }: { status?: SystemStatus }) {
|
|||||||
<span>{status.os.name}</span>
|
<span>{status.os.name}</span>
|
||||||
<span className="text-ctp-surface2">·</span>
|
<span className="text-ctp-surface2">·</span>
|
||||||
<span>{formatMs(status.server_time_ms)}</span>
|
<span>{formatMs(status.server_time_ms)}</span>
|
||||||
|
{requestCount > 0 ? (
|
||||||
|
<>
|
||||||
|
<span className="text-ctp-surface2">·</span>
|
||||||
|
<span
|
||||||
|
className="text-ctp-teal"
|
||||||
|
title={`${requestCount} e621 request${
|
||||||
|
requestCount === 1 ? "" : "s"
|
||||||
|
} this session · last ${last ? `${formatMs(last.ms)} (${last.path})` : "—"}`}
|
||||||
|
>
|
||||||
|
e621: {formatMs(totalMs)}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
{status.e621_time_ms !== null ? (
|
{status.e621_time_ms !== null ? (
|
||||||
<>
|
<>
|
||||||
<span className="text-ctp-surface2">·</span>
|
<span className="text-ctp-surface2">·</span>
|
||||||
<span>e621: {formatMs(status.e621_time_ms)}</span>
|
<span title="Server-side e621 time">
|
||||||
|
e621 (server): {formatMs(status.e621_time_ms)}
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { useE621Metrics } from "@/store/e621Metrics";
|
||||||
|
|
||||||
export interface E621Credentials {
|
export interface E621Credentials {
|
||||||
username: string;
|
username: string;
|
||||||
api_key: string;
|
api_key: string;
|
||||||
base_url: string;
|
base_url: string;
|
||||||
configured: boolean;
|
configured: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface E621User {
|
export interface E621User {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -141,6 +142,8 @@ export function e621Request<T>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startedAt = performance.now();
|
||||||
|
try {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: options.method ?? "GET",
|
method: options.method ?? "GET",
|
||||||
headers,
|
headers,
|
||||||
@@ -161,6 +164,11 @@ export function e621Request<T>(
|
|||||||
return undefined as T;
|
return undefined as T;
|
||||||
}
|
}
|
||||||
return (await response.json()) as T;
|
return (await response.json()) as T;
|
||||||
|
} finally {
|
||||||
|
useE621Metrics
|
||||||
|
.getState()
|
||||||
|
.record(path, performance.now() - startedAt);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
export interface E621RequestSample {
|
||||||
|
path: string;
|
||||||
|
ms: number;
|
||||||
|
at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface E621MetricsState {
|
||||||
|
requestCount: number;
|
||||||
|
totalMs: number;
|
||||||
|
last: E621RequestSample | null;
|
||||||
|
record: (path: string, ms: number) => void;
|
||||||
|
reset: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Rolling e621 request timings for the current SPA session. */
|
||||||
|
export const useE621Metrics = create<E621MetricsState>((set) => ({
|
||||||
|
requestCount: 0,
|
||||||
|
totalMs: 0,
|
||||||
|
last: null,
|
||||||
|
|
||||||
|
record: (path, ms) =>
|
||||||
|
set((state) => ({
|
||||||
|
requestCount: state.requestCount + 1,
|
||||||
|
totalMs: state.totalMs + ms,
|
||||||
|
last: { path, ms, at: Date.now() },
|
||||||
|
})),
|
||||||
|
|
||||||
|
reset: () => set({ requestCount: 0, totalMs: 0, last: null }),
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user