aios-management-web 0.1.0
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/.env.json +21 -0
- package/README.md +257 -0
- package/data/management-console.db +0 -0
- package/data/management-console.db-shm +0 -0
- package/data/management-console.db-wal +0 -0
- package/dist/assets/index-CV_wjCAG.js +464 -0
- package/dist/assets/index-DfMPB0eV.css +1 -0
- package/dist/index.html +13 -0
- package/docs/spec.md +199 -0
- package/index.html +12 -0
- package/package.json +37 -0
- package/scripts/reset-kernel.js +59 -0
- package/scripts/reset-password.js +22 -0
- package/server/fakes.js +57 -0
- package/server/index.js +21 -0
- package/server/src/api/middleware/auth.js +29 -0
- package/server/src/api/middleware/internal.js +44 -0
- package/server/src/api/routes/index.js +677 -0
- package/server/src/app.js +90 -0
- package/server/src/background/index.js +106 -0
- package/server/src/background/protocol.js +15 -0
- package/server/src/config/env.js +90 -0
- package/server/src/db/index.js +501 -0
- package/server/src/infra/mqtt/management-rpc-client.js +213 -0
- package/server/src/infra/providers/hzg-provider-client.js +39 -0
- package/server/src/infra/s3/object-storage.js +97 -0
- package/server/src/services/agent-quota.js +54 -0
- package/server/src/services/agent-service.js +696 -0
- package/server/src/services/agent-status-sync-service.js +132 -0
- package/server/src/services/audit-log-service.js +39 -0
- package/server/src/services/auth-service.js +153 -0
- package/server/src/services/catalog-sync-service.js +712 -0
- package/server/src/services/external-service.js +308 -0
- package/server/src/services/kernel-reset-service.js +86 -0
- package/server/src/services/portal-service.js +555 -0
- package/server/src/services/system-service.js +580 -0
- package/server/src/services/topic-ping-service.js +282 -0
- package/server/src/utils/errors.js +36 -0
- package/server/src/utils/security.js +22 -0
- package/server/test/agent-service-alignment.test.js +316 -0
- package/server/test/agent-service-create.test.js +662 -0
- package/server/test/agent-status-sync-service.test.js +167 -0
- package/server/test/agent-update-audit.test.js +63 -0
- package/server/test/auth-middleware.test.js +71 -0
- package/server/test/background-services.test.js +160 -0
- package/server/test/catalog-sync-service.test.js +920 -0
- package/server/test/db-reset-migration.test.js +123 -0
- package/server/test/env-config.test.js +68 -0
- package/server/test/external-service.test.js +380 -0
- package/server/test/hzg-provider-client.test.js +50 -0
- package/server/test/internal-auth-middleware.test.js +66 -0
- package/server/test/kernel-reset-service.test.js +112 -0
- package/server/test/management-rpc-client.test.js +105 -0
- package/server/test/portal-service-access-tokens.test.js +121 -0
- package/server/test/portal-service-alignment.test.js +318 -0
- package/server/test/portal-service-management-logs.test.js +114 -0
- package/server/test/reset-kernel-cli.test.js +23 -0
- package/server/test/service-api-auth-middleware.test.js +59 -0
- package/server/test/system-service-alignment.test.js +265 -0
- package/server/test/topic-ping-service.test.js +182 -0
- package/server/test/usage-refresh-audit-route.test.js +82 -0
- package/src/App.jsx +1 -0
- package/src/api.js +1 -0
- package/src/app/App.jsx +346 -0
- package/src/app/api-client.js +112 -0
- package/src/components/AppShell.jsx +117 -0
- package/src/components/CardTitleWithReload.jsx +20 -0
- package/src/components/DeleteActionButton.jsx +31 -0
- package/src/main.jsx +14 -0
- package/src/pages/AgentsPage.jsx +647 -0
- package/src/pages/AiosUsersPage.jsx +151 -0
- package/src/pages/DashboardPage.jsx +72 -0
- package/src/pages/LoginPage.jsx +41 -0
- package/src/pages/SettingsPage.jsx +431 -0
- package/src/pages/SkillsPage.jsx +175 -0
- package/src/pages/SystemLogsPage.jsx +349 -0
- package/src/pages/SystemsPage.jsx +498 -0
- package/src/pages/TemplatesPage.jsx +207 -0
- package/src/pages/UserManagementPage.jsx +25 -0
- package/src/pages/UsersPage.jsx +192 -0
- package/src/pages/system-logs/SystemLogsTabs.jsx +362 -0
- package/src/styles.css +222 -0
- package/src/utils/format.js +63 -0
- package/test/.reports/fast-2026-05-25T08-32-39-420Z.json +299 -0
- package/test/integration/common.js +208 -0
- package/test/integration/fast.js +135 -0
- package/test/integration/full.js +306 -0
- package/test/run-browser-e2e.js +212 -0
- package/test/run-jasmine.js +21 -0
- package/test/setup.js +1 -0
- package/vite.config.js +12 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import AdmZip from "adm-zip";
|
|
2
|
+
|
|
3
|
+
import { jsonParse } from "../db/index.js";
|
|
4
|
+
import { badRequest, conflict, notFound } from "../utils/errors.js";
|
|
5
|
+
|
|
6
|
+
const APPLICATION_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
7
|
+
const VALID_SCHEMES = new Set(["http", "https"]);
|
|
8
|
+
|
|
9
|
+
function normalizeText(value) {
|
|
10
|
+
return String(value || "").trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizePort(value) {
|
|
14
|
+
const port = Number(value);
|
|
15
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
16
|
+
throw badRequest("端口号必须是 1-65535 的整数");
|
|
17
|
+
}
|
|
18
|
+
return port;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeScheme(value) {
|
|
22
|
+
const scheme = normalizeText(value).toLowerCase();
|
|
23
|
+
if (!VALID_SCHEMES.has(scheme)) {
|
|
24
|
+
throw badRequest("协议仅支持 http 或 https");
|
|
25
|
+
}
|
|
26
|
+
return scheme;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeHost(value) {
|
|
30
|
+
const host = normalizeText(value);
|
|
31
|
+
if (!host) {
|
|
32
|
+
throw badRequest("主机名不能为空");
|
|
33
|
+
}
|
|
34
|
+
if (host.includes("/") || host.includes("\\") || host.includes("?") || host.includes("#")) {
|
|
35
|
+
throw badRequest("主机名格式不合法");
|
|
36
|
+
}
|
|
37
|
+
return host;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeApplicationName(value) {
|
|
41
|
+
const applicationName = normalizeText(value).toLowerCase();
|
|
42
|
+
if (!applicationName) {
|
|
43
|
+
throw badRequest("应用名不能为空");
|
|
44
|
+
}
|
|
45
|
+
if (!APPLICATION_NAME_PATTERN.test(applicationName)) {
|
|
46
|
+
throw badRequest("应用名需符合 Ubuntu 目录名规则:仅允许小写字母、数字和中划线,且不能以中划线开头或结尾");
|
|
47
|
+
}
|
|
48
|
+
return applicationName;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function hasOwn(payload, key) {
|
|
52
|
+
return Object.prototype.hasOwnProperty.call(payload || {}, key);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildBaseUrl(scheme, host, port, applicationName) {
|
|
56
|
+
return `${scheme}://${host}:${port}/${applicationName}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function decorateSystemRow(row) {
|
|
60
|
+
if (!row) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const baseUrl = buildBaseUrl(row.scheme, row.host, row.port, row.application_name);
|
|
65
|
+
return {
|
|
66
|
+
...row,
|
|
67
|
+
is_builtin: Boolean(row.is_builtin),
|
|
68
|
+
ontology_name: row.application_name,
|
|
69
|
+
system_id: row.application_name,
|
|
70
|
+
service_endpoint: baseUrl,
|
|
71
|
+
session_endpoint: baseUrl,
|
|
72
|
+
base_url: baseUrl,
|
|
73
|
+
last_connectivity_test_result: jsonParse(row.last_connectivity_test_result_json, {})
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class SystemService {
|
|
78
|
+
constructor({ db, objectStorage, rpcClient }) {
|
|
79
|
+
this.db = db;
|
|
80
|
+
this.objectStorage = objectStorage;
|
|
81
|
+
this.rpcClient = rpcClient;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
listSystems() {
|
|
85
|
+
return this.db.prepare(`
|
|
86
|
+
SELECT
|
|
87
|
+
s.*,
|
|
88
|
+
a.original_name AS ontology_file_name
|
|
89
|
+
FROM business_systems s
|
|
90
|
+
LEFT JOIN artifacts a ON a.id = s.ontology_artifact_id
|
|
91
|
+
ORDER BY s.updated_at DESC
|
|
92
|
+
`).all().map(decorateSystemRow);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
getSystemById(id) {
|
|
96
|
+
return decorateSystemRow(
|
|
97
|
+
this.db.prepare(`
|
|
98
|
+
SELECT
|
|
99
|
+
s.*,
|
|
100
|
+
a.original_name AS ontology_file_name
|
|
101
|
+
FROM business_systems s
|
|
102
|
+
LEFT JOIN artifacts a ON a.id = s.ontology_artifact_id
|
|
103
|
+
WHERE s.id = ?
|
|
104
|
+
`).get(id)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getInvocationBaseUrlByApplicationName(applicationName) {
|
|
109
|
+
const normalizedApplicationName = normalizeApplicationName(applicationName);
|
|
110
|
+
const row = this.db.prepare(`
|
|
111
|
+
SELECT *
|
|
112
|
+
FROM business_systems
|
|
113
|
+
WHERE application_name = ? AND status = 'active'
|
|
114
|
+
`).get(normalizedApplicationName);
|
|
115
|
+
if (!row) {
|
|
116
|
+
throw notFound(`未找到业务系统:${normalizedApplicationName}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
application_name: row.application_name,
|
|
121
|
+
provider: row.provider,
|
|
122
|
+
baseUrl: buildBaseUrl(row.scheme, row.host, row.port, row.application_name),
|
|
123
|
+
scheme: row.scheme,
|
|
124
|
+
host: row.host,
|
|
125
|
+
port: row.port,
|
|
126
|
+
status: row.status
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
findLatestActiveSystemByProvider(provider) {
|
|
131
|
+
const row = this.db.prepare(`
|
|
132
|
+
SELECT *
|
|
133
|
+
FROM business_systems
|
|
134
|
+
WHERE provider = ? AND status = 'active'
|
|
135
|
+
ORDER BY updated_at DESC, id DESC
|
|
136
|
+
LIMIT 1
|
|
137
|
+
`).get(normalizeText(provider).toLowerCase());
|
|
138
|
+
return decorateSystemRow(row);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
validateUniqueApplicationName(applicationName, excludeId = null) {
|
|
142
|
+
const existing = excludeId === null
|
|
143
|
+
? this.db.prepare(`
|
|
144
|
+
SELECT id
|
|
145
|
+
FROM business_systems
|
|
146
|
+
WHERE application_name = ?
|
|
147
|
+
`).get(applicationName)
|
|
148
|
+
: this.db.prepare(`
|
|
149
|
+
SELECT id
|
|
150
|
+
FROM business_systems
|
|
151
|
+
WHERE application_name = ? AND id <> ?
|
|
152
|
+
`).get(applicationName, excludeId);
|
|
153
|
+
|
|
154
|
+
if (existing) {
|
|
155
|
+
throw conflict(`应用名已存在:${applicationName}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
normalizeCreatePayload(payload) {
|
|
160
|
+
const provider = normalizeText(payload.provider).toLowerCase();
|
|
161
|
+
const applicationName = normalizeApplicationName(payload.application_name);
|
|
162
|
+
if (!provider || !applicationName) {
|
|
163
|
+
throw badRequest("提供方和应用名不能为空");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
provider,
|
|
168
|
+
application_name: applicationName,
|
|
169
|
+
description: normalizeText(payload.description),
|
|
170
|
+
scheme: normalizeScheme(payload.scheme),
|
|
171
|
+
host: normalizeHost(payload.host),
|
|
172
|
+
port: normalizePort(payload.port),
|
|
173
|
+
status: normalizeText(payload.status) === "disabled" ? "disabled" : "active"
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
normalizeUpdatePayload(payload, current) {
|
|
178
|
+
const provider = hasOwn(payload, "provider") ? normalizeText(payload.provider).toLowerCase() : current.provider;
|
|
179
|
+
const applicationName = hasOwn(payload, "application_name")
|
|
180
|
+
? normalizeApplicationName(payload.application_name)
|
|
181
|
+
: current.application_name;
|
|
182
|
+
if (!provider || !applicationName) {
|
|
183
|
+
throw badRequest("提供方和应用名不能为空");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
provider,
|
|
188
|
+
application_name: applicationName,
|
|
189
|
+
description: hasOwn(payload, "description") ? normalizeText(payload.description) : current.description,
|
|
190
|
+
scheme: hasOwn(payload, "scheme") ? normalizeScheme(payload.scheme) : current.scheme,
|
|
191
|
+
host: hasOwn(payload, "host") ? normalizeHost(payload.host) : current.host,
|
|
192
|
+
port: hasOwn(payload, "port") ? normalizePort(payload.port) : current.port,
|
|
193
|
+
status: hasOwn(payload, "status")
|
|
194
|
+
? (normalizeText(payload.status) === "disabled" ? "disabled" : "active")
|
|
195
|
+
: current.status
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
getArtifactStorageInfo(artifactId) {
|
|
200
|
+
if (!artifactId) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return this.db.prepare(`
|
|
205
|
+
SELECT bucket, object_key
|
|
206
|
+
FROM artifacts
|
|
207
|
+
WHERE id = ?
|
|
208
|
+
`).get(artifactId) || null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async listRemoteApplicationNames() {
|
|
212
|
+
const remote = await this.rpcClient.call("apps.list", {});
|
|
213
|
+
const items = Array.isArray(remote?.items) ? remote.items : [];
|
|
214
|
+
const names = new Set();
|
|
215
|
+
|
|
216
|
+
for (const item of items) {
|
|
217
|
+
const applicationName = normalizeText(
|
|
218
|
+
item?.applicationName || item?.id || item?.name || item?.ontologyName
|
|
219
|
+
).toLowerCase();
|
|
220
|
+
if (applicationName) {
|
|
221
|
+
names.add(applicationName);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return names;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async deleteRemoteOntologiesIfPresent(applicationNames, { ignoreDeleteErrors = false } = {}) {
|
|
229
|
+
const normalizedNames = Array.from(new Set(
|
|
230
|
+
applicationNames
|
|
231
|
+
.map((value) => normalizeText(value).toLowerCase())
|
|
232
|
+
.filter(Boolean)
|
|
233
|
+
));
|
|
234
|
+
if (normalizedNames.length === 0) {
|
|
235
|
+
return {
|
|
236
|
+
deletedApplicationNames: []
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const remoteNames = await this.listRemoteApplicationNames();
|
|
241
|
+
const deletedApplicationNames = [];
|
|
242
|
+
|
|
243
|
+
for (const applicationName of normalizedNames) {
|
|
244
|
+
if (!remoteNames.has(applicationName)) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
await this.rpcClient.call("apps.delete", { systemId: applicationName });
|
|
250
|
+
deletedApplicationNames.push(applicationName);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (!ignoreDeleteErrors) {
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { deletedApplicationNames };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async createSystem({ payload, file, createdBy }) {
|
|
262
|
+
const normalized = this.normalizeCreatePayload(payload);
|
|
263
|
+
this.validateUniqueApplicationName(normalized.application_name);
|
|
264
|
+
|
|
265
|
+
let artifactId = null;
|
|
266
|
+
let bucket = null;
|
|
267
|
+
let objectKey = null;
|
|
268
|
+
if (file) {
|
|
269
|
+
const artifact = await this.persistOntologyArtifact({ file, createdBy });
|
|
270
|
+
artifactId = artifact.id;
|
|
271
|
+
bucket = artifact.bucket;
|
|
272
|
+
objectKey = artifact.objectKey;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const { deletedApplicationNames } = await this.deleteRemoteOntologiesIfPresent([
|
|
276
|
+
normalized.application_name
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
const baseUrl = buildBaseUrl(
|
|
280
|
+
normalized.scheme,
|
|
281
|
+
normalized.host,
|
|
282
|
+
normalized.port,
|
|
283
|
+
normalized.application_name
|
|
284
|
+
);
|
|
285
|
+
await this.rpcClient.call("apps.create", {
|
|
286
|
+
systemId: normalized.application_name,
|
|
287
|
+
name: normalized.application_name,
|
|
288
|
+
provider: normalized.provider,
|
|
289
|
+
ontologyName: normalized.application_name,
|
|
290
|
+
description: normalized.description,
|
|
291
|
+
applicationEndpoint: baseUrl,
|
|
292
|
+
sessionServiceEndpoint: baseUrl,
|
|
293
|
+
status: normalized.status,
|
|
294
|
+
bucket,
|
|
295
|
+
objectKey,
|
|
296
|
+
replaceOntology: Boolean(bucket && objectKey)
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const now = new Date().toISOString();
|
|
300
|
+
const result = this.db.prepare(`
|
|
301
|
+
INSERT INTO business_systems (
|
|
302
|
+
provider, application_name, description, ontology_artifact_id,
|
|
303
|
+
scheme, host, port, status, last_connectivity_test_status, last_connectivity_test_result_json,
|
|
304
|
+
created_at, updated_at
|
|
305
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
306
|
+
`).run(
|
|
307
|
+
normalized.provider,
|
|
308
|
+
normalized.application_name,
|
|
309
|
+
normalized.description,
|
|
310
|
+
artifactId,
|
|
311
|
+
normalized.scheme,
|
|
312
|
+
normalized.host,
|
|
313
|
+
normalized.port,
|
|
314
|
+
normalized.status,
|
|
315
|
+
"unknown",
|
|
316
|
+
JSON.stringify({}),
|
|
317
|
+
now,
|
|
318
|
+
now
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
system: this.getSystemById(result.lastInsertRowid),
|
|
323
|
+
kernelOntologyDeleted: deletedApplicationNames.length > 0,
|
|
324
|
+
deletedKernelOntologyNames: deletedApplicationNames
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async persistOntologyArtifact({ file, createdBy }) {
|
|
329
|
+
const zip = new AdmZip(file.buffer);
|
|
330
|
+
const names = zip
|
|
331
|
+
.getEntries()
|
|
332
|
+
.filter((entry) => !entry.isDirectory)
|
|
333
|
+
.map((entry) => entry.entryName.replace(/^\/+/, ""));
|
|
334
|
+
if (!names.includes("index.md")) {
|
|
335
|
+
throw badRequest("Ontology 压缩包第一层必须包含 index.md");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const artifact = await this.objectStorage.uploadAdminArtifact({ kind: "ontology", file });
|
|
339
|
+
const result = this.db.prepare(`
|
|
340
|
+
INSERT INTO artifacts (
|
|
341
|
+
kind, bucket, object_key, original_name, mime_type, byte_size, created_by, created_at
|
|
342
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
343
|
+
`).run(
|
|
344
|
+
"ontology",
|
|
345
|
+
artifact.bucket,
|
|
346
|
+
artifact.objectKey,
|
|
347
|
+
file.originalname,
|
|
348
|
+
file.mimetype || "application/octet-stream",
|
|
349
|
+
file.size,
|
|
350
|
+
createdBy,
|
|
351
|
+
new Date().toISOString()
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
id: result.lastInsertRowid,
|
|
356
|
+
...artifact
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async updateSystem(id, { payload, file, createdBy }) {
|
|
361
|
+
const current = this.db.prepare("SELECT * FROM business_systems WHERE id = ?").get(id);
|
|
362
|
+
if (!current) {
|
|
363
|
+
throw notFound("业务系统不存在");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const normalized = this.normalizeUpdatePayload(payload, current);
|
|
367
|
+
this.validateUniqueApplicationName(normalized.application_name, id);
|
|
368
|
+
|
|
369
|
+
let artifactId = current.ontology_artifact_id;
|
|
370
|
+
let artifact;
|
|
371
|
+
if (file) {
|
|
372
|
+
artifact = await this.persistOntologyArtifact({ file, createdBy });
|
|
373
|
+
artifactId = artifact.id;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const existingArtifact = artifact || this.getArtifactStorageInfo(artifactId);
|
|
377
|
+
|
|
378
|
+
const baseUrl = buildBaseUrl(
|
|
379
|
+
normalized.scheme,
|
|
380
|
+
normalized.host,
|
|
381
|
+
normalized.port,
|
|
382
|
+
normalized.application_name
|
|
383
|
+
);
|
|
384
|
+
const previousSystemId = current.application_name;
|
|
385
|
+
const { deletedApplicationNames } = await this.deleteRemoteOntologiesIfPresent([
|
|
386
|
+
previousSystemId,
|
|
387
|
+
normalized.application_name
|
|
388
|
+
], { ignoreDeleteErrors: true });
|
|
389
|
+
|
|
390
|
+
await this.rpcClient.call("apps.create", {
|
|
391
|
+
systemId: normalized.application_name,
|
|
392
|
+
name: normalized.application_name,
|
|
393
|
+
provider: normalized.provider,
|
|
394
|
+
ontologyName: normalized.application_name,
|
|
395
|
+
description: normalized.description,
|
|
396
|
+
applicationEndpoint: baseUrl,
|
|
397
|
+
sessionServiceEndpoint: baseUrl,
|
|
398
|
+
status: normalized.status,
|
|
399
|
+
bucket: existingArtifact?.bucket,
|
|
400
|
+
objectKey: existingArtifact?.object_key || existingArtifact?.objectKey,
|
|
401
|
+
replaceOntology: Boolean(existingArtifact?.bucket && (existingArtifact?.object_key || existingArtifact?.objectKey))
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
this.db.prepare(`
|
|
405
|
+
UPDATE business_systems
|
|
406
|
+
SET provider = ?, application_name = ?, description = ?, ontology_artifact_id = ?,
|
|
407
|
+
scheme = ?, host = ?, port = ?, status = ?, updated_at = ?
|
|
408
|
+
WHERE id = ?
|
|
409
|
+
`).run(
|
|
410
|
+
normalized.provider,
|
|
411
|
+
normalized.application_name,
|
|
412
|
+
normalized.description,
|
|
413
|
+
artifactId,
|
|
414
|
+
normalized.scheme,
|
|
415
|
+
normalized.host,
|
|
416
|
+
normalized.port,
|
|
417
|
+
normalized.status,
|
|
418
|
+
new Date().toISOString(),
|
|
419
|
+
id
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
system: this.getSystemById(id),
|
|
424
|
+
kernelOntologyDeleted: deletedApplicationNames.length > 0,
|
|
425
|
+
deletedKernelOntologyNames: deletedApplicationNames
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async setSystemStatus(id, status) {
|
|
430
|
+
const current = this.db.prepare("SELECT * FROM business_systems WHERE id = ?").get(id);
|
|
431
|
+
if (!current) {
|
|
432
|
+
throw notFound("业务系统不存在");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const normalizedStatus = normalizeText(status) === "disabled" ? "disabled" : "active";
|
|
436
|
+
await this.rpcClient.call(normalizedStatus === "active" ? "apps.enable" : "apps.disable", {
|
|
437
|
+
systemId: current.application_name
|
|
438
|
+
});
|
|
439
|
+
this.db.prepare(`
|
|
440
|
+
UPDATE business_systems
|
|
441
|
+
SET status = ?, updated_at = ?
|
|
442
|
+
WHERE id = ?
|
|
443
|
+
`).run(normalizedStatus, new Date().toISOString(), id);
|
|
444
|
+
|
|
445
|
+
return this.getSystemById(id);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async deleteSystem(id) {
|
|
449
|
+
const current = this.db.prepare("SELECT * FROM business_systems WHERE id = ?").get(id);
|
|
450
|
+
if (!current) {
|
|
451
|
+
throw notFound("业务系统不存在");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (current.is_builtin) {
|
|
455
|
+
throw conflict("内置 ontology 不允许删除");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
await this.rpcClient.call("apps.delete", { systemId: current.application_name });
|
|
459
|
+
this.db.prepare("DELETE FROM business_systems WHERE id = ?").run(id);
|
|
460
|
+
return { ok: true };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async testConnectivity(id) {
|
|
464
|
+
const current = this.db.prepare("SELECT * FROM business_systems WHERE id = ?").get(id);
|
|
465
|
+
if (!current) {
|
|
466
|
+
throw notFound("业务系统不存在");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const baseUrl = buildBaseUrl(current.scheme, current.host, current.port, current.application_name);
|
|
470
|
+
const response = await fetch(baseUrl, { method: "GET" });
|
|
471
|
+
const ok = response.ok;
|
|
472
|
+
const result = {
|
|
473
|
+
ok,
|
|
474
|
+
baseUrl,
|
|
475
|
+
service: { status: response.status, url: baseUrl },
|
|
476
|
+
tested_at: new Date().toISOString()
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
this.db.prepare(`
|
|
480
|
+
UPDATE business_systems
|
|
481
|
+
SET last_connectivity_test_status = ?, last_connectivity_test_result_json = ?, updated_at = ?
|
|
482
|
+
WHERE id = ?
|
|
483
|
+
`).run(ok ? "ok" : "failed", JSON.stringify(result), new Date().toISOString(), id);
|
|
484
|
+
|
|
485
|
+
return result;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
findSystemForInvocation(provider, applicationName) {
|
|
489
|
+
return decorateSystemRow(this.db.prepare(`
|
|
490
|
+
SELECT *
|
|
491
|
+
FROM business_systems
|
|
492
|
+
WHERE provider = ? AND application_name = ? AND status = 'active'
|
|
493
|
+
`).get(normalizeText(provider).toLowerCase(), normalizeApplicationName(applicationName)));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async recordInvocation(log) {
|
|
497
|
+
this.db.prepare(`
|
|
498
|
+
INSERT INTO system_invocation_logs (
|
|
499
|
+
trace_id, agent_slug, session_id, provider, application_name,
|
|
500
|
+
command_name, request_payload_json, response_payload_json, response_time_ms,
|
|
501
|
+
success, error_message, created_at
|
|
502
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
503
|
+
`).run(
|
|
504
|
+
log.trace_id,
|
|
505
|
+
log.agent_slug || null,
|
|
506
|
+
log.session_id || null,
|
|
507
|
+
log.provider,
|
|
508
|
+
log.application_name,
|
|
509
|
+
log.command_name,
|
|
510
|
+
JSON.stringify(log.request_payload ?? {}),
|
|
511
|
+
JSON.stringify(log.response_payload ?? {}),
|
|
512
|
+
log.response_time_ms,
|
|
513
|
+
log.success ? 1 : 0,
|
|
514
|
+
log.error_message || null,
|
|
515
|
+
log.created_at || new Date().toISOString()
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
await this.rpcClient.call("apps.log.record", {
|
|
520
|
+
systemId: log.application_name,
|
|
521
|
+
agentId: log.agent_slug,
|
|
522
|
+
conversationId: log.session_id,
|
|
523
|
+
provider: log.provider,
|
|
524
|
+
applicationName: log.application_name,
|
|
525
|
+
commandName: log.command_name,
|
|
526
|
+
inputParams: log.request_payload,
|
|
527
|
+
responseTimeMs: log.response_time_ms,
|
|
528
|
+
success: log.success,
|
|
529
|
+
responseContent: log.response_payload
|
|
530
|
+
});
|
|
531
|
+
} catch {}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
listLogs({ applicationName = "", agentSlug = "" } = {}) {
|
|
535
|
+
return this.db.prepare(`
|
|
536
|
+
SELECT *
|
|
537
|
+
FROM system_invocation_logs
|
|
538
|
+
WHERE (? = '' OR application_name = ?)
|
|
539
|
+
AND (? = '' OR agent_slug = ?)
|
|
540
|
+
ORDER BY created_at DESC
|
|
541
|
+
LIMIT 200
|
|
542
|
+
`).all(
|
|
543
|
+
normalizeText(applicationName).toLowerCase(),
|
|
544
|
+
normalizeText(applicationName).toLowerCase(),
|
|
545
|
+
normalizeText(agentSlug),
|
|
546
|
+
normalizeText(agentSlug)
|
|
547
|
+
).map((row) => ({
|
|
548
|
+
...row,
|
|
549
|
+
system_id: row.application_name,
|
|
550
|
+
request_payload: jsonParse(row.request_payload_json, {}),
|
|
551
|
+
response_payload: jsonParse(row.response_payload_json, {})
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
listStats({ applicationName = "", agentSlug = "" } = {}) {
|
|
556
|
+
return this.db.prepare(`
|
|
557
|
+
SELECT
|
|
558
|
+
application_name,
|
|
559
|
+
provider,
|
|
560
|
+
command_name,
|
|
561
|
+
COUNT(*) AS call_count,
|
|
562
|
+
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) AS success_count,
|
|
563
|
+
ROUND(AVG(response_time_ms), 1) AS avg_response_ms
|
|
564
|
+
FROM system_invocation_logs
|
|
565
|
+
WHERE (? = '' OR application_name = ?)
|
|
566
|
+
AND (? = '' OR agent_slug = ?)
|
|
567
|
+
GROUP BY application_name, provider, command_name
|
|
568
|
+
ORDER BY call_count DESC, avg_response_ms ASC
|
|
569
|
+
`).all(
|
|
570
|
+
normalizeText(applicationName).toLowerCase(),
|
|
571
|
+
normalizeText(applicationName).toLowerCase(),
|
|
572
|
+
normalizeText(agentSlug),
|
|
573
|
+
normalizeText(agentSlug)
|
|
574
|
+
).map((row) => ({
|
|
575
|
+
...row,
|
|
576
|
+
system_id: row.application_name,
|
|
577
|
+
success_rate: row.call_count ? Number(((row.success_count / row.call_count) * 100).toFixed(1)) : 0
|
|
578
|
+
}));
|
|
579
|
+
}
|
|
580
|
+
}
|