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,111 +0,0 @@
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
2
|
-
import { Card, Empty, Tabs } from 'antd';
|
3
|
-
import { Column } from '@ant-design/plots';
|
4
|
-
import QueueTrendChart from './QueueTrendChart';
|
5
|
-
import { fetchQueueTrend } from '../../services/queueTrend';
|
6
|
-
|
7
|
-
const { TabPane } = Tabs;
|
8
|
-
|
9
|
-
const QueueChart = ({
|
10
|
-
data,
|
11
|
-
loading,
|
12
|
-
queues = [],
|
13
|
-
timeRange = '1h',
|
14
|
-
customTimeRange = null,
|
15
|
-
mode = 'distribution' // 'distribution' | 'trend'
|
16
|
-
}) => {
|
17
|
-
const [trendData, setTrendData] = useState([]);
|
18
|
-
const [activeTab, setActiveTab] = useState('trend');
|
19
|
-
|
20
|
-
// 获取趋势数据
|
21
|
-
useEffect(() => {
|
22
|
-
if (queues && queues.length > 0 && (mode === 'trend' || activeTab === 'trend')) {
|
23
|
-
// 获取队列名称列表
|
24
|
-
const queueNames = queues.map(q => q.name || q);
|
25
|
-
|
26
|
-
// 从API获取趋势数据
|
27
|
-
const loadTrendData = async () => {
|
28
|
-
try {
|
29
|
-
const data = await fetchQueueTrend(timeRange, queueNames, customTimeRange);
|
30
|
-
setTrendData(data);
|
31
|
-
} catch (error) {
|
32
|
-
console.error('Failed to load trend data:', error);
|
33
|
-
// 如果失败,使用空数据
|
34
|
-
setTrendData([]);
|
35
|
-
}
|
36
|
-
};
|
37
|
-
|
38
|
-
loadTrendData();
|
39
|
-
}
|
40
|
-
}, [queues, timeRange, customTimeRange, mode, activeTab]);
|
41
|
-
|
42
|
-
// 柱状图配置
|
43
|
-
const columnConfig = {
|
44
|
-
data: data || [],
|
45
|
-
xField: 'name',
|
46
|
-
yField: 'value',
|
47
|
-
label: {
|
48
|
-
position: 'middle',
|
49
|
-
style: {
|
50
|
-
fill: '#FFFFFF',
|
51
|
-
opacity: 0.6,
|
52
|
-
},
|
53
|
-
},
|
54
|
-
xAxis: {
|
55
|
-
label: {
|
56
|
-
autoHide: true,
|
57
|
-
autoRotate: false,
|
58
|
-
},
|
59
|
-
},
|
60
|
-
meta: {
|
61
|
-
name: { alias: '队列' },
|
62
|
-
value: { alias: '任务数' },
|
63
|
-
},
|
64
|
-
};
|
65
|
-
|
66
|
-
// 如果指定为趋势模式,直接返回趋势图
|
67
|
-
if (mode === 'trend') {
|
68
|
-
return (
|
69
|
-
<QueueTrendChart
|
70
|
-
data={trendData}
|
71
|
-
loading={loading}
|
72
|
-
timeRange={timeRange}
|
73
|
-
/>
|
74
|
-
);
|
75
|
-
}
|
76
|
-
|
77
|
-
// 如果没有指定模式,显示标签页切换
|
78
|
-
if (!mode || mode === 'both') {
|
79
|
-
return (
|
80
|
-
<Tabs activeKey={activeTab} onChange={setActiveTab}>
|
81
|
-
<TabPane tab="处理趋势" key="trend">
|
82
|
-
<QueueTrendChart
|
83
|
-
data={trendData}
|
84
|
-
loading={loading}
|
85
|
-
timeRange={timeRange}
|
86
|
-
/>
|
87
|
-
</TabPane>
|
88
|
-
<TabPane tab="队列分布" key="distribution">
|
89
|
-
{data && data.length > 0 ? (
|
90
|
-
<Column {...columnConfig} />
|
91
|
-
) : (
|
92
|
-
<Empty description="暂无数据" />
|
93
|
-
)}
|
94
|
-
</TabPane>
|
95
|
-
</Tabs>
|
96
|
-
);
|
97
|
-
}
|
98
|
-
|
99
|
-
// 默认显示分布图
|
100
|
-
return (
|
101
|
-
<Card title="队列任务分布" loading={loading}>
|
102
|
-
{data && data.length > 0 ? (
|
103
|
-
<Column {...columnConfig} />
|
104
|
-
) : (
|
105
|
-
<Empty description="暂无数据" />
|
106
|
-
)}
|
107
|
-
</Card>
|
108
|
-
);
|
109
|
-
};
|
110
|
-
|
111
|
-
export default QueueChart;
|
@@ -1,115 +0,0 @@
|
|
1
|
-
import { useState, useEffect } from 'react';
|
2
|
-
import { Line } from '@ant-design/plots';
|
3
|
-
import { Empty, Spin } from 'antd';
|
4
|
-
import dayjs from 'dayjs';
|
5
|
-
|
6
|
-
const QueueTrendChart = ({ data = [], loading = false }) => {
|
7
|
-
const [chartData, setChartData] = useState([]);
|
8
|
-
|
9
|
-
useEffect(() => {
|
10
|
-
if (data && data.length > 0) {
|
11
|
-
// 处理数据,转换为Date对象并按时间排序
|
12
|
-
const processedData = data.map(item => ({
|
13
|
-
...item,
|
14
|
-
date: new Date(item.time), // 使用date字段,与示例保持一致
|
15
|
-
value: item.value || 0,
|
16
|
-
queue: item.queue
|
17
|
-
}));
|
18
|
-
|
19
|
-
// 按时间排序确保连线正确
|
20
|
-
processedData.sort((a, b) => a.date.getTime() - b.date.getTime());
|
21
|
-
|
22
|
-
setChartData(processedData);
|
23
|
-
}
|
24
|
-
}, [data]);
|
25
|
-
|
26
|
-
// 图表配置 - 参考Ant Design Charts示例
|
27
|
-
const config = {
|
28
|
-
data: chartData,
|
29
|
-
xField: (d) => d.date, // 返回Date对象,让图表库自动处理
|
30
|
-
yField: 'value',
|
31
|
-
seriesField: 'queue', // 如果有多个队列,按队列分组
|
32
|
-
|
33
|
-
// 连接空值配置
|
34
|
-
connectNulls: {
|
35
|
-
connect: true,
|
36
|
-
connectStroke: '#ccc',
|
37
|
-
},
|
38
|
-
|
39
|
-
// X轴会自动优化显示,不需要手动设置formatter
|
40
|
-
xAxis: {
|
41
|
-
type: 'time',
|
42
|
-
nice: true,
|
43
|
-
},
|
44
|
-
|
45
|
-
// Y轴配置
|
46
|
-
yAxis: {
|
47
|
-
label: {
|
48
|
-
formatter: (v) => {
|
49
|
-
// 格式化数字显示
|
50
|
-
if (v >= 1000000) {
|
51
|
-
return `${(v / 1000000).toFixed(1)}M`;
|
52
|
-
} else if (v >= 1000) {
|
53
|
-
return `${(v / 1000).toFixed(1)}K`;
|
54
|
-
}
|
55
|
-
return String(v);
|
56
|
-
}
|
57
|
-
}
|
58
|
-
},
|
59
|
-
|
60
|
-
// 悬浮提示配置
|
61
|
-
tooltip: {
|
62
|
-
showTitle: true,
|
63
|
-
title: (title) => {
|
64
|
-
// 格式化时间显示
|
65
|
-
return dayjs(title).format('YYYY-MM-DD HH:mm:ss');
|
66
|
-
},
|
67
|
-
formatter: (datum) => {
|
68
|
-
return {
|
69
|
-
name: datum.queue || '任务数',
|
70
|
-
value: datum.value.toLocaleString(), // 添加千分位分隔符
|
71
|
-
};
|
72
|
-
},
|
73
|
-
},
|
74
|
-
|
75
|
-
// 平滑曲线
|
76
|
-
smooth: true,
|
77
|
-
|
78
|
-
// 动画配置
|
79
|
-
animation: {
|
80
|
-
appear: {
|
81
|
-
animation: 'path-in',
|
82
|
-
duration: 1000,
|
83
|
-
},
|
84
|
-
},
|
85
|
-
|
86
|
-
// 响应式配置
|
87
|
-
autoFit: true,
|
88
|
-
height: 300,
|
89
|
-
|
90
|
-
// 主题颜色
|
91
|
-
color: ['#5B8FF9', '#61DDAA', '#65789B', '#F6BD16', '#7262FD'],
|
92
|
-
};
|
93
|
-
|
94
|
-
// 空数据处理
|
95
|
-
if (!loading && (!chartData || chartData.length === 0)) {
|
96
|
-
return (
|
97
|
-
<div style={{ height: 300, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
98
|
-
<Empty description="暂无数据" />
|
99
|
-
</div>
|
100
|
-
);
|
101
|
-
}
|
102
|
-
|
103
|
-
// 加载状态
|
104
|
-
if (loading) {
|
105
|
-
return (
|
106
|
-
<div style={{ height: 300, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
107
|
-
<Spin size="large" tip="加载中..." />
|
108
|
-
</div>
|
109
|
-
);
|
110
|
-
}
|
111
|
-
|
112
|
-
return <Line {...config} />;
|
113
|
-
};
|
114
|
-
|
115
|
-
export default QueueTrendChart;
|
@@ -1,40 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { Card, Empty } from 'antd';
|
3
|
-
import { Pie } from '@ant-design/plots';
|
4
|
-
|
5
|
-
const WorkerChart = ({ data, loading }) => {
|
6
|
-
const chartData = data || [
|
7
|
-
{ type: '在线', value: 0 },
|
8
|
-
{ type: '离线', value: 0 },
|
9
|
-
];
|
10
|
-
|
11
|
-
const config = {
|
12
|
-
appendPadding: 10,
|
13
|
-
data: chartData,
|
14
|
-
angleField: 'value',
|
15
|
-
colorField: 'type',
|
16
|
-
radius: 0.8,
|
17
|
-
label: {
|
18
|
-
type: 'inner',
|
19
|
-
offset: '-30%',
|
20
|
-
content: ({ percent }) => `${(percent * 100).toFixed(0)}%`,
|
21
|
-
style: {
|
22
|
-
fontSize: 14,
|
23
|
-
textAlign: 'center',
|
24
|
-
},
|
25
|
-
},
|
26
|
-
interactions: [{ type: 'element-active' }],
|
27
|
-
};
|
28
|
-
|
29
|
-
return (
|
30
|
-
<Card title="Worker 状态分布" loading={loading}>
|
31
|
-
{chartData.some(item => item.value > 0) ? (
|
32
|
-
<Pie {...config} />
|
33
|
-
) : (
|
34
|
-
<Empty description="暂无数据" />
|
35
|
-
)}
|
36
|
-
</Card>
|
37
|
-
);
|
38
|
-
};
|
39
|
-
|
40
|
-
export default WorkerChart;
|
@@ -1,18 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import { Card, Statistic } from 'antd';
|
3
|
-
|
4
|
-
const StatsCard = ({ title, value, icon, prefix, suffix, valueStyle }) => {
|
5
|
-
return (
|
6
|
-
<Card>
|
7
|
-
<Statistic
|
8
|
-
title={title}
|
9
|
-
value={value}
|
10
|
-
prefix={icon}
|
11
|
-
suffix={suffix}
|
12
|
-
valueStyle={valueStyle}
|
13
|
-
/>
|
14
|
-
</Card>
|
15
|
-
);
|
16
|
-
};
|
17
|
-
|
18
|
-
export default StatsCard;
|
@@ -1,95 +0,0 @@
|
|
1
|
-
/* 整体布局 */
|
2
|
-
.app-layout {
|
3
|
-
height: 100vh;
|
4
|
-
overflow: hidden;
|
5
|
-
}
|
6
|
-
|
7
|
-
/* 主布局区域 */
|
8
|
-
.main-layout {
|
9
|
-
transition: margin-left 0.2s;
|
10
|
-
display: flex;
|
11
|
-
flex-direction: column;
|
12
|
-
height: 100vh;
|
13
|
-
}
|
14
|
-
|
15
|
-
/* 顶部导航栏 */
|
16
|
-
.app-header {
|
17
|
-
background: #2a2d31;
|
18
|
-
padding: 0;
|
19
|
-
height: 48px;
|
20
|
-
line-height: 48px;
|
21
|
-
display: flex;
|
22
|
-
justify-content: space-between;
|
23
|
-
align-items: center;
|
24
|
-
border-bottom: 1px solid #f0f2f5;
|
25
|
-
z-index: 10;
|
26
|
-
position: sticky;
|
27
|
-
top: 0;
|
28
|
-
}
|
29
|
-
|
30
|
-
.header-left {
|
31
|
-
flex: 1;
|
32
|
-
display: flex;
|
33
|
-
align-items: center;
|
34
|
-
height: 100%;
|
35
|
-
overflow: hidden;
|
36
|
-
}
|
37
|
-
|
38
|
-
.header-right {
|
39
|
-
display: flex;
|
40
|
-
align-items: center;
|
41
|
-
height: 100%;
|
42
|
-
gap: 16px;
|
43
|
-
}
|
44
|
-
|
45
|
-
/* 内容区域 */
|
46
|
-
.app-content {
|
47
|
-
flex: 1;
|
48
|
-
overflow: auto;
|
49
|
-
background: #f0f2f5;
|
50
|
-
padding: 0;
|
51
|
-
}
|
52
|
-
|
53
|
-
/* 页面内容容器 */
|
54
|
-
.page-container {
|
55
|
-
height: 100%;
|
56
|
-
background: #fff;
|
57
|
-
}
|
58
|
-
|
59
|
-
/* 滚动条样式 */
|
60
|
-
.app-content::-webkit-scrollbar {
|
61
|
-
width: 8px;
|
62
|
-
height: 8px;
|
63
|
-
}
|
64
|
-
|
65
|
-
.app-content::-webkit-scrollbar-track {
|
66
|
-
background: #f0f0f0;
|
67
|
-
}
|
68
|
-
|
69
|
-
.app-content::-webkit-scrollbar-thumb {
|
70
|
-
background: #bfbfbf;
|
71
|
-
border-radius: 4px;
|
72
|
-
}
|
73
|
-
|
74
|
-
.app-content::-webkit-scrollbar-thumb:hover {
|
75
|
-
background: #8c8c8c;
|
76
|
-
}
|
77
|
-
|
78
|
-
/* 响应式调整 */
|
79
|
-
@media (max-width: 768px) {
|
80
|
-
.main-layout {
|
81
|
-
margin-left: 0 !important;
|
82
|
-
}
|
83
|
-
|
84
|
-
.app-sider {
|
85
|
-
position: fixed;
|
86
|
-
z-index: 1000;
|
87
|
-
height: 100vh;
|
88
|
-
left: -200px;
|
89
|
-
transition: left 0.2s;
|
90
|
-
}
|
91
|
-
|
92
|
-
.app-sider.ant-layout-sider-collapsed {
|
93
|
-
left: 0;
|
94
|
-
}
|
95
|
-
}
|
@@ -1,49 +0,0 @@
|
|
1
|
-
import React, { useState } from 'react';
|
2
|
-
import { Layout } from 'antd';
|
3
|
-
import { Outlet } from 'react-router-dom';
|
4
|
-
import SideMenu from './SideMenu';
|
5
|
-
import TabsNav from './TabsNav';
|
6
|
-
import UserInfo from './UserInfo';
|
7
|
-
import NamespaceSelector from '../NamespaceSelector';
|
8
|
-
import { useNamespace } from '../../contexts/NamespaceContext';
|
9
|
-
import './AppLayout.css';
|
10
|
-
|
11
|
-
const { Header, Content } = Layout;
|
12
|
-
|
13
|
-
const AppLayout = () => {
|
14
|
-
const { currentNamespace, setCurrentNamespace } = useNamespace();
|
15
|
-
|
16
|
-
console.log('🔧 AppLayout渲染,currentNamespace:', currentNamespace);
|
17
|
-
console.log('🔧 AppLayout渲染,setCurrentNamespace:', typeof setCurrentNamespace);
|
18
|
-
|
19
|
-
return (
|
20
|
-
<Layout className="app-layout">
|
21
|
-
{/* 左侧菜单 */}
|
22
|
-
<SideMenu />
|
23
|
-
|
24
|
-
{/* 右侧主体 */}
|
25
|
-
<Layout className="main-layout" style={{ marginLeft: 64 }}>
|
26
|
-
{/* 顶部导航栏 */}
|
27
|
-
<Header className="app-header">
|
28
|
-
<div className="header-left">
|
29
|
-
<TabsNav />
|
30
|
-
</div>
|
31
|
-
<div className="header-right">
|
32
|
-
<NamespaceSelector
|
33
|
-
value={currentNamespace}
|
34
|
-
onChange={setCurrentNamespace}
|
35
|
-
/>
|
36
|
-
<UserInfo />
|
37
|
-
</div>
|
38
|
-
</Header>
|
39
|
-
|
40
|
-
{/* 内容区域 */}
|
41
|
-
<Content className="app-content">
|
42
|
-
<Outlet />
|
43
|
-
</Content>
|
44
|
-
</Layout>
|
45
|
-
</Layout>
|
46
|
-
);
|
47
|
-
};
|
48
|
-
|
49
|
-
export default AppLayout;
|
@@ -1,106 +0,0 @@
|
|
1
|
-
.app-header {
|
2
|
-
background: rgba(20, 20, 20, 0.95) !important;
|
3
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
4
|
-
padding: 0;
|
5
|
-
position: relative;
|
6
|
-
height: 48px !important;
|
7
|
-
line-height: 48px !important;
|
8
|
-
}
|
9
|
-
|
10
|
-
.header-container {
|
11
|
-
max-width: 1440px;
|
12
|
-
margin: 0 auto;
|
13
|
-
padding: 0 24px;
|
14
|
-
display: flex;
|
15
|
-
align-items: center;
|
16
|
-
justify-content: space-between;
|
17
|
-
height: 48px;
|
18
|
-
}
|
19
|
-
|
20
|
-
.header-left {
|
21
|
-
display: flex;
|
22
|
-
align-items: center;
|
23
|
-
}
|
24
|
-
|
25
|
-
.app-logo {
|
26
|
-
display: flex;
|
27
|
-
align-items: center;
|
28
|
-
gap: 8px;
|
29
|
-
color: white;
|
30
|
-
font-size: 16px;
|
31
|
-
font-weight: 700;
|
32
|
-
cursor: pointer;
|
33
|
-
}
|
34
|
-
|
35
|
-
.logo-icon {
|
36
|
-
font-size: 18px;
|
37
|
-
color: var(--primary-color);
|
38
|
-
}
|
39
|
-
|
40
|
-
.logo-text {
|
41
|
-
background: linear-gradient(45deg, #1890ff, #52c41a);
|
42
|
-
-webkit-background-clip: text;
|
43
|
-
-webkit-text-fill-color: transparent;
|
44
|
-
background-clip: text;
|
45
|
-
}
|
46
|
-
|
47
|
-
.header-center {
|
48
|
-
flex: 1;
|
49
|
-
display: flex;
|
50
|
-
justify-content: center;
|
51
|
-
}
|
52
|
-
|
53
|
-
.header-menu {
|
54
|
-
background: transparent !important;
|
55
|
-
border: none !important;
|
56
|
-
line-height: 48px !important;
|
57
|
-
}
|
58
|
-
|
59
|
-
.header-menu .ant-menu-item {
|
60
|
-
color: rgba(255, 255, 255, 0.65) !important;
|
61
|
-
height: 48px !important;
|
62
|
-
line-height: 48px !important;
|
63
|
-
}
|
64
|
-
|
65
|
-
.header-menu .ant-menu-item:hover {
|
66
|
-
color: white !important;
|
67
|
-
}
|
68
|
-
|
69
|
-
.header-menu .ant-menu-item-selected {
|
70
|
-
color: var(--primary-color) !important;
|
71
|
-
}
|
72
|
-
|
73
|
-
.header-right {
|
74
|
-
display: flex;
|
75
|
-
align-items: center;
|
76
|
-
}
|
77
|
-
|
78
|
-
.refresh-btn {
|
79
|
-
color: rgba(255, 255, 255, 0.65) !important;
|
80
|
-
}
|
81
|
-
|
82
|
-
.refresh-btn:hover {
|
83
|
-
color: white !important;
|
84
|
-
}
|
85
|
-
|
86
|
-
/* 命名空间选择器样式 */
|
87
|
-
.header-right {
|
88
|
-
display: flex;
|
89
|
-
align-items: center;
|
90
|
-
gap: 16px;
|
91
|
-
}
|
92
|
-
|
93
|
-
/* 脉动动画 */
|
94
|
-
@keyframes pulse {
|
95
|
-
0% {
|
96
|
-
opacity: 1;
|
97
|
-
}
|
98
|
-
50% {
|
99
|
-
opacity: 0.5;
|
100
|
-
}
|
101
|
-
100% {
|
102
|
-
opacity: 1;
|
103
|
-
}
|
104
|
-
}
|
105
|
-
|
106
|
-
/* 响应式设计 */
|
@@ -1,106 +0,0 @@
|
|
1
|
-
import React from 'react'
|
2
|
-
import { useNavigate, useLocation } from 'react-router-dom'
|
3
|
-
import { Layout, Menu } from 'antd'
|
4
|
-
import {
|
5
|
-
DashboardOutlined,
|
6
|
-
AppstoreOutlined,
|
7
|
-
TeamOutlined,
|
8
|
-
RocketOutlined,
|
9
|
-
LoadingOutlined,
|
10
|
-
ClockCircleOutlined,
|
11
|
-
AlertOutlined,
|
12
|
-
CloudServerOutlined
|
13
|
-
} from '@ant-design/icons'
|
14
|
-
import { useLoading } from '../../contexts/LoadingContext'
|
15
|
-
import { useNamespace } from '../../contexts/NamespaceContext'
|
16
|
-
import NamespaceSelector from '../NamespaceSelector'
|
17
|
-
import './Header.css'
|
18
|
-
|
19
|
-
const { Header: AntHeader } = Layout
|
20
|
-
|
21
|
-
const Header = () => {
|
22
|
-
const navigate = useNavigate()
|
23
|
-
const location = useLocation()
|
24
|
-
const { isLoading } = useLoading()
|
25
|
-
const { currentNamespace, setCurrentNamespace } = useNamespace()
|
26
|
-
|
27
|
-
const menuItems = [
|
28
|
-
{
|
29
|
-
key: '/dashboard',
|
30
|
-
icon: <DashboardOutlined />,
|
31
|
-
label: '概览',
|
32
|
-
},
|
33
|
-
{
|
34
|
-
key: '/queues',
|
35
|
-
icon: <AppstoreOutlined />,
|
36
|
-
label: '队列',
|
37
|
-
},
|
38
|
-
// {
|
39
|
-
// key: '/workers',
|
40
|
-
// icon: <TeamOutlined />,
|
41
|
-
// label: 'Workers',
|
42
|
-
// },
|
43
|
-
{
|
44
|
-
key: '/scheduled-tasks',
|
45
|
-
icon: <ClockCircleOutlined />,
|
46
|
-
label: '定时任务',
|
47
|
-
},
|
48
|
-
{
|
49
|
-
key: '/alerts',
|
50
|
-
icon: <AlertOutlined />,
|
51
|
-
label: '监控告警',
|
52
|
-
},
|
53
|
-
]
|
54
|
-
|
55
|
-
const handleMenuClick = ({ key }) => {
|
56
|
-
navigate(key)
|
57
|
-
}
|
58
|
-
|
59
|
-
return (
|
60
|
-
<AntHeader className="app-header">
|
61
|
-
<div className="header-container">
|
62
|
-
<div className="header-left">
|
63
|
-
<div className="app-logo">
|
64
|
-
{isLoading ? (
|
65
|
-
<LoadingOutlined
|
66
|
-
className="logo-icon"
|
67
|
-
style={{
|
68
|
-
fontSize: 18,
|
69
|
-
color: '#1890ff'
|
70
|
-
}}
|
71
|
-
spin
|
72
|
-
/>
|
73
|
-
) : (
|
74
|
-
<RocketOutlined className="logo-icon" />
|
75
|
-
)}
|
76
|
-
<span className="logo-text">JetTask Monitor</span>
|
77
|
-
</div>
|
78
|
-
</div>
|
79
|
-
|
80
|
-
<div className="header-center">
|
81
|
-
<Menu
|
82
|
-
mode="horizontal"
|
83
|
-
selectedKeys={[location.pathname]}
|
84
|
-
items={menuItems}
|
85
|
-
onClick={handleMenuClick}
|
86
|
-
className="header-menu"
|
87
|
-
/>
|
88
|
-
</div>
|
89
|
-
|
90
|
-
<div className="header-right">
|
91
|
-
<NamespaceSelector
|
92
|
-
value={currentNamespace}
|
93
|
-
onChange={(namespace) => {
|
94
|
-
console.log('🔧 Header收到命名空间切换:', namespace);
|
95
|
-
setCurrentNamespace(namespace);
|
96
|
-
}}
|
97
|
-
style={{ marginLeft: 'auto' }}
|
98
|
-
/>
|
99
|
-
</div>
|
100
|
-
|
101
|
-
</div>
|
102
|
-
</AntHeader>
|
103
|
-
)
|
104
|
-
}
|
105
|
-
|
106
|
-
export default Header
|