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.
Files changed (66) hide show
  1. jettask/core/cli.py +242 -0
  2. jettask/pg_consumer/sql/add_execution_time_field.sql +29 -0
  3. jettask/pg_consumer/sql/create_new_tables.sql +137 -0
  4. jettask/pg_consumer/sql/create_tables_v3.sql +175 -0
  5. jettask/pg_consumer/sql/migrate_to_new_structure.sql +179 -0
  6. jettask/pg_consumer/sql/modify_time_fields.sql +69 -0
  7. jettask/webui/frontend/package.json +30 -0
  8. jettask/webui/frontend/src/App.css +109 -0
  9. jettask/webui/frontend/src/App.jsx +66 -0
  10. jettask/webui/frontend/src/components/NamespaceSelector.jsx +166 -0
  11. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +298 -0
  12. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +638 -0
  13. jettask/webui/frontend/src/components/QueueDetailsTable.css +65 -0
  14. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +487 -0
  15. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +465 -0
  16. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +423 -0
  17. jettask/webui/frontend/src/components/TaskFilter.jsx +425 -0
  18. jettask/webui/frontend/src/components/TimeRangeSelector.css +21 -0
  19. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +160 -0
  20. jettask/webui/frontend/src/components/charts/QueueChart.jsx +111 -0
  21. jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +115 -0
  22. jettask/webui/frontend/src/components/charts/WorkerChart.jsx +40 -0
  23. jettask/webui/frontend/src/components/common/StatsCard.jsx +18 -0
  24. jettask/webui/frontend/src/components/layout/AppLayout.css +95 -0
  25. jettask/webui/frontend/src/components/layout/AppLayout.jsx +49 -0
  26. jettask/webui/frontend/src/components/layout/Header.css +106 -0
  27. jettask/webui/frontend/src/components/layout/Header.jsx +106 -0
  28. jettask/webui/frontend/src/components/layout/SideMenu.css +137 -0
  29. jettask/webui/frontend/src/components/layout/SideMenu.jsx +209 -0
  30. jettask/webui/frontend/src/components/layout/TabsNav.css +244 -0
  31. jettask/webui/frontend/src/components/layout/TabsNav.jsx +206 -0
  32. jettask/webui/frontend/src/components/layout/UserInfo.css +197 -0
  33. jettask/webui/frontend/src/components/layout/UserInfo.jsx +197 -0
  34. jettask/webui/frontend/src/contexts/LoadingContext.jsx +27 -0
  35. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +72 -0
  36. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +245 -0
  37. jettask/webui/frontend/src/index.css +114 -0
  38. jettask/webui/frontend/src/main.jsx +20 -0
  39. jettask/webui/frontend/src/pages/Alerts.jsx +684 -0
  40. jettask/webui/frontend/src/pages/Dashboard/index.css +35 -0
  41. jettask/webui/frontend/src/pages/Dashboard/index.jsx +281 -0
  42. jettask/webui/frontend/src/pages/Dashboard.jsx +1330 -0
  43. jettask/webui/frontend/src/pages/QueueDetail.jsx +1117 -0
  44. jettask/webui/frontend/src/pages/QueueMonitor.jsx +527 -0
  45. jettask/webui/frontend/src/pages/Queues.jsx +12 -0
  46. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +809 -0
  47. jettask/webui/frontend/src/pages/Settings.jsx +800 -0
  48. jettask/webui/frontend/src/pages/Workers.jsx +12 -0
  49. jettask/webui/frontend/src/services/api.js +114 -0
  50. jettask/webui/frontend/src/services/queueTrend.js +152 -0
  51. jettask/webui/frontend/src/utils/suppressWarnings.js +22 -0
  52. jettask/webui/frontend/src/utils/userPreferences.js +154 -0
  53. jettask/webui/frontend/vite.config.js +26 -0
  54. {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/METADATA +1 -1
  55. {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/RECORD +59 -14
  56. jettask/webui/static/dist/assets/index-7129cfe1.css +0 -1
  57. jettask/webui/static/dist/assets/index-8d1935cc.js +0 -774
  58. jettask/webui/static/dist/index.html +0 -15
  59. jettask/webui/static/index.html +0 -1734
  60. jettask/webui/static/queue.html +0 -981
  61. jettask/webui/static/queues.html +0 -549
  62. jettask/webui/static/workers.html +0 -734
  63. {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/WHEEL +0 -0
  64. {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/entry_points.txt +0 -0
  65. {jettask-0.2.7.dist-info → jettask-0.2.9.dist-info}/licenses/LICENSE +0 -0
  66. {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;