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.
Files changed (91) hide show
  1. package/.env.json +21 -0
  2. package/README.md +257 -0
  3. package/data/management-console.db +0 -0
  4. package/data/management-console.db-shm +0 -0
  5. package/data/management-console.db-wal +0 -0
  6. package/dist/assets/index-CV_wjCAG.js +464 -0
  7. package/dist/assets/index-DfMPB0eV.css +1 -0
  8. package/dist/index.html +13 -0
  9. package/docs/spec.md +199 -0
  10. package/index.html +12 -0
  11. package/package.json +37 -0
  12. package/scripts/reset-kernel.js +59 -0
  13. package/scripts/reset-password.js +22 -0
  14. package/server/fakes.js +57 -0
  15. package/server/index.js +21 -0
  16. package/server/src/api/middleware/auth.js +29 -0
  17. package/server/src/api/middleware/internal.js +44 -0
  18. package/server/src/api/routes/index.js +677 -0
  19. package/server/src/app.js +90 -0
  20. package/server/src/background/index.js +106 -0
  21. package/server/src/background/protocol.js +15 -0
  22. package/server/src/config/env.js +90 -0
  23. package/server/src/db/index.js +501 -0
  24. package/server/src/infra/mqtt/management-rpc-client.js +213 -0
  25. package/server/src/infra/providers/hzg-provider-client.js +39 -0
  26. package/server/src/infra/s3/object-storage.js +97 -0
  27. package/server/src/services/agent-quota.js +54 -0
  28. package/server/src/services/agent-service.js +696 -0
  29. package/server/src/services/agent-status-sync-service.js +132 -0
  30. package/server/src/services/audit-log-service.js +39 -0
  31. package/server/src/services/auth-service.js +153 -0
  32. package/server/src/services/catalog-sync-service.js +712 -0
  33. package/server/src/services/external-service.js +308 -0
  34. package/server/src/services/kernel-reset-service.js +86 -0
  35. package/server/src/services/portal-service.js +555 -0
  36. package/server/src/services/system-service.js +580 -0
  37. package/server/src/services/topic-ping-service.js +282 -0
  38. package/server/src/utils/errors.js +36 -0
  39. package/server/src/utils/security.js +22 -0
  40. package/server/test/agent-service-alignment.test.js +316 -0
  41. package/server/test/agent-service-create.test.js +662 -0
  42. package/server/test/agent-status-sync-service.test.js +167 -0
  43. package/server/test/agent-update-audit.test.js +63 -0
  44. package/server/test/auth-middleware.test.js +71 -0
  45. package/server/test/background-services.test.js +160 -0
  46. package/server/test/catalog-sync-service.test.js +920 -0
  47. package/server/test/db-reset-migration.test.js +123 -0
  48. package/server/test/env-config.test.js +68 -0
  49. package/server/test/external-service.test.js +380 -0
  50. package/server/test/hzg-provider-client.test.js +50 -0
  51. package/server/test/internal-auth-middleware.test.js +66 -0
  52. package/server/test/kernel-reset-service.test.js +112 -0
  53. package/server/test/management-rpc-client.test.js +105 -0
  54. package/server/test/portal-service-access-tokens.test.js +121 -0
  55. package/server/test/portal-service-alignment.test.js +318 -0
  56. package/server/test/portal-service-management-logs.test.js +114 -0
  57. package/server/test/reset-kernel-cli.test.js +23 -0
  58. package/server/test/service-api-auth-middleware.test.js +59 -0
  59. package/server/test/system-service-alignment.test.js +265 -0
  60. package/server/test/topic-ping-service.test.js +182 -0
  61. package/server/test/usage-refresh-audit-route.test.js +82 -0
  62. package/src/App.jsx +1 -0
  63. package/src/api.js +1 -0
  64. package/src/app/App.jsx +346 -0
  65. package/src/app/api-client.js +112 -0
  66. package/src/components/AppShell.jsx +117 -0
  67. package/src/components/CardTitleWithReload.jsx +20 -0
  68. package/src/components/DeleteActionButton.jsx +31 -0
  69. package/src/main.jsx +14 -0
  70. package/src/pages/AgentsPage.jsx +647 -0
  71. package/src/pages/AiosUsersPage.jsx +151 -0
  72. package/src/pages/DashboardPage.jsx +72 -0
  73. package/src/pages/LoginPage.jsx +41 -0
  74. package/src/pages/SettingsPage.jsx +431 -0
  75. package/src/pages/SkillsPage.jsx +175 -0
  76. package/src/pages/SystemLogsPage.jsx +349 -0
  77. package/src/pages/SystemsPage.jsx +498 -0
  78. package/src/pages/TemplatesPage.jsx +207 -0
  79. package/src/pages/UserManagementPage.jsx +25 -0
  80. package/src/pages/UsersPage.jsx +192 -0
  81. package/src/pages/system-logs/SystemLogsTabs.jsx +362 -0
  82. package/src/styles.css +222 -0
  83. package/src/utils/format.js +63 -0
  84. package/test/.reports/fast-2026-05-25T08-32-39-420Z.json +299 -0
  85. package/test/integration/common.js +208 -0
  86. package/test/integration/fast.js +135 -0
  87. package/test/integration/full.js +306 -0
  88. package/test/run-browser-e2e.js +212 -0
  89. package/test/run-jasmine.js +21 -0
  90. package/test/setup.js +1 -0
  91. package/vite.config.js +12 -0
@@ -0,0 +1,175 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Alert, Button, Card, Form, Input, Modal, Spin, Table, Tag, Upload, message } from "antd";
3
+
4
+ import { api } from "../app/api-client.js";
5
+ import { CardTitleWithReload } from "../components/CardTitleWithReload.jsx";
6
+ import { DeleteActionButton } from "../components/DeleteActionButton.jsx";
7
+
8
+ const skillIdPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
9
+
10
+ function getRemoteStatusLabel(status) {
11
+ switch (status) {
12
+ case "installed":
13
+ return "已安装";
14
+ case "cataloged":
15
+ return "已归档";
16
+ case "ready":
17
+ return "就绪";
18
+ default:
19
+ return status || "-";
20
+ }
21
+ }
22
+
23
+ export function SkillsPage() {
24
+ const [items, setItems] = useState([]);
25
+ const [loading, setLoading] = useState(false);
26
+ const [open, setOpen] = useState(false);
27
+ const [submitLoading, setSubmitLoading] = useState(false);
28
+ const [submitMaskOpen, setSubmitMaskOpen] = useState(false);
29
+ const [submitMaskText, setSubmitMaskText] = useState("正在处理,请稍候...");
30
+ const [form] = Form.useForm();
31
+ const [messageApi, contextHolder] = message.useMessage();
32
+
33
+ const load = async () => {
34
+ setLoading(true);
35
+ try {
36
+ const skills = await api.get("/api/skills");
37
+ setItems(skills);
38
+ } finally {
39
+ setLoading(false);
40
+ }
41
+ };
42
+
43
+ useEffect(() => {
44
+ void load();
45
+ }, []);
46
+
47
+ const handleSubmit = async () => {
48
+ try {
49
+ const values = await form.validateFields();
50
+ const formData = new FormData();
51
+ formData.set("slug", String(values.slug || "").trim());
52
+ formData.set("description", String(values.description || ""));
53
+ if (values.artifact?.[0]?.originFileObj) {
54
+ formData.set("artifact", values.artifact[0].originFileObj);
55
+ }
56
+
57
+ setSubmitLoading(true);
58
+ setSubmitMaskText("正在上传并安装全局技能,请稍候...");
59
+ setSubmitMaskOpen(true);
60
+
61
+ await api.multipart("/api/skills", formData);
62
+ setOpen(false);
63
+ form.resetFields();
64
+ await load();
65
+ messageApi.success(`全局技能 ${values.slug} 处理完成`);
66
+ } catch (error) {
67
+ if (error?.errorFields) {
68
+ return;
69
+ }
70
+ messageApi.error(error.message || "全局技能处理失败");
71
+ } finally {
72
+ setSubmitLoading(false);
73
+ setSubmitMaskOpen(false);
74
+ }
75
+ };
76
+
77
+ return (
78
+ <>
79
+ {contextHolder}
80
+ <Modal
81
+ open={submitMaskOpen}
82
+ footer={null}
83
+ closable={false}
84
+ maskClosable={false}
85
+ centered
86
+ width={320}
87
+ >
88
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 12, padding: "16px 0" }}>
89
+ <Spin size="large" />
90
+ <div>{submitMaskText}</div>
91
+ </div>
92
+ </Modal>
93
+ <Card
94
+ className="page-card"
95
+ title={<CardTitleWithReload title="全局技能" loading={loading} onReload={() => load()} />}
96
+ extra={<Button type="primary" onClick={() => setOpen(true)}>新增全局技能</Button>}
97
+ >
98
+ <Table
99
+ rowKey="id"
100
+ loading={loading}
101
+ dataSource={items}
102
+ pagination={{
103
+ pageSize: 50,
104
+ showSizeChanger: false
105
+ }}
106
+ columns={[
107
+ { title: "技能 ID", dataIndex: "slug" },
108
+ {
109
+ title: "类型",
110
+ render: (_, row) => (row.is_builtin ? <Tag color="blue">内置</Tag> : <Tag>自定义</Tag>)
111
+ },
112
+ { title: "远程状态", dataIndex: "remote_status", render: getRemoteStatusLabel },
113
+ {
114
+ title: "操作",
115
+ render: (_, row) => (
116
+ <DeleteActionButton
117
+ hidden={row.is_builtin}
118
+ title={`确认删除全局技能 ${row.slug} 吗?`}
119
+ description="删除后会移除管理记录;如果该技能已完成全局安装,也会触发远程卸载。"
120
+ onConfirm={async () => {
121
+ await api.delete(`/api/skills/${row.id}`);
122
+ await load();
123
+ }}
124
+ />
125
+ )
126
+ }
127
+ ]}
128
+ />
129
+ <Modal
130
+ open={open}
131
+ title="新增全局技能"
132
+ okText="确认"
133
+ cancelText="取消"
134
+ confirmLoading={submitLoading}
135
+ onCancel={() => setOpen(false)}
136
+ onOk={async () => {
137
+ await handleSubmit();
138
+ }}
139
+ >
140
+ <Form form={form} layout="vertical">
141
+
142
+ <Form.Item
143
+ name="slug"
144
+ label="技能 ID"
145
+ rules={[
146
+ { required: true, message: "请输入技能 ID" },
147
+ {
148
+ pattern: skillIdPattern,
149
+ message: "技能 ID 需符合 slug 规则:仅允许小写字母、数字和中划线"
150
+ }
151
+ ]}
152
+ >
153
+ <Input />
154
+ </Form.Item>
155
+ <Form.Item name="description" label="描述">
156
+ <Input.TextArea rows={3} />
157
+ </Form.Item>
158
+ <Form.Item
159
+ name="artifact"
160
+ label="技能 zip"
161
+ valuePropName="fileList"
162
+ getValueFromEvent={(event) => event?.fileList || []}
163
+ rules={[{ required: true, message: "必须上传技能 zip" }]}
164
+ extra="zip 第一层必须包含 SKILL.md。安装过程将会跳过内建的安全检查机制,请自行确保该技能的安全性。"
165
+ >
166
+ <Upload beforeUpload={() => false} maxCount={1}>
167
+ <Button>选择文件</Button>
168
+ </Upload>
169
+ </Form.Item>
170
+ </Form>
171
+ </Modal>
172
+ </Card>
173
+ </>
174
+ );
175
+ }
@@ -0,0 +1,349 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Button, Card, Descriptions, Drawer, Space, Table, Tabs, Tag } from "antd";
3
+
4
+ import { api } from "../app/api-client.js";
5
+ import { CardTitleWithReload } from "../components/CardTitleWithReload.jsx";
6
+ import { formatDateTime } from "../utils/format.js";
7
+
8
+ const jsonBlockStyle = {
9
+ margin: 0,
10
+ padding: 12,
11
+ borderRadius: 8,
12
+ background: "#fafafa",
13
+ border: "1px solid #f0f0f0",
14
+ overflowX: "auto",
15
+ whiteSpace: "pre-wrap",
16
+ wordBreak: "break-word",
17
+ fontSize: 12,
18
+ lineHeight: 1.6
19
+ };
20
+
21
+ function formatJson(value) {
22
+ if (value === undefined || value === null) {
23
+ return "-";
24
+ }
25
+
26
+ try {
27
+ return JSON.stringify(value, null, 2);
28
+ } catch {
29
+ return String(value);
30
+ }
31
+ }
32
+
33
+ function renderResultTag(value) {
34
+ if (value === null || value === undefined) {
35
+ return <Tag>处理中</Tag>;
36
+ }
37
+
38
+ return <Tag color={value ? "green" : "red"}>{value ? "成功" : "失败"}</Tag>;
39
+ }
40
+
41
+ function getSystemInvocationDetailItems(log) {
42
+ if (!log) {
43
+ return [];
44
+ }
45
+
46
+ return [
47
+ { key: "created_at", label: "时间", children: formatDateTime(log.created_at) },
48
+ { key: "provider", label: "提供方", children: log.provider || "-" },
49
+ { key: "application_name", label: "应用名", children: log.application_name || "-" },
50
+ { key: "command_name", label: "指令", children: log.command_name || "-" },
51
+ { key: "agent_slug", label: "数字员工", children: log.agent_slug || "-" },
52
+ { key: "session_id", label: "会话ID", children: log.session_id || "-" },
53
+ { key: "trace_id", label: "请求追踪ID", children: log.trace_id || "-" },
54
+ {
55
+ key: "response_time_ms",
56
+ label: "耗时",
57
+ children: typeof log.response_time_ms === "number" ? `${log.response_time_ms} ms` : "-"
58
+ },
59
+ {
60
+ key: "success",
61
+ label: "执行结果",
62
+ children: renderResultTag(log.success)
63
+ },
64
+ { key: "error_message", label: "错误", children: log.error_message || "-" }
65
+ ];
66
+ }
67
+
68
+ function getKernelInvocationDetailItems(log) {
69
+ if (!log) {
70
+ return [];
71
+ }
72
+
73
+ return [
74
+ { key: "request_id", label: "请求ID", children: log.request_id || "-" },
75
+ { key: "created_at", label: "发送时间", children: formatDateTime(log.created_at) },
76
+ { key: "completed_at", label: "完成时间", children: formatDateTime(log.completed_at) },
77
+ { key: "action", label: "指令", children: log.action || "-" },
78
+ {
79
+ key: "ok",
80
+ label: "执行结果",
81
+ children: renderResultTag(log.ok)
82
+ },
83
+ {
84
+ key: "response_time_ms",
85
+ label: "耗时",
86
+ children: typeof log.response_time_ms === "number" ? `${log.response_time_ms} ms` : "-"
87
+ }
88
+ ];
89
+ }
90
+
91
+ export function SystemLogsPage() {
92
+ const [auditItems, setAuditItems] = useState([]);
93
+ const [auditPage, setAuditPage] = useState(1);
94
+ const [auditPageSize] = useState(50);
95
+ const [auditTotal, setAuditTotal] = useState(0);
96
+ const [auditLoading, setAuditLoading] = useState(false);
97
+
98
+ const [systemLogs, setSystemLogs] = useState([]);
99
+ const [systemLogsLoading, setSystemLogsLoading] = useState(false);
100
+ const [systemLogDetail, setSystemLogDetail] = useState(null);
101
+ const [systemLogDetailOpen, setSystemLogDetailOpen] = useState(false);
102
+
103
+ const [kernelItems, setKernelItems] = useState([]);
104
+ const [kernelPage, setKernelPage] = useState(1);
105
+ const [kernelPageSize] = useState(50);
106
+ const [kernelTotal, setKernelTotal] = useState(0);
107
+ const [kernelLoading, setKernelLoading] = useState(false);
108
+ const [kernelDetail, setKernelDetail] = useState(null);
109
+ const [kernelDetailOpen, setKernelDetailOpen] = useState(false);
110
+
111
+ const loadAuditLogs = async (nextPage = auditPage) => {
112
+ setAuditLoading(true);
113
+ try {
114
+ const result = await api.get(`/api/audit-logs?page=${nextPage}&pageSize=${auditPageSize}`);
115
+ setAuditItems(result.items || []);
116
+ setAuditTotal(result.total || 0);
117
+ setAuditPage(result.page || nextPage);
118
+ } finally {
119
+ setAuditLoading(false);
120
+ }
121
+ };
122
+
123
+ const loadSystemInvocationLogs = async () => {
124
+ setSystemLogsLoading(true);
125
+ try {
126
+ const result = await api.get("/api/systems/logs");
127
+ setSystemLogs(Array.isArray(result) ? result : []);
128
+ } finally {
129
+ setSystemLogsLoading(false);
130
+ }
131
+ };
132
+
133
+ const loadKernelLogs = async (nextPage = kernelPage) => {
134
+ setKernelLoading(true);
135
+ try {
136
+ const result = await api.get(`/api/kernel/logs?page=${nextPage}&pageSize=${kernelPageSize}`);
137
+ setKernelItems(result.items || []);
138
+ setKernelTotal(result.total || 0);
139
+ setKernelPage(result.page || nextPage);
140
+ } finally {
141
+ setKernelLoading(false);
142
+ }
143
+ };
144
+
145
+ useEffect(() => {
146
+ void Promise.all([
147
+ loadAuditLogs(1),
148
+ loadSystemInvocationLogs(),
149
+ loadKernelLogs(1)
150
+ ]);
151
+ }, []);
152
+
153
+ return (
154
+ <>
155
+ <Tabs
156
+ defaultActiveKey="audit"
157
+ items={[
158
+ {
159
+ key: "audit",
160
+ label: "审计日志",
161
+ children: (
162
+ <Card
163
+ className="page-card"
164
+ title={<CardTitleWithReload title="审计日志" loading={auditLoading} onReload={() => loadAuditLogs()} />}
165
+ >
166
+ <Table
167
+ rowKey="id"
168
+ loading={auditLoading}
169
+ dataSource={auditItems}
170
+ pagination={{
171
+ current: auditPage,
172
+ pageSize: auditPageSize,
173
+ total: auditTotal,
174
+ showSizeChanger: false,
175
+ onChange: (nextPage) => {
176
+ void loadAuditLogs(nextPage);
177
+ }
178
+ }}
179
+ columns={[
180
+ { title: "时间", dataIndex: "created_at", render: formatDateTime, width: 180 },
181
+ { title: "操作人", dataIndex: "username", width: 160 },
182
+ { title: "操作", dataIndex: "action", width: 220 },
183
+ { title: "详情", dataIndex: "detail" }
184
+ ]}
185
+ />
186
+ </Card>
187
+ )
188
+ },
189
+ {
190
+ key: "system",
191
+ label: "业务系统调用日志",
192
+ children: (
193
+ <Card
194
+ className="page-card"
195
+ title={<CardTitleWithReload title="业务系统调用日志" loading={systemLogsLoading} onReload={() => loadSystemInvocationLogs()} />}
196
+ >
197
+ <Table
198
+ rowKey="trace_id"
199
+ loading={systemLogsLoading}
200
+ dataSource={systemLogs}
201
+ pagination={{
202
+ pageSize: 50,
203
+ showSizeChanger: false
204
+ }}
205
+ columns={[
206
+ { title: "时间", dataIndex: "created_at", render: formatDateTime },
207
+ { title: "应用名", dataIndex: "application_name" },
208
+ { title: "数字员工", dataIndex: "agent_slug", render: (value) => value || "-" },
209
+ { title: "接口", render: (_, row) => `${row.provider}/${row.application_name}/${row.command_name}` },
210
+ {
211
+ title: "耗时",
212
+ dataIndex: "response_time_ms",
213
+ render: (value) => (typeof value === "number" ? `${value} ms` : "-")
214
+ },
215
+ {
216
+ title: "执行结果",
217
+ dataIndex: "success",
218
+ render: renderResultTag
219
+ },
220
+ {
221
+ title: "操作",
222
+ render: (_, row) => (
223
+ <Button
224
+ size="small"
225
+ onClick={() => {
226
+ setSystemLogDetail(row);
227
+ setSystemLogDetailOpen(true);
228
+ }}
229
+ >
230
+ 详情
231
+ </Button>
232
+ )
233
+ }
234
+ ]}
235
+ />
236
+ </Card>
237
+ )
238
+ },
239
+ {
240
+ key: "kernel",
241
+ label: "内核调用日志",
242
+ children: (
243
+ <Card
244
+ className="page-card"
245
+ title={<CardTitleWithReload title="内核调用日志" loading={kernelLoading} onReload={() => loadKernelLogs()} />}
246
+ >
247
+ <Table
248
+ rowKey="request_id"
249
+ loading={kernelLoading}
250
+ dataSource={kernelItems}
251
+ pagination={{
252
+ current: kernelPage,
253
+ pageSize: kernelPageSize,
254
+ total: kernelTotal,
255
+ showSizeChanger: false,
256
+ onChange: (nextPage) => {
257
+ void loadKernelLogs(nextPage);
258
+ }
259
+ }}
260
+ columns={[
261
+ { title: "请求ID", dataIndex: "request_id" },
262
+ { title: "发送时间", dataIndex: "created_at", render: formatDateTime, width: 180 },
263
+ { title: "指令", dataIndex: "action" },
264
+ {
265
+ title: "执行结果",
266
+ dataIndex: "ok",
267
+ width: 120,
268
+ render: renderResultTag
269
+ },
270
+ {
271
+ title: "操作",
272
+ width: 100,
273
+ render: (_, row) => (
274
+ <Button
275
+ size="small"
276
+ onClick={() => {
277
+ setKernelDetail(row);
278
+ setKernelDetailOpen(true);
279
+ }}
280
+ >
281
+ 详情
282
+ </Button>
283
+ )
284
+ }
285
+ ]}
286
+ />
287
+ </Card>
288
+ )
289
+ }
290
+ ]}
291
+ />
292
+ <Drawer
293
+ open={systemLogDetailOpen}
294
+ title="业务系统调用详情"
295
+ width={860}
296
+ onClose={() => {
297
+ setSystemLogDetailOpen(false);
298
+ setSystemLogDetail(null);
299
+ }}
300
+ >
301
+ <Space direction="vertical" size={16} style={{ width: "100%" }}>
302
+ <Card className="page-card" title="基本信息">
303
+ <Descriptions
304
+ bordered
305
+ size="small"
306
+ column={1}
307
+ items={getSystemInvocationDetailItems(systemLogDetail)}
308
+ />
309
+ </Card>
310
+ <Card className="page-card" title="请求参数">
311
+ <pre style={jsonBlockStyle}>{formatJson(systemLogDetail?.request_payload)}</pre>
312
+ </Card>
313
+ <Card className="page-card" title="返回结果">
314
+ <pre style={jsonBlockStyle}>{formatJson(systemLogDetail?.response_payload)}</pre>
315
+ </Card>
316
+ </Space>
317
+ </Drawer>
318
+ <Drawer
319
+ open={kernelDetailOpen}
320
+ title="内核调用详情"
321
+ width={860}
322
+ onClose={() => {
323
+ setKernelDetailOpen(false);
324
+ setKernelDetail(null);
325
+ }}
326
+ >
327
+ <Space direction="vertical" size={16} style={{ width: "100%" }}>
328
+ <Card className="page-card" title="基本信息">
329
+ <Descriptions
330
+ bordered
331
+ size="small"
332
+ column={1}
333
+ items={getKernelInvocationDetailItems(kernelDetail)}
334
+ />
335
+ </Card>
336
+ <Card className="page-card" title="参数">
337
+ <pre style={jsonBlockStyle}>{formatJson(kernelDetail?.params)}</pre>
338
+ </Card>
339
+ <Card className="page-card" title="返回结果">
340
+ <pre style={jsonBlockStyle}>{formatJson(kernelDetail?.result)}</pre>
341
+ </Card>
342
+ <Card className="page-card" title="错误">
343
+ <pre style={jsonBlockStyle}>{formatJson(kernelDetail?.error)}</pre>
344
+ </Card>
345
+ </Space>
346
+ </Drawer>
347
+ </>
348
+ );
349
+ }