@yuhere/js-jsonrpc2 2.1.9 → 2.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/SKILL.md +21 -3
- package/htdoc/assets/{index-BAZmvr5x.css → index-Ck-39Igh.css} +221 -0
- package/htdoc/assets/{index-sVQlhxjI.js → index-Cr0Jnrgb.js} +716 -35
- package/htdoc/assets/index-Cr0Jnrgb.js.map +1 -0
- package/htdoc/index.html +2 -2
- package/package.json +3 -3
- package/webui/src/App.tsx +101 -20
- package/webui/src/api/glob.ts +90 -0
- package/webui/src/api/metrics.ts +135 -0
- package/webui/src/api/paramForm.ts +4 -3
- package/webui/src/components/MetricsPanel.md +126 -0
- package/webui/src/components/MetricsPanel.tsx +290 -0
- package/webui/src/components/SchemaForm.tsx +29 -27
- package/webui/src/components/SchemaView.tsx +3 -3
- package/webui/src/hooks/useMetrics.ts +91 -0
- package/webui/src/styles/geek.css +221 -0
- package/htdoc/assets/index-sVQlhxjI.js.map +0 -1
|
@@ -8013,6 +8013,133 @@ function useMethodDetail(rpc, method) {
|
|
|
8013
8013
|
return detail;
|
|
8014
8014
|
}
|
|
8015
8015
|
//#endregion
|
|
8016
|
+
//#region webui/src/api/metrics.ts
|
|
8017
|
+
/** 拉取 system.metrics;如果系统方法不可用(withSystemMethods=false),返回 undefined。 */
|
|
8018
|
+
async function fetchMetrics(rpc) {
|
|
8019
|
+
try {
|
|
8020
|
+
const raw = await rpc.call("system.metrics");
|
|
8021
|
+
if (!raw || typeof raw !== "object" || typeof raw.methods !== "object") return void 0;
|
|
8022
|
+
return raw;
|
|
8023
|
+
} catch {
|
|
8024
|
+
return;
|
|
8025
|
+
}
|
|
8026
|
+
}
|
|
8027
|
+
/** 在给定直方图上估算某个分位数(线性插值)。 */
|
|
8028
|
+
function quantileFromHistogram(h, q) {
|
|
8029
|
+
if (h.count === 0) return 0;
|
|
8030
|
+
const target = q * h.count;
|
|
8031
|
+
let prevLe = 0;
|
|
8032
|
+
let prevCount = 0;
|
|
8033
|
+
for (const b of h.buckets) {
|
|
8034
|
+
if (b.le === Number.POSITIVE_INFINITY) {
|
|
8035
|
+
const lo = prevLe;
|
|
8036
|
+
const hi = h.max;
|
|
8037
|
+
const span = b.count - prevCount || 1;
|
|
8038
|
+
const ratio = (target - prevCount) / span;
|
|
8039
|
+
return lo + (hi - lo) * ratio;
|
|
8040
|
+
}
|
|
8041
|
+
if (b.count >= target) {
|
|
8042
|
+
const lo = prevLe;
|
|
8043
|
+
const hi = b.le;
|
|
8044
|
+
const span = b.count - prevCount || 1;
|
|
8045
|
+
const ratio = (target - prevCount) / span;
|
|
8046
|
+
return lo + (hi - lo) * ratio;
|
|
8047
|
+
}
|
|
8048
|
+
prevLe = b.le;
|
|
8049
|
+
prevCount = b.count;
|
|
8050
|
+
}
|
|
8051
|
+
return h.max;
|
|
8052
|
+
}
|
|
8053
|
+
function deriveMethod(method, stats, prev, dtSeconds) {
|
|
8054
|
+
const totalErrors = Object.values(stats.errorsByCode).reduce((a, b) => a + b, 0);
|
|
8055
|
+
const opened = stats.streams.opened || 0;
|
|
8056
|
+
const errorRate = stats.calls > 0 ? totalErrors / stats.calls : 0;
|
|
8057
|
+
const callsPerSecond = prev && dtSeconds && dtSeconds > 0 ? Math.max(0, (stats.calls - prev.calls) / dtSeconds) : void 0;
|
|
8058
|
+
return {
|
|
8059
|
+
method,
|
|
8060
|
+
stats,
|
|
8061
|
+
...callsPerSecond !== void 0 ? { callsPerSecond } : {},
|
|
8062
|
+
errorRate,
|
|
8063
|
+
p50: quantileFromHistogram(stats.duration, .5),
|
|
8064
|
+
p95: quantileFromHistogram(stats.duration, .95),
|
|
8065
|
+
p99: quantileFromHistogram(stats.duration, .99),
|
|
8066
|
+
streamCompletionRate: opened > 0 ? stats.streams.completed / opened : 0,
|
|
8067
|
+
streamAbortRate: opened > 0 ? stats.streams.aborted / opened : 0,
|
|
8068
|
+
ttfbP50: quantileFromHistogram(stats.streamTimeToFirstChunk, .5),
|
|
8069
|
+
ttfbP95: quantileFromHistogram(stats.streamTimeToFirstChunk, .95),
|
|
8070
|
+
avgChunksPerStream: opened > 0 ? stats.streamChunksTotal / (stats.streams.completed + stats.streams.failed + stats.streams.aborted || 1) : 0
|
|
8071
|
+
};
|
|
8072
|
+
}
|
|
8073
|
+
//#endregion
|
|
8074
|
+
//#region webui/src/hooks/useMetrics.ts
|
|
8075
|
+
function useMetrics(rpc, enabled) {
|
|
8076
|
+
const [snapshot, setSnapshot] = (0, import_react.useState)(void 0);
|
|
8077
|
+
const [previous, setPrevious] = (0, import_react.useState)(void 0);
|
|
8078
|
+
const [fetchedAt, setFetchedAt] = (0, import_react.useState)(void 0);
|
|
8079
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
8080
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
8081
|
+
const [available, setAvailable] = (0, import_react.useState)(true);
|
|
8082
|
+
const [autoRefresh, setAutoRefresh] = (0, import_react.useState)(true);
|
|
8083
|
+
const [intervalMs, setIntervalMs] = (0, import_react.useState)(3e3);
|
|
8084
|
+
const inFlight = (0, import_react.useRef)(false);
|
|
8085
|
+
const refresh = (0, import_react.useCallback)(async () => {
|
|
8086
|
+
if (inFlight.current) return;
|
|
8087
|
+
inFlight.current = true;
|
|
8088
|
+
setLoading(true);
|
|
8089
|
+
setError(null);
|
|
8090
|
+
try {
|
|
8091
|
+
const next = await fetchMetrics(rpc);
|
|
8092
|
+
if (!next) {
|
|
8093
|
+
setAvailable(false);
|
|
8094
|
+
return;
|
|
8095
|
+
}
|
|
8096
|
+
setAvailable(true);
|
|
8097
|
+
const now = Date.now();
|
|
8098
|
+
setSnapshot((curr) => {
|
|
8099
|
+
if (curr) setPrevious({
|
|
8100
|
+
snapshot: curr,
|
|
8101
|
+
fetchedAt: fetchedAt ?? now
|
|
8102
|
+
});
|
|
8103
|
+
return next;
|
|
8104
|
+
});
|
|
8105
|
+
setFetchedAt(now);
|
|
8106
|
+
} catch (e) {
|
|
8107
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
8108
|
+
} finally {
|
|
8109
|
+
setLoading(false);
|
|
8110
|
+
inFlight.current = false;
|
|
8111
|
+
}
|
|
8112
|
+
}, [rpc, fetchedAt]);
|
|
8113
|
+
(0, import_react.useEffect)(() => {
|
|
8114
|
+
if (!enabled) return;
|
|
8115
|
+
refresh();
|
|
8116
|
+
}, [enabled, rpc]);
|
|
8117
|
+
(0, import_react.useEffect)(() => {
|
|
8118
|
+
if (!enabled || !autoRefresh || !available) return;
|
|
8119
|
+
const t = setInterval(() => void refresh(), intervalMs);
|
|
8120
|
+
return () => clearInterval(t);
|
|
8121
|
+
}, [
|
|
8122
|
+
enabled,
|
|
8123
|
+
autoRefresh,
|
|
8124
|
+
available,
|
|
8125
|
+
intervalMs,
|
|
8126
|
+
refresh
|
|
8127
|
+
]);
|
|
8128
|
+
return {
|
|
8129
|
+
snapshot,
|
|
8130
|
+
previous,
|
|
8131
|
+
fetchedAt,
|
|
8132
|
+
loading,
|
|
8133
|
+
error,
|
|
8134
|
+
refresh,
|
|
8135
|
+
autoRefresh,
|
|
8136
|
+
setAutoRefresh,
|
|
8137
|
+
intervalMs,
|
|
8138
|
+
setIntervalMs,
|
|
8139
|
+
available
|
|
8140
|
+
};
|
|
8141
|
+
}
|
|
8142
|
+
//#endregion
|
|
8016
8143
|
//#region node_modules/react/cjs/react-jsx-runtime.production.min.js
|
|
8017
8144
|
/**
|
|
8018
8145
|
* @license React
|
|
@@ -8026,6 +8153,7 @@ function useMethodDetail(rpc, method) {
|
|
|
8026
8153
|
var require_react_jsx_runtime_production_min = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
8027
8154
|
var f = require_react();
|
|
8028
8155
|
var k = Symbol.for("react.element");
|
|
8156
|
+
var l = Symbol.for("react.fragment");
|
|
8029
8157
|
var m = Object.prototype.hasOwnProperty;
|
|
8030
8158
|
var n = f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner;
|
|
8031
8159
|
var p = {
|
|
@@ -8050,6 +8178,7 @@ var require_react_jsx_runtime_production_min = /* @__PURE__ */ __commonJSMin(((e
|
|
|
8050
8178
|
_owner: n.current
|
|
8051
8179
|
};
|
|
8052
8180
|
}
|
|
8181
|
+
exports.Fragment = l;
|
|
8053
8182
|
exports.jsx = q;
|
|
8054
8183
|
exports.jsxs = q;
|
|
8055
8184
|
}));
|
|
@@ -8280,8 +8409,8 @@ var KIND_COLORS$1 = {
|
|
|
8280
8409
|
record: "var(--syn-object)",
|
|
8281
8410
|
union: "var(--syn-union)",
|
|
8282
8411
|
enum: "var(--syn-union)",
|
|
8283
|
-
nullable: "var(--syn-muted)",
|
|
8284
8412
|
unknown: "var(--syn-muted)",
|
|
8413
|
+
null: "var(--syn-muted)",
|
|
8285
8414
|
stream: "var(--syn-container)"
|
|
8286
8415
|
};
|
|
8287
8416
|
function colorOf$1(kind) {
|
|
@@ -8323,8 +8452,8 @@ function SchemaNode({ schema, depth, defaultOpen, label }) {
|
|
|
8323
8452
|
children: "<stream>"
|
|
8324
8453
|
}),
|
|
8325
8454
|
schema.nullable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
8326
|
-
className: "schema-nullable",
|
|
8327
|
-
children: "
|
|
8455
|
+
className: "schema-nullable-badge",
|
|
8456
|
+
children: "nullable"
|
|
8328
8457
|
}),
|
|
8329
8458
|
constraintsText && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
8330
8459
|
className: "schema-constraints",
|
|
@@ -8457,10 +8586,11 @@ function isSchema$1(v) {
|
|
|
8457
8586
|
}
|
|
8458
8587
|
/**
|
|
8459
8588
|
* 根据 schema 生成一个合理的示例值。
|
|
8589
|
+
* - nullable 优先返回 null
|
|
8590
|
+
* - null 直接返回 null
|
|
8460
8591
|
* - 原始类型返回零值(''、0、false)
|
|
8461
8592
|
* - literal 返回固定值
|
|
8462
8593
|
* - enum 返回第一个枚举值
|
|
8463
|
-
* - nullable 返回 null
|
|
8464
8594
|
* - 容器类型递归展开
|
|
8465
8595
|
* - unknown 返回 null
|
|
8466
8596
|
*/
|
|
@@ -8471,12 +8601,12 @@ function sampleFromSchema(schema) {
|
|
|
8471
8601
|
case "string": return "";
|
|
8472
8602
|
case "number": return 0;
|
|
8473
8603
|
case "boolean": return false;
|
|
8604
|
+
case "null": return null;
|
|
8474
8605
|
case "literal": return schema.constraints?.value ?? null;
|
|
8475
8606
|
case "enum": {
|
|
8476
8607
|
const values = schema.constraints?.values;
|
|
8477
8608
|
return Array.isArray(values) && values.length > 0 ? values[0] : "";
|
|
8478
8609
|
}
|
|
8479
|
-
case "nullable": return isSchema$1(c.inner) ? sampleFromSchema(c.inner) : null;
|
|
8480
8610
|
case "array": return [isSchema$1(c.items) ? sampleFromSchema(c.items) : null];
|
|
8481
8611
|
case "tuple": return (Array.isArray(c.items) ? c.items : []).map((it) => isSchema$1(it) ? sampleFromSchema(it) : null);
|
|
8482
8612
|
case "union": {
|
|
@@ -8506,8 +8636,8 @@ var KIND_COLORS = {
|
|
|
8506
8636
|
record: "var(--syn-object)",
|
|
8507
8637
|
union: "var(--syn-union)",
|
|
8508
8638
|
enum: "var(--syn-union)",
|
|
8509
|
-
|
|
8510
|
-
|
|
8639
|
+
unknown: "var(--syn-muted)",
|
|
8640
|
+
null: "var(--syn-muted)"
|
|
8511
8641
|
};
|
|
8512
8642
|
function colorOf(kind) {
|
|
8513
8643
|
return KIND_COLORS[kind] ?? "var(--fg)";
|
|
@@ -8536,16 +8666,7 @@ function SchemaForm({ params, values, onChange, disabled }) {
|
|
|
8536
8666
|
function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen = true, disabled }) {
|
|
8537
8667
|
const [open, setOpen] = (0, import_react.useState)(defaultOpen);
|
|
8538
8668
|
const kind = schema.kind;
|
|
8539
|
-
if (
|
|
8540
|
-
const inner = schema.children && isSchema$1(schema.children.inner) ? schema.children.inner : void 0;
|
|
8541
|
-
if (!inner) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UnknownLeaf, {
|
|
8542
|
-
schema,
|
|
8543
|
-
value,
|
|
8544
|
-
onChange,
|
|
8545
|
-
label,
|
|
8546
|
-
depth,
|
|
8547
|
-
disabled
|
|
8548
|
-
});
|
|
8669
|
+
if (schema.nullable) {
|
|
8549
8670
|
const isNull = value === null || value === void 0;
|
|
8550
8671
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
8551
8672
|
className: "sf-node sf-row",
|
|
@@ -8559,9 +8680,17 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
|
|
|
8559
8680
|
}),
|
|
8560
8681
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
8561
8682
|
className: "sf-kind",
|
|
8562
|
-
style: { color: colorOf(
|
|
8683
|
+
style: { color: colorOf(kind) },
|
|
8684
|
+
children: kind
|
|
8685
|
+
}),
|
|
8686
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
8687
|
+
className: "sf-nullable-badge",
|
|
8563
8688
|
children: "nullable"
|
|
8564
8689
|
}),
|
|
8690
|
+
schema.description && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
8691
|
+
className: "sf-desc",
|
|
8692
|
+
children: ["— ", schema.description]
|
|
8693
|
+
}),
|
|
8565
8694
|
!isNull && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
8566
8695
|
type: "button",
|
|
8567
8696
|
className: "tiny",
|
|
@@ -8573,7 +8702,10 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
|
|
|
8573
8702
|
type: "button",
|
|
8574
8703
|
className: "tiny",
|
|
8575
8704
|
disabled,
|
|
8576
|
-
onClick: () => onChange(sampleFromSchema(
|
|
8705
|
+
onClick: () => onChange(sampleFromSchema({
|
|
8706
|
+
...schema,
|
|
8707
|
+
nullable: false
|
|
8708
|
+
})),
|
|
8577
8709
|
children: "填值"
|
|
8578
8710
|
})
|
|
8579
8711
|
]
|
|
@@ -8583,7 +8715,10 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
|
|
|
8583
8715
|
className: "sf-null-indicator",
|
|
8584
8716
|
children: "null"
|
|
8585
8717
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchemaField, {
|
|
8586
|
-
schema:
|
|
8718
|
+
schema: {
|
|
8719
|
+
...schema,
|
|
8720
|
+
nullable: false
|
|
8721
|
+
},
|
|
8587
8722
|
value,
|
|
8588
8723
|
onChange,
|
|
8589
8724
|
depth: 0,
|
|
@@ -8705,7 +8840,7 @@ function SchemaField({ schema, value, onChange, label, depth = 0, defaultOpen =
|
|
|
8705
8840
|
});
|
|
8706
8841
|
}
|
|
8707
8842
|
function isScalarKind(kind) {
|
|
8708
|
-
return kind === "string" || kind === "number" || kind === "boolean" || kind === "enum" || kind === "literal";
|
|
8843
|
+
return kind === "string" || kind === "number" || kind === "boolean" || kind === "enum" || kind === "literal" || kind === "null";
|
|
8709
8844
|
}
|
|
8710
8845
|
function ScalarInput({ schema, value, onChange, disabled }) {
|
|
8711
8846
|
switch (schema.kind) {
|
|
@@ -8748,6 +8883,16 @@ function ScalarInput({ schema, value, onChange, disabled }) {
|
|
|
8748
8883
|
children: "(固定值)"
|
|
8749
8884
|
})]
|
|
8750
8885
|
});
|
|
8886
|
+
case "null": return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
8887
|
+
className: "sf-scalar",
|
|
8888
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", {
|
|
8889
|
+
className: "literal-value",
|
|
8890
|
+
children: "null"
|
|
8891
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
8892
|
+
className: "hint",
|
|
8893
|
+
children: "(固定值)"
|
|
8894
|
+
})]
|
|
8895
|
+
});
|
|
8751
8896
|
default: return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
8752
8897
|
className: "sf-input",
|
|
8753
8898
|
type: "text",
|
|
@@ -9275,6 +9420,449 @@ function formatError(e) {
|
|
|
9275
9420
|
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
9276
9421
|
}
|
|
9277
9422
|
//#endregion
|
|
9423
|
+
//#region webui/src/components/MetricsPanel.tsx
|
|
9424
|
+
function formatDuration(seconds) {
|
|
9425
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return "—";
|
|
9426
|
+
if (seconds < .001) return `${(seconds * 1e6).toFixed(0)} µs`;
|
|
9427
|
+
if (seconds < 1) return `${(seconds * 1e3).toFixed(1)} ms`;
|
|
9428
|
+
if (seconds < 60) return `${seconds.toFixed(2)} s`;
|
|
9429
|
+
return `${(seconds / 60).toFixed(1)} min`;
|
|
9430
|
+
}
|
|
9431
|
+
function formatRate(v) {
|
|
9432
|
+
if (v === void 0) return "—";
|
|
9433
|
+
if (v >= 100) return v.toFixed(0);
|
|
9434
|
+
if (v >= 10) return v.toFixed(1);
|
|
9435
|
+
return v.toFixed(2);
|
|
9436
|
+
}
|
|
9437
|
+
function formatPercent(v) {
|
|
9438
|
+
if (!Number.isFinite(v)) return "—";
|
|
9439
|
+
return `${(v * 100).toFixed(1)}%`;
|
|
9440
|
+
}
|
|
9441
|
+
function rateClass(v) {
|
|
9442
|
+
if (v === void 0) return "";
|
|
9443
|
+
if (v > 100) return "rate-hot";
|
|
9444
|
+
if (v > 10) return "rate-warm";
|
|
9445
|
+
return "rate-cold";
|
|
9446
|
+
}
|
|
9447
|
+
function MetricsPanel({ snapshot, previous, fetchedAt, loading, error, available, onRefresh, autoRefresh, onAutoRefreshChange, intervalMs, onIntervalChange }) {
|
|
9448
|
+
const derived = (0, import_react.useMemo)(() => {
|
|
9449
|
+
if (!snapshot) return [];
|
|
9450
|
+
const dtSeconds = previous && fetchedAt ? (fetchedAt - previous.fetchedAt) / 1e3 : void 0;
|
|
9451
|
+
const rows = [];
|
|
9452
|
+
for (const [method, stats] of Object.entries(snapshot.methods)) {
|
|
9453
|
+
const prev = previous?.snapshot.methods[method];
|
|
9454
|
+
rows.push(deriveMethod(method, stats, prev, dtSeconds));
|
|
9455
|
+
}
|
|
9456
|
+
rows.sort((a, b) => b.stats.calls - a.stats.calls);
|
|
9457
|
+
return rows;
|
|
9458
|
+
}, [
|
|
9459
|
+
snapshot,
|
|
9460
|
+
previous,
|
|
9461
|
+
fetchedAt
|
|
9462
|
+
]);
|
|
9463
|
+
const streamMethods = derived.filter((r) => r.stats.streams.opened > 0);
|
|
9464
|
+
const nonStreamMethods = derived.filter((r) => r.stats.streams.opened === 0);
|
|
9465
|
+
const totals = (0, import_react.useMemo)(() => {
|
|
9466
|
+
if (!snapshot) return {
|
|
9467
|
+
calls: 0,
|
|
9468
|
+
errors: 0,
|
|
9469
|
+
methods: 0,
|
|
9470
|
+
streams: 0
|
|
9471
|
+
};
|
|
9472
|
+
let calls = 0;
|
|
9473
|
+
let errors = 0;
|
|
9474
|
+
let streams = 0;
|
|
9475
|
+
for (const s of Object.values(snapshot.methods)) {
|
|
9476
|
+
calls += s.calls;
|
|
9477
|
+
errors += Object.values(s.errorsByCode).reduce((a, b) => a + b, 0);
|
|
9478
|
+
streams += s.streams.opened;
|
|
9479
|
+
}
|
|
9480
|
+
return {
|
|
9481
|
+
calls,
|
|
9482
|
+
errors,
|
|
9483
|
+
methods: Object.keys(snapshot.methods).length,
|
|
9484
|
+
streams
|
|
9485
|
+
};
|
|
9486
|
+
}, [snapshot]);
|
|
9487
|
+
if (!available) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
9488
|
+
className: "metrics",
|
|
9489
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Metrics" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
9490
|
+
className: "hint",
|
|
9491
|
+
children: [
|
|
9492
|
+
"服务端未启用 ",
|
|
9493
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "system.metrics" }),
|
|
9494
|
+
"(",
|
|
9495
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "withSystemMethods: false" }),
|
|
9496
|
+
" 或未注册)。"
|
|
9497
|
+
]
|
|
9498
|
+
})]
|
|
9499
|
+
});
|
|
9500
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
9501
|
+
className: "metrics",
|
|
9502
|
+
children: [
|
|
9503
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
|
|
9504
|
+
className: "metrics-header",
|
|
9505
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Metrics" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
9506
|
+
className: "metrics-controls",
|
|
9507
|
+
children: [
|
|
9508
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
|
|
9509
|
+
className: "metrics-auto",
|
|
9510
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
9511
|
+
type: "checkbox",
|
|
9512
|
+
checked: autoRefresh,
|
|
9513
|
+
onChange: (e) => onAutoRefreshChange(e.target.checked)
|
|
9514
|
+
}), "自动刷新"]
|
|
9515
|
+
}),
|
|
9516
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
9517
|
+
className: "sf-input",
|
|
9518
|
+
value: intervalMs,
|
|
9519
|
+
onChange: (e) => onIntervalChange(Number(e.target.value)),
|
|
9520
|
+
disabled: !autoRefresh,
|
|
9521
|
+
title: "刷新间隔",
|
|
9522
|
+
children: [
|
|
9523
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
9524
|
+
value: 1e3,
|
|
9525
|
+
children: "1s"
|
|
9526
|
+
}),
|
|
9527
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
9528
|
+
value: 3e3,
|
|
9529
|
+
children: "3s"
|
|
9530
|
+
}),
|
|
9531
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
9532
|
+
value: 5e3,
|
|
9533
|
+
children: "5s"
|
|
9534
|
+
}),
|
|
9535
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
9536
|
+
value: 1e4,
|
|
9537
|
+
children: "10s"
|
|
9538
|
+
})
|
|
9539
|
+
]
|
|
9540
|
+
}),
|
|
9541
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
9542
|
+
onClick: onRefresh,
|
|
9543
|
+
disabled: loading,
|
|
9544
|
+
children: loading ? "刷新中…" : "立即刷新"
|
|
9545
|
+
}),
|
|
9546
|
+
snapshot && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
9547
|
+
className: "hint",
|
|
9548
|
+
children: ["uptime ", formatDuration(snapshot.uptimeMs / 1e3)]
|
|
9549
|
+
})
|
|
9550
|
+
]
|
|
9551
|
+
})]
|
|
9552
|
+
}),
|
|
9553
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
9554
|
+
className: "error",
|
|
9555
|
+
children: error
|
|
9556
|
+
}),
|
|
9557
|
+
!snapshot && !error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
9558
|
+
className: "hint",
|
|
9559
|
+
children: "加载中…"
|
|
9560
|
+
}),
|
|
9561
|
+
snapshot && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
9562
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
9563
|
+
className: "metrics-cards",
|
|
9564
|
+
children: [
|
|
9565
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
|
|
9566
|
+
label: "Methods",
|
|
9567
|
+
value: String(totals.methods)
|
|
9568
|
+
}),
|
|
9569
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
|
|
9570
|
+
label: "Calls",
|
|
9571
|
+
value: String(totals.calls)
|
|
9572
|
+
}),
|
|
9573
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
|
|
9574
|
+
label: "Streams",
|
|
9575
|
+
value: String(totals.streams)
|
|
9576
|
+
}),
|
|
9577
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
|
|
9578
|
+
label: "Errors",
|
|
9579
|
+
value: String(totals.errors),
|
|
9580
|
+
tone: totals.errors > 0 ? "danger" : void 0
|
|
9581
|
+
}),
|
|
9582
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
|
|
9583
|
+
label: "Error rate",
|
|
9584
|
+
value: totals.calls > 0 ? formatPercent(totals.errors / totals.calls) : "—",
|
|
9585
|
+
tone: totals.calls > 0 && totals.errors / totals.calls > .05 ? "danger" : void 0
|
|
9586
|
+
}),
|
|
9587
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricCard, {
|
|
9588
|
+
label: "Invalid requests",
|
|
9589
|
+
value: String(snapshot.invalidRequests),
|
|
9590
|
+
tone: snapshot.invalidRequests > 0 ? "warn" : void 0
|
|
9591
|
+
})
|
|
9592
|
+
]
|
|
9593
|
+
}),
|
|
9594
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: ["Non-stream methods", /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
9595
|
+
className: "hint",
|
|
9596
|
+
children: [
|
|
9597
|
+
" (",
|
|
9598
|
+
nonStreamMethods.length,
|
|
9599
|
+
")"
|
|
9600
|
+
]
|
|
9601
|
+
})] }),
|
|
9602
|
+
nonStreamMethods.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
9603
|
+
className: "hint",
|
|
9604
|
+
children: "暂无数据。"
|
|
9605
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NonStreamTable, { rows: nonStreamMethods }),
|
|
9606
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h3", { children: ["Stream methods", /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
9607
|
+
className: "hint",
|
|
9608
|
+
children: [
|
|
9609
|
+
" (",
|
|
9610
|
+
streamMethods.length,
|
|
9611
|
+
")"
|
|
9612
|
+
]
|
|
9613
|
+
})] }),
|
|
9614
|
+
streamMethods.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
9615
|
+
className: "hint",
|
|
9616
|
+
children: "暂无流式调用。"
|
|
9617
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StreamTable, { rows: streamMethods })
|
|
9618
|
+
] })
|
|
9619
|
+
]
|
|
9620
|
+
});
|
|
9621
|
+
}
|
|
9622
|
+
function MetricCard({ label, value, tone }) {
|
|
9623
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
9624
|
+
className: `metric-card${tone ? ` metric-card-${tone}` : ""}`,
|
|
9625
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
9626
|
+
className: "metric-card-label",
|
|
9627
|
+
children: label
|
|
9628
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
9629
|
+
className: "metric-card-value",
|
|
9630
|
+
children: value
|
|
9631
|
+
})]
|
|
9632
|
+
});
|
|
9633
|
+
}
|
|
9634
|
+
function NonStreamTable({ rows }) {
|
|
9635
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
|
|
9636
|
+
className: "metrics-table",
|
|
9637
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
9638
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "method" }),
|
|
9639
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9640
|
+
className: "num",
|
|
9641
|
+
children: "calls"
|
|
9642
|
+
}),
|
|
9643
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9644
|
+
className: "num",
|
|
9645
|
+
children: "rate/s"
|
|
9646
|
+
}),
|
|
9647
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9648
|
+
className: "num",
|
|
9649
|
+
children: "errors"
|
|
9650
|
+
}),
|
|
9651
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9652
|
+
className: "num",
|
|
9653
|
+
children: "err%"
|
|
9654
|
+
}),
|
|
9655
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9656
|
+
className: "num",
|
|
9657
|
+
children: "p50"
|
|
9658
|
+
}),
|
|
9659
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9660
|
+
className: "num",
|
|
9661
|
+
children: "p95"
|
|
9662
|
+
}),
|
|
9663
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9664
|
+
className: "num",
|
|
9665
|
+
children: "p99"
|
|
9666
|
+
}),
|
|
9667
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9668
|
+
className: "num",
|
|
9669
|
+
children: "avg"
|
|
9670
|
+
})
|
|
9671
|
+
] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: rows.map((r) => {
|
|
9672
|
+
const errorCount = Object.values(r.stats.errorsByCode).reduce((a, b) => a + b, 0);
|
|
9673
|
+
const avg = r.stats.duration.count > 0 ? r.stats.duration.sum / r.stats.duration.count : 0;
|
|
9674
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
9675
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9676
|
+
className: "method-name",
|
|
9677
|
+
title: r.method,
|
|
9678
|
+
children: r.method
|
|
9679
|
+
}),
|
|
9680
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9681
|
+
className: "num",
|
|
9682
|
+
children: r.stats.calls
|
|
9683
|
+
}),
|
|
9684
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9685
|
+
className: `num ${rateClass(r.callsPerSecond)}`,
|
|
9686
|
+
children: formatRate(r.callsPerSecond)
|
|
9687
|
+
}),
|
|
9688
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9689
|
+
className: `num${errorCount > 0 ? " error" : ""}`,
|
|
9690
|
+
children: errorCount
|
|
9691
|
+
}),
|
|
9692
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9693
|
+
className: `num${r.errorRate > .05 ? " error" : ""}`,
|
|
9694
|
+
children: formatPercent(r.errorRate)
|
|
9695
|
+
}),
|
|
9696
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9697
|
+
className: "num",
|
|
9698
|
+
children: formatDuration(r.p50)
|
|
9699
|
+
}),
|
|
9700
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9701
|
+
className: "num",
|
|
9702
|
+
children: formatDuration(r.p95)
|
|
9703
|
+
}),
|
|
9704
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9705
|
+
className: "num",
|
|
9706
|
+
children: formatDuration(r.p99)
|
|
9707
|
+
}),
|
|
9708
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9709
|
+
className: "num",
|
|
9710
|
+
children: formatDuration(avg)
|
|
9711
|
+
})
|
|
9712
|
+
] }, r.method);
|
|
9713
|
+
}) })]
|
|
9714
|
+
});
|
|
9715
|
+
}
|
|
9716
|
+
function StreamTable({ rows }) {
|
|
9717
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
|
|
9718
|
+
className: "metrics-table",
|
|
9719
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
9720
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "method" }),
|
|
9721
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9722
|
+
className: "num",
|
|
9723
|
+
children: "opened"
|
|
9724
|
+
}),
|
|
9725
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9726
|
+
className: "num",
|
|
9727
|
+
children: "completed"
|
|
9728
|
+
}),
|
|
9729
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9730
|
+
className: "num",
|
|
9731
|
+
children: "failed"
|
|
9732
|
+
}),
|
|
9733
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9734
|
+
className: "num",
|
|
9735
|
+
children: "aborted"
|
|
9736
|
+
}),
|
|
9737
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9738
|
+
className: "num",
|
|
9739
|
+
children: "complete%"
|
|
9740
|
+
}),
|
|
9741
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9742
|
+
className: "num",
|
|
9743
|
+
children: "abort%"
|
|
9744
|
+
}),
|
|
9745
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9746
|
+
className: "num",
|
|
9747
|
+
children: "avg chunks"
|
|
9748
|
+
}),
|
|
9749
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9750
|
+
className: "num",
|
|
9751
|
+
children: "ttfb p50"
|
|
9752
|
+
}),
|
|
9753
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9754
|
+
className: "num",
|
|
9755
|
+
children: "ttfb p95"
|
|
9756
|
+
}),
|
|
9757
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
9758
|
+
className: "num",
|
|
9759
|
+
children: "duration avg"
|
|
9760
|
+
})
|
|
9761
|
+
] }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: rows.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
9762
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9763
|
+
className: "method-name",
|
|
9764
|
+
title: r.method,
|
|
9765
|
+
children: r.method
|
|
9766
|
+
}),
|
|
9767
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9768
|
+
className: "num",
|
|
9769
|
+
children: r.stats.streams.opened
|
|
9770
|
+
}),
|
|
9771
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9772
|
+
className: "num",
|
|
9773
|
+
children: r.stats.streams.completed
|
|
9774
|
+
}),
|
|
9775
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9776
|
+
className: `num${r.stats.streams.failed > 0 ? " error" : ""}`,
|
|
9777
|
+
children: r.stats.streams.failed
|
|
9778
|
+
}),
|
|
9779
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9780
|
+
className: `num${r.stats.streams.aborted > 0 ? " warn" : ""}`,
|
|
9781
|
+
children: r.stats.streams.aborted
|
|
9782
|
+
}),
|
|
9783
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9784
|
+
className: "num",
|
|
9785
|
+
children: formatPercent(r.streamCompletionRate)
|
|
9786
|
+
}),
|
|
9787
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9788
|
+
className: "num",
|
|
9789
|
+
children: formatPercent(r.streamAbortRate)
|
|
9790
|
+
}),
|
|
9791
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9792
|
+
className: "num",
|
|
9793
|
+
children: r.avgChunksPerStream.toFixed(1)
|
|
9794
|
+
}),
|
|
9795
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9796
|
+
className: "num",
|
|
9797
|
+
children: formatDuration(r.ttfbP50)
|
|
9798
|
+
}),
|
|
9799
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9800
|
+
className: "num",
|
|
9801
|
+
children: formatDuration(r.ttfbP95)
|
|
9802
|
+
}),
|
|
9803
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
9804
|
+
className: "num",
|
|
9805
|
+
children: formatDuration(r.stats.streamDuration.count > 0 ? r.stats.streamDuration.sum / r.stats.streamDuration.count : 0)
|
|
9806
|
+
})
|
|
9807
|
+
] }, r.method)) })]
|
|
9808
|
+
});
|
|
9809
|
+
}
|
|
9810
|
+
//#endregion
|
|
9811
|
+
//#region webui/src/api/glob.ts
|
|
9812
|
+
function globToRegExp(glob) {
|
|
9813
|
+
let re = "^";
|
|
9814
|
+
for (let i = 0; i < glob.length; i++) {
|
|
9815
|
+
const ch = glob[i];
|
|
9816
|
+
if (ch === "*") {
|
|
9817
|
+
while (i + 1 < glob.length && glob[i + 1] === "*") i++;
|
|
9818
|
+
re += ".*";
|
|
9819
|
+
} else if (ch === "?") re += ".";
|
|
9820
|
+
else if ("\\^$+{}()|[]".includes(ch)) re += "\\" + ch;
|
|
9821
|
+
else re += ch;
|
|
9822
|
+
}
|
|
9823
|
+
re += "$";
|
|
9824
|
+
return new RegExp(re);
|
|
9825
|
+
}
|
|
9826
|
+
function compileGlobFilter(input) {
|
|
9827
|
+
const raw = input ?? "";
|
|
9828
|
+
const trimmed = raw.trim();
|
|
9829
|
+
if (trimmed.length === 0) return {
|
|
9830
|
+
includes: [],
|
|
9831
|
+
excludes: [],
|
|
9832
|
+
raw,
|
|
9833
|
+
passthrough: true
|
|
9834
|
+
};
|
|
9835
|
+
const includes = [];
|
|
9836
|
+
const excludes = [];
|
|
9837
|
+
for (const part of trimmed.split(",")) {
|
|
9838
|
+
const pat = part.trim();
|
|
9839
|
+
if (pat.length === 0) continue;
|
|
9840
|
+
if (pat.startsWith("!")) {
|
|
9841
|
+
const body = pat.slice(1).trim();
|
|
9842
|
+
if (body.length > 0) excludes.push(globToRegExp(body));
|
|
9843
|
+
} else includes.push(globToRegExp(pat));
|
|
9844
|
+
}
|
|
9845
|
+
return {
|
|
9846
|
+
includes,
|
|
9847
|
+
excludes,
|
|
9848
|
+
raw,
|
|
9849
|
+
passthrough: false
|
|
9850
|
+
};
|
|
9851
|
+
}
|
|
9852
|
+
function matchMethod(filter, method) {
|
|
9853
|
+
if (filter.passthrough) return true;
|
|
9854
|
+
if (filter.excludes.some((re) => re.test(method))) return false;
|
|
9855
|
+
if (filter.includes.length === 0) return true;
|
|
9856
|
+
return filter.includes.some((re) => re.test(method));
|
|
9857
|
+
}
|
|
9858
|
+
/** 便捷:直接对 MetricsSnapshot 的 methods 做过滤,返回浅拷贝。 */
|
|
9859
|
+
function filterSnapshotMethods(filter, methods) {
|
|
9860
|
+
if (filter.passthrough) return methods;
|
|
9861
|
+
const out = {};
|
|
9862
|
+
for (const [method, stats] of Object.entries(methods)) if (matchMethod(filter, method)) out[method] = stats;
|
|
9863
|
+
return out;
|
|
9864
|
+
}
|
|
9865
|
+
//#endregion
|
|
9278
9866
|
//#region webui/src/api/docGen.ts
|
|
9279
9867
|
function isSchema(v) {
|
|
9280
9868
|
return !!v && typeof v === "object" && typeof v.kind === "string";
|
|
@@ -9411,17 +9999,25 @@ function App() {
|
|
|
9411
9999
|
const [transport, setTransport] = (0, import_react.useState)("http");
|
|
9412
10000
|
const [selected, setSelected] = (0, import_react.useState)("");
|
|
9413
10001
|
const [generatingDocs, setGeneratingDocs] = (0, import_react.useState)(false);
|
|
9414
|
-
const
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
}, [transport]);
|
|
9420
|
-
(0, import_react.useEffect)(() =>
|
|
9421
|
-
return () => rpc.close();
|
|
9422
|
-
}, [rpc]);
|
|
10002
|
+
const [view, setView] = (0, import_react.useState)("methods");
|
|
10003
|
+
const [methodFilter, setMethodFilter] = (0, import_react.useState)("");
|
|
10004
|
+
const rpc = (0, import_react.useMemo)(() => createRpc({
|
|
10005
|
+
transport,
|
|
10006
|
+
endpoint: ENDPOINT
|
|
10007
|
+
}), [transport]);
|
|
10008
|
+
(0, import_react.useEffect)(() => () => rpc.close(), [rpc]);
|
|
9423
10009
|
const { methods, loading, error, refresh } = useMethods(rpc);
|
|
9424
10010
|
const detail = useMethodDetail(rpc, selected);
|
|
10011
|
+
const metrics = useMetrics(rpc, view === "metrics");
|
|
10012
|
+
const compiledFilter = (0, import_react.useMemo)(() => compileGlobFilter(methodFilter), [methodFilter]);
|
|
10013
|
+
const filteredSnapshot = (0, import_react.useMemo)(() => {
|
|
10014
|
+
if (!metrics.snapshot) return void 0;
|
|
10015
|
+
if (compiledFilter.passthrough) return metrics.snapshot;
|
|
10016
|
+
return {
|
|
10017
|
+
...metrics.snapshot,
|
|
10018
|
+
methods: filterSnapshotMethods(compiledFilter, metrics.snapshot.methods)
|
|
10019
|
+
};
|
|
10020
|
+
}, [metrics.snapshot, compiledFilter]);
|
|
9425
10021
|
const generateDocs = (0, import_react.useCallback)(async () => {
|
|
9426
10022
|
if (methods.length === 0) {
|
|
9427
10023
|
alert("方法列表为空,请先刷新方法列表。");
|
|
@@ -9449,14 +10045,87 @@ function App() {
|
|
|
9449
10045
|
generatingDocs
|
|
9450
10046
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
|
|
9451
10047
|
className: "sidebar",
|
|
9452
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.
|
|
10048
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
10049
|
+
className: "view-switch segmented",
|
|
10050
|
+
role: "group",
|
|
10051
|
+
"aria-label": "视图",
|
|
10052
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
10053
|
+
type: "button",
|
|
10054
|
+
className: view === "methods" ? "active" : "",
|
|
10055
|
+
onClick: () => setView("methods"),
|
|
10056
|
+
children: "Methods"
|
|
10057
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
10058
|
+
type: "button",
|
|
10059
|
+
className: view === "metrics" ? "active" : "",
|
|
10060
|
+
onClick: () => setView("metrics"),
|
|
10061
|
+
children: "Metrics"
|
|
10062
|
+
})]
|
|
10063
|
+
}), view === "methods" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Methods" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodTree, {
|
|
9453
10064
|
methods,
|
|
9454
10065
|
selected,
|
|
9455
10066
|
onSelect: setSelected
|
|
10067
|
+
})] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
10068
|
+
className: "metrics-sidebar",
|
|
10069
|
+
children: [
|
|
10070
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "Metrics" }),
|
|
10071
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
10072
|
+
className: "hint",
|
|
10073
|
+
style: { margin: "0 0 8px" },
|
|
10074
|
+
children: "📊 服务端 RPC 调用统计。"
|
|
10075
|
+
}),
|
|
10076
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
|
|
10077
|
+
className: "metrics-filter",
|
|
10078
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
10079
|
+
className: "metrics-filter-label",
|
|
10080
|
+
children: "Filter"
|
|
10081
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
10082
|
+
className: "sf-input",
|
|
10083
|
+
type: "text",
|
|
10084
|
+
placeholder: "user.*, !system.*",
|
|
10085
|
+
value: methodFilter,
|
|
10086
|
+
onChange: (e) => setMethodFilter(e.target.value),
|
|
10087
|
+
spellCheck: false
|
|
10088
|
+
})]
|
|
10089
|
+
}),
|
|
10090
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
10091
|
+
className: "hint metrics-filter-help",
|
|
10092
|
+
children: [
|
|
10093
|
+
"支持 ",
|
|
10094
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "*" }),
|
|
10095
|
+
" / ",
|
|
10096
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "?" }),
|
|
10097
|
+
" / ",
|
|
10098
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "!" }),
|
|
10099
|
+
",多个模式用 ",
|
|
10100
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "," }),
|
|
10101
|
+
" 分隔。",
|
|
10102
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
|
|
10103
|
+
"例:",
|
|
10104
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "user.*" }),
|
|
10105
|
+
"、",
|
|
10106
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "*.get" }),
|
|
10107
|
+
"、",
|
|
10108
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "!system.*" }),
|
|
10109
|
+
"、",
|
|
10110
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "user.*,!user.secret" })
|
|
10111
|
+
]
|
|
10112
|
+
}),
|
|
10113
|
+
metrics.snapshot && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
10114
|
+
className: "hint metrics-filter-stat",
|
|
10115
|
+
children: [
|
|
10116
|
+
"匹配 ",
|
|
10117
|
+
Object.keys(filteredSnapshot?.methods ?? {}).length,
|
|
10118
|
+
" /",
|
|
10119
|
+
" ",
|
|
10120
|
+
Object.keys(metrics.snapshot.methods).length,
|
|
10121
|
+
" 个方法"
|
|
10122
|
+
]
|
|
10123
|
+
})
|
|
10124
|
+
]
|
|
9456
10125
|
})]
|
|
9457
|
-
}), /* @__PURE__ */ (0, import_jsx_runtime.
|
|
10126
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
9458
10127
|
className: "content",
|
|
9459
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodDetail, {
|
|
10128
|
+
children: view === "methods" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodDetail, {
|
|
9460
10129
|
method: selected,
|
|
9461
10130
|
detail
|
|
9462
10131
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TestPanel, {
|
|
@@ -9465,7 +10134,19 @@ function App() {
|
|
|
9465
10134
|
rpc,
|
|
9466
10135
|
transport,
|
|
9467
10136
|
endpoint: ENDPOINT
|
|
9468
|
-
})]
|
|
10137
|
+
})] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricsPanel, {
|
|
10138
|
+
snapshot: filteredSnapshot,
|
|
10139
|
+
previous: metrics.previous,
|
|
10140
|
+
fetchedAt: metrics.fetchedAt,
|
|
10141
|
+
loading: metrics.loading,
|
|
10142
|
+
error: metrics.error,
|
|
10143
|
+
available: metrics.available,
|
|
10144
|
+
onRefresh: () => void metrics.refresh(),
|
|
10145
|
+
autoRefresh: metrics.autoRefresh,
|
|
10146
|
+
onAutoRefreshChange: metrics.setAutoRefresh,
|
|
10147
|
+
intervalMs: metrics.intervalMs,
|
|
10148
|
+
onIntervalChange: metrics.setIntervalMs
|
|
10149
|
+
})
|
|
9469
10150
|
})] })]
|
|
9470
10151
|
});
|
|
9471
10152
|
}
|
|
@@ -9474,4 +10155,4 @@ function App() {
|
|
|
9474
10155
|
import_client.createRoot(document.getElementById("root")).render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.StrictMode, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThemeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(App, {}) }) }));
|
|
9475
10156
|
//#endregion
|
|
9476
10157
|
|
|
9477
|
-
//# sourceMappingURL=index-
|
|
10158
|
+
//# sourceMappingURL=index-Cr0Jnrgb.js.map
|