@yuhere/js-jsonrpc2 2.1.9 → 2.1.11
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/dist/cli/cli.js +9 -1
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/demo-methods.d.ts.map +1 -1
- package/dist/cli/demo-methods.js +24 -25
- package/dist/cli/demo-methods.js.map +1 -1
- package/dist/core/Repository.d.ts +3 -7
- package/dist/core/Repository.d.ts.map +1 -1
- package/dist/core/Repository.js +159 -19
- package/dist/core/Repository.js.map +1 -1
- package/dist/core/descriptor.d.ts.map +1 -1
- package/dist/core/llm-tools.d.ts.map +1 -1
- package/dist/core/llm-tools.js +19 -14
- package/dist/core/llm-tools.js.map +1 -1
- package/dist/core/system-methods.d.ts.map +1 -1
- package/dist/core/system-methods.js +22 -6
- package/dist/core/system-methods.js.map +1 -1
- package/dist/metrics/memory.d.ts +59 -0
- package/dist/metrics/memory.d.ts.map +1 -0
- package/dist/metrics/memory.js +188 -0
- package/dist/metrics/memory.js.map +1 -0
- package/dist/metrics/prom.d.ts +1 -0
- package/dist/metrics/prom.d.ts.map +1 -0
- package/dist/metrics/prom.js +105 -0
- package/dist/metrics/prom.js.map +1 -0
- package/dist/schema/json/index.d.ts +1 -0
- package/dist/schema/json/index.d.ts.map +1 -1
- package/dist/schema/json/primitives.d.ts +81 -42
- package/dist/schema/json/primitives.d.ts.map +1 -1
- package/dist/schema/json/primitives.js +95 -67
- package/dist/schema/json/primitives.js.map +1 -1
- package/dist/schema/z/protocol.d.ts +1 -1
- package/dist/transport/express/middleware.d.ts +1 -1
- package/dist/transport/express/middleware.d.ts.map +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- 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
|