@evomap/evolver-webui 2.0.0-beta.1 → 2.0.0-beta.4
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/assetLineage.d.ts +70 -0
- package/dist/assetLineage.js +256 -0
- package/dist/console.d.ts +1 -1
- package/dist/console.js +103 -43
- package/dist/diagnosticSanitize.d.ts +2 -0
- package/dist/diagnosticSanitize.js +49 -0
- package/dist/eventSnapshot.d.ts +18 -0
- package/dist/eventSnapshot.js +149 -0
- package/dist/githubPrDiagnostics.d.ts +57 -0
- package/dist/githubPrDiagnostics.js +164 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/logDiagnostics.d.ts +18 -0
- package/dist/logDiagnostics.js +81 -0
- package/dist/observabilityRelations.d.ts +22 -0
- package/dist/observabilityRelations.js +93 -0
- package/dist/personalityDiagnostics.d.ts +41 -0
- package/dist/personalityDiagnostics.js +101 -0
- package/dist/server.d.ts +39 -3
- package/dist/server.js +234 -20
- package/package.json +7 -2
package/dist/server.js
CHANGED
|
@@ -2,21 +2,84 @@ import { createServer } from 'node:http';
|
|
|
2
2
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
3
|
import { events as ev, assetstore, mailbox as mb, ops } from '@evomap/evolver-core';
|
|
4
4
|
import { CONSOLE_HTML } from './console.js';
|
|
5
|
+
import { EventSnapshotCache, fileEventSnapshotSource } from './eventSnapshot.js';
|
|
6
|
+
import { listLineageAssets, loadAssetLineage } from './assetLineage.js';
|
|
7
|
+
import { eventListRelations } from './observabilityRelations.js';
|
|
8
|
+
import { redactDiagnosticText, sanitizeDiagnosticValue } from './diagnosticSanitize.js';
|
|
5
9
|
const LOOPBACK = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
|
|
10
|
+
const DASHBOARD_COOKIE = 'evolver_dashboard';
|
|
11
|
+
const BROWSER_BLOCKED_PORTS = new Set([
|
|
12
|
+
1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95,
|
|
13
|
+
101, 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179,
|
|
14
|
+
389, 427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601,
|
|
15
|
+
636, 989, 990, 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061, 6000, 6566,
|
|
16
|
+
6665, 6666, 6667, 6668, 6669, 6697, 10080,
|
|
17
|
+
]);
|
|
6
18
|
/** The empty value summary (zero entries) — the shape /api/value returns when no provider is wired, so the card
|
|
7
19
|
* always gets a valid ValueSummary to render. Derived from core's aggregator to stay shape-identical. */
|
|
8
20
|
const EMPTY_VALUE_SUMMARY = ops.valueSummary([]);
|
|
21
|
+
const MEMORY_GRAPH_REASON_PATTERN = /scoped memory-graph outcome ([+-]\d+\.\d{3}) \(boost=([+-]?\d+\.\d{2})\)/;
|
|
22
|
+
const MEMORY_GRAPH_RECOVERY_STATES = new Set(['healthy', 'degraded', 'recovered', 'empty']);
|
|
23
|
+
function boundedMemoryGraphCount(value) {
|
|
24
|
+
const count = Number(value);
|
|
25
|
+
return Number.isFinite(count) && count > 0 ? Math.min(1_000_000, Math.floor(count)) : 0;
|
|
26
|
+
}
|
|
27
|
+
function sanitizeMemoryGraphReason(value) {
|
|
28
|
+
if (typeof value !== 'string')
|
|
29
|
+
return undefined;
|
|
30
|
+
const match = MEMORY_GRAPH_REASON_PATTERN.exec(value);
|
|
31
|
+
if (!match?.[1] || !match[2])
|
|
32
|
+
return undefined;
|
|
33
|
+
const outcome = Number(match[1]);
|
|
34
|
+
const boost = Number(match[2]);
|
|
35
|
+
if (!Number.isFinite(outcome) || !Number.isFinite(boost) || Math.abs(outcome) > 1 || Math.abs(boost) > 1)
|
|
36
|
+
return undefined;
|
|
37
|
+
return `scoped memory-graph outcome ${match[1]} (boost=${match[2]})`;
|
|
38
|
+
}
|
|
39
|
+
function sanitizeMemoryGraphStatus(value) {
|
|
40
|
+
const raw = value;
|
|
41
|
+
const recovery = MEMORY_GRAPH_RECOVERY_STATES.has(raw['recovery'])
|
|
42
|
+
? raw['recovery']
|
|
43
|
+
: 'degraded';
|
|
44
|
+
const selectionReason = sanitizeMemoryGraphReason(raw['selectionReason']);
|
|
45
|
+
return {
|
|
46
|
+
recovery,
|
|
47
|
+
compactedRecords: boundedMemoryGraphCount(raw['compactedRecords']),
|
|
48
|
+
activeRecords: boundedMemoryGraphCount(raw['activeRecords']),
|
|
49
|
+
corruptLines: boundedMemoryGraphCount(raw['corruptLines']),
|
|
50
|
+
oversizedLines: boundedMemoryGraphCount(raw['oversizedLines']),
|
|
51
|
+
oversizedFiles: boundedMemoryGraphCount(raw['oversizedFiles']),
|
|
52
|
+
archives: boundedMemoryGraphCount(raw['archives']),
|
|
53
|
+
...(selectionReason ? { selectionReason } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
9
56
|
/** Constant-time token compare (avoids leaking the token via timing). */
|
|
10
57
|
function tokenEq(a, b) {
|
|
11
58
|
const ba = Buffer.from(a), bb = Buffer.from(b);
|
|
12
59
|
return ba.length === bb.length && timingSafeEqual(ba, bb);
|
|
13
60
|
}
|
|
61
|
+
function cookieValue(header, name) {
|
|
62
|
+
for (const part of (header ?? '').split(';')) {
|
|
63
|
+
const at = part.indexOf('=');
|
|
64
|
+
if (at < 0 || part.slice(0, at).trim() !== name)
|
|
65
|
+
continue;
|
|
66
|
+
return part.slice(at + 1).trim();
|
|
67
|
+
}
|
|
68
|
+
return '';
|
|
69
|
+
}
|
|
14
70
|
function positiveIntParam(value) {
|
|
15
71
|
if (value === null || value.trim() === '')
|
|
16
72
|
return undefined;
|
|
17
73
|
const n = Number(value);
|
|
18
74
|
return Number.isFinite(n) && n > 0 ? Math.floor(n) : undefined;
|
|
19
75
|
}
|
|
76
|
+
function requireGet(req, res) {
|
|
77
|
+
if (req.method === 'GET')
|
|
78
|
+
return true;
|
|
79
|
+
res.writeHead(405, { allow: 'GET', 'content-type': 'application/json' });
|
|
80
|
+
res.end(JSON.stringify({ error: 'method_not_allowed' }));
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
20
83
|
const HUMAN_ACTIONS = {
|
|
21
84
|
observe: 'actor.human.observe', nudge: 'actor.human.nudge', intervene: 'actor.human.intervene', teach: 'actor.human.teach',
|
|
22
85
|
};
|
|
@@ -33,62 +96,188 @@ export class WebUIServer {
|
|
|
33
96
|
actorId;
|
|
34
97
|
/** Review ledger backing the human-review queue. Undefined when no LocalJsonlProvider store is available. */
|
|
35
98
|
review;
|
|
36
|
-
|
|
99
|
+
provenance;
|
|
100
|
+
/** Token guarding /api/*; supplied by the browser via Bearer, with ?token= retained for compatibility. */
|
|
37
101
|
token;
|
|
102
|
+
launchTicket;
|
|
103
|
+
launchTicketAvailable = true;
|
|
104
|
+
eventSnapshots;
|
|
38
105
|
constructor(deps) {
|
|
39
106
|
this.deps = deps;
|
|
40
107
|
this.host = deps.host ?? '127.0.0.1';
|
|
41
108
|
this.now = deps.now ?? (() => Date.now());
|
|
42
109
|
this.actorId = deps.actorId ?? 'console';
|
|
43
110
|
this.token = deps.token ?? randomBytes(16).toString('hex');
|
|
111
|
+
this.launchTicket = deps.launchTicket ?? randomBytes(16).toString('hex');
|
|
112
|
+
this.eventSnapshots = new EventSnapshotCache(deps.eventSource ?? fileEventSnapshotSource(deps.eventsPath));
|
|
44
113
|
this.ingestor = deps.ingestor ?? new ev.Ingestor({ path: deps.eventsPath });
|
|
45
114
|
// Co-locate the review ledger with the store so the queue reads the same review.jsonl the CLI writes.
|
|
46
115
|
this.review = deps.review ?? (deps.store instanceof assetstore.LocalJsonlProvider ? new assetstore.ReviewLedger(deps.store.baseDir) : undefined);
|
|
116
|
+
this.provenance = deps.provenance ?? (deps.store instanceof assetstore.LocalJsonlProvider ? new assetstore.ProvenanceStore(deps.store.baseDir) : undefined);
|
|
47
117
|
this.server = createServer((req, res) => { void this.handle(req, res); });
|
|
48
118
|
}
|
|
49
|
-
listen(port = 0) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
119
|
+
async listen(port = 0) {
|
|
120
|
+
if (port !== 0 && BROWSER_BLOCKED_PORTS.has(port))
|
|
121
|
+
throw new Error('webui_port_blocked');
|
|
122
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
123
|
+
const assigned = await this.listenOnce(port);
|
|
124
|
+
if (port !== 0 || !BROWSER_BLOCKED_PORTS.has(assigned))
|
|
125
|
+
return assigned;
|
|
126
|
+
await this.close();
|
|
127
|
+
}
|
|
128
|
+
throw new Error('webui_safe_port_unavailable');
|
|
129
|
+
}
|
|
130
|
+
listenOnce(port) {
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
const onError = (error) => reject(error);
|
|
133
|
+
this.server.once('error', onError);
|
|
134
|
+
this.server.listen(port, this.host, () => {
|
|
135
|
+
this.server.removeListener('error', onError);
|
|
136
|
+
const a = this.server.address();
|
|
137
|
+
resolve(a && typeof a === 'object' ? a.port : port);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
close() {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
this.server.close((error) => (error ? reject(error) : resolve()));
|
|
144
|
+
this.server.closeAllConnections();
|
|
145
|
+
});
|
|
54
146
|
}
|
|
55
|
-
close() { return new Promise((res, rej) => this.server.close((e) => (e ? rej(e) : res()))); }
|
|
56
147
|
async handle(req, res) {
|
|
57
148
|
try {
|
|
58
149
|
if (!LOOPBACK.has(req.socket.remoteAddress ?? ''))
|
|
59
150
|
return this.send(res, 403, 'text/plain', 'non-loopback');
|
|
60
151
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
61
152
|
const p = url.pathname;
|
|
153
|
+
if (p === '/launch') {
|
|
154
|
+
if (req.method !== 'GET') {
|
|
155
|
+
res.writeHead(405, { allow: 'GET', 'content-type': 'text/plain' });
|
|
156
|
+
res.end('method not allowed');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const ticket = url.searchParams.get('ticket') ?? '';
|
|
160
|
+
if (!this.launchTicketAvailable || !tokenEq(ticket, this.launchTicket)) {
|
|
161
|
+
return this.send(res, 401, 'text/plain', 'unauthorized');
|
|
162
|
+
}
|
|
163
|
+
this.launchTicketAvailable = false;
|
|
164
|
+
res.writeHead(302, {
|
|
165
|
+
location: '/',
|
|
166
|
+
'set-cookie': `${DASHBOARD_COOKIE}=${this.token}; HttpOnly; SameSite=Strict; Path=/`,
|
|
167
|
+
'cache-control': 'no-store',
|
|
168
|
+
'referrer-policy': 'no-referrer',
|
|
169
|
+
});
|
|
170
|
+
res.end();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
62
173
|
// Root HTML is a static shell (no data) → served freely; it reads ?token= and authenticates the /api calls.
|
|
63
174
|
if (p === '/' || p === '/index.html')
|
|
64
175
|
return this.send(res, 200, 'text/html; charset=utf-8', CONSOLE_HTML);
|
|
65
176
|
// Everything else (all /api/*) requires the token — accepted via Bearer header or ?token= (browser convenience).
|
|
66
177
|
const auth = req.headers['authorization'] ?? '';
|
|
67
|
-
const
|
|
68
|
-
|
|
178
|
+
const bearer = auth.startsWith('Bearer ') ? auth.slice(7) : '';
|
|
179
|
+
const queryToken = url.searchParams.get('token') ?? '';
|
|
180
|
+
const cookieToken = cookieValue(req.headers.cookie, DASHBOARD_COOKIE);
|
|
181
|
+
const bearerValid = tokenEq(bearer, this.token);
|
|
182
|
+
const queryValid = tokenEq(queryToken, this.token);
|
|
183
|
+
const cookieValid = tokenEq(cookieToken, this.token);
|
|
184
|
+
if (!bearerValid && !queryValid && !cookieValid) {
|
|
69
185
|
return this.send(res, 401, 'application/json', JSON.stringify({ error: 'unauthorized' }));
|
|
186
|
+
}
|
|
187
|
+
const stateChanging = req.method !== 'GET' && req.method !== 'HEAD';
|
|
188
|
+
if (stateChanging && cookieValid && !bearerValid && !queryValid) {
|
|
189
|
+
const expectedOrigin = req.headers.host ? `http://${req.headers.host}` : '';
|
|
190
|
+
if (!expectedOrigin || req.headers.origin !== expectedOrigin) {
|
|
191
|
+
return this.send(res, 403, 'application/json', JSON.stringify({ error: 'same_origin_required' }));
|
|
192
|
+
}
|
|
193
|
+
if (!(req.headers['content-type'] ?? '').toLowerCase().startsWith('application/json')) {
|
|
194
|
+
return this.send(res, 415, 'application/json', JSON.stringify({ error: 'json_required' }));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
70
197
|
if (p === '/api/status')
|
|
71
|
-
return this.json(res, ev.statusReport(
|
|
198
|
+
return this.json(res, ev.statusReport(await this.eventSnapshots.read()));
|
|
72
199
|
if (p === '/api/cycles')
|
|
73
|
-
return this.json(res, ev.listCycles(
|
|
74
|
-
if (p === '/api/cycle')
|
|
75
|
-
|
|
200
|
+
return this.json(res, ev.listCycles(await this.eventSnapshots.read()));
|
|
201
|
+
if (p === '/api/cycle') {
|
|
202
|
+
const cycle = ev.showCycle(await this.eventSnapshots.read(), url.searchParams.get('id') ?? '');
|
|
203
|
+
const safeTimeline = cycle.timeline.map((event) => ({
|
|
204
|
+
...event,
|
|
205
|
+
title: redactDiagnosticText(event.title),
|
|
206
|
+
...(event.why ? { why: redactDiagnosticText(event.why) } : {}),
|
|
207
|
+
...(event.payload ? { payload: sanitizeDiagnosticValue(event.payload) } : {}),
|
|
208
|
+
}));
|
|
209
|
+
const relationEvents = cycle.timeline.map((event) => ({
|
|
210
|
+
seq: event.seq, type: event.type, ts: event.ts, payload: event.payload,
|
|
211
|
+
human: { title: event.title, ...(event.why ? { why: event.why } : {}) },
|
|
212
|
+
}));
|
|
213
|
+
return this.json(res, { ...cycle, timeline: safeTimeline, relations: eventListRelations(relationEvents) });
|
|
214
|
+
}
|
|
76
215
|
if (p === '/api/narrative') {
|
|
77
|
-
return this.json(res, ev.buildNarrativeSnapshot(
|
|
216
|
+
return this.json(res, ev.buildNarrativeSnapshot(await this.eventSnapshots.read(), { limit: positiveIntParam(url.searchParams.get('limit')) }));
|
|
78
217
|
}
|
|
79
218
|
if (p === '/api/triggers')
|
|
80
|
-
return this.json(res, ev.listTriggers(
|
|
219
|
+
return this.json(res, ev.listTriggers(await this.eventSnapshots.read()));
|
|
81
220
|
if (p === '/api/daily-summary') {
|
|
82
221
|
const day = url.searchParams.get('day') ?? new Date(this.now()).toISOString().slice(0, 10);
|
|
83
|
-
return this.json(res, ev.dailySummary(
|
|
222
|
+
return this.json(res, ev.dailySummary(await this.eventSnapshots.read(), day));
|
|
84
223
|
}
|
|
85
224
|
if (p === '/api/value') {
|
|
86
225
|
// Thin pass-through: the provider (composition layer) owns prices + traces; the server only scopes the
|
|
87
226
|
// window and serializes. No provider wired → an empty summary so the card renders "no savings yet".
|
|
88
227
|
const window = ops.windowFromSpec(url.searchParams.get('window') ?? undefined, this.now());
|
|
89
|
-
const summary = this.deps.valueSummary ? this.deps.valueSummary(window) : EMPTY_VALUE_SUMMARY;
|
|
228
|
+
const summary = this.deps.valueSummary ? this.deps.valueSummary(window, await this.eventSnapshots.read()) : EMPTY_VALUE_SUMMARY;
|
|
90
229
|
return this.json(res, summary);
|
|
91
230
|
}
|
|
231
|
+
if (p === '/api/personality') {
|
|
232
|
+
if (!requireGet(req, res))
|
|
233
|
+
return;
|
|
234
|
+
return this.json(res, this.deps.personalityDiagnostics
|
|
235
|
+
? await this.deps.personalityDiagnostics()
|
|
236
|
+
: { available: false, error: 'personality_unavailable' });
|
|
237
|
+
}
|
|
238
|
+
if (p === '/api/memory-graph') {
|
|
239
|
+
if (!requireGet(req, res))
|
|
240
|
+
return;
|
|
241
|
+
if (!this.deps.memoryGraphStatus)
|
|
242
|
+
return this.json(res, { available: false });
|
|
243
|
+
try {
|
|
244
|
+
const status = sanitizeMemoryGraphStatus(this.deps.memoryGraphStatus());
|
|
245
|
+
return this.json(res, { available: true, ...status });
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return this.send(res, 503, 'application/json', JSON.stringify({
|
|
249
|
+
available: false,
|
|
250
|
+
error: 'memory_graph_unavailable',
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (p === '/api/logs') {
|
|
255
|
+
if (!requireGet(req, res))
|
|
256
|
+
return;
|
|
257
|
+
return this.json(res, this.deps.logDiagnostics
|
|
258
|
+
? await this.deps.logDiagnostics()
|
|
259
|
+
: { available: false, error: 'logs_unavailable' });
|
|
260
|
+
}
|
|
261
|
+
if (p === '/api/github-prs') {
|
|
262
|
+
if (!requireGet(req, res))
|
|
263
|
+
return;
|
|
264
|
+
return this.json(res, this.deps.githubPrDiagnostics
|
|
265
|
+
? await this.deps.githubPrDiagnostics()
|
|
266
|
+
: { available: false, error: 'github_prs_unavailable' });
|
|
267
|
+
}
|
|
268
|
+
if (p === '/api/retention') {
|
|
269
|
+
if (!this.deps.retentionReport)
|
|
270
|
+
return this.json(res, { available: false });
|
|
271
|
+
try {
|
|
272
|
+
return this.json(res, { ...this.deps.retentionReport(), available: true });
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return this.send(res, 503, 'application/json', JSON.stringify({
|
|
276
|
+
available: false,
|
|
277
|
+
error: 'retention_unavailable',
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
92
281
|
if (p === '/api/mailbox') {
|
|
93
282
|
if (!this.deps.mailbox)
|
|
94
283
|
return this.json(res, { pending: 0, dlq: 0, messages: [] });
|
|
@@ -104,6 +293,31 @@ export class WebUIServer {
|
|
|
104
293
|
const kind = url.searchParams.get('kind');
|
|
105
294
|
return this.json(res, (await this.deps.store.list(kind ?? undefined, 100)).map((a) => ({ asset_id: a.asset_id, type: a.type, summary: a.summary })));
|
|
106
295
|
}
|
|
296
|
+
if (p === '/api/asset-lineage/assets') {
|
|
297
|
+
if (!requireGet(req, res))
|
|
298
|
+
return;
|
|
299
|
+
return this.json(res, await listLineageAssets(this.deps.store, {
|
|
300
|
+
page: positiveIntParam(url.searchParams.get('page')),
|
|
301
|
+
pageSize: positiveIntParam(url.searchParams.get('pageSize')),
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
if (p === '/api/asset-lineage') {
|
|
305
|
+
if (!requireGet(req, res))
|
|
306
|
+
return;
|
|
307
|
+
return this.json(res, await loadAssetLineage({
|
|
308
|
+
store: this.deps.store,
|
|
309
|
+
events: () => this.eventSnapshots.read(),
|
|
310
|
+
review: this.review,
|
|
311
|
+
provenance: this.provenance,
|
|
312
|
+
}, url.searchParams.get('id') ?? '', {
|
|
313
|
+
page: positiveIntParam(url.searchParams.get('page')),
|
|
314
|
+
pageSize: positiveIntParam(url.searchParams.get('pageSize')),
|
|
315
|
+
capsulePage: positiveIntParam(url.searchParams.get('capsulePage')),
|
|
316
|
+
capsulePageSize: positiveIntParam(url.searchParams.get('capsulePageSize')),
|
|
317
|
+
eventPage: positiveIntParam(url.searchParams.get('eventPage')),
|
|
318
|
+
eventPageSize: positiveIntParam(url.searchParams.get('eventPageSize')),
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
107
321
|
if (p === '/api/review' && req.method === 'POST') {
|
|
108
322
|
// Approve/reject a quarantined draft from the console: the SAME gate the CLI uses — flip the ReviewLedger
|
|
109
323
|
// state + record an audited actor.human.review.* event. Loopback + bearer already guard the route; actor is
|
|
@@ -143,7 +357,7 @@ export class WebUIServer {
|
|
|
143
357
|
// record) are appended as context from a bounded list. Pending (quarantined) sorts first. Read-only here.
|
|
144
358
|
// Match the CLI's semantics exactly: only the unattended distill-observer counts as "auto-drafted"
|
|
145
359
|
// (manual `ingest --distill` / skill2gep emit gene.distilled with a different source and are NOT auto).
|
|
146
|
-
const autoDrafted = new Set(this.
|
|
360
|
+
const autoDrafted = new Set((await this.eventSnapshots.read())
|
|
147
361
|
.filter((e) => e.type === 'gene.distilled' && e.payload?.['source'] === 'distill-observer')
|
|
148
362
|
.map((e) => String(e.payload?.['assetId'] ?? ''))
|
|
149
363
|
.filter(Boolean));
|
|
@@ -178,8 +392,8 @@ export class WebUIServer {
|
|
|
178
392
|
}
|
|
179
393
|
return this.send(res, 404, 'application/json', JSON.stringify({ error: 'not found' }));
|
|
180
394
|
}
|
|
181
|
-
catch
|
|
182
|
-
return this.send(res, 500, 'application/json', JSON.stringify({ error:
|
|
395
|
+
catch {
|
|
396
|
+
return this.send(res, 500, 'application/json', JSON.stringify({ error: 'dashboard_request_failed' }));
|
|
183
397
|
}
|
|
184
398
|
}
|
|
185
399
|
readJson(req) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver-webui",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "保活/可视化 WebUI",
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@evomap/evolver-core": "2.0.0-beta.
|
|
16
|
+
"@evomap/evolver-core": "2.0.0-beta.4"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/EvoMap/evolver.git"
|
|
17
21
|
},
|
|
18
22
|
"publishConfig": {
|
|
19
23
|
"access": "public",
|
|
@@ -21,6 +25,7 @@
|
|
|
21
25
|
},
|
|
22
26
|
"files": [
|
|
23
27
|
"dist/",
|
|
28
|
+
"assets/",
|
|
24
29
|
"README.md",
|
|
25
30
|
"package.json"
|
|
26
31
|
]
|