jettask 0.2.5__py3-none-any.whl → 0.2.7__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/monitor/run_backlog_collector.py +96 -0
- jettask/monitor/stream_backlog_monitor.py +362 -0
- jettask/pg_consumer/pg_consumer_v2.py +403 -0
- jettask/pg_consumer/sql_utils.py +182 -0
- jettask/scheduler/__init__.py +17 -0
- jettask/scheduler/add_execution_count.sql +11 -0
- jettask/scheduler/add_priority_field.sql +26 -0
- jettask/scheduler/add_scheduler_id.sql +25 -0
- jettask/scheduler/add_scheduler_id_index.sql +10 -0
- jettask/scheduler/loader.py +249 -0
- jettask/scheduler/make_scheduler_id_required.sql +28 -0
- jettask/scheduler/manager.py +696 -0
- jettask/scheduler/migrate_interval_seconds.sql +9 -0
- jettask/scheduler/models.py +200 -0
- jettask/scheduler/multi_namespace_scheduler.py +294 -0
- jettask/scheduler/performance_optimization.sql +45 -0
- jettask/scheduler/run_scheduler.py +186 -0
- jettask/scheduler/scheduler.py +715 -0
- jettask/scheduler/schema.sql +84 -0
- jettask/scheduler/unified_manager.py +450 -0
- jettask/scheduler/unified_scheduler_manager.py +280 -0
- jettask/webui/backend/api/__init__.py +3 -0
- jettask/webui/backend/api/v1/__init__.py +17 -0
- jettask/webui/backend/api/v1/monitoring.py +431 -0
- jettask/webui/backend/api/v1/namespaces.py +504 -0
- jettask/webui/backend/api/v1/queues.py +342 -0
- jettask/webui/backend/api/v1/tasks.py +367 -0
- jettask/webui/backend/core/__init__.py +3 -0
- jettask/webui/backend/core/cache.py +221 -0
- jettask/webui/backend/core/database.py +200 -0
- jettask/webui/backend/core/exceptions.py +102 -0
- jettask/webui/backend/models/__init__.py +3 -0
- jettask/webui/backend/models/requests.py +236 -0
- jettask/webui/backend/models/responses.py +230 -0
- jettask/webui/backend/services/__init__.py +3 -0
- jettask/webui/frontend/index.html +13 -0
- jettask/webui/models/__init__.py +3 -0
- jettask/webui/models/namespace.py +63 -0
- jettask/webui/sql/batch_upsert_functions.sql +178 -0
- jettask/webui/sql/init_database.sql +640 -0
- {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/METADATA +80 -10
- {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/RECORD +46 -53
- jettask/webui/frontend/package-lock.json +0 -4833
- jettask/webui/frontend/package.json +0 -30
- jettask/webui/frontend/src/App.css +0 -109
- jettask/webui/frontend/src/App.jsx +0 -66
- jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -166
- jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -298
- jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -638
- jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -65
- jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -487
- jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -465
- jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -423
- jettask/webui/frontend/src/components/TaskFilter.jsx +0 -425
- jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -21
- jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -160
- jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -111
- jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -115
- jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -40
- jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -18
- jettask/webui/frontend/src/components/layout/AppLayout.css +0 -95
- jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -49
- jettask/webui/frontend/src/components/layout/Header.css +0 -106
- jettask/webui/frontend/src/components/layout/Header.jsx +0 -106
- jettask/webui/frontend/src/components/layout/SideMenu.css +0 -137
- jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -209
- jettask/webui/frontend/src/components/layout/TabsNav.css +0 -244
- jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -206
- jettask/webui/frontend/src/components/layout/UserInfo.css +0 -197
- jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -197
- jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -27
- jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -72
- jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -245
- jettask/webui/frontend/src/index.css +0 -114
- jettask/webui/frontend/src/main.jsx +0 -20
- jettask/webui/frontend/src/pages/Alerts.jsx +0 -684
- jettask/webui/frontend/src/pages/Dashboard/index.css +0 -35
- jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -281
- jettask/webui/frontend/src/pages/Dashboard.jsx +0 -1330
- jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -1117
- jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -527
- jettask/webui/frontend/src/pages/Queues.jsx +0 -12
- jettask/webui/frontend/src/pages/ScheduledTasks.jsx +0 -809
- jettask/webui/frontend/src/pages/Settings.jsx +0 -800
- jettask/webui/frontend/src/pages/Workers.jsx +0 -12
- jettask/webui/frontend/src/services/api.js +0 -114
- jettask/webui/frontend/src/services/queueTrend.js +0 -152
- jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
- jettask/webui/frontend/src/utils/userPreferences.js +0 -154
- {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/WHEEL +0 -0
- {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/entry_points.txt +0 -0
- {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.5.dist-info → jettask-0.2.7.dist-info}/top_level.txt +0 -0
@@ -1,800 +0,0 @@
|
|
1
|
-
import { useState, useEffect } from 'react';
|
2
|
-
import {
|
3
|
-
Card,
|
4
|
-
Button,
|
5
|
-
Table,
|
6
|
-
Modal,
|
7
|
-
Form,
|
8
|
-
Input,
|
9
|
-
InputNumber,
|
10
|
-
message,
|
11
|
-
Space,
|
12
|
-
Tag,
|
13
|
-
Popconfirm,
|
14
|
-
Typography,
|
15
|
-
Row,
|
16
|
-
Col,
|
17
|
-
Descriptions,
|
18
|
-
Tooltip,
|
19
|
-
Alert,
|
20
|
-
Divider,
|
21
|
-
Select,
|
22
|
-
Switch
|
23
|
-
} from 'antd';
|
24
|
-
import {
|
25
|
-
PlusOutlined,
|
26
|
-
EditOutlined,
|
27
|
-
DeleteOutlined,
|
28
|
-
CopyOutlined,
|
29
|
-
DatabaseOutlined,
|
30
|
-
CloudServerOutlined,
|
31
|
-
KeyOutlined,
|
32
|
-
SettingOutlined,
|
33
|
-
GlobalOutlined,
|
34
|
-
BellOutlined,
|
35
|
-
SecurityScanOutlined
|
36
|
-
} from '@ant-design/icons';
|
37
|
-
import axios from 'axios';
|
38
|
-
import { useNamespace } from '../contexts/NamespaceContext';
|
39
|
-
|
40
|
-
const { Title, Text, Paragraph } = Typography;
|
41
|
-
const { Option } = Select;
|
42
|
-
|
43
|
-
function Settings() {
|
44
|
-
const { refreshNamespaceList } = useNamespace(); // 获取刷新方法
|
45
|
-
// 命名空间管理相关状态
|
46
|
-
const [namespaces, setNamespaces] = useState([]);
|
47
|
-
const [loading, setLoading] = useState(false);
|
48
|
-
const [modalVisible, setModalVisible] = useState(false);
|
49
|
-
const [editingNamespace, setEditingNamespace] = useState(null);
|
50
|
-
const [form] = Form.useForm();
|
51
|
-
const [selectedNamespace, setSelectedNamespace] = useState(null);
|
52
|
-
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
53
|
-
|
54
|
-
// 系统设置相关状态
|
55
|
-
const [systemSettings, setSystemSettings] = useState({
|
56
|
-
theme: 'light',
|
57
|
-
autoRefresh: true,
|
58
|
-
refreshInterval: 30,
|
59
|
-
notifications: true,
|
60
|
-
security: {
|
61
|
-
sessionTimeout: 120,
|
62
|
-
enableSSL: false
|
63
|
-
}
|
64
|
-
});
|
65
|
-
|
66
|
-
// 获取命名空间列表
|
67
|
-
const fetchNamespaces = async () => {
|
68
|
-
setLoading(true);
|
69
|
-
try {
|
70
|
-
const response = await axios.get('/api/namespaces');
|
71
|
-
setNamespaces(response.data);
|
72
|
-
} catch (error) {
|
73
|
-
console.error('Failed to fetch namespaces:', error);
|
74
|
-
message.error('获取命名空间列表失败');
|
75
|
-
} finally {
|
76
|
-
setLoading(false);
|
77
|
-
}
|
78
|
-
};
|
79
|
-
|
80
|
-
useEffect(() => {
|
81
|
-
fetchNamespaces();
|
82
|
-
}, []);
|
83
|
-
|
84
|
-
// 创建或编辑命名空间
|
85
|
-
const handleSubmit = async () => {
|
86
|
-
try {
|
87
|
-
const values = await form.validateFields();
|
88
|
-
|
89
|
-
if (editingNamespace) {
|
90
|
-
// 编辑模式
|
91
|
-
const updateData = {
|
92
|
-
description: values.description,
|
93
|
-
redis_config: {
|
94
|
-
host: values.redis_host,
|
95
|
-
port: values.redis_port,
|
96
|
-
password: values.redis_password,
|
97
|
-
db: values.redis_db || 0
|
98
|
-
},
|
99
|
-
pg_config: {
|
100
|
-
host: values.pg_host,
|
101
|
-
port: values.pg_port,
|
102
|
-
user: values.pg_user,
|
103
|
-
password: values.pg_password,
|
104
|
-
database: values.pg_database
|
105
|
-
}
|
106
|
-
};
|
107
|
-
|
108
|
-
const response = await axios.put(
|
109
|
-
`/api/namespaces/${editingNamespace.name}`,
|
110
|
-
updateData
|
111
|
-
);
|
112
|
-
|
113
|
-
message.success('命名空间配置已更新');
|
114
|
-
setModalVisible(false);
|
115
|
-
form.resetFields();
|
116
|
-
setEditingNamespace(null);
|
117
|
-
fetchNamespaces();
|
118
|
-
refreshNamespaceList(); // 触发全局命名空间列表刷新
|
119
|
-
|
120
|
-
// 显示更新后的版本信息
|
121
|
-
Modal.success({
|
122
|
-
title: '配置更新成功',
|
123
|
-
content: (
|
124
|
-
<div>
|
125
|
-
<p>命名空间 "{editingNamespace.name}" 的配置已成功更新。</p>
|
126
|
-
<p>新版本号:v{response.data.version}</p>
|
127
|
-
<Alert
|
128
|
-
message="版本自动递增"
|
129
|
-
description="配置更新后,版本号会自动递增。Worker将在下次重启时获取最新配置。"
|
130
|
-
type="info"
|
131
|
-
showIcon
|
132
|
-
/>
|
133
|
-
</div>
|
134
|
-
),
|
135
|
-
width: 600
|
136
|
-
});
|
137
|
-
} else {
|
138
|
-
// 创建新命名空间
|
139
|
-
const response = await axios.post('/api/namespaces', {
|
140
|
-
name: values.name,
|
141
|
-
description: values.description,
|
142
|
-
redis_config: {
|
143
|
-
host: values.redis_host,
|
144
|
-
port: values.redis_port,
|
145
|
-
password: values.redis_password,
|
146
|
-
db: values.redis_db || 0
|
147
|
-
},
|
148
|
-
pg_config: {
|
149
|
-
host: values.pg_host,
|
150
|
-
port: values.pg_port,
|
151
|
-
user: values.pg_user,
|
152
|
-
password: values.pg_password,
|
153
|
-
database: values.pg_database
|
154
|
-
}
|
155
|
-
});
|
156
|
-
|
157
|
-
message.success('命名空间创建成功');
|
158
|
-
setModalVisible(false);
|
159
|
-
form.resetFields();
|
160
|
-
fetchNamespaces();
|
161
|
-
refreshNamespaceList(); // 触发全局命名空间列表刷新
|
162
|
-
|
163
|
-
// 显示连接URL
|
164
|
-
const fullUrl = `http://localhost:8001${response.data.connection_url}`;
|
165
|
-
Modal.success({
|
166
|
-
title: '命名空间创建成功',
|
167
|
-
content: (
|
168
|
-
<div>
|
169
|
-
<p>命名空间 "{values.name}" 已成功创建。</p>
|
170
|
-
<p>连接路径:</p>
|
171
|
-
<Input.TextArea
|
172
|
-
value={response.data.connection_url}
|
173
|
-
readOnly
|
174
|
-
autoSize={{ minRows: 1, maxRows: 2 }}
|
175
|
-
/>
|
176
|
-
<Alert
|
177
|
-
style={{ marginTop: 12, marginBottom: 12 }}
|
178
|
-
message="使用说明"
|
179
|
-
description={
|
180
|
-
<div>
|
181
|
-
<p>在JetTask应用中使用时,需要添加完整的服务器地址:</p>
|
182
|
-
<code style={{ background: '#f5f5f5', padding: '4px 8px', borderRadius: 3, display: 'block', marginTop: 8 }}>
|
183
|
-
task_center_url='http://[服务器地址]:8001{response.data.connection_url}'
|
184
|
-
</code>
|
185
|
-
<p style={{ marginTop: 8 }}>示例:</p>
|
186
|
-
<code style={{ background: '#f5f5f5', padding: '4px 8px', borderRadius: 3, display: 'block' }}>
|
187
|
-
task_center_url='{fullUrl}'
|
188
|
-
</code>
|
189
|
-
</div>
|
190
|
-
}
|
191
|
-
type="info"
|
192
|
-
showIcon
|
193
|
-
/>
|
194
|
-
<Button
|
195
|
-
type="primary"
|
196
|
-
icon={<CopyOutlined />}
|
197
|
-
onClick={() => {
|
198
|
-
navigator.clipboard.writeText(fullUrl);
|
199
|
-
message.success('完整URL已复制到剪贴板');
|
200
|
-
}}
|
201
|
-
>
|
202
|
-
复制完整URL(本地开发)
|
203
|
-
</Button>
|
204
|
-
<Button
|
205
|
-
type="link"
|
206
|
-
icon={<CopyOutlined />}
|
207
|
-
onClick={() => {
|
208
|
-
navigator.clipboard.writeText(response.data.connection_url);
|
209
|
-
message.success('路径已复制到剪贴板');
|
210
|
-
}}
|
211
|
-
style={{ marginLeft: 8 }}
|
212
|
-
>
|
213
|
-
仅复制路径
|
214
|
-
</Button>
|
215
|
-
</div>
|
216
|
-
),
|
217
|
-
width: 700
|
218
|
-
});
|
219
|
-
}
|
220
|
-
} catch (error) {
|
221
|
-
console.error('Failed to save namespace:', error);
|
222
|
-
message.error(error.response?.data?.detail || '操作失败');
|
223
|
-
}
|
224
|
-
};
|
225
|
-
|
226
|
-
// 删除命名空间
|
227
|
-
const handleDelete = async (namespace) => {
|
228
|
-
if (namespace.name === 'default') {
|
229
|
-
message.error('默认命名空间不能删除');
|
230
|
-
return;
|
231
|
-
}
|
232
|
-
|
233
|
-
try {
|
234
|
-
await axios.delete(`/api/namespaces/${namespace.name}`);
|
235
|
-
message.success('命名空间已删除');
|
236
|
-
fetchNamespaces();
|
237
|
-
refreshNamespaceList(); // 触发全局命名空间列表刷新
|
238
|
-
} catch (error) {
|
239
|
-
console.error('Failed to delete namespace:', error);
|
240
|
-
message.error(error.response?.data?.detail || '删除失败');
|
241
|
-
}
|
242
|
-
};
|
243
|
-
|
244
|
-
// 查看命名空间详情
|
245
|
-
const handleViewDetails = async (namespace) => {
|
246
|
-
try {
|
247
|
-
const response = await axios.get(`/api/namespaces/${namespace.name}`);
|
248
|
-
setSelectedNamespace({
|
249
|
-
...namespace,
|
250
|
-
config: response.data
|
251
|
-
});
|
252
|
-
setDetailModalVisible(true);
|
253
|
-
} catch (error) {
|
254
|
-
console.error('Failed to fetch namespace config:', error);
|
255
|
-
message.error('获取命名空间配置失败');
|
256
|
-
}
|
257
|
-
};
|
258
|
-
|
259
|
-
// 编辑命名空间
|
260
|
-
const handleEdit = async (namespace) => {
|
261
|
-
try {
|
262
|
-
// 获取最新配置
|
263
|
-
const response = await axios.get(`/api/namespaces/${namespace.name}`);
|
264
|
-
const config = response.data;
|
265
|
-
|
266
|
-
// 设置编辑状态
|
267
|
-
setEditingNamespace(namespace);
|
268
|
-
|
269
|
-
// 填充表单数据
|
270
|
-
form.setFieldsValue({
|
271
|
-
name: namespace.name,
|
272
|
-
description: namespace.description || '',
|
273
|
-
redis_host: config.redis_config?.host || 'localhost',
|
274
|
-
redis_port: config.redis_config?.port || 6379,
|
275
|
-
redis_password: config.redis_config?.password || '',
|
276
|
-
redis_db: config.redis_config?.db || 0,
|
277
|
-
pg_host: config.pg_config?.host || 'localhost',
|
278
|
-
pg_port: config.pg_config?.port || 5432,
|
279
|
-
pg_user: config.pg_config?.user || 'jettask',
|
280
|
-
pg_password: config.pg_config?.password || '',
|
281
|
-
pg_database: config.pg_config?.database || 'jettask'
|
282
|
-
});
|
283
|
-
|
284
|
-
setModalVisible(true);
|
285
|
-
} catch (error) {
|
286
|
-
console.error('Failed to fetch namespace config:', error);
|
287
|
-
message.error('获取命名空间配置失败');
|
288
|
-
}
|
289
|
-
};
|
290
|
-
|
291
|
-
// 系统设置保存
|
292
|
-
const handleSystemSettingsSave = () => {
|
293
|
-
message.success('系统设置已保存');
|
294
|
-
};
|
295
|
-
|
296
|
-
const namespaceColumns = [
|
297
|
-
{
|
298
|
-
title: '命名空间名称',
|
299
|
-
dataIndex: 'name',
|
300
|
-
key: 'name',
|
301
|
-
width: 200,
|
302
|
-
ellipsis: true,
|
303
|
-
render: (text, record) => (
|
304
|
-
<Space>
|
305
|
-
<Tooltip title={text}>
|
306
|
-
<Text strong style={{ maxWidth: 150, display: 'inline-block' }} ellipsis>{text}</Text>
|
307
|
-
</Tooltip>
|
308
|
-
{text === 'default' && <Tag color="blue">默认</Tag>}
|
309
|
-
</Space>
|
310
|
-
)
|
311
|
-
},
|
312
|
-
{
|
313
|
-
title: '描述',
|
314
|
-
dataIndex: 'description',
|
315
|
-
key: 'description',
|
316
|
-
ellipsis: true,
|
317
|
-
render: (text) => text || '-'
|
318
|
-
},
|
319
|
-
{
|
320
|
-
title: '版本',
|
321
|
-
dataIndex: 'version',
|
322
|
-
key: 'version',
|
323
|
-
width: 80,
|
324
|
-
render: (text) => <Tag color="blue">v{text || 1}</Tag>
|
325
|
-
},
|
326
|
-
{
|
327
|
-
title: '连接路径',
|
328
|
-
dataIndex: 'connection_url',
|
329
|
-
key: 'connection_url',
|
330
|
-
ellipsis: true,
|
331
|
-
render: (text) => (
|
332
|
-
<Tooltip title={`完整URL: http://[服务器]:8001${text}`}>
|
333
|
-
<Text copyable={{ text }} style={{ maxWidth: 300 }} ellipsis>
|
334
|
-
{text}
|
335
|
-
</Text>
|
336
|
-
</Tooltip>
|
337
|
-
)
|
338
|
-
},
|
339
|
-
{
|
340
|
-
title: '创建时间',
|
341
|
-
dataIndex: 'created_at',
|
342
|
-
key: 'created_at',
|
343
|
-
width: 180,
|
344
|
-
render: (text) => text ? new Date(text).toLocaleString() : '-'
|
345
|
-
},
|
346
|
-
{
|
347
|
-
title: '操作',
|
348
|
-
key: 'actions',
|
349
|
-
width: 250,
|
350
|
-
render: (_, record) => (
|
351
|
-
<Space>
|
352
|
-
<Tooltip title="查看配置">
|
353
|
-
<Button
|
354
|
-
type="link"
|
355
|
-
icon={<DatabaseOutlined />}
|
356
|
-
onClick={() => handleViewDetails(record)}
|
357
|
-
/>
|
358
|
-
</Tooltip>
|
359
|
-
<Tooltip title="复制路径">
|
360
|
-
<Button
|
361
|
-
type="link"
|
362
|
-
icon={<CopyOutlined />}
|
363
|
-
onClick={() => {
|
364
|
-
navigator.clipboard.writeText(record.connection_url);
|
365
|
-
message.success(`路径已复制: ${record.connection_url}`);
|
366
|
-
}}
|
367
|
-
/>
|
368
|
-
</Tooltip>
|
369
|
-
<Tooltip title="编辑配置">
|
370
|
-
<Button
|
371
|
-
type="link"
|
372
|
-
icon={<EditOutlined />}
|
373
|
-
onClick={() => handleEdit(record)}
|
374
|
-
/>
|
375
|
-
</Tooltip>
|
376
|
-
{record.name !== 'default' && (
|
377
|
-
<Popconfirm
|
378
|
-
title="确定要删除这个命名空间吗?"
|
379
|
-
description="删除后无法恢复,请谨慎操作。"
|
380
|
-
onConfirm={() => handleDelete(record)}
|
381
|
-
okText="确定"
|
382
|
-
cancelText="取消"
|
383
|
-
>
|
384
|
-
<Tooltip title="删除">
|
385
|
-
<Button
|
386
|
-
type="link"
|
387
|
-
danger
|
388
|
-
icon={<DeleteOutlined />}
|
389
|
-
/>
|
390
|
-
</Tooltip>
|
391
|
-
</Popconfirm>
|
392
|
-
)}
|
393
|
-
</Space>
|
394
|
-
)
|
395
|
-
}
|
396
|
-
];
|
397
|
-
|
398
|
-
return (
|
399
|
-
<div style={{ padding: 24 }}>
|
400
|
-
{/* 页面标题 */}
|
401
|
-
<Title level={2}>
|
402
|
-
<SettingOutlined /> 系统设置
|
403
|
-
</Title>
|
404
|
-
<Text type="secondary" style={{ display: 'block', marginBottom: 24 }}>
|
405
|
-
管理系统配置、命名空间、通知和安全设置等
|
406
|
-
</Text>
|
407
|
-
|
408
|
-
{/* 命名空间管理 */}
|
409
|
-
<Card style={{ marginBottom: 24 }}>
|
410
|
-
<Title level={3}>
|
411
|
-
<GlobalOutlined style={{ marginRight: 8 }} />
|
412
|
-
命名空间管理
|
413
|
-
</Title>
|
414
|
-
<Text type="secondary" style={{ display: 'block', marginBottom: 16 }}>
|
415
|
-
管理多租户命名空间,每个命名空间拥有独立的配置和数据隔离
|
416
|
-
</Text>
|
417
|
-
|
418
|
-
<Row justify="space-between" align="middle" style={{ marginBottom: 16 }}>
|
419
|
-
<Col>
|
420
|
-
<Alert
|
421
|
-
message="使用说明"
|
422
|
-
description={
|
423
|
-
<div style={{ fontSize: '13px' }}>
|
424
|
-
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
425
|
-
<div>
|
426
|
-
<strong>快速使用:</strong> ① 创建命名空间 → ② 复制连接路径 → ③ 在代码中使用:
|
427
|
-
<code style={{ background: '#f5f5f5', padding: '2px 6px', borderRadius: 3, marginLeft: 8 }}>
|
428
|
-
Jettask(task_center_url='http://[服务器]:8001[路径]')
|
429
|
-
</code>
|
430
|
-
</div>
|
431
|
-
<div>
|
432
|
-
<strong>特性:</strong> 多租户隔离 | 配置热更新 | 版本自动递增 | 默认命名空间保护
|
433
|
-
</div>
|
434
|
-
</Space>
|
435
|
-
</div>
|
436
|
-
}
|
437
|
-
type="info"
|
438
|
-
showIcon
|
439
|
-
closable
|
440
|
-
style={{ marginBottom: 16 }}
|
441
|
-
/>
|
442
|
-
</Col>
|
443
|
-
<Col>
|
444
|
-
<Button
|
445
|
-
type="primary"
|
446
|
-
icon={<PlusOutlined />}
|
447
|
-
onClick={() => {
|
448
|
-
setEditingNamespace(null);
|
449
|
-
form.resetFields();
|
450
|
-
setModalVisible(true);
|
451
|
-
}}
|
452
|
-
>
|
453
|
-
创建命名空间
|
454
|
-
</Button>
|
455
|
-
</Col>
|
456
|
-
</Row>
|
457
|
-
|
458
|
-
<Table
|
459
|
-
columns={namespaceColumns}
|
460
|
-
dataSource={namespaces}
|
461
|
-
rowKey="id"
|
462
|
-
loading={loading}
|
463
|
-
pagination={false}
|
464
|
-
size="small"
|
465
|
-
/>
|
466
|
-
</Card>
|
467
|
-
|
468
|
-
{/* 系统配置 */}
|
469
|
-
<Card style={{ marginBottom: 24 }}>
|
470
|
-
<Title level={3}>
|
471
|
-
<SettingOutlined style={{ marginRight: 8 }} />
|
472
|
-
系统配置
|
473
|
-
</Title>
|
474
|
-
<Text type="secondary" style={{ display: 'block', marginBottom: 16 }}>
|
475
|
-
配置系统的基本行为和外观设置
|
476
|
-
</Text>
|
477
|
-
|
478
|
-
<Form layout="vertical" onFinish={handleSystemSettingsSave}>
|
479
|
-
<Row gutter={24}>
|
480
|
-
<Col span={12}>
|
481
|
-
<Form.Item label="主题设置">
|
482
|
-
<Select
|
483
|
-
value={systemSettings.theme}
|
484
|
-
onChange={(value) => setSystemSettings({...systemSettings, theme: value})}
|
485
|
-
>
|
486
|
-
<Option value="light">浅色主题</Option>
|
487
|
-
<Option value="dark">深色主题</Option>
|
488
|
-
<Option value="auto">跟随系统</Option>
|
489
|
-
</Select>
|
490
|
-
</Form.Item>
|
491
|
-
</Col>
|
492
|
-
<Col span={12}>
|
493
|
-
<Form.Item label="自动刷新间隔(秒)">
|
494
|
-
<InputNumber
|
495
|
-
value={systemSettings.refreshInterval}
|
496
|
-
onChange={(value) => setSystemSettings({...systemSettings, refreshInterval: value})}
|
497
|
-
min={5}
|
498
|
-
max={300}
|
499
|
-
style={{ width: '100%' }}
|
500
|
-
/>
|
501
|
-
</Form.Item>
|
502
|
-
</Col>
|
503
|
-
</Row>
|
504
|
-
|
505
|
-
<Row gutter={24}>
|
506
|
-
<Col span={12}>
|
507
|
-
<Form.Item label="启用自动刷新">
|
508
|
-
<Switch
|
509
|
-
checked={systemSettings.autoRefresh}
|
510
|
-
onChange={(checked) => setSystemSettings({...systemSettings, autoRefresh: checked})}
|
511
|
-
/>
|
512
|
-
</Form.Item>
|
513
|
-
</Col>
|
514
|
-
<Col span={12}>
|
515
|
-
<Form.Item label="启用系统通知">
|
516
|
-
<Switch
|
517
|
-
checked={systemSettings.notifications}
|
518
|
-
onChange={(checked) => setSystemSettings({...systemSettings, notifications: checked})}
|
519
|
-
/>
|
520
|
-
</Form.Item>
|
521
|
-
</Col>
|
522
|
-
</Row>
|
523
|
-
</Form>
|
524
|
-
</Card>
|
525
|
-
|
526
|
-
{/* 通知设置 */}
|
527
|
-
<Card style={{ marginBottom: 24 }}>
|
528
|
-
<Title level={3}>
|
529
|
-
<BellOutlined style={{ marginRight: 8 }} />
|
530
|
-
通知设置
|
531
|
-
</Title>
|
532
|
-
<Text type="secondary" style={{ display: 'block', marginBottom: 16 }}>
|
533
|
-
配置系统告警和通知方式
|
534
|
-
</Text>
|
535
|
-
<Alert
|
536
|
-
message="功能开发中"
|
537
|
-
description="通知设置功能正在开发中,敬请期待。"
|
538
|
-
type="info"
|
539
|
-
showIcon
|
540
|
-
/>
|
541
|
-
</Card>
|
542
|
-
|
543
|
-
{/* 安全设置 */}
|
544
|
-
<Card>
|
545
|
-
<Title level={3}>
|
546
|
-
<SecurityScanOutlined style={{ marginRight: 8 }} />
|
547
|
-
安全设置
|
548
|
-
</Title>
|
549
|
-
<Text type="secondary" style={{ display: 'block', marginBottom: 16 }}>
|
550
|
-
配置系统安全策略和访问控制
|
551
|
-
</Text>
|
552
|
-
|
553
|
-
<Row gutter={24}>
|
554
|
-
<Col span={12}>
|
555
|
-
<Form.Item label="会话超时时间(分钟)">
|
556
|
-
<InputNumber
|
557
|
-
value={systemSettings.security.sessionTimeout}
|
558
|
-
onChange={(value) => setSystemSettings({
|
559
|
-
...systemSettings,
|
560
|
-
security: {...systemSettings.security, sessionTimeout: value}
|
561
|
-
})}
|
562
|
-
min={15}
|
563
|
-
max={480}
|
564
|
-
style={{ width: '100%' }}
|
565
|
-
/>
|
566
|
-
</Form.Item>
|
567
|
-
</Col>
|
568
|
-
<Col span={12}>
|
569
|
-
<Form.Item label="启用SSL">
|
570
|
-
<Switch
|
571
|
-
checked={systemSettings.security.enableSSL}
|
572
|
-
onChange={(checked) => setSystemSettings({
|
573
|
-
...systemSettings,
|
574
|
-
security: {...systemSettings.security, enableSSL: checked}
|
575
|
-
})}
|
576
|
-
/>
|
577
|
-
</Form.Item>
|
578
|
-
</Col>
|
579
|
-
</Row>
|
580
|
-
|
581
|
-
<Alert
|
582
|
-
message="安全提示"
|
583
|
-
description="建议在生产环境中启用SSL,并设置合理的会话超时时间。"
|
584
|
-
type="warning"
|
585
|
-
showIcon
|
586
|
-
style={{ marginTop: 16 }}
|
587
|
-
/>
|
588
|
-
</Card>
|
589
|
-
|
590
|
-
{/* 创建/编辑命名空间的模态框 */}
|
591
|
-
<Modal
|
592
|
-
title={editingNamespace ? '编辑命名空间' : '创建命名空间'}
|
593
|
-
open={modalVisible}
|
594
|
-
onOk={handleSubmit}
|
595
|
-
onCancel={() => {
|
596
|
-
setModalVisible(false);
|
597
|
-
form.resetFields();
|
598
|
-
setEditingNamespace(null);
|
599
|
-
}}
|
600
|
-
width={700}
|
601
|
-
>
|
602
|
-
<Form
|
603
|
-
form={form}
|
604
|
-
layout="vertical"
|
605
|
-
initialValues={{
|
606
|
-
redis_host: 'localhost',
|
607
|
-
redis_port: 6379,
|
608
|
-
redis_db: 0,
|
609
|
-
pg_host: 'localhost',
|
610
|
-
pg_port: 5432,
|
611
|
-
pg_user: 'jettask',
|
612
|
-
pg_database: 'jettask'
|
613
|
-
}}
|
614
|
-
>
|
615
|
-
<Form.Item
|
616
|
-
name="name"
|
617
|
-
label="命名空间名称"
|
618
|
-
rules={[
|
619
|
-
{ required: true, message: '请输入命名空间名称' },
|
620
|
-
{ pattern: /^[a-zA-Z0-9_-]+$/, message: '只能包含字母、数字、下划线和中划线' }
|
621
|
-
]}
|
622
|
-
>
|
623
|
-
<Input
|
624
|
-
placeholder="例如:production、dev-team-1"
|
625
|
-
disabled={!!editingNamespace}
|
626
|
-
/>
|
627
|
-
</Form.Item>
|
628
|
-
|
629
|
-
<Form.Item
|
630
|
-
name="description"
|
631
|
-
label="描述"
|
632
|
-
>
|
633
|
-
<Input.TextArea
|
634
|
-
placeholder="命名空间的描述信息(可选)"
|
635
|
-
rows={2}
|
636
|
-
/>
|
637
|
-
</Form.Item>
|
638
|
-
|
639
|
-
<Row gutter={24}>
|
640
|
-
<Col span={12}>
|
641
|
-
<Card
|
642
|
-
title={<Space><CloudServerOutlined /> Redis 配置</Space>}
|
643
|
-
size="small"
|
644
|
-
style={{ marginBottom: 16 }}
|
645
|
-
>
|
646
|
-
<Form.Item
|
647
|
-
name="redis_host"
|
648
|
-
label="主机地址"
|
649
|
-
rules={[{ required: true, message: '请输入Redis主机地址' }]}
|
650
|
-
>
|
651
|
-
<Input placeholder="localhost" />
|
652
|
-
</Form.Item>
|
653
|
-
<Form.Item
|
654
|
-
name="redis_port"
|
655
|
-
label="端口"
|
656
|
-
rules={[{ required: true, message: '请输入Redis端口' }]}
|
657
|
-
>
|
658
|
-
<InputNumber min={1} max={65535} style={{ width: '100%' }} />
|
659
|
-
</Form.Item>
|
660
|
-
<Form.Item
|
661
|
-
name="redis_password"
|
662
|
-
label="密码"
|
663
|
-
>
|
664
|
-
<Input.Password placeholder="可选" />
|
665
|
-
</Form.Item>
|
666
|
-
<Form.Item
|
667
|
-
name="redis_db"
|
668
|
-
label="数据库索引"
|
669
|
-
>
|
670
|
-
<InputNumber min={0} max={15} style={{ width: '100%' }} />
|
671
|
-
</Form.Item>
|
672
|
-
</Card>
|
673
|
-
</Col>
|
674
|
-
|
675
|
-
<Col span={12}>
|
676
|
-
<Card
|
677
|
-
title={<Space><DatabaseOutlined /> PostgreSQL 配置</Space>}
|
678
|
-
size="small"
|
679
|
-
style={{ marginBottom: 16 }}
|
680
|
-
>
|
681
|
-
<Form.Item
|
682
|
-
name="pg_host"
|
683
|
-
label="主机地址"
|
684
|
-
rules={[{ required: true, message: '请输入PostgreSQL主机地址' }]}
|
685
|
-
>
|
686
|
-
<Input placeholder="localhost" />
|
687
|
-
</Form.Item>
|
688
|
-
<Form.Item
|
689
|
-
name="pg_port"
|
690
|
-
label="端口"
|
691
|
-
rules={[{ required: true, message: '请输入PostgreSQL端口' }]}
|
692
|
-
>
|
693
|
-
<InputNumber min={1} max={65535} style={{ width: '100%' }} />
|
694
|
-
</Form.Item>
|
695
|
-
<Form.Item
|
696
|
-
name="pg_user"
|
697
|
-
label="用户名"
|
698
|
-
rules={[{ required: true, message: '请输入用户名' }]}
|
699
|
-
>
|
700
|
-
<Input placeholder="jettask" />
|
701
|
-
</Form.Item>
|
702
|
-
<Form.Item
|
703
|
-
name="pg_password"
|
704
|
-
label="密码"
|
705
|
-
rules={[{ required: true, message: '请输入密码' }]}
|
706
|
-
>
|
707
|
-
<Input.Password />
|
708
|
-
</Form.Item>
|
709
|
-
<Form.Item
|
710
|
-
name="pg_database"
|
711
|
-
label="数据库名"
|
712
|
-
rules={[{ required: true, message: '请输入数据库名' }]}
|
713
|
-
>
|
714
|
-
<Input placeholder="jettask" />
|
715
|
-
</Form.Item>
|
716
|
-
</Card>
|
717
|
-
</Col>
|
718
|
-
</Row>
|
719
|
-
</Form>
|
720
|
-
</Modal>
|
721
|
-
|
722
|
-
{/* 查看命名空间详情的模态框 */}
|
723
|
-
<Modal
|
724
|
-
title={`命名空间详情 - ${selectedNamespace?.name}`}
|
725
|
-
open={detailModalVisible}
|
726
|
-
onCancel={() => {
|
727
|
-
setDetailModalVisible(false);
|
728
|
-
setSelectedNamespace(null);
|
729
|
-
}}
|
730
|
-
footer={null}
|
731
|
-
width={800}
|
732
|
-
>
|
733
|
-
{selectedNamespace && (
|
734
|
-
<div>
|
735
|
-
<Descriptions bordered column={1} style={{ marginBottom: 24 }}>
|
736
|
-
<Descriptions.Item label="命名空间名称">
|
737
|
-
{selectedNamespace.name}
|
738
|
-
</Descriptions.Item>
|
739
|
-
<Descriptions.Item label="描述">
|
740
|
-
{selectedNamespace.description || '-'}
|
741
|
-
</Descriptions.Item>
|
742
|
-
<Descriptions.Item label="版本">
|
743
|
-
<Tag color="blue">v{selectedNamespace.version || 1}</Tag>
|
744
|
-
</Descriptions.Item>
|
745
|
-
<Descriptions.Item label="连接URL">
|
746
|
-
<Paragraph copyable={{ text: selectedNamespace.connection_url }}>
|
747
|
-
{selectedNamespace.connection_url}
|
748
|
-
</Paragraph>
|
749
|
-
</Descriptions.Item>
|
750
|
-
<Descriptions.Item label="创建时间">
|
751
|
-
{selectedNamespace.created_at ? new Date(selectedNamespace.created_at).toLocaleString() : '-'}
|
752
|
-
</Descriptions.Item>
|
753
|
-
<Descriptions.Item label="更新时间">
|
754
|
-
{selectedNamespace.updated_at ? new Date(selectedNamespace.updated_at).toLocaleString() : '-'}
|
755
|
-
</Descriptions.Item>
|
756
|
-
</Descriptions>
|
757
|
-
|
758
|
-
{selectedNamespace.config && (
|
759
|
-
<>
|
760
|
-
<Title level={5}>Redis 配置</Title>
|
761
|
-
<Descriptions bordered size="small" style={{ marginBottom: 24 }}>
|
762
|
-
<Descriptions.Item label="主机" span={2}>
|
763
|
-
{selectedNamespace.config.redis_config?.host}
|
764
|
-
</Descriptions.Item>
|
765
|
-
<Descriptions.Item label="端口">
|
766
|
-
{selectedNamespace.config.redis_config?.port}
|
767
|
-
</Descriptions.Item>
|
768
|
-
<Descriptions.Item label="密码" span={2}>
|
769
|
-
{selectedNamespace.config.redis_config?.password ? '******' : '未设置'}
|
770
|
-
</Descriptions.Item>
|
771
|
-
<Descriptions.Item label="数据库">
|
772
|
-
{selectedNamespace.config.redis_config?.db || 0}
|
773
|
-
</Descriptions.Item>
|
774
|
-
</Descriptions>
|
775
|
-
|
776
|
-
<Title level={5}>PostgreSQL 配置</Title>
|
777
|
-
<Descriptions bordered size="small">
|
778
|
-
<Descriptions.Item label="主机" span={2}>
|
779
|
-
{selectedNamespace.config.pg_config?.host}
|
780
|
-
</Descriptions.Item>
|
781
|
-
<Descriptions.Item label="端口">
|
782
|
-
{selectedNamespace.config.pg_config?.port}
|
783
|
-
</Descriptions.Item>
|
784
|
-
<Descriptions.Item label="用户名" span={2}>
|
785
|
-
{selectedNamespace.config.pg_config?.user}
|
786
|
-
</Descriptions.Item>
|
787
|
-
<Descriptions.Item label="数据库">
|
788
|
-
{selectedNamespace.config.pg_config?.database}
|
789
|
-
</Descriptions.Item>
|
790
|
-
</Descriptions>
|
791
|
-
</>
|
792
|
-
)}
|
793
|
-
</div>
|
794
|
-
)}
|
795
|
-
</Modal>
|
796
|
-
</div>
|
797
|
-
);
|
798
|
-
}
|
799
|
-
|
800
|
-
export default Settings;
|