speed up admin ui polling and payloads
This commit is contained in:
+10
-3
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+79
-26
@@ -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);
|
||||||
setStatus(payload);
|
startTransition(() => {
|
||||||
if (refreshRuleForm) {
|
setStatus(payload);
|
||||||
setRuleForm(ruleFormFromStatus(payload));
|
if (refreshRuleForm) {
|
||||||
setProfileForm(profileFormFromStatus(payload));
|
setRuleForm(ruleFormFromStatus(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,36 +549,47 @@ 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 || [];
|
||||||
setRequests(entries);
|
startTransition(() => {
|
||||||
setRequestsMeta(`查询命中 ${payload.total_entries ?? entries.length} 条,最新序号 ${payload.latest_seq ?? 0}。`);
|
setRequests(entries);
|
||||||
|
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 || [];
|
||||||
setProfiles(items);
|
startTransition(() => {
|
||||||
setProfilesMeta(`目录:${payload.profiles_dir || "-"};当前运行:${payload.active_profile || "-"}。`);
|
setProfiles(items);
|
||||||
|
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");
|
||||||
setLogs((current) => {
|
startTransition(() => {
|
||||||
if (!incremental || latestLogSeq === 0) {
|
setLogs((current) => {
|
||||||
return rendered || "当前还没有日志。";
|
if (!incremental || latestLogSeq === 0) {
|
||||||
|
return rendered || "当前还没有日志。";
|
||||||
|
}
|
||||||
|
return rendered ? `${current.trim()}\n${rendered}` : current;
|
||||||
|
});
|
||||||
|
setLogsMeta(`已载入 ${payload.total_entries ?? payload.entries?.length ?? 0} 条日志,最新序号 ${payload.latest_seq ?? latestLogSeq}。`);
|
||||||
|
if (typeof payload.latest_seq === "number") {
|
||||||
|
setLatestLogSeq(payload.latest_seq);
|
||||||
}
|
}
|
||||||
return rendered ? `${current.trim()}\n${rendered}` : current;
|
|
||||||
});
|
});
|
||||||
setLogsMeta(`已载入 ${payload.total_entries ?? payload.entries?.length ?? 0} 条日志,最新序号 ${payload.latest_seq ?? latestLogSeq}。`);
|
|
||||||
if (typeof payload.latest_seq === "number") {
|
|
||||||
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>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user