@loom-framework/core 0.1.0-alpha.165 → 0.1.0-alpha.167
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/builtin-skills/loom/SKILL.md +9 -6
- package/builtin-skills/loom/references/eject.md +130 -0
- package/dist/cli/commands/eject.d.ts.map +1 -1
- package/dist/cli/commands/eject.js +108 -279
- package/dist/cli/commands/eject.js.map +1 -1
- package/dist/cli/commands/generate-system-settings.d.ts +3 -3
- package/dist/cli/commands/generate-system-settings.d.ts.map +1 -1
- package/dist/cli/commands/generate-system-settings.js +29 -204
- package/dist/cli/commands/generate-system-settings.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +27 -44
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/helpers/app-tsx-utils.d.ts +12 -0
- package/dist/cli/helpers/app-tsx-utils.d.ts.map +1 -0
- package/dist/cli/helpers/app-tsx-utils.js +22 -0
- package/dist/cli/helpers/app-tsx-utils.js.map +1 -0
- package/dist/cli/helpers/app-tsx-wiring-engine.d.ts +70 -0
- package/dist/cli/helpers/app-tsx-wiring-engine.d.ts.map +1 -0
- package/dist/cli/helpers/app-tsx-wiring-engine.js +546 -0
- package/dist/cli/helpers/app-tsx-wiring-engine.js.map +1 -0
- package/dist/cli/helpers/app-tsx-wiring.d.ts +4 -39
- package/dist/cli/helpers/app-tsx-wiring.d.ts.map +1 -1
- package/dist/cli/helpers/app-tsx-wiring.js +5 -608
- package/dist/cli/helpers/app-tsx-wiring.js.map +1 -1
- package/dist/cli/helpers/system-page-config.d.ts +27 -0
- package/dist/cli/helpers/system-page-config.d.ts.map +1 -0
- package/dist/cli/helpers/system-page-config.js +73 -0
- package/dist/cli/helpers/system-page-config.js.map +1 -0
- package/dist/cli/templates/login-page.d.ts +2 -3
- package/dist/cli/templates/login-page.d.ts.map +1 -1
- package/dist/cli/templates/login-page.js +19 -27
- package/dist/cli/templates/login-page.js.map +1 -1
- package/dist/cli/templates/model-management-page.d.ts +2 -3
- package/dist/cli/templates/model-management-page.d.ts.map +1 -1
- package/dist/cli/templates/model-management-page.js +12 -7
- package/dist/cli/templates/model-management-page.js.map +1 -1
- package/dist/cli/templates/notification-center-page.d.ts +2 -3
- package/dist/cli/templates/notification-center-page.d.ts.map +1 -1
- package/dist/cli/templates/notification-center-page.js +26 -60
- package/dist/cli/templates/notification-center-page.js.map +1 -1
- package/dist/cli/templates/notification-detail-page.d.ts +2 -3
- package/dist/cli/templates/notification-detail-page.d.ts.map +1 -1
- package/dist/cli/templates/notification-detail-page.js +14 -39
- package/dist/cli/templates/notification-detail-page.js.map +1 -1
- package/dist/cli/templates/process-management-page.d.ts +3 -3
- package/dist/cli/templates/process-management-page.d.ts.map +1 -1
- package/dist/cli/templates/process-management-page.js +76 -563
- package/dist/cli/templates/process-management-page.js.map +1 -1
- package/dist/cli/templates/skill-management-page.d.ts +2 -3
- package/dist/cli/templates/skill-management-page.d.ts.map +1 -1
- package/dist/cli/templates/skill-management-page.js +13 -13
- package/dist/cli/templates/skill-management-page.js.map +1 -1
- package/dist/cli/templates/user-management-page.d.ts +2 -4
- package/dist/cli/templates/user-management-page.d.ts.map +1 -1
- package/dist/cli/templates/user-management-page.js +22 -20
- package/dist/cli/templates/user-management-page.js.map +1 -1
- package/package.json +7 -3
|
@@ -1,33 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Process Management page template
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* GENERATED by scripts/generate-templates.ts — v0.1.0-alpha.167 — DO NOT EDIT MANUALLY.
|
|
5
|
+
* Source: packages/frontend-antd/src/components/pages/process-management-page.tsx
|
|
6
6
|
*/
|
|
7
7
|
export function processManagementPageTemplate() {
|
|
8
|
-
return
|
|
8
|
+
return `/**
|
|
9
|
+
* ProcessManagementPage — Orchestrator composing Process sub-components
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
9
13
|
import {
|
|
10
14
|
Card,
|
|
11
|
-
Tree,
|
|
12
15
|
Tabs,
|
|
13
|
-
Table,
|
|
14
|
-
Tag,
|
|
15
16
|
Button,
|
|
16
17
|
Modal,
|
|
17
18
|
Spin,
|
|
19
|
+
Tooltip,
|
|
18
20
|
Segmented,
|
|
19
21
|
Dropdown,
|
|
20
22
|
Flex,
|
|
21
23
|
Breadcrumb,
|
|
22
24
|
Space,
|
|
23
|
-
Descriptions,
|
|
24
|
-
Tooltip,
|
|
25
|
-
Timeline,
|
|
26
25
|
Empty,
|
|
27
26
|
message,
|
|
28
|
-
Typography,
|
|
29
27
|
theme,
|
|
30
|
-
DatePicker,
|
|
31
28
|
} from 'antd';
|
|
32
29
|
import {
|
|
33
30
|
PlayCircleOutlined,
|
|
@@ -35,50 +32,22 @@ import {
|
|
|
35
32
|
DeleteOutlined,
|
|
36
33
|
ReloadOutlined,
|
|
37
34
|
PlusOutlined,
|
|
38
|
-
FileTextOutlined,
|
|
39
|
-
FolderOutlined,
|
|
40
|
-
ClockCircleOutlined,
|
|
41
|
-
ThunderboltOutlined,
|
|
42
|
-
CalendarOutlined,
|
|
43
|
-
ExclamationCircleOutlined,
|
|
44
35
|
MoreOutlined,
|
|
45
36
|
HomeOutlined,
|
|
46
37
|
EyeOutlined,
|
|
47
38
|
CodeOutlined,
|
|
48
|
-
MessageOutlined,
|
|
49
39
|
} from '@ant-design/icons';
|
|
50
40
|
import {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
fetchProcesses,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} from '
|
|
61
|
-
import type {
|
|
62
|
-
ProcessInfo, ProcessLogEntry, ProcessLogListResponse, ProcessMetricsAggregate, FileContent,
|
|
63
|
-
ProcessMetricSnapshot, ProcessMetricsViewConfig,
|
|
64
|
-
} from '@loom-framework/frontend-antd';
|
|
65
|
-
import { DashboardChart } from '@loom-framework/frontend-antd';
|
|
66
|
-
import type { Dayjs } from 'dayjs';
|
|
67
|
-
|
|
68
|
-
const { Text } = Typography;
|
|
69
|
-
|
|
70
|
-
interface ProcessTreeNode {
|
|
71
|
-
key: string;
|
|
72
|
-
title: string;
|
|
73
|
-
icon: React.ReactNode;
|
|
74
|
-
isLeaf: boolean;
|
|
75
|
-
isFile?: boolean;
|
|
76
|
-
isDir?: boolean;
|
|
77
|
-
isProcess?: boolean;
|
|
78
|
-
filePath?: string;
|
|
79
|
-
processName?: string;
|
|
80
|
-
children?: ProcessTreeNode[];
|
|
81
|
-
}
|
|
41
|
+
import type { ProcessInfo, FileContent, ProcessTreeNode } from '@loom-framework/frontend-antd';
|
|
42
|
+
import { useLocale, registerMessages, useLoomTheme, useAppShell, SkillFileViewer, ProcessTree, buildTreeData, loadDirChildren, removeTreeNode, enrichTreeWithFiles, ProcessOverview, ProcessLogs, ProcessMetrics } from '@loom-framework/frontend-antd';
|
|
43
|
+
fetchProcesses,
|
|
44
|
+
fetchProcess,
|
|
45
|
+
fetchProcessFileContent,
|
|
46
|
+
runProcess,
|
|
47
|
+
pauseProcess,
|
|
48
|
+
resumeProcess,
|
|
49
|
+
deleteProcess,
|
|
50
|
+
} from './process-api.js';
|
|
82
51
|
|
|
83
52
|
registerMessages('zh-CN', {
|
|
84
53
|
'process.title': '过程管理',
|
|
@@ -106,11 +75,6 @@ registerMessages('zh-CN', {
|
|
|
106
75
|
'process.noProcesses': '暂无过程',
|
|
107
76
|
'process.noMetrics': '未配置指标',
|
|
108
77
|
'process.noLogs': '暂无执行日志',
|
|
109
|
-
'process.timeRange': '时间范围',
|
|
110
|
-
'process.total': '共 {total} 条',
|
|
111
|
-
'process.runId': '运行 ID',
|
|
112
|
-
'process.triggerType': '触发方式',
|
|
113
|
-
'process.startedAt': '开始时间',
|
|
114
78
|
'process.queue': '执行队列',
|
|
115
79
|
'process.queueRunning': '运行中',
|
|
116
80
|
'process.queueQueued': '等待中',
|
|
@@ -139,23 +103,6 @@ registerMessages('zh-CN', {
|
|
|
139
103
|
'process.selectToView': '选择左侧过程或文件以查看详情',
|
|
140
104
|
'process.preview': '预览',
|
|
141
105
|
'process.source': '源码',
|
|
142
|
-
'process.colDate': '日期',
|
|
143
|
-
'process.colTotal': '总执行',
|
|
144
|
-
'process.colCompleted': '成功',
|
|
145
|
-
'process.colFailed': '失败',
|
|
146
|
-
'process.colTimeout': '超时',
|
|
147
|
-
'process.colAvgDuration': '平均耗时',
|
|
148
|
-
'process.colRunId': '执行 ID',
|
|
149
|
-
'process.colTriggerType': '触发类型',
|
|
150
|
-
'process.colResult': '结果',
|
|
151
|
-
'process.colTime': '时间',
|
|
152
|
-
'process.viewSession': '查看 Loom 会话',
|
|
153
|
-
'process.businessMetrics': '业务指标',
|
|
154
|
-
'process.operationalMetrics': '运行统计',
|
|
155
|
-
'process.noBusinessMetrics': '暂无业务指标数据',
|
|
156
|
-
'process.metricValue': '指标值',
|
|
157
|
-
'process.metricTarget': '目标',
|
|
158
|
-
'process.metricTrend': '趋势',
|
|
159
106
|
});
|
|
160
107
|
|
|
161
108
|
registerMessages('en-US', {
|
|
@@ -184,11 +131,6 @@ registerMessages('en-US', {
|
|
|
184
131
|
'process.noProcesses': 'No processes',
|
|
185
132
|
'process.noMetrics': 'No metrics configured',
|
|
186
133
|
'process.noLogs': 'No execution logs',
|
|
187
|
-
'process.timeRange': 'Time Range',
|
|
188
|
-
'process.total': '{total} total',
|
|
189
|
-
'process.runId': 'Run ID',
|
|
190
|
-
'process.triggerType': 'Trigger',
|
|
191
|
-
'process.startedAt': 'Started At',
|
|
192
134
|
'process.queue': 'Execution Queue',
|
|
193
135
|
'process.queueRunning': 'Running',
|
|
194
136
|
'process.queueQueued': 'Queued',
|
|
@@ -217,53 +159,13 @@ registerMessages('en-US', {
|
|
|
217
159
|
'process.selectToView': 'Select a process or file to view details',
|
|
218
160
|
'process.preview': 'Preview',
|
|
219
161
|
'process.source': 'Source',
|
|
220
|
-
'process.colDate': 'Date',
|
|
221
|
-
'process.colTotal': 'Total',
|
|
222
|
-
'process.colCompleted': 'Completed',
|
|
223
|
-
'process.colFailed': 'Failed',
|
|
224
|
-
'process.colTimeout': 'Timeout',
|
|
225
|
-
'process.colAvgDuration': 'Avg Duration',
|
|
226
|
-
'process.colRunId': 'Run ID',
|
|
227
|
-
'process.colTriggerType': 'Trigger',
|
|
228
|
-
'process.colResult': 'Result',
|
|
229
|
-
'process.colTime': 'Time',
|
|
230
|
-
'process.viewSession': 'View Loom Session',
|
|
231
|
-
'process.businessMetrics': 'Business Metrics',
|
|
232
|
-
'process.operationalMetrics': 'Operational Metrics',
|
|
233
|
-
'process.noBusinessMetrics': 'No business metrics data',
|
|
234
|
-
'process.metricValue': 'Value',
|
|
235
|
-
'process.metricTarget': 'Target',
|
|
236
|
-
'process.metricTrend': 'Trend',
|
|
237
162
|
});
|
|
238
163
|
|
|
239
|
-
|
|
240
|
-
idle: 'default',
|
|
241
|
-
running: 'processing',
|
|
242
|
-
busy: 'orange',
|
|
243
|
-
paused: 'warning',
|
|
244
|
-
error: 'error',
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
const STATUS_ICONS: Record<string, React.ReactNode> = {
|
|
248
|
-
idle: <ClockCircleOutlined />,
|
|
249
|
-
running: <PlayCircleOutlined />,
|
|
250
|
-
busy: <ThunderboltOutlined />,
|
|
251
|
-
paused: <PauseCircleOutlined />,
|
|
252
|
-
error: <ExclamationCircleOutlined />,
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
const TRIGGER_ICONS: Record<string, React.ReactNode> = {
|
|
256
|
-
cron: <CalendarOutlined />,
|
|
257
|
-
event: <ThunderboltOutlined />,
|
|
258
|
-
manual: <PlayCircleOutlined />,
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
export default function ProcessManagementPage(): React.ReactElement {
|
|
164
|
+
function ProcessManagementPage(): React.ReactElement {
|
|
262
165
|
const { mode } = useLoomTheme();
|
|
263
166
|
const { t } = useLocale();
|
|
264
167
|
const { breadcrumbs, onNavClick } = useAppShell();
|
|
265
168
|
const { token } = theme.useToken();
|
|
266
|
-
const aiContext = useContext(AIContext);
|
|
267
169
|
|
|
268
170
|
const [processes, setProcesses] = useState<ProcessInfo[]>([]);
|
|
269
171
|
const [treeData, setTreeData] = useState<ProcessTreeNode[]>([]);
|
|
@@ -275,110 +177,38 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
275
177
|
const [loading, setLoading] = useState(false);
|
|
276
178
|
const [viewMode, setViewMode] = useState<'preview' | 'source'>('preview');
|
|
277
179
|
const [activeTab, setActiveTab] = useState('overview');
|
|
278
|
-
const [logs, setLogs] = useState<ProcessLogEntry[]>([]);
|
|
279
|
-
const [logsTotal, setLogsTotal] = useState(0);
|
|
280
|
-
const [logsPagination, setLogsPagination] = useState({ current: 1, pageSize: 20 });
|
|
281
|
-
const [logsDateRange, setLogsDateRange] = useState<[Dayjs | null, Dayjs | null] | null>(null);
|
|
282
|
-
const [logsLoading, setLogsLoading] = useState(false);
|
|
283
|
-
const [metrics, setMetrics] = useState<ProcessMetricsAggregate[]>([]);
|
|
284
|
-
const [metricsConfig, setMetricsConfig] = useState<Record<string, unknown>[]>([]);
|
|
285
|
-
const [metricSnapshots, setMetricSnapshots] = useState<ProcessMetricSnapshot[]>([]);
|
|
286
|
-
const [metricsViewConfig, setMetricsViewConfig] = useState<ProcessMetricsViewConfig | null>(null);
|
|
287
180
|
const [executing, setExecuting] = useState(false);
|
|
288
181
|
|
|
289
|
-
const buildTreeData = useCallback((processList: ProcessInfo[]): ProcessTreeNode[] => {
|
|
290
|
-
return processList.map((proc) => {
|
|
291
|
-
const children: ProcessTreeNode[] = [];
|
|
292
|
-
|
|
293
|
-
if (proc.hasProcessFile) {
|
|
294
|
-
children.push({
|
|
295
|
-
key: \`\${proc.name}/PROCESS.md\`,
|
|
296
|
-
title: 'PROCESS.md',
|
|
297
|
-
icon: <FileTextOutlined style={{ color: token.colorPrimary }} />,
|
|
298
|
-
isLeaf: true,
|
|
299
|
-
isFile: true,
|
|
300
|
-
filePath: 'PROCESS.md',
|
|
301
|
-
processName: proc.name,
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return {
|
|
306
|
-
key: proc.name,
|
|
307
|
-
title: proc.name,
|
|
308
|
-
icon: proc.hasProcessFile ? <FolderOutlined /> : <ThunderboltOutlined style={{ color: token.colorWarning }} />,
|
|
309
|
-
isLeaf: false,
|
|
310
|
-
isFile: false,
|
|
311
|
-
isProcess: true,
|
|
312
|
-
children,
|
|
313
|
-
processName: proc.name,
|
|
314
|
-
};
|
|
315
|
-
});
|
|
316
|
-
}, [token]);
|
|
317
|
-
|
|
318
182
|
const loadProcesses = useCallback(async () => {
|
|
319
183
|
setLoading(true);
|
|
320
184
|
try {
|
|
321
185
|
const result = await fetchProcesses();
|
|
322
186
|
const list = result.data ?? [];
|
|
323
187
|
setProcesses(list);
|
|
324
|
-
const initialTree = buildTreeData(list);
|
|
188
|
+
const initialTree = buildTreeData(list, token);
|
|
325
189
|
setTreeData(initialTree);
|
|
326
190
|
setExpandedKeys(list.map((p) => p.name));
|
|
327
191
|
// Enrich tree with actual file structure (e.g. references/ directories)
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (!node.isProcess || !node.processName) continue;
|
|
331
|
-
const proc = list.find(p => p.name === node.processName);
|
|
332
|
-
if (!proc?.hasProcessFile) continue;
|
|
333
|
-
const hasReferences = node.children?.some(c => c.filePath === 'references' && c.isDir);
|
|
334
|
-
if (hasReferences) continue;
|
|
335
|
-
try {
|
|
336
|
-
const files = await fetchProcessFiles(node.processName);
|
|
337
|
-
const flatFiles: typeof files = [];
|
|
338
|
-
const flatten = (fs: typeof files) => { for (const f of fs) { if (f.isDirectory && f.children) { flatten(f.children); } else if (!f.isDirectory) { flatFiles.push(f); } } };
|
|
339
|
-
flatten(files);
|
|
340
|
-
const refFiles = flatFiles.filter(f => f.path.startsWith('references/'));
|
|
341
|
-
if (refFiles.length > 0) {
|
|
342
|
-
const { children: refChildren } = await loadDirChildren(node.processName, 'references');
|
|
343
|
-
if (!refChildren.length) continue;
|
|
344
|
-
const updated = { ...node, children: [...(node.children ?? []), {
|
|
345
|
-
key: \`\${node.processName}/references\`,
|
|
346
|
-
title: 'references/',
|
|
347
|
-
icon: <FolderOutlined style={{ color: token.colorWarning }} />,
|
|
348
|
-
isLeaf: false,
|
|
349
|
-
isFile: false,
|
|
350
|
-
isDir: true,
|
|
351
|
-
filePath: 'references',
|
|
352
|
-
children: refChildren,
|
|
353
|
-
processName: node.processName,
|
|
354
|
-
}] };
|
|
355
|
-
initialTree[i] = updated;
|
|
356
|
-
setTreeData([...initialTree]);
|
|
357
|
-
}
|
|
358
|
-
} catch { /* ignore */ }
|
|
359
|
-
}
|
|
192
|
+
const enriched = await enrichTreeWithFiles(initialTree, list, token);
|
|
193
|
+
setTreeData(enriched);
|
|
360
194
|
} catch {
|
|
361
195
|
// error handled silently
|
|
362
196
|
} finally {
|
|
363
197
|
setLoading(false);
|
|
364
198
|
}
|
|
365
|
-
}, [
|
|
199
|
+
}, [token]);
|
|
366
200
|
|
|
367
201
|
useEffect(() => { loadProcesses(); }, [loadProcesses]);
|
|
368
202
|
|
|
369
|
-
// Auto-refresh every 5 seconds
|
|
203
|
+
// Auto-refresh every 5 seconds
|
|
370
204
|
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
371
205
|
useEffect(() => {
|
|
372
206
|
const poll = () => {
|
|
373
|
-
if (document.visibilityState === 'visible')
|
|
374
|
-
loadProcesses();
|
|
375
|
-
}
|
|
207
|
+
if (document.visibilityState === 'visible') loadProcesses();
|
|
376
208
|
};
|
|
377
209
|
pollRef.current = setInterval(poll, 5000);
|
|
378
210
|
const handleVisibility = () => {
|
|
379
|
-
if (document.visibilityState === 'visible')
|
|
380
|
-
poll(); // Immediate refresh when tab becomes visible
|
|
381
|
-
}
|
|
211
|
+
if (document.visibilityState === 'visible') poll();
|
|
382
212
|
};
|
|
383
213
|
document.addEventListener('visibilitychange', handleVisibility);
|
|
384
214
|
return () => {
|
|
@@ -387,60 +217,21 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
387
217
|
};
|
|
388
218
|
}, [loadProcesses]);
|
|
389
219
|
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
for (const f of dirFiles) {
|
|
403
|
-
const relativePath = f.path.slice(dirName.length + 1);
|
|
404
|
-
const parts = relativePath.split('/');
|
|
405
|
-
|
|
406
|
-
let currentLevel = dirChildren;
|
|
407
|
-
for (let i = 0; i < parts.length; i++) {
|
|
408
|
-
const part = parts[i]!;
|
|
409
|
-
const isLast = i === parts.length - 1;
|
|
410
|
-
const partialPath = \`\${dirName}/\${parts.slice(0, i + 1).join('/')}\`;
|
|
411
|
-
|
|
412
|
-
if (isLast) {
|
|
413
|
-
currentLevel.push({
|
|
414
|
-
key: \`\${processName}/\${f.path}\`,
|
|
415
|
-
title: part,
|
|
416
|
-
icon: getFileIcon(part, { primary: token.colorPrimary, warning: token.colorWarning, success: token.colorSuccess }),
|
|
417
|
-
isLeaf: true,
|
|
418
|
-
isFile: true,
|
|
419
|
-
filePath: f.path,
|
|
420
|
-
processName,
|
|
421
|
-
});
|
|
422
|
-
} else {
|
|
423
|
-
let dirNode = currentLevel.find((n) => n.isDir && n.filePath === partialPath);
|
|
424
|
-
if (!dirNode) {
|
|
425
|
-
dirNode = {
|
|
426
|
-
key: \`\${processName}/\${partialPath}\`,
|
|
427
|
-
title: \`\${part}/\`,
|
|
428
|
-
icon: <FolderOutlined style={{ color: token.colorWarning }} />,
|
|
429
|
-
isLeaf: false,
|
|
430
|
-
isFile: false,
|
|
431
|
-
isDir: true,
|
|
432
|
-
filePath: partialPath,
|
|
433
|
-
children: [],
|
|
434
|
-
processName,
|
|
435
|
-
};
|
|
436
|
-
currentLevel.push(dirNode);
|
|
437
|
-
}
|
|
438
|
-
currentLevel = dirNode.children!;
|
|
439
|
-
}
|
|
220
|
+
const selectProcess = useCallback(async (name: string) => {
|
|
221
|
+
try {
|
|
222
|
+
const data = await fetchProcess(name);
|
|
223
|
+
setSelectedProcess(data);
|
|
224
|
+
setFileContent(null);
|
|
225
|
+
setProcessMdContent(null);
|
|
226
|
+
setActiveTab('overview');
|
|
227
|
+
if (data.hasProcessFile) {
|
|
228
|
+
try {
|
|
229
|
+
const mdContent = await fetchProcessFileContent(name, 'PROCESS.md');
|
|
230
|
+
setProcessMdContent({ content: mdContent, path: 'PROCESS.md' });
|
|
231
|
+
} catch { /* ignore */ }
|
|
440
232
|
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
}, [token]);
|
|
233
|
+
} catch { /* ignore */ }
|
|
234
|
+
}, []);
|
|
444
235
|
|
|
445
236
|
const handleTreeExpand = useCallback(
|
|
446
237
|
async (keys: React.Key[], info: { node: ProcessTreeNode; expanded: boolean }) => {
|
|
@@ -450,57 +241,24 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
450
241
|
if (node.processName && node.isDir) {
|
|
451
242
|
if (node.children && node.children.length > 0) return;
|
|
452
243
|
const dirName = node.filePath || node.title.replace(/\\/$/, '');
|
|
453
|
-
const result = await loadDirChildren(node.processName, dirName);
|
|
454
|
-
|
|
244
|
+
const result = await loadDirChildren(node.processName, dirName, token);
|
|
455
245
|
if (result.empty) {
|
|
456
|
-
const removeTreeNode = (nodes: ProcessTreeNode[], key: string): ProcessTreeNode[] =>
|
|
457
|
-
nodes
|
|
458
|
-
.filter((n) => n.key !== key)
|
|
459
|
-
.map((n) => (n.children ? { ...n, children: removeTreeNode(n.children, key) } : n));
|
|
460
246
|
setTreeData((prev) => removeTreeNode(prev, node.key as string));
|
|
461
247
|
setExpandedKeys((prev) => prev.filter((k) => k !== node.key));
|
|
462
248
|
} else {
|
|
463
249
|
const updateNode = (nodes: ProcessTreeNode[]): ProcessTreeNode[] =>
|
|
464
250
|
nodes.map((n) => {
|
|
465
|
-
if (n.key === node.key) {
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
if (n.children) {
|
|
469
|
-
return { ...n, children: updateNode(n.children) };
|
|
470
|
-
}
|
|
251
|
+
if (n.key === node.key) return { ...n, children: result.children };
|
|
252
|
+
if (n.children) return { ...n, children: updateNode(n.children) };
|
|
471
253
|
return n;
|
|
472
254
|
});
|
|
473
|
-
|
|
474
255
|
setTreeData((prev) => updateNode(prev));
|
|
475
256
|
}
|
|
476
257
|
}
|
|
477
258
|
},
|
|
478
|
-
[
|
|
259
|
+
[token],
|
|
479
260
|
);
|
|
480
261
|
|
|
481
|
-
const selectProcess = useCallback(async (name: string) => {
|
|
482
|
-
try {
|
|
483
|
-
const data = await fetchProcess(name);
|
|
484
|
-
setSelectedProcess(data);
|
|
485
|
-
setFileContent(null);
|
|
486
|
-
setProcessMdContent(null);
|
|
487
|
-
setLogs([]);
|
|
488
|
-
setMetrics([]);
|
|
489
|
-
setMetricsConfig([]);
|
|
490
|
-
setMetricSnapshots([]);
|
|
491
|
-
setMetricsViewConfig(null);
|
|
492
|
-
setActiveTab('overview');
|
|
493
|
-
if (data.hasProcessFile) {
|
|
494
|
-
try {
|
|
495
|
-
const mdContent = await fetchProcessFileContent(name, 'PROCESS.md');
|
|
496
|
-
setProcessMdContent({ content: mdContent, path: 'PROCESS.md' });
|
|
497
|
-
} catch { /* ignore */ }
|
|
498
|
-
}
|
|
499
|
-
} catch {
|
|
500
|
-
// error handled silently
|
|
501
|
-
}
|
|
502
|
-
}, []);
|
|
503
|
-
|
|
504
262
|
const handleTreeSelect = useCallback(
|
|
505
263
|
async (keys: React.Key[], info: { node: ProcessTreeNode }) => {
|
|
506
264
|
setSelectedKeys(keys);
|
|
@@ -510,7 +268,6 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
510
268
|
return;
|
|
511
269
|
}
|
|
512
270
|
const node = info.node;
|
|
513
|
-
|
|
514
271
|
if (node.isFile && node.processName && node.filePath) {
|
|
515
272
|
setLoading(true);
|
|
516
273
|
try {
|
|
@@ -522,9 +279,7 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
522
279
|
setSelectedProcess(null);
|
|
523
280
|
setProcessMdContent(null);
|
|
524
281
|
}
|
|
525
|
-
} catch {
|
|
526
|
-
// error handled silently
|
|
527
|
-
} finally {
|
|
282
|
+
} catch { /* ignore */ } finally {
|
|
528
283
|
setLoading(false);
|
|
529
284
|
}
|
|
530
285
|
} else if (node.isProcess && node.processName) {
|
|
@@ -540,7 +295,6 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
540
295
|
try {
|
|
541
296
|
const result = await runProcess(selectedProcess.name);
|
|
542
297
|
message.success(t('process.runTriggered', { runId: result.runId }));
|
|
543
|
-
// Immediate refresh to show Running status
|
|
544
298
|
await loadProcesses();
|
|
545
299
|
selectProcess(selectedProcess.name);
|
|
546
300
|
} catch {
|
|
@@ -587,66 +341,16 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
587
341
|
[loadProcesses, t],
|
|
588
342
|
);
|
|
589
343
|
|
|
590
|
-
const loadLogs = useCallback(async (page?: number, pageSize?: number, from?: string, to?: string) => {
|
|
591
|
-
if (!selectedProcess) return;
|
|
592
|
-
setLogsLoading(true);
|
|
593
|
-
try {
|
|
594
|
-
const p = page ?? logsPagination.current;
|
|
595
|
-
const ps = pageSize ?? logsPagination.pageSize;
|
|
596
|
-
const result = await fetchProcessLogs(selectedProcess.name, {
|
|
597
|
-
limit: ps,
|
|
598
|
-
offset: (p - 1) * ps,
|
|
599
|
-
from,
|
|
600
|
-
to,
|
|
601
|
-
});
|
|
602
|
-
setLogs(result.data);
|
|
603
|
-
setLogsTotal(result.total);
|
|
604
|
-
} catch {
|
|
605
|
-
setLogs([]);
|
|
606
|
-
setLogsTotal(0);
|
|
607
|
-
} finally {
|
|
608
|
-
setLogsLoading(false);
|
|
609
|
-
}
|
|
610
|
-
}, [selectedProcess, logsPagination.current, logsPagination.pageSize]);
|
|
611
|
-
|
|
612
|
-
const loadMetrics = useCallback(async () => {
|
|
613
|
-
if (!selectedProcess) return;
|
|
614
|
-
try {
|
|
615
|
-
const [m, c, snapshots, viewConfig] = await Promise.all([
|
|
616
|
-
fetchProcessMetrics(selectedProcess.name),
|
|
617
|
-
fetchProcessMetricsConfig(selectedProcess.name),
|
|
618
|
-
fetchProcessMetricsSnapshots(selectedProcess.name),
|
|
619
|
-
fetchProcessMetricsViewConfig(selectedProcess.name),
|
|
620
|
-
]);
|
|
621
|
-
setMetrics(m);
|
|
622
|
-
setMetricsConfig(c);
|
|
623
|
-
setMetricSnapshots(snapshots);
|
|
624
|
-
setMetricsViewConfig(viewConfig);
|
|
625
|
-
} catch { /* ignore */ }
|
|
626
|
-
}, [selectedProcess]);
|
|
627
|
-
|
|
628
|
-
const handleTabChange = useCallback((key: string) => {
|
|
629
|
-
setActiveTab(key);
|
|
630
|
-
if (key === 'logs') loadLogs(1, logsPagination.pageSize);
|
|
631
|
-
if (key === 'metrics') loadMetrics();
|
|
632
|
-
}, [loadLogs, loadMetrics]);
|
|
633
|
-
|
|
634
344
|
const rightTitle = useMemo(() => {
|
|
635
345
|
if (selectedProcess) return \`\${selectedProcess.name}\`;
|
|
636
346
|
if (fileContent) return fileContent.path;
|
|
637
347
|
return t('process.processDetail');
|
|
638
348
|
}, [selectedProcess, fileContent, t]);
|
|
639
349
|
|
|
640
|
-
// Build status tag for tree node
|
|
641
|
-
const getStatusColor = useCallback((proc: ProcessInfo) => {
|
|
642
|
-
const status = proc.status ?? 'idle';
|
|
643
|
-
return STATUS_COLORS[status] ?? 'default';
|
|
644
|
-
}, []);
|
|
645
|
-
|
|
646
350
|
return (
|
|
647
351
|
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
|
|
648
352
|
<Flex justify="space-between" align="center" style={{ marginBottom: 12 }}>
|
|
649
|
-
<Breadcrumb items={[{ title: <HomeOutlined onClick={() => onNavClick?.('')} style={{ cursor: 'pointer' }} /> }, ...(breadcrumbs || []).map(b => ({ title: b.path ? <a onClick={() => onNavClick?.(b.path!)}>{b.title}</a> : b.title }))]} />
|
|
353
|
+
<Breadcrumb items={[{ title: <HomeOutlined onClick={() => onNavClick?.('')} style={{ cursor: 'pointer' }} /> }, ...(breadcrumbs || []).map((b: { title: string; path?: string }) => ({ title: b.path ? <a onClick={() => onNavClick?.(b.path!)}>{b.title}</a> : b.title }))]} />
|
|
650
354
|
<Space>
|
|
651
355
|
<Button icon={<ReloadOutlined />} onClick={loadProcesses} loading={loading}>
|
|
652
356
|
{t('process.refresh')}
|
|
@@ -688,36 +392,21 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
688
392
|
<Spin spinning={loading && !selectedProcess && !fileContent}>
|
|
689
393
|
<div style={{ display: 'flex', height: '100%', padding: 16, gap: 16, overflow: 'hidden' }}>
|
|
690
394
|
<div style={{ width: 300, minWidth: 240, maxWidth: 500, overflow: 'auto', flexShrink: 0 }}>
|
|
691
|
-
<
|
|
692
|
-
|
|
395
|
+
<ProcessTree
|
|
396
|
+
processes={processes}
|
|
397
|
+
treeData={treeData}
|
|
693
398
|
selectedKeys={selectedKeys}
|
|
694
399
|
expandedKeys={expandedKeys}
|
|
695
|
-
showLine={{ showLeafIcon: false }}
|
|
696
|
-
blockNode
|
|
697
|
-
onSelect={handleTreeSelect}
|
|
698
400
|
onExpand={handleTreeExpand}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const statusLabel = status === 'running' ? t('process.statusRunning')
|
|
709
|
-
: status === 'busy' ? t('process.statusBusy')
|
|
710
|
-
: status === 'paused' ? t('process.statusPaused')
|
|
711
|
-
: status === 'error' ? t('process.statusError')
|
|
712
|
-
: t('process.statusIdle');
|
|
713
|
-
return (
|
|
714
|
-
<Tag color={STATUS_COLORS[status]} icon={STATUS_ICONS[status]} style={{ fontSize: 11, lineHeight: '16px', padding: '0 4px', marginRight: 0 }}>
|
|
715
|
-
{statusLabel}
|
|
716
|
-
</Tag>
|
|
717
|
-
);
|
|
718
|
-
})()}
|
|
719
|
-
</Flex>
|
|
720
|
-
)}
|
|
401
|
+
onSelect={handleTreeSelect}
|
|
402
|
+
onProcessSelect={selectProcess}
|
|
403
|
+
onFileSelect={async (pn, fp) => {
|
|
404
|
+
const content = await fetchProcessFileContent(pn, fp);
|
|
405
|
+
setFileContent({ content, path: fp });
|
|
406
|
+
setSelectedProcess(null);
|
|
407
|
+
setProcessMdContent(null);
|
|
408
|
+
}}
|
|
409
|
+
token={token}
|
|
721
410
|
/>
|
|
722
411
|
</div>
|
|
723
412
|
|
|
@@ -746,213 +435,35 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
746
435
|
{selectedProcess ? (
|
|
747
436
|
<Tabs
|
|
748
437
|
activeKey={activeTab}
|
|
749
|
-
onChange={
|
|
438
|
+
onChange={setActiveTab}
|
|
750
439
|
items={[
|
|
751
440
|
{
|
|
752
441
|
key: 'overview',
|
|
753
442
|
label: t('process.overview'),
|
|
754
443
|
children: (
|
|
755
|
-
<
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
</Tag>
|
|
768
|
-
<Tag>{selectedProcess.hasProcessFile ? t('process.modeFull') : t('process.modeLightweight')}</Tag>
|
|
769
|
-
</Space>
|
|
770
|
-
<Space>
|
|
771
|
-
<Tooltip title={selectedProcess.paused ? t('process.resume') : t('process.pause')}>
|
|
772
|
-
<Button icon={selectedProcess.paused ? <PlayCircleOutlined /> : <PauseCircleOutlined />}
|
|
773
|
-
onClick={selectedProcess.paused ? handleResume : handlePause} />
|
|
774
|
-
</Tooltip>
|
|
775
|
-
<Tooltip title={t('process.run')}>
|
|
776
|
-
<Button type="primary" icon={<PlayCircleOutlined />} onClick={handleRun} loading={executing} />
|
|
777
|
-
</Tooltip>
|
|
778
|
-
</Space>
|
|
779
|
-
</div>
|
|
780
|
-
|
|
781
|
-
<Descriptions bordered column={2} size="small"
|
|
782
|
-
styles={{ label: { width: 120, fontWeight: 500, backgroundColor: token.colorFillQuaternary } }}
|
|
783
|
-
items={[
|
|
784
|
-
{ key: 'trigger', label: t('process.trigger'), children: (
|
|
785
|
-
<Space>
|
|
786
|
-
{selectedProcess.cron && <Tag icon={<CalendarOutlined />}>{selectedProcess.cron}</Tag>}
|
|
787
|
-
{selectedProcess.onEvent && <Tag icon={<ThunderboltOutlined />}>{selectedProcess.onEvent}</Tag>}
|
|
788
|
-
{!selectedProcess.cron && !selectedProcess.onEvent && <Tag>{t('process.manual')}</Tag>}
|
|
789
|
-
</Space>
|
|
790
|
-
)},
|
|
791
|
-
{ key: 'owner', label: t('process.owner'), children: selectedProcess.owner },
|
|
792
|
-
...(selectedProcess.description ? [{ key: 'desc', label: t('process.description'), children: selectedProcess.description }] : []),
|
|
793
|
-
...(selectedProcess.prompt ? [{ key: 'prompt', label: t('process.prompt'), children: <Text code style={{ whiteSpace: 'pre-wrap', fontSize: 12, maxWidth: 400, display: 'inline-block' }}>{selectedProcess.prompt}</Text> }] : []),
|
|
794
|
-
]}
|
|
795
|
-
/>
|
|
796
|
-
|
|
797
|
-
{selectedProcess.hasProcessFile && processMdContent && (
|
|
798
|
-
<SkillFileViewer
|
|
799
|
-
skillDetail={null}
|
|
800
|
-
fileContent={processMdContent}
|
|
801
|
-
viewMode={viewMode}
|
|
802
|
-
onViewModeChange={setViewMode}
|
|
803
|
-
mode={mode}
|
|
804
|
-
token={token}
|
|
805
|
-
t={t}
|
|
806
|
-
/>
|
|
807
|
-
)}
|
|
808
|
-
</Space>
|
|
444
|
+
<ProcessOverview
|
|
445
|
+
process={selectedProcess}
|
|
446
|
+
processMdContent={processMdContent}
|
|
447
|
+
viewMode={viewMode}
|
|
448
|
+
onViewModeChange={setViewMode}
|
|
449
|
+
onRun={handleRun}
|
|
450
|
+
onPause={handlePause}
|
|
451
|
+
onResume={handleResume}
|
|
452
|
+
executing={executing}
|
|
453
|
+
mode={mode}
|
|
454
|
+
token={token}
|
|
455
|
+
/>
|
|
809
456
|
),
|
|
810
457
|
},
|
|
811
458
|
{
|
|
812
459
|
key: 'logs',
|
|
813
460
|
label: t('process.logs'),
|
|
814
|
-
children:
|
|
815
|
-
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
816
|
-
<Flex justify="flex-end" align="center">
|
|
817
|
-
<Space>
|
|
818
|
-
<DatePicker.RangePicker
|
|
819
|
-
placeholder={[t('process.timeRange'), '']}
|
|
820
|
-
onChange={(dates: [Dayjs | null, Dayjs | null] | null) => {
|
|
821
|
-
setLogsDateRange(dates);
|
|
822
|
-
setLogsPagination(prev => ({ ...prev, current: 1 }));
|
|
823
|
-
const from = dates?.[0]?.startOf('day')?.toISOString();
|
|
824
|
-
const to = dates?.[1]?.endOf('day')?.toISOString();
|
|
825
|
-
loadLogs(1, logsPagination.pageSize, from, to);
|
|
826
|
-
}}
|
|
827
|
-
size="small"
|
|
828
|
-
value={logsDateRange}
|
|
829
|
-
/>
|
|
830
|
-
<Button icon={<ReloadOutlined />} onClick={() => { setLogsPagination(prev => ({ ...prev, current: 1 })); const from = logsDateRange?.[0]?.startOf('day')?.toISOString(); const to = logsDateRange?.[1]?.endOf('day')?.toISOString(); loadLogs(1, logsPagination.pageSize, from, to); }} size="small" loading={logsLoading}>
|
|
831
|
-
{t('process.refresh')}
|
|
832
|
-
</Button>
|
|
833
|
-
</Space>
|
|
834
|
-
</Flex>
|
|
835
|
-
<Table<ProcessLogEntry>
|
|
836
|
-
dataSource={logs}
|
|
837
|
-
columns={[
|
|
838
|
-
{ title: t('process.runId'), dataIndex: 'runId', key: 'runId', width: 180, render: (runId: string) => <Tooltip title={runId}><Text strong style={{ fontFamily: 'monospace', fontSize: 12 }}>{runId.slice(0, 16)}</Text></Tooltip> },
|
|
839
|
-
{ title: t('process.triggerType'), dataIndex: 'triggerType', key: 'triggerType', width: 120, render: (triggerType: ProcessLogEntry['triggerType']) => <Tag icon={TRIGGER_ICONS[triggerType]}>{triggerType}</Tag> },
|
|
840
|
-
{ title: t('process.status'), dataIndex: 'status', key: 'status', width: 100, render: (status: ProcessLogEntry['status']) => <Tag color={status === 'completed' ? 'success' : status === 'failed' ? 'error' : status === 'timeout' ? 'warning' : 'processing'}>{status}</Tag> },
|
|
841
|
-
{ title: t('process.startedAt'), dataIndex: 'startedAt', key: 'startedAt', width: 180, render: (startedAt: string) => <Text type="secondary">{new Date(startedAt).toLocaleString()}</Text> },
|
|
842
|
-
{ title: t('process.duration'), dataIndex: 'durationMs', key: 'durationMs', width: 100, render: (durationMs: number) => durationMs > 0 ? <Text type="secondary">{(durationMs / 1000).toFixed(1)}s</Text> : <Text type="secondary">—</Text> },
|
|
843
|
-
{ title: '', key: 'actions', width: 48, render: (_: unknown, record: ProcessLogEntry) => record.sessionId && aiContext?.switchToSession ? <Tooltip title={t('process.viewSession')}><Button type="link" size="small" icon={<MessageOutlined />} onClick={() => aiContext.switchToSession!(record.sessionId)} /></Tooltip> : null },
|
|
844
|
-
]}
|
|
845
|
-
rowKey="runId"
|
|
846
|
-
size="small"
|
|
847
|
-
loading={logsLoading}
|
|
848
|
-
pagination={{
|
|
849
|
-
current: logsPagination.current,
|
|
850
|
-
pageSize: logsPagination.pageSize,
|
|
851
|
-
total: logsTotal,
|
|
852
|
-
showSizeChanger: true,
|
|
853
|
-
showTotal: (tot: number) => t('process.total').replace('{total}', String(tot)),
|
|
854
|
-
onChange: (page: number, pageSize: number) => {
|
|
855
|
-
setLogsPagination({ current: page, pageSize });
|
|
856
|
-
const from = logsDateRange?.[0]?.startOf('day')?.toISOString();
|
|
857
|
-
const to = logsDateRange?.[1]?.endOf('day')?.toISOString();
|
|
858
|
-
loadLogs(page, pageSize, from, to);
|
|
859
|
-
},
|
|
860
|
-
}}
|
|
861
|
-
locale={{ emptyText: t('process.noLogs') }}
|
|
862
|
-
/>
|
|
863
|
-
</Space>
|
|
864
|
-
),
|
|
461
|
+
children: <ProcessLogs processName={selectedProcess.name} />,
|
|
865
462
|
},
|
|
866
463
|
...(selectedProcess.hasProcessFile ? [{
|
|
867
464
|
key: 'metrics',
|
|
868
465
|
label: t('process.metrics'),
|
|
869
|
-
children:
|
|
870
|
-
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
|
871
|
-
{/* Business Metrics — agent-reported */}
|
|
872
|
-
{metricsViewConfig && metricsViewConfig.metrics.length > 0 && (
|
|
873
|
-
<div>
|
|
874
|
-
<Text strong style={{ fontSize: 14, marginBottom: 8, display: 'block' }}>{t('process.businessMetrics')}</Text>
|
|
875
|
-
<div style={{ display: 'grid', gridTemplateColumns: \`repeat(auto-fill, minmax(200px, 1fr))\`, gap: 12, marginBottom: 16 }}>
|
|
876
|
-
{metricsViewConfig.metrics.map((m) => {
|
|
877
|
-
const latestSnapshot = [...metricSnapshots].filter(s => s.metricName === m.metricName).sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
|
|
878
|
-
const trend = (() => {
|
|
879
|
-
const ms = metricSnapshots.filter(s => s.metricName === m.metricName).sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
880
|
-
if (ms.length < 2) return null;
|
|
881
|
-
const diff = ms[ms.length - 1]!.value - ms[0]!.value;
|
|
882
|
-
if (diff === 0) return { color: 'default', label: '→' };
|
|
883
|
-
const good = m.direction === 'decreasing' ? diff < 0 : m.direction === 'increasing' ? diff > 0 : true;
|
|
884
|
-
return { color: good ? 'success' : 'error', label: diff > 0 ? '↑' : '↓' };
|
|
885
|
-
})();
|
|
886
|
-
return (
|
|
887
|
-
<Card key={m.metricName} size="small">
|
|
888
|
-
<div style={{ fontSize: 12, color: token.colorTextSecondary }}>{m.title}</div>
|
|
889
|
-
<div style={{ fontSize: 24, fontWeight: 600, marginTop: 4 }}>
|
|
890
|
-
{latestSnapshot ? \`\${latestSnapshot.value}\${latestSnapshot.unit ?? m.unit ?? ''}\` : '-'}
|
|
891
|
-
{trend && <Tag color={trend.color} style={{ marginLeft: 6, fontSize: 12 }}>{trend.label}</Tag>}
|
|
892
|
-
</div>
|
|
893
|
-
{m.target !== undefined && <div style={{ fontSize: 11, color: token.colorTextTertiary }}>Target: {m.target}{m.unit ?? ''}</div>}
|
|
894
|
-
</Card>
|
|
895
|
-
);
|
|
896
|
-
})}
|
|
897
|
-
</div>
|
|
898
|
-
{metricsViewConfig.charts.length > 0 && metricSnapshots.length > 0 && (
|
|
899
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
|
900
|
-
{metricsViewConfig.charts.map((chart, i) => {
|
|
901
|
-
const chartMetricName = (chart as Record<string, unknown>).metricName as string | undefined;
|
|
902
|
-
const chartTitle = (chart as Record<string, unknown>).title as string | undefined;
|
|
903
|
-
const filteredSnapshots = chartMetricName
|
|
904
|
-
? metricSnapshots
|
|
905
|
-
.filter(s => s.metricName === chartMetricName)
|
|
906
|
-
.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
|
|
907
|
-
: metricSnapshots;
|
|
908
|
-
const { metricName: _mn, title: _t, ...chartSpec } = chart as Record<string, unknown>;
|
|
909
|
-
return (
|
|
910
|
-
<Card key={i} size="small" {...(chartTitle ? { title: chartTitle } : {})}>
|
|
911
|
-
<DashboardChart spec={{ ...chartSpec, data: filteredSnapshots }} />
|
|
912
|
-
</Card>
|
|
913
|
-
);
|
|
914
|
-
})}
|
|
915
|
-
</div>
|
|
916
|
-
)}
|
|
917
|
-
</div>
|
|
918
|
-
)}
|
|
919
|
-
{!metricsViewConfig && metricSnapshots.length > 0 && (
|
|
920
|
-
<div>
|
|
921
|
-
<Text strong style={{ fontSize: 14, marginBottom: 8, display: 'block' }}>{t('process.businessMetrics')}</Text>
|
|
922
|
-
<Text type="secondary">{t('process.noBusinessMetrics')}</Text>
|
|
923
|
-
</div>
|
|
924
|
-
)}
|
|
925
|
-
{/* Operational Metrics — system aggregates */}
|
|
926
|
-
<div>
|
|
927
|
-
<Text strong style={{ fontSize: 14, marginBottom: 8, display: 'block' }}>{t('process.operationalMetrics')}</Text>
|
|
928
|
-
{metricsConfig.length === 0 && metrics.length === 0 ? (
|
|
929
|
-
<Empty description={t('process.noMetrics')} />
|
|
930
|
-
) : (
|
|
931
|
-
<>
|
|
932
|
-
{metricsConfig.map((widget, i) => (
|
|
933
|
-
<DashboardChart key={\`op-\${i}\`} spec={{ ...widget, data: metrics }} />
|
|
934
|
-
))}
|
|
935
|
-
{metrics.length > 0 && (
|
|
936
|
-
<Table
|
|
937
|
-
dataSource={metrics}
|
|
938
|
-
rowKey="date"
|
|
939
|
-
size="small"
|
|
940
|
-
pagination={{ pageSize: 7 }}
|
|
941
|
-
columns={[
|
|
942
|
-
{ title: t('process.colDate'), dataIndex: 'date', key: 'date' },
|
|
943
|
-
{ title: t('process.colTotal'), dataIndex: 'totalRuns', key: 'totalRuns' },
|
|
944
|
-
{ title: t('process.colCompleted'), dataIndex: 'completedRuns', key: 'completedRuns' },
|
|
945
|
-
{ title: t('process.colFailed'), dataIndex: 'failedRuns', key: 'failedRuns' },
|
|
946
|
-
{ title: t('process.colTimeout'), dataIndex: 'timeoutRuns', key: 'timeoutRuns' },
|
|
947
|
-
{ title: t('process.colAvgDuration'), dataIndex: 'avgDurationMs', key: 'avgDurationMs', render: (ms: number) => \`\${(ms / 1000).toFixed(1)}s\` },
|
|
948
|
-
]}
|
|
949
|
-
/>
|
|
950
|
-
)}
|
|
951
|
-
</>
|
|
952
|
-
)}
|
|
953
|
-
</div>
|
|
954
|
-
</Space>
|
|
955
|
-
),
|
|
466
|
+
children: <ProcessMetrics processName={selectedProcess.name} hasProcessFile={selectedProcess.hasProcessFile} />,
|
|
956
467
|
}] : []),
|
|
957
468
|
]}
|
|
958
469
|
/>
|
|
@@ -980,6 +491,8 @@ export default function ProcessManagementPage(): React.ReactElement {
|
|
|
980
491
|
</div>
|
|
981
492
|
);
|
|
982
493
|
}
|
|
494
|
+
|
|
495
|
+
export default ProcessManagementPage;
|
|
983
496
|
`;
|
|
984
497
|
}
|
|
985
498
|
//# sourceMappingURL=process-management-page.js.map
|