speed up admin ui polling and payloads

This commit is contained in:
2026-06-29 20:20:19 +08:00
parent 2db86643c8
commit ab0572baf6
2 changed files with 89 additions and 29 deletions
+10 -3
View File
@@ -1117,7 +1117,8 @@ function buildLogsSnapshot(monitor, sinceSeq = null) {
}; };
} }
async function buildPersistentLogsSnapshot(runtime, sinceSeq = null) { async function buildPersistentLogsSnapshot(runtime, sinceSeq = null, limit = 500) {
const safeLimit = Number.isInteger(limit) && limit > 0 ? Math.min(limit, 1000) : 500;
const text = runtime.logPath ? await readOptionalText(runtime.logPath) : null; const text = runtime.logPath ? await readOptionalText(runtime.logPath) : null;
if (!text) { if (!text) {
return buildLogsSnapshot(runtime.monitor, sinceSeq); return buildLogsSnapshot(runtime.monitor, sinceSeq);
@@ -1129,7 +1130,7 @@ async function buildPersistentLogsSnapshot(runtime, sinceSeq = null) {
.map((line, index) => parseLogLine(line, index + 1)); .map((line, index) => parseLogLine(line, index + 1));
const entries = Number.isInteger(sinceSeq) const entries = Number.isInteger(sinceSeq)
? allEntries.filter((entry) => entry.seq > sinceSeq) ? allEntries.filter((entry) => entry.seq > sinceSeq)
: allEntries.slice(-500); : allEntries.slice(-safeLimit);
return { return {
total_entries: allEntries.length, total_entries: allEntries.length,
@@ -1945,10 +1946,16 @@ async function handleManagementRequest(runtime, req, res, requestUrl) {
if (pathname === LOGS_API_PATH && req.method === "GET") { if (pathname === LOGS_API_PATH && req.method === "GET") {
const sinceSeqRaw = requestUrl.searchParams.get("since_seq"); const sinceSeqRaw = requestUrl.searchParams.get("since_seq");
const limitRaw = requestUrl.searchParams.get("limit");
const sinceSeq = sinceSeqRaw === null ? null : Number.parseInt(sinceSeqRaw, 10); const sinceSeq = sinceSeqRaw === null ? null : Number.parseInt(sinceSeqRaw, 10);
const limit = limitRaw === null ? 500 : Number.parseInt(limitRaw, 10);
jsonResponse(res, 200, { jsonResponse(res, 200, {
ok: true, ok: true,
...await buildPersistentLogsSnapshot(runtime, Number.isInteger(sinceSeq) ? sinceSeq : null), ...await buildPersistentLogsSnapshot(
runtime,
Number.isInteger(sinceSeq) ? sinceSeq : null,
Number.isInteger(limit) ? limit : 500,
),
}); });
return true; return true;
} }
+63 -10
View File
@@ -1,4 +1,4 @@
import { FormEvent, useEffect, useState } from "react"; import { FormEvent, startTransition, useEffect, useState } from "react";
type PageKey = "overview" | "requests" | "profiles" | "rules" | "logs"; type PageKey = "overview" | "requests" | "profiles" | "rules" | "logs";
type Tone = "" | "success" | "error"; type Tone = "" | "success" | "error";
@@ -222,6 +222,20 @@ const api = {
restore: "/__codex_retry_gateway/api/restore", restore: "/__codex_retry_gateway/api/restore",
}; };
const REQUEST_PAGE_SIZE = 40;
const LOG_PAGE_SIZE = 200;
const zhNumberFormatter = new Intl.NumberFormat("zh-CN");
const zhTimestampFormatter = new Intl.DateTimeFormat("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hourCycle: "h23",
});
type ProfileProbePayload = { type ProfileProbePayload = {
ok: boolean; ok: boolean;
profile?: string; profile?: string;
@@ -294,7 +308,7 @@ const defaultProfileForm: ProfileFormState = {
function numberFormat(value: unknown) { function numberFormat(value: unknown) {
return typeof value === "number" && Number.isFinite(value) return typeof value === "number" && Number.isFinite(value)
? new Intl.NumberFormat("zh-CN").format(value) ? zhNumberFormatter.format(value)
: "-"; : "-";
} }
@@ -303,7 +317,7 @@ function timestamp(value?: string | null) {
return "-"; return "-";
} }
const date = new Date(value); const date = new Date(value);
return Number.isNaN(date.getTime()) ? value : date.toLocaleString("zh-CN", { hour12: false }); return Number.isNaN(date.getTime()) ? value : zhTimestampFormatter.format(date);
} }
function durationSeconds(value?: number | null) { function durationSeconds(value?: number | null) {
@@ -482,6 +496,7 @@ export default function App() {
}); });
const [status, setStatus] = useState<StatusPayload | null>(null); const [status, setStatus] = useState<StatusPayload | null>(null);
const [requests, setRequests] = useState<RequestEntry[]>([]); const [requests, setRequests] = useState<RequestEntry[]>([]);
const [requestsTotal, setRequestsTotal] = useState(0);
const [requestsMeta, setRequestsMeta] = useState("正在读取请求记录..."); const [requestsMeta, setRequestsMeta] = useState("正在读取请求记录...");
const [profiles, setProfiles] = useState<Profile[]>([]); const [profiles, setProfiles] = useState<Profile[]>([]);
const [profilesMeta, setProfilesMeta] = useState("正在读取 profiles..."); const [profilesMeta, setProfilesMeta] = useState("正在读取 profiles...");
@@ -490,6 +505,7 @@ export default function App() {
const [latestLogSeq, setLatestLogSeq] = useState(0); const [latestLogSeq, setLatestLogSeq] = useState(0);
const [requestQuery, setRequestQuery] = useState(""); const [requestQuery, setRequestQuery] = useState("");
const [requestFilter, setRequestFilter] = useState("all"); const [requestFilter, setRequestFilter] = useState("all");
const [requestLimit, setRequestLimit] = useState(REQUEST_PAGE_SIZE);
const [ruleForm, setRuleForm] = useState<RuleFormState>(ruleFormFromStatus(null)); const [ruleForm, setRuleForm] = useState<RuleFormState>(ruleFormFromStatus(null));
const [profileForm, setProfileForm] = useState<ProfileFormState>(defaultProfileForm); const [profileForm, setProfileForm] = useState<ProfileFormState>(defaultProfileForm);
const [ruleMessage, setRuleMessage] = useState<MessageState>({ text: "", tone: "" }); const [ruleMessage, setRuleMessage] = useState<MessageState>({ text: "", tone: "" });
@@ -513,16 +529,18 @@ export default function App() {
async function loadStatus(refreshRuleForm = false) { async function loadStatus(refreshRuleForm = false) {
const payload = await fetchJson<StatusPayload>(api.status); const payload = await fetchJson<StatusPayload>(api.status);
startTransition(() => {
setStatus(payload); setStatus(payload);
if (refreshRuleForm) { if (refreshRuleForm) {
setRuleForm(ruleFormFromStatus(payload)); setRuleForm(ruleFormFromStatus(payload));
setProfileForm(profileFormFromStatus(payload)); setProfileForm(profileFormFromStatus(payload));
} }
});
} }
async function loadRequests() { async function loadRequests(limitOverride = requestLimit) {
const url = new URL(api.requests, window.location.origin); const url = new URL(api.requests, window.location.origin);
url.searchParams.set("limit", "100"); url.searchParams.set("limit", String(limitOverride));
if (requestQuery.trim()) { if (requestQuery.trim()) {
url.searchParams.set("query", requestQuery.trim()); url.searchParams.set("query", requestQuery.trim());
} }
@@ -531,26 +549,36 @@ export default function App() {
} }
const payload = await fetchJson<RequestsPayload>(url.toString()); const payload = await fetchJson<RequestsPayload>(url.toString());
const entries = payload.entries || []; const entries = payload.entries || [];
startTransition(() => {
setRequests(entries); setRequests(entries);
setRequestsMeta(`查询命中 ${payload.total_entries ?? entries.length} 条,最新序号 ${payload.latest_seq ?? 0}`); setRequestsTotal(payload.total_entries ?? entries.length);
setRequestsMeta(
`查询命中 ${payload.total_entries ?? entries.length} 条,当前展示最近 ${entries.length} 条,最新序号 ${payload.latest_seq ?? 0}`,
);
});
} }
async function loadProfiles() { async function loadProfiles() {
const payload = await fetchJson<ProfilesPayload>(api.profiles); const payload = await fetchJson<ProfilesPayload>(api.profiles);
const items = payload.profiles || []; const items = payload.profiles || [];
startTransition(() => {
setProfiles(items); setProfiles(items);
setProfilesMeta(`目录:${payload.profiles_dir || "-"};当前运行:${payload.active_profile || "-"}`); setProfilesMeta(`目录:${payload.profiles_dir || "-"};当前运行:${payload.active_profile || "-"}`);
});
} }
async function loadLogs(incremental = false) { async function loadLogs(incremental = false) {
const url = new URL(api.logs, window.location.origin); const url = new URL(api.logs, window.location.origin);
if (incremental && latestLogSeq > 0) { if (incremental && latestLogSeq > 0) {
url.searchParams.set("since_seq", String(latestLogSeq)); url.searchParams.set("since_seq", String(latestLogSeq));
} else {
url.searchParams.set("limit", String(LOG_PAGE_SIZE));
} }
const payload = await fetchJson<LogsPayload>(url.toString()); const payload = await fetchJson<LogsPayload>(url.toString());
const rendered = (payload.entries || []) const rendered = (payload.entries || [])
.map((entry) => `${entry.at || "-"} ${entry.message || ""}`) .map((entry) => `${entry.at || "-"} ${entry.message || ""}`)
.join("\n"); .join("\n");
startTransition(() => {
setLogs((current) => { setLogs((current) => {
if (!incremental || latestLogSeq === 0) { if (!incremental || latestLogSeq === 0) {
return rendered || "当前还没有日志。"; return rendered || "当前还没有日志。";
@@ -561,6 +589,7 @@ export default function App() {
if (typeof payload.latest_seq === "number") { if (typeof payload.latest_seq === "number") {
setLatestLogSeq(payload.latest_seq); setLatestLogSeq(payload.latest_seq);
} }
});
} }
async function loadPageData(targetPage: PageKey, { incrementalLogs = true } = {}) { async function loadPageData(targetPage: PageKey, { incrementalLogs = true } = {}) {
@@ -617,12 +646,24 @@ export default function App() {
useEffect(() => { useEffect(() => {
const timer = window.setInterval(() => { const timer = window.setInterval(() => {
refreshLiveData().catch((error) => { loadStatus(false).catch((error) => {
setRuleMessage({ text: error?.message || String(error), tone: "error" });
});
}, 10000);
return () => window.clearInterval(timer);
}, [restoreRequested, switchingTo]);
useEffect(() => {
if (page !== "requests" && page !== "logs") {
return;
}
const timer = window.setInterval(() => {
loadPageData(page, { incrementalLogs: true }).catch((error) => {
setRuleMessage({ text: error?.message || String(error), tone: "error" }); setRuleMessage({ text: error?.message || String(error), tone: "error" });
}); });
}, 2500); }, 2500);
return () => window.clearInterval(timer); return () => window.clearInterval(timer);
}, [page, requestQuery, requestFilter, latestLogSeq, restoreRequested, switchingTo]); }, [page, requestQuery, requestFilter, latestLogSeq, requestLimit]);
useEffect(() => { useEffect(() => {
if (page === "overview" || page === "rules") { if (page === "overview" || page === "rules") {
@@ -676,17 +717,21 @@ export default function App() {
} }
} }
useEffect(() => {
setRequestLimit(REQUEST_PAGE_SIZE);
}, [requestQuery, requestFilter]);
useEffect(() => { useEffect(() => {
if (page !== "requests") { if (page !== "requests") {
return; return;
} }
const timer = window.setTimeout(() => { const timer = window.setTimeout(() => {
loadRequests().catch((error) => { loadRequests(requestLimit).catch((error) => {
setRuleMessage({ text: error?.message || String(error), tone: "error" }); setRuleMessage({ text: error?.message || String(error), tone: "error" });
}); });
}, 180); }, 180);
return () => window.clearTimeout(timer); return () => window.clearTimeout(timer);
}, [page, requestQuery, requestFilter]); }, [page, requestQuery, requestFilter, requestLimit]);
async function saveProfile(event: FormEvent) { async function saveProfile(event: FormEvent) {
event.preventDefault(); event.preventDefault();
@@ -987,6 +1032,14 @@ export default function App() {
<button className="secondary" type="button" onClick={() => loadRequests()}> <button className="secondary" type="button" onClick={() => loadRequests()}>
</button> </button>
<button
className="ghost"
type="button"
disabled={requests.length >= requestsTotal && requestsTotal > 0}
onClick={() => setRequestLimit((current) => current + REQUEST_PAGE_SIZE)}
>
</button>
</div> </div>
} }
> >