jettask 0.2.7__py3-none-any.whl → 0.2.9__py3-none-any.whl
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.
- jettask/core/cli.py +242 -0
- jettask/pg_consumer/sql/add_execution_time_field.sql +29 -0
- jettask/pg_consumer/sql/create_new_tables.sql +137 -0
- jettask/pg_consumer/sql/create_tables_v3.sql +175 -0
- jettask/pg_consumer/sql/migrate_to_new_structure.sql +179 -0
- jettask/pg_consumer/sql/modify_time_fields.sql +69 -0
- jettask/webui/frontend/package.json +30 -0
- jettask/webui/frontend/src/App.css +109 -0
- jettask/webui/frontend/src/App.jsx +66 -0
- jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
- jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
- jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
- jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
- jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
- jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
- jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
- jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
- jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
- jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
- jettask/webui/frontend/src/components/charts/QueueChart.jsx +111 -0
- jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +115 -0
- jettask/webui/frontend/src/components/charts/WorkerChart.jsx +40 -0
- jettask/webui/frontend/src/components/common/StatsCard.jsx +18 -0
- jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
- jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
- jettask/webui/frontend/src/components/layout/Header.css +106 -0
- jettask/webui/frontend/src/components/layout/Header.jsx +106 -0
- jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
- jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
- jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
- jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
- jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
- jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
- jettask/webui/frontend/src/contexts/LoadingContext.jsx +27 -0
- jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
- jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
- jettask/webui/frontend/src/index.css +114 -0
- jettask/webui/frontend/src/main.jsx +20 -0
- jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
- jettask/webui/frontend/src/pages/Dashboard/index.css +35 -0
- jettask/webui/frontend/src/pages/Dashboard/index.jsx +281 -0
- jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
- jettask/webui/frontend/src/pages/QueueDetail.jsx +1117 -0
- jettask/webui/frontend/src/pages/QueueMonitor.jsx +527 -0
- jettask/webui/frontend/src/pages/Queues.jsx +12 -0
- jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
- jettask/webui/frontend/src/pages/Settings.jsx +800 -0
- jettask/webui/frontend/src/pages/Workers.jsx +12 -0
- jettask/webui/frontend/src/services/api.js +114 -0
- jettask/webui/frontend/src/services/queueTrend.js +152 -0
- jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
- jettask/webui/frontend/src/utils/userPreferences.js +154 -0
- jettask/webui/frontend/vite.config.js +26 -0
- {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/METADATA +1 -1
- {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/RECORD +59 -14
- jettask/webui/static/dist/assets/index-7129cfe1.css +0 -1
- jettask/webui/static/dist/assets/index-8d1935cc.js +0 -774
- jettask/webui/static/dist/index.html +0 -15
- jettask/webui/static/index.html +0 -1734
- jettask/webui/static/queue.html +0 -981
- jettask/webui/static/queues.html +0 -549
- jettask/webui/static/workers.html +0 -734
- {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/WHEEL +0 -0
- {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
-- 数据迁移脚本
|
2
|
+
-- 从旧的单表结构迁移到新的双表结构
|
3
|
+
|
4
|
+
BEGIN;
|
5
|
+
|
6
|
+
-- 1. 先备份原表(如果存在)
|
7
|
+
DO $$
|
8
|
+
BEGIN
|
9
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'tasks') THEN
|
10
|
+
-- 如果tasks表已存在,重命名为备份表
|
11
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'tasks_backup') THEN
|
12
|
+
ALTER TABLE tasks RENAME TO tasks_backup;
|
13
|
+
ELSE
|
14
|
+
-- 如果备份表已存在,创建带时间戳的备份
|
15
|
+
EXECUTE format('ALTER TABLE tasks RENAME TO tasks_backup_%s',
|
16
|
+
to_char(now(), 'YYYYMMDD_HH24MISS'));
|
17
|
+
END IF;
|
18
|
+
END IF;
|
19
|
+
END $$;
|
20
|
+
|
21
|
+
-- 2. 执行创建新表的SQL(引用create_new_tables.sql的内容)
|
22
|
+
-- 注意:在实际使用时,应该先运行create_new_tables.sql
|
23
|
+
|
24
|
+
-- 3. 如果有旧数据,进行数据迁移
|
25
|
+
DO $$
|
26
|
+
BEGIN
|
27
|
+
-- 检查是否有备份表
|
28
|
+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'tasks_backup') THEN
|
29
|
+
-- 迁移任务基础信息到新的tasks表
|
30
|
+
INSERT INTO tasks (
|
31
|
+
stream_id,
|
32
|
+
queue,
|
33
|
+
task_name,
|
34
|
+
task_type,
|
35
|
+
payload,
|
36
|
+
priority,
|
37
|
+
created_at,
|
38
|
+
scheduled_at,
|
39
|
+
status,
|
40
|
+
source,
|
41
|
+
metadata
|
42
|
+
)
|
43
|
+
SELECT
|
44
|
+
COALESCE(task_id, 'unknown-' || id::text) as stream_id, -- 使用task_id作为stream_id
|
45
|
+
COALESCE(queue_name, 'default') as queue,
|
46
|
+
COALESCE(task_name, 'unknown') as task_name,
|
47
|
+
task_type,
|
48
|
+
COALESCE(task_data, '{}'::jsonb) as payload,
|
49
|
+
COALESCE(priority, 0) as priority,
|
50
|
+
created_at,
|
51
|
+
trigger_time as scheduled_at,
|
52
|
+
CASE
|
53
|
+
WHEN status IN ('pending', 'running', 'completed', 'failed') THEN status
|
54
|
+
WHEN status = 'success' THEN 'completed'
|
55
|
+
ELSE 'pending'
|
56
|
+
END as status,
|
57
|
+
'migration' as source,
|
58
|
+
jsonb_build_object(
|
59
|
+
'migrated_at', now(),
|
60
|
+
'original_id', id,
|
61
|
+
'original_status', status
|
62
|
+
) as metadata
|
63
|
+
FROM tasks_backup
|
64
|
+
ON CONFLICT (stream_id) DO NOTHING; -- 避免重复
|
65
|
+
|
66
|
+
-- 迁移执行记录到task_runs表
|
67
|
+
-- 由于旧表是单表结构,我们为每个任务创建一条运行记录
|
68
|
+
INSERT INTO task_runs (
|
69
|
+
task_id,
|
70
|
+
stream_id,
|
71
|
+
consumer_group,
|
72
|
+
consumer_name,
|
73
|
+
worker_id,
|
74
|
+
status,
|
75
|
+
start_time,
|
76
|
+
end_time,
|
77
|
+
retry_count,
|
78
|
+
error_message,
|
79
|
+
result
|
80
|
+
)
|
81
|
+
SELECT
|
82
|
+
t.id as task_id,
|
83
|
+
t.stream_id,
|
84
|
+
'default_group' as consumer_group, -- 默认消费者组
|
85
|
+
tb.consumer as consumer_name,
|
86
|
+
tb.consumer as worker_id,
|
87
|
+
CASE
|
88
|
+
WHEN tb.status = 'success' THEN 'success'
|
89
|
+
WHEN tb.status IN ('failed', 'error') THEN 'failed'
|
90
|
+
WHEN tb.status = 'running' THEN 'running'
|
91
|
+
WHEN tb.status = 'timeout' THEN 'timeout'
|
92
|
+
ELSE 'pending'
|
93
|
+
END as status,
|
94
|
+
tb.started_at as start_time,
|
95
|
+
tb.completed_at as end_time,
|
96
|
+
COALESCE(tb.retry_count, 0) as retry_count,
|
97
|
+
tb.error as error_message,
|
98
|
+
tb.result
|
99
|
+
FROM tasks_backup tb
|
100
|
+
JOIN tasks t ON t.stream_id = COALESCE(tb.task_id, 'unknown-' || tb.id::text)
|
101
|
+
WHERE tb.status IS NOT NULL;
|
102
|
+
|
103
|
+
RAISE NOTICE 'Data migration completed successfully';
|
104
|
+
ELSE
|
105
|
+
RAISE NOTICE 'No backup table found, skipping data migration';
|
106
|
+
END IF;
|
107
|
+
END $$;
|
108
|
+
|
109
|
+
-- 4. 创建兼容性视图(可选)
|
110
|
+
-- 如果有代码依赖旧的表结构,可以创建视图提供兼容性
|
111
|
+
CREATE OR REPLACE VIEW tasks_legacy AS
|
112
|
+
SELECT
|
113
|
+
t.id,
|
114
|
+
t.stream_id as task_id,
|
115
|
+
t.queue as queue_name,
|
116
|
+
t.task_name,
|
117
|
+
t.task_type,
|
118
|
+
t.payload as task_data,
|
119
|
+
t.priority,
|
120
|
+
tr.consumer_name as consumer,
|
121
|
+
tr.status,
|
122
|
+
tr.start_time as started_at,
|
123
|
+
tr.end_time as completed_at,
|
124
|
+
tr.retry_count,
|
125
|
+
tr.error_message as error,
|
126
|
+
tr.result,
|
127
|
+
t.created_at,
|
128
|
+
tr.updated_at,
|
129
|
+
t.scheduled_at as trigger_time
|
130
|
+
FROM tasks t
|
131
|
+
LEFT JOIN task_runs tr ON t.id = tr.task_id
|
132
|
+
AND tr.consumer_group = 'default_group'; -- 兼容旧代码,只显示默认组
|
133
|
+
|
134
|
+
-- 5. 更新序列(如果需要)
|
135
|
+
DO $$
|
136
|
+
DECLARE
|
137
|
+
max_id BIGINT;
|
138
|
+
BEGIN
|
139
|
+
-- 获取最大ID
|
140
|
+
SELECT COALESCE(MAX(id), 0) INTO max_id FROM tasks;
|
141
|
+
-- 更新序列
|
142
|
+
IF max_id > 0 THEN
|
143
|
+
EXECUTE format('ALTER SEQUENCE tasks_id_seq RESTART WITH %s', max_id + 1);
|
144
|
+
END IF;
|
145
|
+
|
146
|
+
SELECT COALESCE(MAX(id), 0) INTO max_id FROM task_runs;
|
147
|
+
IF max_id > 0 THEN
|
148
|
+
EXECUTE format('ALTER SEQUENCE task_runs_id_seq RESTART WITH %s', max_id + 1);
|
149
|
+
END IF;
|
150
|
+
END $$;
|
151
|
+
|
152
|
+
COMMIT;
|
153
|
+
|
154
|
+
-- 6. 验证迁移结果
|
155
|
+
SELECT
|
156
|
+
'Tasks Table' as table_name,
|
157
|
+
COUNT(*) as row_count
|
158
|
+
FROM tasks
|
159
|
+
UNION ALL
|
160
|
+
SELECT
|
161
|
+
'Task Runs Table' as table_name,
|
162
|
+
COUNT(*) as row_count
|
163
|
+
FROM task_runs
|
164
|
+
UNION ALL
|
165
|
+
SELECT
|
166
|
+
'Original Backup Table' as table_name,
|
167
|
+
COUNT(*) as row_count
|
168
|
+
FROM tasks_backup
|
169
|
+
WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'tasks_backup');
|
170
|
+
|
171
|
+
-- 显示迁移统计
|
172
|
+
SELECT
|
173
|
+
'Migration Summary' as info,
|
174
|
+
jsonb_build_object(
|
175
|
+
'total_tasks', (SELECT COUNT(*) FROM tasks),
|
176
|
+
'total_runs', (SELECT COUNT(*) FROM task_runs),
|
177
|
+
'unique_consumer_groups', (SELECT COUNT(DISTINCT consumer_group) FROM task_runs),
|
178
|
+
'migration_time', now()
|
179
|
+
) as details;
|
@@ -0,0 +1,69 @@
|
|
1
|
+
-- 修改task_runs表的时间字段,改为存储秒数而不是毫秒
|
2
|
+
-- 1. 重命名字段并修改类型
|
3
|
+
-- 2. 将已有的毫秒数据转换为秒
|
4
|
+
|
5
|
+
DO $$
|
6
|
+
BEGIN
|
7
|
+
-- 处理duration_ms字段
|
8
|
+
IF EXISTS (
|
9
|
+
SELECT 1 FROM information_schema.columns
|
10
|
+
WHERE table_name = 'task_runs' AND column_name = 'duration_ms'
|
11
|
+
) THEN
|
12
|
+
-- 如果duration字段不存在,创建它
|
13
|
+
IF NOT EXISTS (
|
14
|
+
SELECT 1 FROM information_schema.columns
|
15
|
+
WHERE table_name = 'task_runs' AND column_name = 'duration'
|
16
|
+
) THEN
|
17
|
+
ALTER TABLE task_runs ADD COLUMN duration DOUBLE PRECISION;
|
18
|
+
END IF;
|
19
|
+
|
20
|
+
-- 将毫秒转换为秒
|
21
|
+
UPDATE task_runs
|
22
|
+
SET duration = duration_ms / 1000.0
|
23
|
+
WHERE duration_ms IS NOT NULL AND duration IS NULL;
|
24
|
+
|
25
|
+
-- 删除旧字段
|
26
|
+
ALTER TABLE task_runs DROP COLUMN IF EXISTS duration_ms;
|
27
|
+
END IF;
|
28
|
+
|
29
|
+
-- 处理execution_time_ms字段
|
30
|
+
IF EXISTS (
|
31
|
+
SELECT 1 FROM information_schema.columns
|
32
|
+
WHERE table_name = 'task_runs' AND column_name = 'execution_time_ms'
|
33
|
+
) THEN
|
34
|
+
-- 如果execution_time字段不存在,创建它
|
35
|
+
IF NOT EXISTS (
|
36
|
+
SELECT 1 FROM information_schema.columns
|
37
|
+
WHERE table_name = 'task_runs' AND column_name = 'execution_time'
|
38
|
+
) THEN
|
39
|
+
ALTER TABLE task_runs ADD COLUMN execution_time DOUBLE PRECISION;
|
40
|
+
END IF;
|
41
|
+
|
42
|
+
-- 将毫秒转换为秒
|
43
|
+
UPDATE task_runs
|
44
|
+
SET execution_time = execution_time_ms / 1000.0
|
45
|
+
WHERE execution_time_ms IS NOT NULL AND execution_time IS NULL;
|
46
|
+
|
47
|
+
-- 删除旧字段
|
48
|
+
ALTER TABLE task_runs DROP COLUMN IF EXISTS execution_time_ms;
|
49
|
+
END IF;
|
50
|
+
|
51
|
+
-- 如果字段已经是正确的格式,确保它们存在
|
52
|
+
IF NOT EXISTS (
|
53
|
+
SELECT 1 FROM information_schema.columns
|
54
|
+
WHERE table_name = 'task_runs' AND column_name = 'duration'
|
55
|
+
) THEN
|
56
|
+
ALTER TABLE task_runs ADD COLUMN duration DOUBLE PRECISION;
|
57
|
+
END IF;
|
58
|
+
|
59
|
+
IF NOT EXISTS (
|
60
|
+
SELECT 1 FROM information_schema.columns
|
61
|
+
WHERE table_name = 'task_runs' AND column_name = 'execution_time'
|
62
|
+
) THEN
|
63
|
+
ALTER TABLE task_runs ADD COLUMN execution_time DOUBLE PRECISION;
|
64
|
+
END IF;
|
65
|
+
END $$;
|
66
|
+
|
67
|
+
-- 添加注释
|
68
|
+
COMMENT ON COLUMN task_runs.duration IS '总耗时(秒),从任务创建到执行完成的时间';
|
69
|
+
COMMENT ON COLUMN task_runs.execution_time IS '实际执行时间(秒),从任务开始执行到执行完成的时间';
|
@@ -0,0 +1,30 @@
|
|
1
|
+
{
|
2
|
+
"name": "jettask-monitor",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"private": true,
|
5
|
+
"dependencies": {
|
6
|
+
"@ant-design/charts": "^2.6.1",
|
7
|
+
"@ant-design/colors": "^7.2.1",
|
8
|
+
"@ant-design/cssinjs": "^1.24.0",
|
9
|
+
"@ant-design/icons": "^5.0.1",
|
10
|
+
"@ant-design/plots": "^2.6.3",
|
11
|
+
"@ant-design/pro-components": "^2.8.10",
|
12
|
+
"@ant-design/pro-table": "^3.21.0",
|
13
|
+
"antd": "^5.4.0",
|
14
|
+
"axios": "^1.3.4",
|
15
|
+
"dayjs": "^1.11.7",
|
16
|
+
"react": "^18.2.0",
|
17
|
+
"react-dom": "^18.2.0",
|
18
|
+
"react-router-dom": "^6.8.2",
|
19
|
+
"uuid": "^11.1.0"
|
20
|
+
},
|
21
|
+
"devDependencies": {
|
22
|
+
"@vitejs/plugin-react": "^3.1.0",
|
23
|
+
"vite": "^4.1.4"
|
24
|
+
},
|
25
|
+
"scripts": {
|
26
|
+
"dev": "vite",
|
27
|
+
"build": "vite build",
|
28
|
+
"preview": "vite preview"
|
29
|
+
}
|
30
|
+
}
|
@@ -0,0 +1,109 @@
|
|
1
|
+
/* 全局样式重置 */
|
2
|
+
* {
|
3
|
+
margin: 0;
|
4
|
+
padding: 0;
|
5
|
+
box-sizing: border-box;
|
6
|
+
}
|
7
|
+
|
8
|
+
html, body, #root {
|
9
|
+
height: 100%;
|
10
|
+
width: 100%;
|
11
|
+
overflow: hidden;
|
12
|
+
}
|
13
|
+
|
14
|
+
body {
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
16
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
17
|
+
sans-serif;
|
18
|
+
-webkit-font-smoothing: antialiased;
|
19
|
+
-moz-osx-font-smoothing: grayscale;
|
20
|
+
}
|
21
|
+
|
22
|
+
/* 页面容器样式 */
|
23
|
+
.page-container {
|
24
|
+
padding: 24px;
|
25
|
+
height: 100%;
|
26
|
+
overflow: auto;
|
27
|
+
background: #f0f2f5;
|
28
|
+
}
|
29
|
+
|
30
|
+
/* 页面内容包装器 - 用于需要padding的页面 */
|
31
|
+
.page-wrapper {
|
32
|
+
padding: 24px;
|
33
|
+
height: 100%;
|
34
|
+
overflow: auto;
|
35
|
+
background: #f0f2f5;
|
36
|
+
}
|
37
|
+
|
38
|
+
.page-header {
|
39
|
+
background: #fff;
|
40
|
+
padding: 16px 24px;
|
41
|
+
border-radius: 4px;
|
42
|
+
margin-bottom: 16px;
|
43
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
44
|
+
}
|
45
|
+
|
46
|
+
.page-title {
|
47
|
+
margin: 0;
|
48
|
+
font-size: 20px;
|
49
|
+
font-weight: 500;
|
50
|
+
color: #262626;
|
51
|
+
}
|
52
|
+
|
53
|
+
/* 卡片样式优化 */
|
54
|
+
.ant-card {
|
55
|
+
border-radius: 4px;
|
56
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
|
57
|
+
}
|
58
|
+
|
59
|
+
/* 表格样式优化 */
|
60
|
+
.ant-table {
|
61
|
+
font-size: 13px;
|
62
|
+
}
|
63
|
+
|
64
|
+
.ant-table-thead > tr > th {
|
65
|
+
background: #fafafa;
|
66
|
+
font-weight: 600;
|
67
|
+
}
|
68
|
+
|
69
|
+
/* 按钮样式优化 */
|
70
|
+
.ant-btn {
|
71
|
+
border-radius: 4px;
|
72
|
+
font-weight: 400;
|
73
|
+
}
|
74
|
+
|
75
|
+
/* 滚动条统一样式 */
|
76
|
+
::-webkit-scrollbar {
|
77
|
+
width: 8px;
|
78
|
+
height: 8px;
|
79
|
+
}
|
80
|
+
|
81
|
+
::-webkit-scrollbar-track {
|
82
|
+
background: #f0f0f0;
|
83
|
+
border-radius: 4px;
|
84
|
+
}
|
85
|
+
|
86
|
+
::-webkit-scrollbar-thumb {
|
87
|
+
background: #bfbfbf;
|
88
|
+
border-radius: 4px;
|
89
|
+
}
|
90
|
+
|
91
|
+
::-webkit-scrollbar-thumb:hover {
|
92
|
+
background: #8c8c8c;
|
93
|
+
}
|
94
|
+
|
95
|
+
/* 动画效果 */
|
96
|
+
@keyframes fadeIn {
|
97
|
+
from {
|
98
|
+
opacity: 0;
|
99
|
+
transform: translateY(10px);
|
100
|
+
}
|
101
|
+
to {
|
102
|
+
opacity: 1;
|
103
|
+
transform: translateY(0);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
.fade-in {
|
108
|
+
animation: fadeIn 0.3s ease-in-out;
|
109
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
|
2
|
+
import { ConfigProvider, theme } from 'antd'
|
3
|
+
import zhCN from 'antd/locale/zh_CN'
|
4
|
+
import AppLayout from './components/layout/AppLayout'
|
5
|
+
import Dashboard from './pages/Dashboard'
|
6
|
+
import Queues from './pages/Queues'
|
7
|
+
import Workers from './pages/Workers'
|
8
|
+
import QueueDetail from './pages/QueueDetail'
|
9
|
+
import ScheduledTasks from './pages/ScheduledTasks'
|
10
|
+
import Alerts from './pages/Alerts'
|
11
|
+
import Settings from './pages/Settings'
|
12
|
+
import { LoadingProvider } from './contexts/LoadingContext'
|
13
|
+
import { NamespaceProvider } from './contexts/NamespaceContext'
|
14
|
+
import './App.css'
|
15
|
+
|
16
|
+
function App() {
|
17
|
+
// 自定义主题
|
18
|
+
const customTheme = {
|
19
|
+
algorithm: theme.defaultAlgorithm,
|
20
|
+
token: {
|
21
|
+
colorPrimary: '#1890ff',
|
22
|
+
borderRadius: 4,
|
23
|
+
colorBgContainer: '#ffffff',
|
24
|
+
},
|
25
|
+
components: {
|
26
|
+
Layout: {
|
27
|
+
siderBg: '#1a1d21',
|
28
|
+
headerBg: '#2a2d31',
|
29
|
+
},
|
30
|
+
Menu: {
|
31
|
+
darkItemBg: 'transparent',
|
32
|
+
darkItemSelectedBg: '#1890ff',
|
33
|
+
darkItemHoverBg: 'rgba(24, 144, 255, 0.1)',
|
34
|
+
},
|
35
|
+
},
|
36
|
+
}
|
37
|
+
|
38
|
+
return (
|
39
|
+
<ConfigProvider theme={customTheme} locale={zhCN}>
|
40
|
+
<LoadingProvider>
|
41
|
+
<NamespaceProvider>
|
42
|
+
<Router>
|
43
|
+
<Routes>
|
44
|
+
<Route path="/" element={<AppLayout />}>
|
45
|
+
<Route index element={<Navigate to="/dashboard" replace />} />
|
46
|
+
<Route path="dashboard" element={<Dashboard />} />
|
47
|
+
<Route path="queues" element={<Queues />} />
|
48
|
+
<Route path="queue/:queueName" element={<QueueDetail />} />
|
49
|
+
<Route path="scheduled-tasks" element={<ScheduledTasks />} />
|
50
|
+
<Route path="alerts" element={<Alerts />} />
|
51
|
+
{/* 新增路由 */}
|
52
|
+
<Route path="analytics" element={<div style={{ padding: 24 }}>数据分析页面开发中...</div>} />
|
53
|
+
<Route path="performance" element={<div style={{ padding: 24 }}>性能监控页面开发中...</div>} />
|
54
|
+
<Route path="logs" element={<div style={{ padding: 24 }}>日志查询页面开发中...</div>} />
|
55
|
+
<Route path="api-docs" element={<div style={{ padding: 24 }}>API文档页面开发中...</div>} />
|
56
|
+
<Route path="settings" element={<Settings />} />
|
57
|
+
</Route>
|
58
|
+
</Routes>
|
59
|
+
</Router>
|
60
|
+
</NamespaceProvider>
|
61
|
+
</LoadingProvider>
|
62
|
+
</ConfigProvider>
|
63
|
+
)
|
64
|
+
}
|
65
|
+
|
66
|
+
export default App
|
@@ -0,0 +1,166 @@
|
|
1
|
+
/**
|
2
|
+
* 命名空间选择器组件
|
3
|
+
* 放置在页面顶部,用于切换不同的命名空间
|
4
|
+
*/
|
5
|
+
import React, { useState, useEffect } from 'react';
|
6
|
+
import { Select, Space, Tag, Tooltip, message, Button, Dropdown, Modal } from 'antd';
|
7
|
+
import { DatabaseOutlined, CloudServerOutlined, SettingOutlined, PlusOutlined, EditOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
|
8
|
+
import { useNavigate } from 'react-router-dom';
|
9
|
+
import { useNamespace } from '../contexts/NamespaceContext';
|
10
|
+
|
11
|
+
const { Option } = Select;
|
12
|
+
|
13
|
+
const NamespaceSelector = ({ value, onChange, style }) => {
|
14
|
+
const [namespaces, setNamespaces] = useState([]);
|
15
|
+
const [loading, setLoading] = useState(false);
|
16
|
+
const navigate = useNavigate();
|
17
|
+
const { refreshTrigger } = useNamespace(); // 获取刷新触发器
|
18
|
+
|
19
|
+
// 获取命名空间列表
|
20
|
+
const fetchNamespaces = async () => {
|
21
|
+
setLoading(true);
|
22
|
+
try {
|
23
|
+
const response = await fetch('/api/data/namespaces');
|
24
|
+
if (response.ok) {
|
25
|
+
const data = await response.json();
|
26
|
+
setNamespaces(data);
|
27
|
+
|
28
|
+
// 如果没有选中的命名空间,默认选中第一个
|
29
|
+
if (!value && data.length > 0) {
|
30
|
+
const firstNamespace = data[0].name;
|
31
|
+
if (onChange) {
|
32
|
+
onChange(firstNamespace);
|
33
|
+
}
|
34
|
+
} else if (data.length === 0) {
|
35
|
+
// 如果没有任何命名空间,弹出提示并引导到管理页面
|
36
|
+
showNoNamespaceModal();
|
37
|
+
}
|
38
|
+
} else {
|
39
|
+
message.error('获取命名空间列表失败');
|
40
|
+
}
|
41
|
+
} catch (error) {
|
42
|
+
console.error('获取命名空间失败:', error);
|
43
|
+
message.error('连接任务中心失败');
|
44
|
+
} finally {
|
45
|
+
setLoading(false);
|
46
|
+
}
|
47
|
+
};
|
48
|
+
|
49
|
+
// 组件挂载时获取命名空间列表,以及当refreshTrigger变化时刷新
|
50
|
+
useEffect(() => {
|
51
|
+
fetchNamespaces();
|
52
|
+
}, [refreshTrigger]); // 监听刷新触发器而不是value
|
53
|
+
|
54
|
+
// 处理命名空间切换
|
55
|
+
const handleNamespaceChange = (namespaceName) => {
|
56
|
+
console.log('🔧 NamespaceSelector切换命名空间:', namespaceName);
|
57
|
+
console.log('🔧 当前props.value:', value);
|
58
|
+
console.log('🔧 onChange函数存在:', !!onChange);
|
59
|
+
|
60
|
+
if (onChange) {
|
61
|
+
onChange(namespaceName);
|
62
|
+
console.log('🔧 已调用onChange函数');
|
63
|
+
}
|
64
|
+
message.success(`已切换到命名空间: ${namespaceName}`);
|
65
|
+
};
|
66
|
+
|
67
|
+
// 显示无命名空间提示弹窗
|
68
|
+
const showNoNamespaceModal = () => {
|
69
|
+
Modal.confirm({
|
70
|
+
title: '暂无可用命名空间',
|
71
|
+
icon: <ExclamationCircleOutlined />,
|
72
|
+
content: '当前系统中没有配置任何命名空间,请先创建一个命名空间。',
|
73
|
+
okText: '去管理命名空间',
|
74
|
+
cancelText: '取消',
|
75
|
+
onOk: () => {
|
76
|
+
navigate('/settings');
|
77
|
+
},
|
78
|
+
});
|
79
|
+
};
|
80
|
+
|
81
|
+
// 获取命名空间标签颜色
|
82
|
+
const getNamespaceTagColor = (name) => {
|
83
|
+
if (name === 'default') return 'blue';
|
84
|
+
if (name.includes('test')) return 'orange';
|
85
|
+
if (name.includes('prod')) return 'green';
|
86
|
+
if (name.includes('dev')) return 'purple';
|
87
|
+
return 'default';
|
88
|
+
};
|
89
|
+
|
90
|
+
|
91
|
+
return (
|
92
|
+
<Space style={{ ...style }}>
|
93
|
+
<span style={{ color: 'rgba(255, 255, 255, 0.65)', fontSize: '14px' }}>
|
94
|
+
<DatabaseOutlined style={{ marginRight: '6px' }} />
|
95
|
+
命名空间:
|
96
|
+
</span>
|
97
|
+
|
98
|
+
<Select
|
99
|
+
value={value}
|
100
|
+
onChange={handleNamespaceChange}
|
101
|
+
loading={loading}
|
102
|
+
style={{ minWidth: 200 }}
|
103
|
+
placeholder="请选择命名空间"
|
104
|
+
showSearch
|
105
|
+
optionFilterProp="children"
|
106
|
+
filterOption={(input, option) =>
|
107
|
+
option.children.props.children[1]?.toLowerCase().includes(input.toLowerCase())
|
108
|
+
}
|
109
|
+
dropdownRender={(menu) => (
|
110
|
+
<>
|
111
|
+
{menu}
|
112
|
+
<div
|
113
|
+
style={{
|
114
|
+
padding: '8px 12px',
|
115
|
+
textAlign: 'center',
|
116
|
+
borderTop: '1px solid #f0f0f0',
|
117
|
+
cursor: 'pointer'
|
118
|
+
}}
|
119
|
+
onClick={(e) => {
|
120
|
+
e.stopPropagation();
|
121
|
+
e.preventDefault();
|
122
|
+
console.log('🔧 点击管理命名空间区域');
|
123
|
+
navigate('/settings');
|
124
|
+
}}
|
125
|
+
>
|
126
|
+
<SettingOutlined style={{ marginRight: 4 }} />
|
127
|
+
管理命名空间
|
128
|
+
</div>
|
129
|
+
</>
|
130
|
+
)}
|
131
|
+
>
|
132
|
+
{namespaces.map(ns => (
|
133
|
+
<Option key={ns.name} value={ns.name}>
|
134
|
+
<Space>
|
135
|
+
<Tag color={getNamespaceTagColor(ns.name)} style={{ margin: 0 }}>
|
136
|
+
{ns.name}
|
137
|
+
</Tag>
|
138
|
+
{ns.description && (
|
139
|
+
<Tooltip title={ns.description}>
|
140
|
+
<span style={{ color: '#999', fontSize: '12px' }}>
|
141
|
+
{ns.description.length > 20
|
142
|
+
? `${ns.description.substring(0, 20)}...`
|
143
|
+
: ns.description}
|
144
|
+
</span>
|
145
|
+
</Tooltip>
|
146
|
+
)}
|
147
|
+
</Space>
|
148
|
+
</Option>
|
149
|
+
))}
|
150
|
+
</Select>
|
151
|
+
|
152
|
+
{value && (
|
153
|
+
<Tooltip title={`当前命名空间: ${value}`}>
|
154
|
+
<CloudServerOutlined
|
155
|
+
style={{
|
156
|
+
color: '#52c41a',
|
157
|
+
fontSize: '16px'
|
158
|
+
}}
|
159
|
+
/>
|
160
|
+
</Tooltip>
|
161
|
+
)}
|
162
|
+
</Space>
|
163
|
+
);
|
164
|
+
};
|
165
|
+
|
166
|
+
export default NamespaceSelector;
|