@yuku123/z-graph-component 0.1.0 → 0.1.2
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/dist/ComponentDoc-yLmdiyDK.js +1334 -0
- package/dist/index.js +8 -7
- package/dist/pages.js +374 -361
- package/dist/z-graph-component.css +1 -1
- package/package.json +4 -3
- package/dist/GraphApp-C6N6khIq.js +0 -1514
|
@@ -0,0 +1,1334 @@
|
|
|
1
|
+
import { useSearchParams as Me, useNavigate as De, Routes as Le, Route as ae, Navigate as He } from "react-router-dom";
|
|
2
|
+
import Ue, { useState as p, useCallback as ne, useEffect as Ce, useMemo as Z, useRef as Be } from "react";
|
|
3
|
+
import { Alert as M, Card as v, Descriptions as be, Space as R, Select as Ge, Button as J, Row as pe, Col as B, Table as ie, Empty as ve, Modal as qe, Tag as f, Input as Te, Statistic as fe, Tooltip as xe } from "antd";
|
|
4
|
+
import { ReloadOutlined as Re, TableOutlined as Fe, NodeIndexOutlined as Je, ProfileOutlined as We, PlayCircleOutlined as Ve, ThunderboltOutlined as Ye, BranchesOutlined as Ae, HistoryOutlined as Ke } from "@ant-design/icons";
|
|
5
|
+
import N from "@yuku123/z-frontend-common";
|
|
6
|
+
import { PageHeader as le, EmptyState as me, ListTable as Pe, DEFAULT_PAGINATION as ze, ComponentDoc as Xe } from "@yuku123/z-frontend-common/ui";
|
|
7
|
+
const Qe = { BASE_URL: "/", DEV: !1, MODE: "production", PROD: !0, SSR: !1 };
|
|
8
|
+
let Se = typeof window < "u" && window.__Z_GRAPH_API__ || Qe && void 0 || "/api";
|
|
9
|
+
function Ze(e, t) {
|
|
10
|
+
return !e || e === "/api" ? "/api" + t : e.replace(/\/$/, "") + t;
|
|
11
|
+
}
|
|
12
|
+
async function he(e, t = {}) {
|
|
13
|
+
const a = Ze(Se, e), l = await fetch(a, {
|
|
14
|
+
headers: { Accept: "application/json", ...t.headers || {} },
|
|
15
|
+
...t
|
|
16
|
+
});
|
|
17
|
+
if (!l.ok) {
|
|
18
|
+
let d = "";
|
|
19
|
+
try {
|
|
20
|
+
d = await l.text();
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
throw new Error(`HTTP ${l.status} ${l.statusText}: ${d || a}`);
|
|
24
|
+
}
|
|
25
|
+
const i = await l.text(), m = l.headers.get("X-Response-Time");
|
|
26
|
+
if (!i) return { data: null, elapsed: m };
|
|
27
|
+
try {
|
|
28
|
+
return { data: JSON.parse(i), elapsed: m };
|
|
29
|
+
} catch {
|
|
30
|
+
return { data: i, elapsed: m };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function V(e, t = {}) {
|
|
34
|
+
return (await he(e, t)).data;
|
|
35
|
+
}
|
|
36
|
+
const en = {
|
|
37
|
+
baseUrl: () => Se,
|
|
38
|
+
setBaseUrl: (e) => {
|
|
39
|
+
Se = e, typeof window < "u" && (window.__Z_GRAPH_API__ = e);
|
|
40
|
+
},
|
|
41
|
+
// 健康检查
|
|
42
|
+
health: () => V("/health"),
|
|
43
|
+
// 元数据
|
|
44
|
+
branches: () => V("/meta/branches"),
|
|
45
|
+
commits: () => V("/meta/commits"),
|
|
46
|
+
schema: (e = "main") => V("/meta/schema?branch=" + encodeURIComponent(e)),
|
|
47
|
+
stats: (e = "main") => V("/meta/stats?branch=" + encodeURIComponent(e)),
|
|
48
|
+
metrics: () => V("/meta/metrics"),
|
|
49
|
+
// 请求日志
|
|
50
|
+
logs: (e = {}) => {
|
|
51
|
+
const t = new URLSearchParams();
|
|
52
|
+
return e.limit && t.set("limit", e.limit), e.offset && t.set("offset", e.offset), e.method && t.set("method", e.method), e.status && t.set("status", e.status), e.path && t.set("path", e.path), V("/meta/logs" + (t.toString() ? "?" + t.toString() : ""));
|
|
53
|
+
},
|
|
54
|
+
// 导出 / 导入
|
|
55
|
+
exportData: (e = "main") => he("/meta/export?branch=" + encodeURIComponent(e)),
|
|
56
|
+
// 批量查询
|
|
57
|
+
batch: async (e) => he("/query/batch", {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
body: JSON.stringify({ statements: e })
|
|
61
|
+
}),
|
|
62
|
+
// 查询 — 直接返回数据数组(后端 /query 响应本身就是数组)
|
|
63
|
+
// 注意:后端会把 commit 字段当成字符串处理,"null" 字符串无法区分真正的 null,
|
|
64
|
+
// 所以 commit 为空时直接不发送该字段,避免触发"Unknown commit: null" 错误。
|
|
65
|
+
query: (e, t = "main", a = null) => he("/query", {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "Content-Type": "application/json" },
|
|
68
|
+
body: JSON.stringify(a ? { cypher: e, branch: t, commit: a } : { cypher: e, branch: t })
|
|
69
|
+
}).then((i) => i.data)
|
|
70
|
+
};
|
|
71
|
+
function kn(e) {
|
|
72
|
+
if (!e) return "";
|
|
73
|
+
try {
|
|
74
|
+
return new Date(Number(e)).toLocaleString();
|
|
75
|
+
} catch {
|
|
76
|
+
return String(e);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function vn(e, t = 8) {
|
|
80
|
+
return e ? e.length > t ? e.substring(0, t) : e : "";
|
|
81
|
+
}
|
|
82
|
+
const G = {
|
|
83
|
+
instance: () => N.get("/__instance").then((e) => e.data),
|
|
84
|
+
health: () => N.get("/health").then((e) => e.data),
|
|
85
|
+
branches: () => N.get("/meta/branches").then((e) => e.data),
|
|
86
|
+
commits: () => N.get("/meta/commits").then((e) => e.data),
|
|
87
|
+
schema: (e) => N.get("/meta/schema", { params: e ? { branch: e } : {} }).then((t) => t.data),
|
|
88
|
+
stats: (e) => N.get("/meta/stats", { params: e ? { branch: e } : {} }).then((t) => t.data),
|
|
89
|
+
metrics: () => N.get("/meta/metrics").then((e) => e.data),
|
|
90
|
+
logs: (e) => N.get("/meta/logs", { params: e }).then((t) => t.data),
|
|
91
|
+
/**
|
|
92
|
+
* 查询走 GET 而不是 POST:`handleQuery` 的 POST 分支用的是手搓的
|
|
93
|
+
* `parseJsonStringMap`(按引号外逗号切 token、再按第一个冒号切 kv),
|
|
94
|
+
* 而 GET 分支用的是 `URLDecoder.decode` —— 对含 `:`、`,`、`{}` 的 Cypher
|
|
95
|
+
* 只有前者会被解析器啃掉。所以这里统一走 GET。
|
|
96
|
+
*/
|
|
97
|
+
query: (e, t, a) => N.get("/query", { params: a ? { cypher: e, branch: t, commit: a } : { cypher: e, branch: t } }).then((l) => l.data),
|
|
98
|
+
explain: (e, t) => N.post("/query/explain", { cypher: e, branch: t }).then((a) => a.data)
|
|
99
|
+
};
|
|
100
|
+
function An(e = "/api") {
|
|
101
|
+
N.defaults.baseURL = e, en.setBaseUrl(e);
|
|
102
|
+
}
|
|
103
|
+
const nn = /^\s*(CREATE|MERGE|DELETE|DROP|ALTER|SET|REMOVE|REPLACE)\b/i;
|
|
104
|
+
function rn(e) {
|
|
105
|
+
if (!e) return !1;
|
|
106
|
+
const t = e.toUpperCase();
|
|
107
|
+
return !!(nn.test(e) || t.startsWith("MATCH") && (t.includes(" SET ") || t.includes(" DELETE ") || t.includes("DETACH DELETE")));
|
|
108
|
+
}
|
|
109
|
+
function re(e) {
|
|
110
|
+
var a;
|
|
111
|
+
const t = (a = e == null ? void 0 : e.response) == null ? void 0 : a.data;
|
|
112
|
+
if (t && typeof t == "object") {
|
|
113
|
+
const l = t.message || t.error, i = t.source ? ` (${t.source})` : "";
|
|
114
|
+
if (l) return `${l}${i}${t.hint ? ` — ${t.hint}` : ""}`;
|
|
115
|
+
}
|
|
116
|
+
return typeof t == "string" && t.trim() ? t.slice(0, 300) : (e == null ? void 0 : e.message) || String(e);
|
|
117
|
+
}
|
|
118
|
+
function we(e) {
|
|
119
|
+
var t, a, l;
|
|
120
|
+
return ((t = e == null ? void 0 : e.response) == null ? void 0 : t.status) === 503 || /not bound in this JVM/.test(String(((l = (a = e == null ? void 0 : e.response) == null ? void 0 : a.data) == null ? void 0 : l.message) || ""));
|
|
121
|
+
}
|
|
122
|
+
const ee = "main";
|
|
123
|
+
var ue = { exports: {} }, se = {};
|
|
124
|
+
/**
|
|
125
|
+
* @license React
|
|
126
|
+
* react-jsx-runtime.production.js
|
|
127
|
+
*
|
|
128
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
129
|
+
*
|
|
130
|
+
* This source code is licensed under the MIT license found in the
|
|
131
|
+
* LICENSE file in the root directory of this source tree.
|
|
132
|
+
*/
|
|
133
|
+
var _e;
|
|
134
|
+
function tn() {
|
|
135
|
+
if (_e) return se;
|
|
136
|
+
_e = 1;
|
|
137
|
+
var e = Symbol.for("react.transitional.element"), t = Symbol.for("react.fragment");
|
|
138
|
+
function a(l, i, m) {
|
|
139
|
+
var d = null;
|
|
140
|
+
if (m !== void 0 && (d = "" + m), i.key !== void 0 && (d = "" + i.key), "key" in i) {
|
|
141
|
+
m = {};
|
|
142
|
+
for (var h in i)
|
|
143
|
+
h !== "key" && (m[h] = i[h]);
|
|
144
|
+
} else m = i;
|
|
145
|
+
return i = m.ref, {
|
|
146
|
+
$$typeof: e,
|
|
147
|
+
type: l,
|
|
148
|
+
key: d,
|
|
149
|
+
ref: i !== void 0 ? i : null,
|
|
150
|
+
props: m
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return se.Fragment = t, se.jsx = a, se.jsxs = a, se;
|
|
154
|
+
}
|
|
155
|
+
var oe = {};
|
|
156
|
+
/**
|
|
157
|
+
* @license React
|
|
158
|
+
* react-jsx-runtime.development.js
|
|
159
|
+
*
|
|
160
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
161
|
+
*
|
|
162
|
+
* This source code is licensed under the MIT license found in the
|
|
163
|
+
* LICENSE file in the root directory of this source tree.
|
|
164
|
+
*/
|
|
165
|
+
var Ie;
|
|
166
|
+
function an() {
|
|
167
|
+
return Ie || (Ie = 1, process.env.NODE_ENV !== "production" && (function() {
|
|
168
|
+
function e(r) {
|
|
169
|
+
if (r == null) return null;
|
|
170
|
+
if (typeof r == "function")
|
|
171
|
+
return r.$$typeof === X ? null : r.displayName || r.name || null;
|
|
172
|
+
if (typeof r == "string") return r;
|
|
173
|
+
switch (r) {
|
|
174
|
+
case o:
|
|
175
|
+
return "Fragment";
|
|
176
|
+
case z:
|
|
177
|
+
return "Profiler";
|
|
178
|
+
case C:
|
|
179
|
+
return "StrictMode";
|
|
180
|
+
case O:
|
|
181
|
+
return "Suspense";
|
|
182
|
+
case Y:
|
|
183
|
+
return "SuspenseList";
|
|
184
|
+
case w:
|
|
185
|
+
return "Activity";
|
|
186
|
+
case I:
|
|
187
|
+
return "ViewTransition";
|
|
188
|
+
}
|
|
189
|
+
if (typeof r == "object")
|
|
190
|
+
switch (typeof r.tag == "number" && console.error(
|
|
191
|
+
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
|
|
192
|
+
), r.$$typeof) {
|
|
193
|
+
case s:
|
|
194
|
+
return "Portal";
|
|
195
|
+
case _:
|
|
196
|
+
return r.displayName || "Context";
|
|
197
|
+
case D:
|
|
198
|
+
return (r._context.displayName || "Context") + ".Consumer";
|
|
199
|
+
case W:
|
|
200
|
+
var c = r.render;
|
|
201
|
+
return r = r.displayName, r || (r = c.displayName || c.name || "", r = r !== "" ? "ForwardRef(" + r + ")" : "ForwardRef"), r;
|
|
202
|
+
case K:
|
|
203
|
+
return c = r.displayName || null, c !== null ? c : e(r.type) || "Memo";
|
|
204
|
+
case g:
|
|
205
|
+
c = r._payload, r = r._init;
|
|
206
|
+
try {
|
|
207
|
+
return e(r(c));
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
function t(r) {
|
|
214
|
+
return "" + r;
|
|
215
|
+
}
|
|
216
|
+
function a(r) {
|
|
217
|
+
try {
|
|
218
|
+
t(r);
|
|
219
|
+
var c = !1;
|
|
220
|
+
} catch {
|
|
221
|
+
c = !0;
|
|
222
|
+
}
|
|
223
|
+
if (c) {
|
|
224
|
+
c = console;
|
|
225
|
+
var j = c.error, b = typeof Symbol == "function" && Symbol.toStringTag && r[Symbol.toStringTag] || r.constructor.name || "Object";
|
|
226
|
+
return j.call(
|
|
227
|
+
c,
|
|
228
|
+
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
|
|
229
|
+
b
|
|
230
|
+
), t(r);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function l(r) {
|
|
234
|
+
if (r === o) return "<>";
|
|
235
|
+
if (typeof r == "object" && r !== null && r.$$typeof === g)
|
|
236
|
+
return "<...>";
|
|
237
|
+
try {
|
|
238
|
+
var c = e(r);
|
|
239
|
+
return c ? "<" + c + ">" : "<...>";
|
|
240
|
+
} catch {
|
|
241
|
+
return "<...>";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function i() {
|
|
245
|
+
var r = $.A;
|
|
246
|
+
return r === null ? null : r.getOwner();
|
|
247
|
+
}
|
|
248
|
+
function m() {
|
|
249
|
+
return Error("react-stack-top-frame");
|
|
250
|
+
}
|
|
251
|
+
function d(r) {
|
|
252
|
+
if (te.call(r, "key")) {
|
|
253
|
+
var c = Object.getOwnPropertyDescriptor(r, "key").get;
|
|
254
|
+
if (c && c.isReactWarning) return !1;
|
|
255
|
+
}
|
|
256
|
+
return r.key !== void 0;
|
|
257
|
+
}
|
|
258
|
+
function h(r, c) {
|
|
259
|
+
function j() {
|
|
260
|
+
L || (L = !0, console.error(
|
|
261
|
+
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
|
|
262
|
+
c
|
|
263
|
+
));
|
|
264
|
+
}
|
|
265
|
+
j.isReactWarning = !0, Object.defineProperty(r, "key", {
|
|
266
|
+
get: j,
|
|
267
|
+
configurable: !0
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function E() {
|
|
271
|
+
var r = e(this.type);
|
|
272
|
+
return H[r] || (H[r] = !0, console.error(
|
|
273
|
+
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
|
|
274
|
+
)), r = this.props.ref, r !== void 0 ? r : null;
|
|
275
|
+
}
|
|
276
|
+
function y(r, c, j, b, F, U) {
|
|
277
|
+
var T = j.ref;
|
|
278
|
+
return r = {
|
|
279
|
+
$$typeof: A,
|
|
280
|
+
type: r,
|
|
281
|
+
key: c,
|
|
282
|
+
props: j,
|
|
283
|
+
_owner: b
|
|
284
|
+
}, (T !== void 0 ? T : null) !== null ? Object.defineProperty(r, "ref", {
|
|
285
|
+
enumerable: !1,
|
|
286
|
+
get: E
|
|
287
|
+
}) : Object.defineProperty(r, "ref", { enumerable: !1, value: null }), r._store = {}, Object.defineProperty(r._store, "validated", {
|
|
288
|
+
configurable: !1,
|
|
289
|
+
enumerable: !1,
|
|
290
|
+
writable: !0,
|
|
291
|
+
value: 0
|
|
292
|
+
}), Object.defineProperty(r, "_debugInfo", {
|
|
293
|
+
configurable: !1,
|
|
294
|
+
enumerable: !1,
|
|
295
|
+
writable: !0,
|
|
296
|
+
value: null
|
|
297
|
+
}), Object.defineProperty(r, "_debugStack", {
|
|
298
|
+
configurable: !1,
|
|
299
|
+
enumerable: !1,
|
|
300
|
+
writable: !0,
|
|
301
|
+
value: F
|
|
302
|
+
}), Object.defineProperty(r, "_debugTask", {
|
|
303
|
+
configurable: !1,
|
|
304
|
+
enumerable: !1,
|
|
305
|
+
writable: !0,
|
|
306
|
+
value: U
|
|
307
|
+
}), Object.freeze && (Object.freeze(r.props), Object.freeze(r)), r;
|
|
308
|
+
}
|
|
309
|
+
function S(r, c, j, b, F, U) {
|
|
310
|
+
var T = c.children;
|
|
311
|
+
if (T !== void 0)
|
|
312
|
+
if (b)
|
|
313
|
+
if (ce(T)) {
|
|
314
|
+
for (b = 0; b < T.length; b++)
|
|
315
|
+
P(T[b]);
|
|
316
|
+
Object.freeze && Object.freeze(T);
|
|
317
|
+
} else
|
|
318
|
+
console.error(
|
|
319
|
+
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
|
|
320
|
+
);
|
|
321
|
+
else P(T);
|
|
322
|
+
if (te.call(c, "key")) {
|
|
323
|
+
T = e(r);
|
|
324
|
+
var Q = Object.keys(c).filter(function($e) {
|
|
325
|
+
return $e !== "key";
|
|
326
|
+
});
|
|
327
|
+
b = 0 < Q.length ? "{key: someKey, " + Q.join(": ..., ") + ": ...}" : "{key: someKey}", ke[T + b] || (Q = 0 < Q.length ? "{" + Q.join(": ..., ") + ": ...}" : "{}", console.error(
|
|
328
|
+
`A props object containing a "key" prop is being spread into JSX:
|
|
329
|
+
let props = %s;
|
|
330
|
+
<%s {...props} />
|
|
331
|
+
React keys must be passed directly to JSX without using spread:
|
|
332
|
+
let props = %s;
|
|
333
|
+
<%s key={someKey} {...props} />`,
|
|
334
|
+
b,
|
|
335
|
+
T,
|
|
336
|
+
Q,
|
|
337
|
+
T
|
|
338
|
+
), ke[T + b] = !0);
|
|
339
|
+
}
|
|
340
|
+
if (T = null, j !== void 0 && (a(j), T = "" + j), d(c) && (a(c.key), T = "" + c.key), "key" in c) {
|
|
341
|
+
j = {};
|
|
342
|
+
for (var ge in c)
|
|
343
|
+
ge !== "key" && (j[ge] = c[ge]);
|
|
344
|
+
} else j = c;
|
|
345
|
+
return T && h(
|
|
346
|
+
j,
|
|
347
|
+
typeof r == "function" ? r.displayName || r.name || "Unknown" : r
|
|
348
|
+
), y(
|
|
349
|
+
r,
|
|
350
|
+
T,
|
|
351
|
+
j,
|
|
352
|
+
i(),
|
|
353
|
+
F,
|
|
354
|
+
U
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
function P(r) {
|
|
358
|
+
x(r) ? r._store && (r._store.validated = 1) : typeof r == "object" && r !== null && r.$$typeof === g && (r._payload.status === "fulfilled" ? x(r._payload.value) && r._payload.value._store && (r._payload.value._store.validated = 1) : r._store && (r._store.validated = 1));
|
|
359
|
+
}
|
|
360
|
+
function x(r) {
|
|
361
|
+
return typeof r == "object" && r !== null && r.$$typeof === A;
|
|
362
|
+
}
|
|
363
|
+
var k = Ue, A = Symbol.for("react.transitional.element"), s = Symbol.for("react.portal"), o = Symbol.for("react.fragment"), C = Symbol.for("react.strict_mode"), z = Symbol.for("react.profiler"), D = Symbol.for("react.consumer"), _ = Symbol.for("react.context"), W = Symbol.for("react.forward_ref"), O = Symbol.for("react.suspense"), Y = Symbol.for("react.suspense_list"), K = Symbol.for("react.memo"), g = Symbol.for("react.lazy"), w = Symbol.for("react.activity"), I = Symbol.for("react.view_transition"), X = Symbol.for("react.client.reference"), $ = k.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, te = Object.prototype.hasOwnProperty, ce = Array.isArray, u = console.createTask ? console.createTask : function() {
|
|
364
|
+
return null;
|
|
365
|
+
};
|
|
366
|
+
k = {
|
|
367
|
+
react_stack_bottom_frame: function(r) {
|
|
368
|
+
return r();
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
var L, H = {}, q = k.react_stack_bottom_frame.bind(
|
|
372
|
+
k,
|
|
373
|
+
m
|
|
374
|
+
)(), de = u(l(m)), ke = {};
|
|
375
|
+
oe.Fragment = o, oe.jsx = function(r, c, j) {
|
|
376
|
+
var b = 1e4 > $.recentlyCreatedOwnerStacks++;
|
|
377
|
+
if (b) {
|
|
378
|
+
var F = Error.stackTraceLimit;
|
|
379
|
+
Error.stackTraceLimit = 10;
|
|
380
|
+
var U = Error("react-stack-top-frame");
|
|
381
|
+
Error.stackTraceLimit = F;
|
|
382
|
+
} else U = q;
|
|
383
|
+
return S(
|
|
384
|
+
r,
|
|
385
|
+
c,
|
|
386
|
+
j,
|
|
387
|
+
!1,
|
|
388
|
+
U,
|
|
389
|
+
b ? u(l(r)) : de
|
|
390
|
+
);
|
|
391
|
+
}, oe.jsxs = function(r, c, j) {
|
|
392
|
+
var b = 1e4 > $.recentlyCreatedOwnerStacks++;
|
|
393
|
+
if (b) {
|
|
394
|
+
var F = Error.stackTraceLimit;
|
|
395
|
+
Error.stackTraceLimit = 10;
|
|
396
|
+
var U = Error("react-stack-top-frame");
|
|
397
|
+
Error.stackTraceLimit = F;
|
|
398
|
+
} else U = q;
|
|
399
|
+
return S(
|
|
400
|
+
r,
|
|
401
|
+
c,
|
|
402
|
+
j,
|
|
403
|
+
!0,
|
|
404
|
+
U,
|
|
405
|
+
b ? u(l(r)) : de
|
|
406
|
+
);
|
|
407
|
+
};
|
|
408
|
+
})()), oe;
|
|
409
|
+
}
|
|
410
|
+
var Oe;
|
|
411
|
+
function sn() {
|
|
412
|
+
return Oe || (Oe = 1, process.env.NODE_ENV === "production" ? ue.exports = tn() : ue.exports = an()), ue.exports;
|
|
413
|
+
}
|
|
414
|
+
var n = sn();
|
|
415
|
+
const on = [
|
|
416
|
+
{ title: "点类型 (Tag)", dataIndex: "Name", key: "Name", render: (e) => /* @__PURE__ */ n.jsx(f, { color: "blue", children: e }) },
|
|
417
|
+
{
|
|
418
|
+
title: "操作",
|
|
419
|
+
key: "action",
|
|
420
|
+
width: 110,
|
|
421
|
+
render: (e, t) => /* @__PURE__ */ n.jsx(J, { type: "link", size: "small", onClick: () => t.onFields(t.Name), children: "字段" })
|
|
422
|
+
}
|
|
423
|
+
], ln = [
|
|
424
|
+
{ title: "边类型 (EdgeType)", dataIndex: "Name", key: "Name", render: (e) => /* @__PURE__ */ n.jsx(f, { color: "purple", children: e }) },
|
|
425
|
+
{
|
|
426
|
+
title: "操作",
|
|
427
|
+
key: "action",
|
|
428
|
+
width: 110,
|
|
429
|
+
render: (e, t) => /* @__PURE__ */ n.jsx(J, { type: "link", size: "small", onClick: () => t.onFields(t.Name), children: "字段" })
|
|
430
|
+
}
|
|
431
|
+
], cn = [
|
|
432
|
+
{ title: "索引名", dataIndex: "Name", key: "Name", render: (e) => /* @__PURE__ */ n.jsx("code", { children: e }) },
|
|
433
|
+
{
|
|
434
|
+
title: "作用对象类型",
|
|
435
|
+
dataIndex: "Kind",
|
|
436
|
+
key: "Kind",
|
|
437
|
+
width: 130,
|
|
438
|
+
render: (e) => e ? /* @__PURE__ */ n.jsx(f, { color: e === "EDGE" ? "purple" : "blue", children: e }) : "-"
|
|
439
|
+
},
|
|
440
|
+
{ title: "挂在", dataIndex: "On", key: "On", width: 160 },
|
|
441
|
+
{ title: "属性", dataIndex: "Property", key: "Property", width: 160 }
|
|
442
|
+
], dn = [
|
|
443
|
+
{ title: "字段", dataIndex: "Field", key: "Field" },
|
|
444
|
+
{ title: "类型", dataIndex: "Type", key: "Type", width: 140, render: (e) => /* @__PURE__ */ n.jsx(f, { children: e }) },
|
|
445
|
+
{
|
|
446
|
+
title: "可空",
|
|
447
|
+
dataIndex: "Null",
|
|
448
|
+
key: "Null",
|
|
449
|
+
width: 100,
|
|
450
|
+
render: (e) => /* @__PURE__ */ n.jsx(f, { color: e === "NO" ? "warning" : "default", children: e })
|
|
451
|
+
}
|
|
452
|
+
];
|
|
453
|
+
function un() {
|
|
454
|
+
const [e, t] = p([ee]), [a, l] = p(ee), [i, m] = p(null), [d, h] = p(null), [E, y] = p(!1), [S, P] = p(null), [x, k] = p(null), [A, s] = p(null), [o, C] = p(!1), z = ne(async (g) => {
|
|
455
|
+
y(!0);
|
|
456
|
+
try {
|
|
457
|
+
const w = g ?? a;
|
|
458
|
+
G.branches().then(($) => {
|
|
459
|
+
Array.isArray($) && $.length > 0 && t($);
|
|
460
|
+
}).catch(() => {
|
|
461
|
+
});
|
|
462
|
+
const [I, X] = await Promise.all([
|
|
463
|
+
G.schema(w),
|
|
464
|
+
G.stats(w).catch(() => null)
|
|
465
|
+
]);
|
|
466
|
+
m(I), h(X), P(null);
|
|
467
|
+
} catch (w) {
|
|
468
|
+
m(null), h(null), P(w);
|
|
469
|
+
} finally {
|
|
470
|
+
y(!1);
|
|
471
|
+
}
|
|
472
|
+
}, [a]);
|
|
473
|
+
Ce(() => {
|
|
474
|
+
z(ee);
|
|
475
|
+
}, []);
|
|
476
|
+
const D = ne(async (g, w) => {
|
|
477
|
+
k({ kind: g, name: w, rows: null }), s(null), C(!0);
|
|
478
|
+
try {
|
|
479
|
+
const I = await G.query(`SHOW ${g} ${w}`, a);
|
|
480
|
+
if (!Array.isArray(I))
|
|
481
|
+
throw new Error(`SHOW ${g} 返回的不是行数组:${JSON.stringify(I)}`);
|
|
482
|
+
k({ kind: g, name: w, rows: I });
|
|
483
|
+
} catch (I) {
|
|
484
|
+
s(I);
|
|
485
|
+
} finally {
|
|
486
|
+
C(!1);
|
|
487
|
+
}
|
|
488
|
+
}, [a]), _ = Z(() => {
|
|
489
|
+
const g = d == null ? void 0 : d.stats;
|
|
490
|
+
return Array.isArray(g) && g.length > 0 ? g[0] : null;
|
|
491
|
+
}, [d]), W = we(S), O = ((i == null ? void 0 : i.tags) ?? []).map((g) => ({ ...g, onFields: (w) => D("TAG", w) })), Y = ((i == null ? void 0 : i.edges) ?? []).map((g) => ({ ...g, onFields: (w) => D("EDGE", w) })), K = (i == null ? void 0 : i.indexes) ?? [];
|
|
492
|
+
return /* @__PURE__ */ n.jsxs("div", { children: [
|
|
493
|
+
/* @__PURE__ */ n.jsx(
|
|
494
|
+
le,
|
|
495
|
+
{
|
|
496
|
+
title: "图 Schema",
|
|
497
|
+
subtitle: "z-graph 点类型 / 边类型 / 属性索引(数据源 GET /api/graph/meta/schema,由 GraphProxyController 转发到本 JVM 内嵌的 GraphControlServer)"
|
|
498
|
+
}
|
|
499
|
+
),
|
|
500
|
+
S && /* @__PURE__ */ n.jsx(
|
|
501
|
+
M,
|
|
502
|
+
{
|
|
503
|
+
style: { marginBottom: 16 },
|
|
504
|
+
type: W ? "error" : "warning",
|
|
505
|
+
showIcon: !0,
|
|
506
|
+
message: `Schema 读取失败:${re(S)}`,
|
|
507
|
+
description: W ? "503 = 本 JVM 的内嵌 GraphControlServer 没 bind 成功(不是「图里没有 schema」)。先去「实例与端口」页看 boundPort 与 foreignListenerSuspected。" : "上游 GraphControlServer 的 handleSchema 内部会跑 SHOW TAGS/EDGES/INDEXES 三次查询,任一抛错就整体 500,错误原文在上面的 message 里。"
|
|
508
|
+
}
|
|
509
|
+
),
|
|
510
|
+
/* @__PURE__ */ n.jsx(
|
|
511
|
+
v,
|
|
512
|
+
{
|
|
513
|
+
loading: E,
|
|
514
|
+
title: "当前分支概览",
|
|
515
|
+
extra: /* @__PURE__ */ n.jsxs(R, { children: [
|
|
516
|
+
/* @__PURE__ */ n.jsx("span", { style: { fontSize: 12, color: "#64748b" }, children: "分支" }),
|
|
517
|
+
/* @__PURE__ */ n.jsx(
|
|
518
|
+
Ge,
|
|
519
|
+
{
|
|
520
|
+
size: "small",
|
|
521
|
+
style: { width: 160 },
|
|
522
|
+
value: a,
|
|
523
|
+
options: e.map((g) => ({ value: g, label: g })),
|
|
524
|
+
onChange: (g) => {
|
|
525
|
+
l(g), z(g);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
),
|
|
529
|
+
/* @__PURE__ */ n.jsx(J, { icon: /* @__PURE__ */ n.jsx(Re, {}), loading: E, onClick: () => z(), children: "刷新" })
|
|
530
|
+
] }),
|
|
531
|
+
children: /* @__PURE__ */ n.jsx(
|
|
532
|
+
be,
|
|
533
|
+
{
|
|
534
|
+
bordered: !0,
|
|
535
|
+
size: "small",
|
|
536
|
+
column: 4,
|
|
537
|
+
items: [
|
|
538
|
+
{ key: "branch", label: "分支", children: (i == null ? void 0 : i.branch) ?? "-" },
|
|
539
|
+
{
|
|
540
|
+
key: "head",
|
|
541
|
+
label: "head commit",
|
|
542
|
+
children: i && (d != null && d.head) ? /* @__PURE__ */ n.jsx("a", { title: d.head, children: /* @__PURE__ */ n.jsxs("code", { children: [
|
|
543
|
+
d.head.slice(0, 12),
|
|
544
|
+
"…"
|
|
545
|
+
] }) }) : "-"
|
|
546
|
+
},
|
|
547
|
+
{ key: "nodes", label: "点数", children: (d == null ? void 0 : d.nodeCount) ?? "-" },
|
|
548
|
+
{ key: "edges", label: "边数", children: (d == null ? void 0 : d.edgeCount) ?? "-" },
|
|
549
|
+
{ key: "tags", label: "点类型数", children: i ? O.length : "-" },
|
|
550
|
+
{ key: "edgeTypes", label: "边类型数", children: i ? Y.length : "-" },
|
|
551
|
+
{ key: "indexes", label: "索引数", children: i ? K.length : "-" },
|
|
552
|
+
{
|
|
553
|
+
key: "labelCount",
|
|
554
|
+
label: "引擎自报统计 (/meta/stats)",
|
|
555
|
+
children: _ ? /* @__PURE__ */ n.jsx("span", { title: JSON.stringify(_), children: `标签 ${_.labelCount} / 边类型 ${_.edgeTypeCount} / 属性索引 ${_.propertyIndexCount}` }) : "-"
|
|
556
|
+
}
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
)
|
|
560
|
+
}
|
|
561
|
+
),
|
|
562
|
+
/* @__PURE__ */ n.jsxs(pe, { gutter: 16, style: { marginTop: 16 }, children: [
|
|
563
|
+
/* @__PURE__ */ n.jsx(B, { span: 12, children: /* @__PURE__ */ n.jsx(v, { title: /* @__PURE__ */ n.jsxs(R, { children: [
|
|
564
|
+
/* @__PURE__ */ n.jsx(Fe, {}),
|
|
565
|
+
"点类型 (TAG)"
|
|
566
|
+
] }), loading: E, children: /* @__PURE__ */ n.jsx(
|
|
567
|
+
ie,
|
|
568
|
+
{
|
|
569
|
+
size: "small",
|
|
570
|
+
rowKey: "Name",
|
|
571
|
+
dataSource: O,
|
|
572
|
+
columns: on,
|
|
573
|
+
pagination: !1,
|
|
574
|
+
loading: E,
|
|
575
|
+
locale: { emptyText: ye(!!i, "没有点类型") }
|
|
576
|
+
}
|
|
577
|
+
) }) }),
|
|
578
|
+
/* @__PURE__ */ n.jsx(B, { span: 12, children: /* @__PURE__ */ n.jsx(v, { title: /* @__PURE__ */ n.jsxs(R, { children: [
|
|
579
|
+
/* @__PURE__ */ n.jsx(Je, {}),
|
|
580
|
+
"边类型 (EDGE TYPE)"
|
|
581
|
+
] }), loading: E, children: /* @__PURE__ */ n.jsx(
|
|
582
|
+
ie,
|
|
583
|
+
{
|
|
584
|
+
size: "small",
|
|
585
|
+
rowKey: "Name",
|
|
586
|
+
dataSource: Y,
|
|
587
|
+
columns: ln,
|
|
588
|
+
pagination: !1,
|
|
589
|
+
loading: E,
|
|
590
|
+
locale: { emptyText: ye(!!i, "没有边类型") }
|
|
591
|
+
}
|
|
592
|
+
) }) })
|
|
593
|
+
] }),
|
|
594
|
+
/* @__PURE__ */ n.jsx(v, { style: { marginTop: 16 }, title: /* @__PURE__ */ n.jsxs(R, { children: [
|
|
595
|
+
/* @__PURE__ */ n.jsx(We, {}),
|
|
596
|
+
"属性索引"
|
|
597
|
+
] }), loading: E, children: /* @__PURE__ */ n.jsx(
|
|
598
|
+
ie,
|
|
599
|
+
{
|
|
600
|
+
size: "small",
|
|
601
|
+
rowKey: "Name",
|
|
602
|
+
dataSource: K,
|
|
603
|
+
columns: cn,
|
|
604
|
+
pagination: !1,
|
|
605
|
+
loading: E,
|
|
606
|
+
locale: { emptyText: ye(!!i, "没有属性索引") }
|
|
607
|
+
}
|
|
608
|
+
) }),
|
|
609
|
+
/* @__PURE__ */ n.jsx(v, { style: { marginTop: 16 }, title: "这页为什么可以什么都没有", children: /* @__PURE__ */ n.jsx(ve, { description: /* @__PURE__ */ n.jsxs("span", { style: { fontSize: 12, color: "#64748b", lineHeight: 1.9 }, children: [
|
|
610
|
+
"z-graph 的 schema 由 Cypher 的 DDL 建立,",
|
|
611
|
+
/* @__PURE__ */ n.jsx("b", { children: "没有独立的建表接口" }),
|
|
612
|
+
":",
|
|
613
|
+
/* @__PURE__ */ n.jsx("code", { children: " CREATE TAG name (f TYPE, …)" }),
|
|
614
|
+
" ·",
|
|
615
|
+
/* @__PURE__ */ n.jsx("code", { children: " CREATE EDGE TYPE name (f TYPE, …)" }),
|
|
616
|
+
" ·",
|
|
617
|
+
/* @__PURE__ */ n.jsx("code", { children: " CREATE INDEX ON tag(prop)" }),
|
|
618
|
+
"; 删除对应 ",
|
|
619
|
+
/* @__PURE__ */ n.jsx("code", { children: "DROP TAG / DROP EDGE TYPE / DROP INDEX" }),
|
|
620
|
+
"(取值与语法以 CypherEngine 的实现为准)。",
|
|
621
|
+
/* @__PURE__ */ n.jsx("br", {}),
|
|
622
|
+
"这些语句会",
|
|
623
|
+
/* @__PURE__ */ n.jsx("b", { children: "真的改图" }),
|
|
624
|
+
"(每个写语句在 GraphQueryService 上开一个事务并提交一个新 commit), 所以本页不提供执行入口 —— 要建 schema 请去「图查询」页,那里对写语句有二次确认。"
|
|
625
|
+
] }) }) }),
|
|
626
|
+
/* @__PURE__ */ n.jsx(
|
|
627
|
+
qe,
|
|
628
|
+
{
|
|
629
|
+
open: !!x,
|
|
630
|
+
width: 640,
|
|
631
|
+
title: x ? `字段明细 · SHOW ${x.kind} ${x.name}` : "",
|
|
632
|
+
footer: null,
|
|
633
|
+
onCancel: () => k(null),
|
|
634
|
+
children: A ? /* @__PURE__ */ n.jsx(M, { type: "error", showIcon: !0, message: `读取失败:${re(A)}` }) : /* @__PURE__ */ n.jsx(
|
|
635
|
+
ie,
|
|
636
|
+
{
|
|
637
|
+
size: "small",
|
|
638
|
+
rowKey: "Field",
|
|
639
|
+
loading: o,
|
|
640
|
+
dataSource: (x == null ? void 0 : x.rows) ?? [],
|
|
641
|
+
columns: dn,
|
|
642
|
+
pagination: !1,
|
|
643
|
+
locale: { emptyText: /* @__PURE__ */ n.jsx(ve, { description: "该类型没有字段(schema 里有名字但属性列表为空)" }) }
|
|
644
|
+
}
|
|
645
|
+
)
|
|
646
|
+
}
|
|
647
|
+
)
|
|
648
|
+
] });
|
|
649
|
+
}
|
|
650
|
+
function ye(e, t) {
|
|
651
|
+
return e ? /* @__PURE__ */ n.jsx(
|
|
652
|
+
me,
|
|
653
|
+
{
|
|
654
|
+
title: `${t}`,
|
|
655
|
+
description: "GET /api/graph/meta/schema 是通的、这一类确实是空数组。z-graph 的内存图刚启动时只有 Initial graph 一个提交,schema 需要显式建。"
|
|
656
|
+
}
|
|
657
|
+
) : /* @__PURE__ */ n.jsx(
|
|
658
|
+
me,
|
|
659
|
+
{
|
|
660
|
+
title: "没有拿到 schema 数据",
|
|
661
|
+
description: "schema 请求本身失败了,见页面顶部的错误条;这里不是「空」,是「没读通」。"
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
const { TextArea: hn } = Te, pn = [
|
|
666
|
+
{ label: "全量取样:MATCH (n) RETURN n LIMIT 20", value: "MATCH (n) RETURN n LIMIT 20" },
|
|
667
|
+
{ label: "Schema:SHOW TAGS", value: "SHOW TAGS" },
|
|
668
|
+
{ label: "Schema:SHOW EDGES", value: "SHOW EDGES" },
|
|
669
|
+
{ label: "Schema:SHOW INDEXES", value: "SHOW INDEXES" },
|
|
670
|
+
{ label: "统计:CALL db.stats()", value: "CALL db.stats()" },
|
|
671
|
+
{ label: "版本:SHOW BRANCHES", value: "SHOW BRANCHES" },
|
|
672
|
+
{ label: "定长跳:MATCH (a)-[r*1..2]->(b) RETURN a,r,b LIMIT 20", value: "MATCH (a)-[r*1..2]->(b) RETURN a,r,b LIMIT 20" }
|
|
673
|
+
];
|
|
674
|
+
function mn(e) {
|
|
675
|
+
return e === null || ["string", "number", "boolean"].includes(typeof e);
|
|
676
|
+
}
|
|
677
|
+
function gn(e) {
|
|
678
|
+
return e == null ? /* @__PURE__ */ n.jsx("span", { style: { color: "#94a3b8" }, children: "null" }) : mn(e) ? String(e) : /* @__PURE__ */ n.jsx("code", { style: { fontSize: 12, wordBreak: "break-all" }, children: JSON.stringify(e) });
|
|
679
|
+
}
|
|
680
|
+
function fn() {
|
|
681
|
+
var I, X, $, te, ce;
|
|
682
|
+
const [e] = Me(), [t, a] = p(e.get("cypher") || "MATCH (n) RETURN n LIMIT 20"), [l, i] = p(e.get("branch") || ee), [m, d] = p(e.get("commit") || ""), [h, E] = p(null), [y, S] = p(null), [P, x] = p(null), [k, A] = p(!1), [s, o] = p(!1), [C, z] = p(null), [D, _] = p(!1), W = Be(0), O = Z(() => rn(t), [t]), Y = ne(async () => {
|
|
683
|
+
A(!0), W.current = performance.now();
|
|
684
|
+
try {
|
|
685
|
+
const u = await G.query(t, l, m.trim() || void 0);
|
|
686
|
+
if (!Array.isArray(u))
|
|
687
|
+
throw new Error(`期望行数组,拿到 ${JSON.stringify(u)}`);
|
|
688
|
+
E(u), S(null), x(null), z(Math.round(performance.now() - W.current)), _(!1);
|
|
689
|
+
} catch (u) {
|
|
690
|
+
E(null), x(u), z(null);
|
|
691
|
+
} finally {
|
|
692
|
+
A(!1);
|
|
693
|
+
}
|
|
694
|
+
}, [t, l, m]), K = () => {
|
|
695
|
+
if (O && !D) {
|
|
696
|
+
_(!0);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
Y();
|
|
700
|
+
}, g = ne(async () => {
|
|
701
|
+
o(!0);
|
|
702
|
+
try {
|
|
703
|
+
const u = await G.explain(t, l);
|
|
704
|
+
S(u), x(null);
|
|
705
|
+
} catch (u) {
|
|
706
|
+
x(u), S(null);
|
|
707
|
+
} finally {
|
|
708
|
+
o(!1);
|
|
709
|
+
}
|
|
710
|
+
}, [t, l]), w = Z(() => {
|
|
711
|
+
if (!Array.isArray(h)) return [];
|
|
712
|
+
const u = [], L = /* @__PURE__ */ new Set();
|
|
713
|
+
return h.forEach((H) => Object.keys(H || {}).forEach((q) => {
|
|
714
|
+
L.has(q) || (L.add(q), u.push(q));
|
|
715
|
+
})), u.map((H, q) => ({
|
|
716
|
+
title: H,
|
|
717
|
+
dataIndex: H,
|
|
718
|
+
key: H,
|
|
719
|
+
ellipsis: !1,
|
|
720
|
+
width: u.length > 4 ? void 0 : 240,
|
|
721
|
+
render: (de) => gn(de),
|
|
722
|
+
// 上游行对象允许同名 key 冲突吗:不会,Map 的 key 唯一;这里用序号兜底防止空 key
|
|
723
|
+
...H === "" ? { title: `(空列名 #${q})` } : {}
|
|
724
|
+
}));
|
|
725
|
+
}, [h]);
|
|
726
|
+
return /* @__PURE__ */ n.jsxs("div", { children: [
|
|
727
|
+
/* @__PURE__ */ n.jsx(le, { title: "图查询" }),
|
|
728
|
+
/* @__PURE__ */ n.jsxs(pe, { gutter: 16, children: [
|
|
729
|
+
/* @__PURE__ */ n.jsx(B, { span: 16, children: /* @__PURE__ */ n.jsx(v, { title: "查询语句", children: /* @__PURE__ */ n.jsxs(R, { direction: "vertical", style: { width: "100%" }, size: 12, children: [
|
|
730
|
+
/* @__PURE__ */ n.jsxs(R, { wrap: !0, children: [
|
|
731
|
+
/* @__PURE__ */ n.jsx("span", { style: { fontSize: 12, color: "#64748b" }, children: "插入示例" }),
|
|
732
|
+
/* @__PURE__ */ n.jsx(
|
|
733
|
+
Ge,
|
|
734
|
+
{
|
|
735
|
+
size: "small",
|
|
736
|
+
style: { width: 340 },
|
|
737
|
+
placeholder: "常用只读查询",
|
|
738
|
+
showSearch: !0,
|
|
739
|
+
optionFilterProp: "label",
|
|
740
|
+
options: pn.map((u) => ({ value: u.value, label: u.label })),
|
|
741
|
+
onChange: (u) => {
|
|
742
|
+
a(u), _(!1);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
)
|
|
746
|
+
] }),
|
|
747
|
+
/* @__PURE__ */ n.jsx(
|
|
748
|
+
hn,
|
|
749
|
+
{
|
|
750
|
+
value: t,
|
|
751
|
+
rows: 4,
|
|
752
|
+
spellCheck: !1,
|
|
753
|
+
style: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace" },
|
|
754
|
+
onChange: (u) => {
|
|
755
|
+
a(u.target.value), _(!1);
|
|
756
|
+
},
|
|
757
|
+
placeholder: "MATCH (n:Person)-[r:KNOWS]->(m) RETURN n.name, r.since, m.name LIMIT 50"
|
|
758
|
+
}
|
|
759
|
+
),
|
|
760
|
+
/* @__PURE__ */ n.jsxs(R, { wrap: !0, children: [
|
|
761
|
+
/* @__PURE__ */ n.jsx("span", { style: { fontSize: 12, color: "#64748b" }, children: "分支" }),
|
|
762
|
+
/* @__PURE__ */ n.jsx(
|
|
763
|
+
Te,
|
|
764
|
+
{
|
|
765
|
+
size: "small",
|
|
766
|
+
style: { width: 140 },
|
|
767
|
+
value: l,
|
|
768
|
+
onChange: (u) => i(u.target.value || ee)
|
|
769
|
+
}
|
|
770
|
+
),
|
|
771
|
+
/* @__PURE__ */ n.jsx("span", { style: { fontSize: 12, color: "#64748b" }, children: "或按 commit 时间旅行(留空=按分支 head)" }),
|
|
772
|
+
/* @__PURE__ */ n.jsx(
|
|
773
|
+
Te,
|
|
774
|
+
{
|
|
775
|
+
size: "small",
|
|
776
|
+
style: { width: 260 },
|
|
777
|
+
value: m,
|
|
778
|
+
disabled: O,
|
|
779
|
+
placeholder: O ? "写语句不能带 commit" : "40 位 commit id",
|
|
780
|
+
onChange: (u) => d(u.target.value.trim())
|
|
781
|
+
}
|
|
782
|
+
)
|
|
783
|
+
] }),
|
|
784
|
+
O && /* @__PURE__ */ n.jsx(
|
|
785
|
+
M,
|
|
786
|
+
{
|
|
787
|
+
type: "warning",
|
|
788
|
+
showIcon: !0,
|
|
789
|
+
message: "这条语句会被上游判定为写操作",
|
|
790
|
+
description: D ? "已确认。再点一次「执行」就会真的改图:GraphControlServer 对写语句开一个事务并提交,产生一个新的不可撤回 commit。" : "上游按前缀/关键字判定写(CREATE / MERGE / MATCH…SET|DELETE / DETACH DELETE),执行时会开事务并落一个新 commit。第一次点击只出这条确认,不会执行。"
|
|
791
|
+
}
|
|
792
|
+
),
|
|
793
|
+
/* @__PURE__ */ n.jsxs(R, { children: [
|
|
794
|
+
/* @__PURE__ */ n.jsx(
|
|
795
|
+
J,
|
|
796
|
+
{
|
|
797
|
+
type: "primary",
|
|
798
|
+
icon: /* @__PURE__ */ n.jsx(Ve, {}),
|
|
799
|
+
loading: k,
|
|
800
|
+
danger: O && D,
|
|
801
|
+
onClick: K,
|
|
802
|
+
children: O && D ? "确认执行(会改图)" : "执行"
|
|
803
|
+
}
|
|
804
|
+
),
|
|
805
|
+
/* @__PURE__ */ n.jsx(J, { icon: /* @__PURE__ */ n.jsx(Ye, {}), loading: s, onClick: g, children: "执行计划" })
|
|
806
|
+
] })
|
|
807
|
+
] }) }) }),
|
|
808
|
+
/* @__PURE__ */ n.jsxs(B, { span: 8, children: [
|
|
809
|
+
/* @__PURE__ */ n.jsxs(v, { title: "本次结果", children: [
|
|
810
|
+
/* @__PURE__ */ n.jsxs(pe, { gutter: 16, children: [
|
|
811
|
+
/* @__PURE__ */ n.jsx(B, { span: 8, children: /* @__PURE__ */ n.jsx(fe, { title: "返回行数", value: h ? h.length : "-" }) }),
|
|
812
|
+
/* @__PURE__ */ n.jsx(B, { span: 8, children: /* @__PURE__ */ n.jsx(
|
|
813
|
+
fe,
|
|
814
|
+
{
|
|
815
|
+
title: "往返耗时",
|
|
816
|
+
suffix: "ms",
|
|
817
|
+
value: C ?? "-"
|
|
818
|
+
}
|
|
819
|
+
) }),
|
|
820
|
+
/* @__PURE__ */ n.jsx(B, { span: 8, children: /* @__PURE__ */ n.jsx(fe, { title: "列数", value: w.length || "-" }) })
|
|
821
|
+
] }),
|
|
822
|
+
/* @__PURE__ */ n.jsxs("div", { style: { marginTop: 12, fontSize: 12, color: "#64748b" }, children: [
|
|
823
|
+
"耗时是前端 axios 往返(含 8888 的代理一跳),不是上游 ",
|
|
824
|
+
/* @__PURE__ */ n.jsx("code", { children: "X-Response-Time" }),
|
|
825
|
+
"; 后者是跨域响应头,axios 读不到,不拿它当自己的数。"
|
|
826
|
+
] })
|
|
827
|
+
] }),
|
|
828
|
+
y && /* @__PURE__ */ n.jsxs(
|
|
829
|
+
v,
|
|
830
|
+
{
|
|
831
|
+
style: { marginTop: 16 },
|
|
832
|
+
title: "执行计划 (POST /query/explain)",
|
|
833
|
+
extra: /* @__PURE__ */ n.jsxs(R, { size: 4, children: [
|
|
834
|
+
/* @__PURE__ */ n.jsx(f, { children: ((I = y.plan) == null ? void 0 : I.queryType) ?? "-" }),
|
|
835
|
+
/* @__PURE__ */ n.jsx(f, { color: ((X = y.plan) == null ? void 0 : X.estimatedComplexity) === "HIGH" ? "error" : (($ = y.plan) == null ? void 0 : $.estimatedComplexity) === "MEDIUM" ? "warning" : "success", children: ((te = y.plan) == null ? void 0 : te.estimatedComplexity) ?? "-" })
|
|
836
|
+
] }),
|
|
837
|
+
children: [
|
|
838
|
+
/* @__PURE__ */ n.jsx(
|
|
839
|
+
ie,
|
|
840
|
+
{
|
|
841
|
+
size: "small",
|
|
842
|
+
rowKey: (u, L) => `${u.operation}-${L}`,
|
|
843
|
+
dataSource: ((ce = y.plan) == null ? void 0 : ce.steps) ?? [],
|
|
844
|
+
pagination: !1,
|
|
845
|
+
columns: [
|
|
846
|
+
{ title: "算子", dataIndex: "operation", key: "operation", width: 140 },
|
|
847
|
+
{ title: "说明", dataIndex: "description", key: "description" }
|
|
848
|
+
],
|
|
849
|
+
locale: { emptyText: /* @__PURE__ */ n.jsx(me, { title: "上游没有给出算子" }) }
|
|
850
|
+
}
|
|
851
|
+
),
|
|
852
|
+
/* @__PURE__ */ n.jsxs("div", { style: { marginTop: 8, fontSize: 12, color: "#64748b" }, children: [
|
|
853
|
+
"这份计划是 ",
|
|
854
|
+
/* @__PURE__ */ n.jsx("code", { children: "handleExplain" }),
|
|
855
|
+
" 按字符串前缀 + 关键字命中拼出来的 (MATCH→Scan、含 WHERE→Filter…),",
|
|
856
|
+
/* @__PURE__ */ n.jsx("b", { children: "不是代价估算器" }),
|
|
857
|
+
"; 复杂度只有 HIGH/MEDIUM/LOW 三档。别按真实执行成本解读。"
|
|
858
|
+
] })
|
|
859
|
+
]
|
|
860
|
+
}
|
|
861
|
+
)
|
|
862
|
+
] })
|
|
863
|
+
] }),
|
|
864
|
+
/* @__PURE__ */ n.jsx(v, { style: { marginTop: 16 }, title: h ? `结果集(${h.length} 行)` : "结果集", children: P ? /* @__PURE__ */ n.jsx(
|
|
865
|
+
M,
|
|
866
|
+
{
|
|
867
|
+
type: "error",
|
|
868
|
+
showIcon: !0,
|
|
869
|
+
message: `查询失败:${re(P)}`,
|
|
870
|
+
description: "上游所有 catch 分支都只回 {error: msg}:Cypher 语法错、Tag 不存在、head 冲突(409 Stale head)都在这里。503 则是内嵌实例没 bind。"
|
|
871
|
+
}
|
|
872
|
+
) : (
|
|
873
|
+
// 结果集走基座 ListTable:分页 / 查询 / EmptyState 三件套统一口径。
|
|
874
|
+
// 结果列是查询动态决定的,所以 searchKeys 直接取当前列的 dataIndex ——
|
|
875
|
+
// 过滤框按结果集里出现过的任意字段做端侧匹配。
|
|
876
|
+
/* @__PURE__ */ n.jsx(
|
|
877
|
+
Pe,
|
|
878
|
+
{
|
|
879
|
+
rows: h ?? [],
|
|
880
|
+
rowKey: (u, L) => L,
|
|
881
|
+
loading: k,
|
|
882
|
+
columns: w,
|
|
883
|
+
scroll: { x: "max-content" },
|
|
884
|
+
searchKeys: w.map((u) => u.dataIndex).filter(Boolean),
|
|
885
|
+
searchPlaceholder: "按结果列过滤",
|
|
886
|
+
emptyTitle: h === null ? "还没有执行过查询" : "返回 0 行",
|
|
887
|
+
emptyDescription: h === null ? "点「执行」或「执行计划」。这里没有预置示例数据。" : "查询成功执行、上游确实给了空数组(空图或未命中)。",
|
|
888
|
+
pagination: { ...ze, size: "small", pageSize: 20 },
|
|
889
|
+
cardProps: { variant: "borderless", styles: { body: { padding: 0 } } }
|
|
890
|
+
}
|
|
891
|
+
)
|
|
892
|
+
) }),
|
|
893
|
+
h && h.length > 0 && /* @__PURE__ */ n.jsx(v, { style: { marginTop: 16 }, title: "原始响应(第一行,逐字段照上游 JSON 渲染)", children: /* @__PURE__ */ n.jsx("pre", { style: { margin: 0, fontSize: 12, overflow: "auto", maxHeight: 260 }, children: JSON.stringify(h[0], null, 2) }) })
|
|
894
|
+
] });
|
|
895
|
+
}
|
|
896
|
+
function je(e) {
|
|
897
|
+
if (e == null) return "-";
|
|
898
|
+
const t = new Date(e);
|
|
899
|
+
return Number.isNaN(t.getTime()) ? String(e) : t.toLocaleString("zh-CN", { hour12: !1 });
|
|
900
|
+
}
|
|
901
|
+
function xn() {
|
|
902
|
+
const e = De(), [t, a] = p([]), [l, i] = p([]), [m, d] = p(!1), [h, E] = p(null), [y, S] = p(null), P = ne(async () => {
|
|
903
|
+
d(!0);
|
|
904
|
+
try {
|
|
905
|
+
const [s, o] = await Promise.all([G.branches(), G.commits()]);
|
|
906
|
+
if (!Array.isArray(s)) throw new Error(`branches 应为字符串数组,拿到 ${JSON.stringify(s)}`);
|
|
907
|
+
if (!Array.isArray(o)) throw new Error(`commits 应为对象数组,拿到 ${JSON.stringify(o)}`);
|
|
908
|
+
a(s), i(o), E(null);
|
|
909
|
+
} catch (s) {
|
|
910
|
+
a([]), i([]), E(s);
|
|
911
|
+
} finally {
|
|
912
|
+
d(!1);
|
|
913
|
+
}
|
|
914
|
+
}, []);
|
|
915
|
+
Ce(() => {
|
|
916
|
+
P();
|
|
917
|
+
}, [P]);
|
|
918
|
+
const x = Z(() => {
|
|
919
|
+
const s = /* @__PURE__ */ new Map();
|
|
920
|
+
return l.forEach((o) => s.set(o.id, o)), s;
|
|
921
|
+
}, [l]), k = Z(() => {
|
|
922
|
+
const s = /* @__PURE__ */ new Map();
|
|
923
|
+
return l.forEach((o) => {
|
|
924
|
+
const C = s.get(o.branch);
|
|
925
|
+
(!C || (o.timestamp ?? 0) >= (C.timestamp ?? 0)) && s.set(o.branch, o);
|
|
926
|
+
}), s;
|
|
927
|
+
}, [l]), A = Z(() => [...y ? l.filter((o) => o.branch === y) : l].sort((o, C) => (C.timestamp ?? 0) - (o.timestamp ?? 0)), [l, y]);
|
|
928
|
+
return h ? /* @__PURE__ */ n.jsxs("div", { children: [
|
|
929
|
+
/* @__PURE__ */ n.jsx(le, { title: "分支与提交" }),
|
|
930
|
+
/* @__PURE__ */ n.jsx(
|
|
931
|
+
M,
|
|
932
|
+
{
|
|
933
|
+
type: we(h) ? "error" : "warning",
|
|
934
|
+
showIcon: !0,
|
|
935
|
+
message: `提交历史读取失败:${re(h)}`,
|
|
936
|
+
description: we(h) ? "503 = 本 JVM 的内嵌 GraphControlServer 没 bind 成功(不是「图里没有提交」)。去「实例与端口」页确认 boundPort 与 foreignListenerSuspected。" : void 0
|
|
937
|
+
}
|
|
938
|
+
)
|
|
939
|
+
] }) : /* @__PURE__ */ n.jsxs("div", { children: [
|
|
940
|
+
/* @__PURE__ */ n.jsx(le, { title: "分支与提交" }),
|
|
941
|
+
/* @__PURE__ */ n.jsx(
|
|
942
|
+
v,
|
|
943
|
+
{
|
|
944
|
+
title: /* @__PURE__ */ n.jsxs(R, { children: [
|
|
945
|
+
/* @__PURE__ */ n.jsx(Ae, {}),
|
|
946
|
+
"分支(",
|
|
947
|
+
t.length,
|
|
948
|
+
")"
|
|
949
|
+
] }),
|
|
950
|
+
extra: /* @__PURE__ */ n.jsx(J, { icon: /* @__PURE__ */ n.jsx(Re, {}), loading: m, onClick: P, children: "刷新" }),
|
|
951
|
+
children: /* @__PURE__ */ n.jsxs(pe, { gutter: [16, 16], children: [
|
|
952
|
+
t.length === 0 && !m && /* @__PURE__ */ n.jsx(B, { span: 24, children: /* @__PURE__ */ n.jsx(
|
|
953
|
+
me,
|
|
954
|
+
{
|
|
955
|
+
title: "一个分支都没有",
|
|
956
|
+
description: "GET /api/graph/meta/branches 是通的、返回了空数组。内嵌实例若刚启动,GraphVersionStore 构造函数会先落一个 Initial graph 根提交并把 main 指过去,所以真为空通常说明连的不是你以为的那个实例。"
|
|
957
|
+
}
|
|
958
|
+
) }),
|
|
959
|
+
t.map((s) => {
|
|
960
|
+
const o = k.get(s), C = l.filter((z) => z.branch === s).length;
|
|
961
|
+
return /* @__PURE__ */ n.jsx(B, { span: 8, children: /* @__PURE__ */ n.jsx(
|
|
962
|
+
v,
|
|
963
|
+
{
|
|
964
|
+
size: "small",
|
|
965
|
+
hoverable: !0,
|
|
966
|
+
onClick: () => S(y === s ? null : s),
|
|
967
|
+
title: /* @__PURE__ */ n.jsxs(R, { children: [
|
|
968
|
+
/* @__PURE__ */ n.jsx(Ae, {}),
|
|
969
|
+
/* @__PURE__ */ n.jsx("code", { children: s })
|
|
970
|
+
] }),
|
|
971
|
+
extra: y === s ? /* @__PURE__ */ n.jsx(f, { color: "processing", children: "已筛选" }) : null,
|
|
972
|
+
children: /* @__PURE__ */ n.jsxs(R, { direction: "vertical", size: 2, style: { width: "100%" }, children: [
|
|
973
|
+
/* @__PURE__ */ n.jsxs("span", { style: { fontSize: 12 }, children: [
|
|
974
|
+
"head:",
|
|
975
|
+
/* @__PURE__ */ n.jsx("code", { children: o ? `${o.id.slice(0, 12)}…` : "未知" })
|
|
976
|
+
] }),
|
|
977
|
+
/* @__PURE__ */ n.jsxs("span", { style: { fontSize: 12, color: "#64748b" }, children: [
|
|
978
|
+
"本分支提交 ",
|
|
979
|
+
C,
|
|
980
|
+
" · 点 ",
|
|
981
|
+
(o == null ? void 0 : o.nodeCount) ?? "-",
|
|
982
|
+
" · 边 ",
|
|
983
|
+
(o == null ? void 0 : o.edgeCount) ?? "-"
|
|
984
|
+
] }),
|
|
985
|
+
/* @__PURE__ */ n.jsxs("span", { style: { fontSize: 12, color: "#64748b" }, children: [
|
|
986
|
+
"最近提交:",
|
|
987
|
+
o ? je(o.timestamp) : "-"
|
|
988
|
+
] })
|
|
989
|
+
] })
|
|
990
|
+
}
|
|
991
|
+
) }, s);
|
|
992
|
+
})
|
|
993
|
+
] })
|
|
994
|
+
}
|
|
995
|
+
),
|
|
996
|
+
/* @__PURE__ */ n.jsx(
|
|
997
|
+
Pe,
|
|
998
|
+
{
|
|
999
|
+
cardTitle: /* @__PURE__ */ n.jsxs(R, { children: [
|
|
1000
|
+
/* @__PURE__ */ n.jsx(Ke, {}),
|
|
1001
|
+
"提交历史(全量 commit 图,",
|
|
1002
|
+
A.length,
|
|
1003
|
+
" 条)"
|
|
1004
|
+
] }),
|
|
1005
|
+
cardExtra: y ? /* @__PURE__ */ n.jsx(R, { children: /* @__PURE__ */ n.jsxs(f, { color: "processing", closable: !0, onClose: () => S(null), children: [
|
|
1006
|
+
"branch = ",
|
|
1007
|
+
y
|
|
1008
|
+
] }) }) : /* @__PURE__ */ n.jsx("span", { style: { fontSize: 12, color: "#64748b" }, children: "点分支卡片可筛选" }),
|
|
1009
|
+
rows: A,
|
|
1010
|
+
rowKey: "id",
|
|
1011
|
+
loading: m,
|
|
1012
|
+
searchKeys: ["id", "author", "message"],
|
|
1013
|
+
searchPlaceholder: "按 commit / 作者 / 说明过滤",
|
|
1014
|
+
emptyTitle: "没有任何提交",
|
|
1015
|
+
emptyDescription: "GET /api/graph/meta/commits 通了但返回空数组 —— 上游 GraphVersionStore 构造时至少会有一个 Initial graph 根提交,所以这一格为空要去「实例与端口」页确认应答者。",
|
|
1016
|
+
cardProps: { style: { marginTop: 16 } },
|
|
1017
|
+
columns: [
|
|
1018
|
+
{
|
|
1019
|
+
title: "commit",
|
|
1020
|
+
dataIndex: "id",
|
|
1021
|
+
key: "id",
|
|
1022
|
+
width: 170,
|
|
1023
|
+
render: (s) => /* @__PURE__ */ n.jsx(xe, { title: s, children: /* @__PURE__ */ n.jsx("code", { children: `${String(s).slice(0, 12)}…` }) })
|
|
1024
|
+
},
|
|
1025
|
+
{
|
|
1026
|
+
title: "分支",
|
|
1027
|
+
dataIndex: "branch",
|
|
1028
|
+
key: "branch",
|
|
1029
|
+
width: 110,
|
|
1030
|
+
render: (s) => /* @__PURE__ */ n.jsx(f, { color: s === ee ? "blue" : "geekblue", children: s })
|
|
1031
|
+
},
|
|
1032
|
+
{ title: "作者", dataIndex: "author", key: "author", width: 110 },
|
|
1033
|
+
{
|
|
1034
|
+
title: "说明",
|
|
1035
|
+
dataIndex: "message",
|
|
1036
|
+
key: "message",
|
|
1037
|
+
ellipsis: { showTitle: !1 },
|
|
1038
|
+
render: (s) => /* @__PURE__ */ n.jsx(xe, { title: s, placement: "topLeft", children: /* @__PURE__ */ n.jsx("span", { children: s || "-" }) })
|
|
1039
|
+
},
|
|
1040
|
+
{ title: "时间", dataIndex: "timestamp", key: "timestamp", width: 190, render: je },
|
|
1041
|
+
{ title: "点", dataIndex: "nodeCount", key: "nodeCount", width: 70 },
|
|
1042
|
+
{ title: "边", dataIndex: "edgeCount", key: "edgeCount", width: 70 },
|
|
1043
|
+
{
|
|
1044
|
+
title: "父提交",
|
|
1045
|
+
dataIndex: "parents",
|
|
1046
|
+
key: "parents",
|
|
1047
|
+
width: 150,
|
|
1048
|
+
render: (s) => !Array.isArray(s) || s.length === 0 ? /* @__PURE__ */ n.jsx(f, { children: "根提交" }) : /* @__PURE__ */ n.jsx(R, { direction: "vertical", size: 0, children: s.map((o) => /* @__PURE__ */ n.jsx(
|
|
1049
|
+
xe,
|
|
1050
|
+
{
|
|
1051
|
+
title: x.has(o) ? `${x.get(o).message} · ${je(x.get(o).timestamp)}` : o,
|
|
1052
|
+
children: /* @__PURE__ */ n.jsx("code", { style: { fontSize: 12 }, children: `${String(o).slice(0, 8)}…` })
|
|
1053
|
+
},
|
|
1054
|
+
o
|
|
1055
|
+
)) })
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
title: "操作",
|
|
1059
|
+
key: "action",
|
|
1060
|
+
width: 130,
|
|
1061
|
+
render: (s, o) => /* @__PURE__ */ n.jsx(
|
|
1062
|
+
J,
|
|
1063
|
+
{
|
|
1064
|
+
type: "link",
|
|
1065
|
+
size: "small",
|
|
1066
|
+
onClick: () => e(`/graph/query?branch=${encodeURIComponent(o.branch)}&commit=${encodeURIComponent(o.id)}`),
|
|
1067
|
+
children: "按此版本查"
|
|
1068
|
+
}
|
|
1069
|
+
)
|
|
1070
|
+
}
|
|
1071
|
+
]
|
|
1072
|
+
}
|
|
1073
|
+
)
|
|
1074
|
+
] });
|
|
1075
|
+
}
|
|
1076
|
+
const yn = [
|
|
1077
|
+
{ title: "方法", dataIndex: "method", key: "method", width: 90, render: (e) => /* @__PURE__ */ n.jsx(f, { children: e }) },
|
|
1078
|
+
{ title: "路径", dataIndex: "path", key: "path", ellipsis: !0 },
|
|
1079
|
+
{ title: "来源 IP", dataIndex: "clientIp", key: "clientIp", width: 150 },
|
|
1080
|
+
{
|
|
1081
|
+
title: "状态",
|
|
1082
|
+
dataIndex: "status",
|
|
1083
|
+
key: "status",
|
|
1084
|
+
width: 90,
|
|
1085
|
+
render: (e) => /* @__PURE__ */ n.jsx(f, { color: e >= 500 ? "error" : e >= 400 ? "warning" : "success", children: e })
|
|
1086
|
+
},
|
|
1087
|
+
{ title: "耗时", dataIndex: "elapsedMs", key: "elapsedMs", width: 90, render: (e) => `${e} ms` },
|
|
1088
|
+
{ title: "请求 ID", dataIndex: "requestId", key: "requestId", width: 170, render: (e) => /* @__PURE__ */ n.jsx("code", { children: e }) },
|
|
1089
|
+
{ title: "线程", dataIndex: "thread", key: "thread", width: 160, render: (e) => /* @__PURE__ */ n.jsx("code", { children: e }) }
|
|
1090
|
+
];
|
|
1091
|
+
function jn(e) {
|
|
1092
|
+
if (e == null) return "-";
|
|
1093
|
+
const t = Math.floor(e / 86400), a = Math.floor(e % 86400 / 3600), l = Math.floor(e % 3600 / 60);
|
|
1094
|
+
return t > 0 ? `${t} 天 ${a} 时 ${l} 分` : a > 0 ? `${a} 时 ${l} 分 ${e % 60} 秒` : `${l} 分 ${e % 60} 秒`;
|
|
1095
|
+
}
|
|
1096
|
+
function Ee(e) {
|
|
1097
|
+
return e == null ? "-" : `${(e / 1024 / 1024).toFixed(1)} MiB`;
|
|
1098
|
+
}
|
|
1099
|
+
function Ne(e, t = "是", a = "否", l = "error") {
|
|
1100
|
+
return e == null ? "-" : e ? /* @__PURE__ */ n.jsx(f, { color: "success", children: t }) : /* @__PURE__ */ n.jsx(f, { color: l, children: a });
|
|
1101
|
+
}
|
|
1102
|
+
function En() {
|
|
1103
|
+
const [e, t] = p(null), [a, l] = p(null), [i, m] = p(null), [d, h] = p(null), [E, y] = p(!1), [S, P] = p(null), x = ne(async () => {
|
|
1104
|
+
y(!0);
|
|
1105
|
+
try {
|
|
1106
|
+
const s = await G.instance();
|
|
1107
|
+
t(s), P(null);
|
|
1108
|
+
try {
|
|
1109
|
+
const [o, C] = await Promise.all([G.metrics(), G.logs({ limit: 20 })]);
|
|
1110
|
+
l(o), m(null), h(C);
|
|
1111
|
+
} catch (o) {
|
|
1112
|
+
l(null), h(null), m(o);
|
|
1113
|
+
}
|
|
1114
|
+
} catch (s) {
|
|
1115
|
+
t(null), P(s);
|
|
1116
|
+
} finally {
|
|
1117
|
+
y(!1);
|
|
1118
|
+
}
|
|
1119
|
+
}, []);
|
|
1120
|
+
Ce(() => {
|
|
1121
|
+
x();
|
|
1122
|
+
}, [x]);
|
|
1123
|
+
const k = !S && e && !e.embeddedRunning, A = !S && e && e.foreignListenerSuspected;
|
|
1124
|
+
return /* @__PURE__ */ n.jsxs("div", { children: [
|
|
1125
|
+
/* @__PURE__ */ n.jsx(le, { title: "实例与端口" }),
|
|
1126
|
+
/* @__PURE__ */ n.jsx(v, { extra: /* @__PURE__ */ n.jsx(R, { children: /* @__PURE__ */ n.jsx(J, { icon: /* @__PURE__ */ n.jsx(Re, {}), loading: E, onClick: x, children: "刷新" }) }), children: S ? /* @__PURE__ */ n.jsx(
|
|
1127
|
+
M,
|
|
1128
|
+
{
|
|
1129
|
+
type: "error",
|
|
1130
|
+
showIcon: !0,
|
|
1131
|
+
message: `自省接口调用失败:${re(S)}`,
|
|
1132
|
+
description: "/api/graph/__instance 由 z-opc 自己的 controller 直接应答,不经过内嵌服务;它都不通说明是 z-opc 侧的路由/认证问题(302 到登录页 = 没带会话 Cookie)。"
|
|
1133
|
+
}
|
|
1134
|
+
) : /* @__PURE__ */ n.jsxs(n.Fragment, { children: [
|
|
1135
|
+
A && /* @__PURE__ */ n.jsx(
|
|
1136
|
+
M,
|
|
1137
|
+
{
|
|
1138
|
+
type: "error",
|
|
1139
|
+
showIcon: !0,
|
|
1140
|
+
style: { marginBottom: 16 },
|
|
1141
|
+
message: "配置端口上有监听者,但它不是本 JVM —— 这一路连的是外部进程,页面数据不能算孵化结果",
|
|
1142
|
+
description: `boundPort=0(本 JVM 的 GraphControlServer 没 bind 上)而 ${e.configuredPort} 端口 TCP 可连。用 lsof -nP -iTCP:${e.configuredPort} -sTCP:LISTEN 取属主 PID,与本页 jvm 字段(${e.jvm})比对;对不上就是假孵化。处理方式只有两种:让出该端口,或给 zgraph.server.port 换一个端口——两者都需要定夺,代理层不会替你去占端口。`
|
|
1143
|
+
}
|
|
1144
|
+
),
|
|
1145
|
+
k && !A && /* @__PURE__ */ n.jsx(
|
|
1146
|
+
M,
|
|
1147
|
+
{
|
|
1148
|
+
type: "warning",
|
|
1149
|
+
showIcon: !0,
|
|
1150
|
+
style: { marginBottom: 16 },
|
|
1151
|
+
message: "内嵌 GraphControlServer 没有 bind 成功,其余三页的空数组是「没起」而不是「图里没数据」",
|
|
1152
|
+
description: `bindError = ${e.bindError ?? "null"}。proxy 只在 boundPort>0 时才转发,所以现在所有 /api/graph/meta* 与 /api/graph/query 都会拿到 503。`
|
|
1153
|
+
}
|
|
1154
|
+
),
|
|
1155
|
+
/* @__PURE__ */ n.jsx(
|
|
1156
|
+
be,
|
|
1157
|
+
{
|
|
1158
|
+
bordered: !0,
|
|
1159
|
+
size: "small",
|
|
1160
|
+
column: 2,
|
|
1161
|
+
items: [
|
|
1162
|
+
{
|
|
1163
|
+
key: "jvm",
|
|
1164
|
+
label: "应答的 JVM (pid@host)",
|
|
1165
|
+
children: /* @__PURE__ */ n.jsx("code", { children: (e == null ? void 0 : e.jvm) ?? "-" })
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
key: "lifecycleBean",
|
|
1169
|
+
label: "内嵌配置已装载",
|
|
1170
|
+
children: e ? e.lifecycleBean ? /* @__PURE__ */ n.jsx(f, { color: "success", children: "是" }) : /* @__PURE__ */ n.jsx(f, { color: "error", children: "否(zgraph.enabled≠true)" }) : "-"
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
key: "configuredPort",
|
|
1174
|
+
label: "配置端口",
|
|
1175
|
+
children: (e == null ? void 0 : e.configuredPort) ?? "-"
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
key: "boundPort",
|
|
1179
|
+
label: "内嵌实例实际 bind 端口",
|
|
1180
|
+
children: e ? e.boundPort > 0 ? /* @__PURE__ */ n.jsx(f, { color: "success", children: e.boundPort }) : /* @__PURE__ */ n.jsx(f, { color: "error", children: "0(没 bind 上)" }) : "-"
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
key: "embeddedRunning",
|
|
1184
|
+
label: "内嵌控制面在跑",
|
|
1185
|
+
children: Ne(e == null ? void 0 : e.embeddedRunning)
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
key: "acceptingNow",
|
|
1189
|
+
label: "本实例端口此刻可连",
|
|
1190
|
+
children: Ne(e == null ? void 0 : e.acceptingNow, "是", "否", "warning")
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
key: "foreign",
|
|
1194
|
+
label: "外来监听者嫌疑",
|
|
1195
|
+
children: e ? e.foreignListenerSuspected ? /* @__PURE__ */ n.jsx(f, { color: "error", children: "有(应答者不是本 JVM)" }) : /* @__PURE__ */ n.jsx(f, { color: "success", children: "无" }) : "-"
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
key: "token",
|
|
1199
|
+
label: "上游 API Token (环境变量)",
|
|
1200
|
+
children: e ? e.apiTokenConfigured ? /* @__PURE__ */ n.jsx(f, { color: "success", children: "已设置" }) : /* @__PURE__ */ n.jsx(f, { color: "warning", children: "未设置 = 通配端口不鉴权" }) : "-"
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
key: "bindError",
|
|
1204
|
+
label: "bind 失败原因",
|
|
1205
|
+
children: e != null && e.bindError ? /* @__PURE__ */ n.jsx("code", { children: e.bindError }) : "-"
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
key: "path",
|
|
1209
|
+
label: "转发前缀",
|
|
1210
|
+
children: /* @__PURE__ */ n.jsx("code", { children: "/api/graph/** → 127.0.0.1:<boundPort>" })
|
|
1211
|
+
}
|
|
1212
|
+
]
|
|
1213
|
+
}
|
|
1214
|
+
),
|
|
1215
|
+
/* @__PURE__ */ n.jsxs("div", { style: { marginTop: 12, color: "#64748b", fontSize: 12 }, children: [
|
|
1216
|
+
"代理是逐字节透传,不在 z-opc 侧重拼 JSON。上游注册的端点:",
|
|
1217
|
+
((e == null ? void 0 : e.upstreamEndpoints) ?? []).map((s) => /* @__PURE__ */ n.jsx(
|
|
1218
|
+
"code",
|
|
1219
|
+
{
|
|
1220
|
+
style: { marginRight: 6 },
|
|
1221
|
+
children: s
|
|
1222
|
+
},
|
|
1223
|
+
s
|
|
1224
|
+
))
|
|
1225
|
+
] })
|
|
1226
|
+
] }) }),
|
|
1227
|
+
/* @__PURE__ */ n.jsx(
|
|
1228
|
+
v,
|
|
1229
|
+
{
|
|
1230
|
+
title: "控制面自报指标 (GET /api/graph/meta/metrics)",
|
|
1231
|
+
style: { marginTop: 16 },
|
|
1232
|
+
extra: /* @__PURE__ */ n.jsx(f, { color: "blue", children: "走上游转发,内嵌没起时这里必然报错" }),
|
|
1233
|
+
children: i ? /* @__PURE__ */ n.jsx(
|
|
1234
|
+
M,
|
|
1235
|
+
{
|
|
1236
|
+
type: "error",
|
|
1237
|
+
showIcon: !0,
|
|
1238
|
+
message: `读不到 /meta/metrics:${re(i)}`
|
|
1239
|
+
}
|
|
1240
|
+
) : /* @__PURE__ */ n.jsx(
|
|
1241
|
+
be,
|
|
1242
|
+
{
|
|
1243
|
+
bordered: !0,
|
|
1244
|
+
size: "small",
|
|
1245
|
+
column: 3,
|
|
1246
|
+
items: [
|
|
1247
|
+
{
|
|
1248
|
+
key: "uptime",
|
|
1249
|
+
label: "控制面存活时长",
|
|
1250
|
+
children: `${jn(a == null ? void 0 : a.uptimeSeconds)}(${(a == null ? void 0 : a.uptimeFormatted) ?? "-"})`
|
|
1251
|
+
},
|
|
1252
|
+
{ key: "totalRequests", label: "累计请求", children: (a == null ? void 0 : a.totalRequests) ?? "-" },
|
|
1253
|
+
{ key: "errorResponses", label: "错误应答", children: (a == null ? void 0 : a.errorResponses) ?? "-" },
|
|
1254
|
+
{ key: "errorRate", label: "错误率", children: (a == null ? void 0 : a.errorRate) ?? "-" },
|
|
1255
|
+
{ key: "authFailures", label: "鉴权失败", children: (a == null ? void 0 : a.authFailures) ?? "-" },
|
|
1256
|
+
{ key: "rateLimited", label: "被限流", children: (a == null ? void 0 : a.rateLimitedRequests) ?? "-" },
|
|
1257
|
+
{
|
|
1258
|
+
key: "heap",
|
|
1259
|
+
label: "JVM 堆 (已用/已申请/上限)",
|
|
1260
|
+
children: a != null && a.jvmMemory ? `${Ee(a.jvmMemory.usedBytes)} / ${Ee(a.jvmMemory.totalBytes)} / ${Ee(a.jvmMemory.maxBytes)}` : "-"
|
|
1261
|
+
},
|
|
1262
|
+
{ key: "cpu", label: "可用核数", children: (a == null ? void 0 : a.availableProcessors) ?? "-" },
|
|
1263
|
+
{
|
|
1264
|
+
key: "rate",
|
|
1265
|
+
label: "限流阈值/活跃桶",
|
|
1266
|
+
children: `${(a == null ? void 0 : a.rateLimitPerMinute) ?? "-"} / ${(a == null ? void 0 : a.activeRateBuckets) ?? "-"}`
|
|
1267
|
+
}
|
|
1268
|
+
]
|
|
1269
|
+
}
|
|
1270
|
+
)
|
|
1271
|
+
}
|
|
1272
|
+
),
|
|
1273
|
+
/* @__PURE__ */ n.jsx(v, { title: "最近请求日志 (GET /api/graph/meta/logs?limit=20)", style: { marginTop: 16 }, children: d ? (
|
|
1274
|
+
// 列表页三件套(分页 + 查询 + EmptyState)由基座 ListTable 一次带齐。
|
|
1275
|
+
// 日志是上游环形缓冲的最近 20 条窗口,所以分页用基座默认口径 + pageSize 20,
|
|
1276
|
+
// 并在 showTotal 里保留"环形缓冲 / 容量"这两个运维要看的数。
|
|
1277
|
+
/* @__PURE__ */ n.jsx(
|
|
1278
|
+
Pe,
|
|
1279
|
+
{
|
|
1280
|
+
rows: d.entries ?? [],
|
|
1281
|
+
rowKey: (s) => `${s.timestamp}-${s.requestId}`,
|
|
1282
|
+
loading: E,
|
|
1283
|
+
columns: yn,
|
|
1284
|
+
searchKeys: ["path", "requestId", "clientIp"],
|
|
1285
|
+
searchPlaceholder: "按路径 / 请求 ID / 来源 IP 过滤",
|
|
1286
|
+
emptyTitle: "暂无请求日志",
|
|
1287
|
+
emptyDescription: "控制面刚起或还没打过请求",
|
|
1288
|
+
pagination: {
|
|
1289
|
+
...ze,
|
|
1290
|
+
size: "small",
|
|
1291
|
+
pageSize: 20,
|
|
1292
|
+
showTotal: (s) => `环形缓冲 ${d.total ?? 0} 条 / 容量 ${d.bufferSize ?? "-"},本页 ${s} 条`
|
|
1293
|
+
},
|
|
1294
|
+
cardProps: { variant: "borderless", styles: { body: { padding: 0 } } }
|
|
1295
|
+
}
|
|
1296
|
+
)
|
|
1297
|
+
) : /* @__PURE__ */ n.jsx(
|
|
1298
|
+
M,
|
|
1299
|
+
{
|
|
1300
|
+
type: "info",
|
|
1301
|
+
showIcon: !0,
|
|
1302
|
+
message: i ? "读不到请求日志(同上)" : "日志缓冲为空 —— 控制面刚起或还没打过请求"
|
|
1303
|
+
}
|
|
1304
|
+
) })
|
|
1305
|
+
] });
|
|
1306
|
+
}
|
|
1307
|
+
function _n() {
|
|
1308
|
+
return /* @__PURE__ */ n.jsxs(Le, { children: [
|
|
1309
|
+
/* @__PURE__ */ n.jsx(ae, { index: !0, element: /* @__PURE__ */ n.jsx(He, { to: "schema", replace: !0 }) }),
|
|
1310
|
+
/* @__PURE__ */ n.jsx(ae, { path: "schema", element: /* @__PURE__ */ n.jsx(un, {}) }),
|
|
1311
|
+
/* @__PURE__ */ n.jsx(ae, { path: "query", element: /* @__PURE__ */ n.jsx(fn, {}) }),
|
|
1312
|
+
/* @__PURE__ */ n.jsx(ae, { path: "branches", element: /* @__PURE__ */ n.jsx(xn, {}) }),
|
|
1313
|
+
/* @__PURE__ */ n.jsx(ae, { path: "instance", element: /* @__PURE__ */ n.jsx(En, {}) })
|
|
1314
|
+
] });
|
|
1315
|
+
}
|
|
1316
|
+
const bn = "# z-graph-component\n\nz-graph(图数据库控制台)跨仓唯一前端面:把「总览 / 图视图 / 分支 / 提交历史 / Cypher 查询 /\nSchema / 请求日志 / API 文档 / 实例自省」这一组图数据库运维视图收敛成一个可复用的 React\n组件包,由 `z-graph-suit` 独立壳、z-team 的 OpsWorkbench(`/graph/*` 通配挂载)或 z-opc 主壳挂载。\n\n- **npm 包名**:`@yuku123/z-graph-component`\n- **宿主接入**:`@yuku123/z-graph-component/pages`(manifest + 页面组件),主壳可只取 `ComponentDocPage`\n- **后端**:z-graph-bolt-server 的 `GraphControlServer`(JDK `com.sun.net.httpserver.HttpServer`,\n 构造即 bind,默认 8090),在 z-opc 里由 `GraphProxyController` 转一道(`/api/graph/**`)\n- **技术栈**:React 19 + antd 6 + react-router-dom 7 + vite(library mode,双入口 `.` / `./pages`)\n\n---\n\n## 一、能力概览\n\n包里有**两套**页面树、**两套**请求层,路由前缀也不同,别混:\n\n### 1.1 manifest 面(`/z-graph/*`,`src/pages-manifest.jsx`)\n\n| 页面 | 路由(相对 `/z-graph/*`) | 源文件 | 能力 |\n|------|--------------------------|--------|------|\n| 首页 | `home` | `pages/HomePage.jsx` | 读 `userInfo` 显示登录人,按 `menuItems` 渲染功能入口卡片 |\n| 总览 | `dashboard` | `console/pages/Dashboard.jsx` | 图数据库总览(点数 / 边数 / head) |\n| 图视图 | `graph` | `console/pages/GraphView.jsx` | 图数据可视化 |\n| 分支 | `branches` | `console/pages/Branches.jsx` | 分支清单 |\n| 提交历史 | `commits` | `console/pages/Commits.jsx` | commit 列表 |\n| Cypher 查询 | `query` | `console/pages/QueryPage.jsx` | Cypher 执行 + 执行计划 |\n| Schema | `schema` | `console/pages/Schema.jsx` | 点类型 / 边类型 / 属性索引 |\n| 请求日志 | `logs` | `console/pages/LogViewer.jsx` | 控制面请求日志 |\n| API 文档 | `api-docs` | `console/pages/ApiDocs.jsx` | 控制面 API 说明 |\n| 实例与端口 | `instance` | `pages/InstanceStatus.jsx` | 实例自省 + 控制面指标 |\n| 登录 | (不在菜单内) | `pages/LoginPage.jsx` | mock 登录,写 `token` / `userInfo` 后跳 `/z-graph/home` |\n| 组件文档 | `docs` | `pages/ComponentDoc.jsx` | 本文件渲染页(左侧 240px TOC + 右侧正文) |\n\n`console/` 下这 8 个旧版页面都套了 `withGraphConsole(...)`(`console/serverHook.jsx`)——\n它在 `withGraphServer` 之外再套一层 `.zgc` 作用域容器。\n**为什么必须有这层**:旧版页面吃的 `console/styles.css` 原本是「独立 SPA 全局样式」\n(裸元素选择器 `button` / `input` / `a` / `table`),而本组件的 CSS 会被主壳 z-opc 当\n**全局样式**注入 ⇒ `button:hover{background:#334155}` 会泄漏到整个主壳,\n压过 z-team 工作台标签栏的 `.tab{background:none}`(特异度 0,1,0 vs 0,1,1)。\n新版页面(`src/pages/*`,antd 实现)不吃这份样式,所以**不加**作用域。\n\n### 1.2 GraphApp 面(`/graph/*`,`src/pages/GraphApp.jsx`)\n\n`GraphApp` 是给 OpsWorkbench 通配挂载用的**子路由集合**,入口重定向到 `schema`:\n\n| 子路由 | 源文件 | 能力 |\n|--------|--------|------|\n| `schema` | `pages/SchemaPage.jsx` | 图 Schema(点类型 / 边类型 / 属性索引) |\n| `query` | `pages/QueryConsole.jsx` | Cypher 执行 + 执行计划 |\n| `branches` | `pages/BranchCommitPage.jsx` | 分支与提交 |\n| `instance` | `pages/InstanceStatus.jsx` | 实例自省 |\n\n### 1.3 两套请求层\n\n| 层 | 文件 | 出口 | 用在哪些页面 |\n|----|------|------|--------------|\n| axios 版 | `src/services/api.js` | `graphApi` | `src/pages/*` 的新版页面(`SchemaPage` / `QueryConsole` / `BranchCommitPage` / `InstanceStatus`) |\n| fetch 版(console) | `src/console/api.js` | `api` | `console/pages/*` 的 8 个旧版页面(经 `withGraphServer` 供给) |\n\n两层的路径**完全一致**,只是实现不同(axios 30s 超时 vs 原生 fetch 无超时)。\n`configureGraph(apiBase)` 会把**两层的 baseURL 一起切**(`console/api.js` 有 `setBaseUrl`),\n避免出现「health 报 200 而 instance 报 404」这种诡异分支。\n\n## 二、导出面\n\n```js\n// 主入口(. 桶):两套请求层 + 域挂载点\nimport {graphApi, graphErrorText, isNotBoundError, isMutatingCypher, DEFAULT_BRANCH,\n configureGraph, api, GraphApp} from '@yuku123/z-graph-component'\n\n// 宿主组装面(./pages 桶):manifest + 页面组件\nimport {menuItems, routes, appMeta, HomePage, LoginPage,\n withGraphServer, withGraphConsole, useGraphServer,\n api, configureGraph, ComponentDocPage} from '@yuku123/z-graph-component/pages'\n\n// 组件文档页(主壳接线用,名字全仓统一)\nimport {ComponentDocPage} from '@yuku123/z-graph-component'\n```\n\n## 三、API 端点\n\n上游**不是 Spring MVC**:`com.zifang.z.graph.bolt.GraphControlServer` 是 JDK\n`com.sun.net.httpserver.HttpServer`,构造即 bind(默认 8090),所以它永远不出现在\n`/actuator/mappings` 里,也不在 vite 的 `/api` 代理范围内。\n\n下表路径是 `src/services/api.js`(`graphApi`)与 `src/console/api.js`(`api`)里出现的\n**实际路径**(不含前缀,前缀见下)。\n\n| 方法 | 路径 | 说明 |\n|---|---|---|\n| GET | `/__instance` | 实例自省(**只由 z-opc 的 `GraphProxyController` 应答**,不经过上游) |\n| GET | `/health` | 健康检查 `{status, head, nodeCount, edgeCount}` |\n| GET | `/meta/branches` | 分支列表(裸数组) |\n| GET | `/meta/commits` | 提交历史(裸数组) |\n| GET | `/meta/schema` | Schema(query:`branch`) |\n| GET | `/meta/stats` | 引擎统计(query:`branch`) |\n| GET | `/meta/metrics` | 控制面自身指标(uptime / 请求数 / 限流 / JVM 内存…) |\n| GET | `/meta/logs` | 请求日志(query:`offset` / `limit` / `method` / `status` / `path`) |\n| GET | `/meta/export` | 导出(**下载件**,只由 console 层暴露) |\n| GET | `/query` | Cypher 查询(query:`cypher` / `branch` / `commit`) |\n| POST | `/query/explain` | 执行计划(body:`{cypher, branch}`) |\n| POST | `/query/batch` | 批量查询(body:`{statements}`,只由 console 层暴露) |\n\n### 3.1 请求前缀\n\n| 部署形态 | 前缀 | 说明 |\n|---|---|---|\n| 独立壳 `z-graph-suit` | `/api` | dev proxy 把 `/api/*` 剥掉前缀转发到 `localhost:8090` |\n| z-opc 主壳 | `/api/graph` | 后端 `GraphProxyController` 的 `@RequestMapping` 前缀就是 `/api/graph` |\n\n`configureGraph(apiBase = '/api')` 是宿主注入前缀的唯一入口(同时切 axios 层与 console fetch 层)。\n\n### 3.2 两个刻意不接的口子\n\n- `POST /meta/import`:它把 JSON 手工拼成 `CREATE` 语句再执行,对共享图实例是不可逆写入,\n 孵化页不提供。\n- `GET /meta/export`:响应带 `Content-Disposition: attachment`,是个下载件而不是表格数据。\n\n## 四、页面说明\n\n### 4.1 首页\n\n登录后的着陆页。读 `localStorage.userInfo` 显示欢迎语与角色标签,再把 `menuItems`(去掉首页自身)\n渲染成入口卡片 —— 菜单加一项,首页卡片自动多一张,不用改页面代码。\n\n### 4.2 总览 / 图视图 / 分支 / 提交历史 / Cypher 查询 / Schema / 请求日志 / API 文档\n\n`console/pages/` 下的 8 个旧版页面(吃 `console/styles.css`,故套 `.zgc` 作用域)。\n它们统一由 `withGraphServer` 供给 `{server, metrics}` —— `useGraphServer` 每 5 秒轮询\n`/health` + `/meta/metrics`,把控制面健康态与指标灌给页面。\n\n### 4.3 实例与端口(`pages/InstanceStatus.jsx`)\n\n`/api/graph/__instance` 的可视化(外加控制面自己的 `/meta/metrics` 与 `/meta/logs`)。\n它把「内嵌没 bind 成功(503)」与「图里确实没数据」分开展示 ——\n代理只在本 JVM `boundPort > 0` 时才转发。\n\n### 4.4 Schema / 查询 / 分支与提交(新版,antd 实现)\n\n`src/pages/` 下的 `SchemaPage` / `QueryConsole` / `BranchCommitPage`,用 axios 层\n(`graphApi`),不吃 console 样式表。`QueryConsole` 对写语句(`CREATE` / `MERGE` / `DELETE` /\n`DROP` / `SET`…)有二次确认,判据是 `isMutatingCypher`。\n\n### 4.5 登录\n\n当前是 **mock 模式**:比对一对写死在 `src/pages/LoginPage.jsx` 里的管理员凭据\n(明文口令不在本文复述,见源码),成功后写 `localStorage.token` 与 `localStorage.userInfo`,\n再 `window.location.href = '/z-graph/home'`。CTC/SSO 尚未接入。\n\n### 4.6 组件文档\n\n本文件。内容以 `?raw` 在编译期内联,零额外请求;左侧 240px TOC 随滚动联动。\n\n## 五、配置\n\n| 配置项 | 默认值 | 说明 |\n|---|---|---|\n| `configureGraph(apiBase)` | `'/api'` | 同时设置 axios 层与 console fetch 层的 baseURL |\n| `console/api.js` 的 baseUrl | `window.__Z_GRAPH_API__` → `VITE_API_BASE` → `'/api'` | 三级兜底;`setBaseUrl` 会写回 `window.__Z_GRAPH_API__` |\n| axios 超时 | `30000` ms | axios 层;console fetch 层**没有超时** |\n| 默认分支 | `main` | `DEFAULT_BRANCH`,与上游 `getOrDefault(\"branch\",\"main\")` 一致 |\n| 健康轮询间隔 | `5000` ms | `useGraphServer` 的轮询周期 |\n\n## 六、已知限制\n\n- ⚠ **平台主壳里的前缀没接上**:`configureGraph` 默认 `'/api'`,但 z-opc 后端的\n `GraphProxyController` 前缀是 `/api/graph`。z-opc 主壳与 z-team 都**没有**调用\n `configureGraph('/api/graph')`(只有 `z-graph-suit/src/main.jsx` 调了 `configureGraph('/api')`),\n ⇒ 在平台里这批请求会打到 `/api/health` 而不是 `/api/graph/health`。\n 修这个要在宿主侧接线(`configureGraph('/api/graph')`),**不在本包的文件所有权范围内**。\n- **查询走 GET 而不是 POST**:`handleQuery` 的 POST 分支用的是手搓的 `parseJsonStringMap`\n (按引号外逗号切 token),GET 分支用的是 `URLDecoder.decode` ——\n 对含 `:`、`,`、`{}` 的 Cypher 只有前者会被解析器啃掉。所以 axios 层统一走 GET;\n 而 console 层的 `api.query` 走的是 POST,两层口径不同。\n- **commit 字段不能传空串**:「null」字符串无法区分真正的 null,所以 commit 为空时\n console 层直接不发送该字段,避免触发 `Unknown commit: null` 错误。\n- **两套请求层的结果形状不同**:console 层返回 `{data, elapsed}`(带耗时),\n axios 层返回裸 data。两层页面不能混用同一个返回值。\n- **上游 8090 是独立进程**:z-graph-bolt-server 实测是 PID 1794 的常驻进程;\n 代理只连本 JVM 内嵌 bind 成功的端口,bind 没成功就直接 503,\n 不会退化成「连 8090 试试看」—— 那样会把外部进程的应答读成自己的。\n- 后端错误体有两套形状:代理层的 `{status:\"error\", message, hint}` 与上游的 `{error:\"…\"}`\n (`GraphControlServer` 所有 catch 分支都只写 `error`),`graphErrorText` 两个都认。\n- 静态 `message` / `notification`(antd 6 下会告警,规范要求改 `App.useApp()`)。\n- z-graph 已被 z-opc 主壳的 `domainRoutes.ts` 与 z-team 的 OpsWorkbench 引用\n ⇒ `/z-graph/docs` 在平台主壳里**可达**。\n\n## 七、变更记录\n\n| 版本 | 变更 |\n|---|---|\n| 0.1.1 | 新增组件文档页 `ComponentDocPage`(lead 008 §7):`src/DOC.md` + 基座 `ComponentDoc` 薄包装 |\n| 0.1.1 | 首版:console 旧版页面集 + 新版 antd 页面 + 双请求层 + manifest + GraphApp 挂载点 |\n";
|
|
1317
|
+
function In() {
|
|
1318
|
+
return /* @__PURE__ */ n.jsx(Xe, { markdown: bn, title: "组件文档 — z-graph" });
|
|
1319
|
+
}
|
|
1320
|
+
export {
|
|
1321
|
+
In as C,
|
|
1322
|
+
ee as D,
|
|
1323
|
+
_n as G,
|
|
1324
|
+
En as I,
|
|
1325
|
+
en as a,
|
|
1326
|
+
re as b,
|
|
1327
|
+
An as c,
|
|
1328
|
+
we as d,
|
|
1329
|
+
kn as f,
|
|
1330
|
+
G as g,
|
|
1331
|
+
rn as i,
|
|
1332
|
+
n as j,
|
|
1333
|
+
vn as s
|
|
1334
|
+
};
|