jettask 0.2.14__py3-none-any.whl → 0.2.16__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/__init__.py +14 -35
- jettask/{webui/__main__.py → __main__.py} +4 -4
- jettask/api/__init__.py +103 -0
- jettask/api/v1/__init__.py +29 -0
- jettask/api/v1/alerts.py +226 -0
- jettask/api/v1/analytics.py +323 -0
- jettask/api/v1/namespaces.py +134 -0
- jettask/api/v1/overview.py +136 -0
- jettask/api/v1/queues.py +530 -0
- jettask/api/v1/scheduled.py +420 -0
- jettask/api/v1/settings.py +44 -0
- jettask/{webui/api.py → api.py} +4 -46
- jettask/{webui/backend → backend}/main.py +21 -109
- jettask/{webui/backend → backend}/main_unified.py +1 -1
- jettask/{webui/backend → backend}/namespace_api_old.py +3 -30
- jettask/{webui/backend → backend}/namespace_data_access.py +2 -1
- jettask/{webui/backend → backend}/unified_api_router.py +14 -74
- jettask/{core/cli.py → cli.py} +106 -26
- jettask/config/nacos_config.py +386 -0
- jettask/core/app.py +8 -100
- jettask/core/db_manager.py +515 -0
- jettask/core/event_pool.py +5 -2
- jettask/core/unified_manager_base.py +47 -14
- jettask/{webui/db_init.py → db_init.py} +1 -1
- jettask/executors/asyncio.py +2 -2
- jettask/{webui/integrated_gradio_app.py → integrated_gradio_app.py} +1 -1
- jettask/{webui/multi_namespace_consumer.py → multi_namespace_consumer.py} +5 -2
- jettask/{webui/pg_consumer.py → pg_consumer.py} +137 -69
- jettask/{webui/run.py → run.py} +1 -1
- jettask/{webui/run_webui.py → run_webui.py} +4 -4
- jettask/scheduler/multi_namespace_scheduler.py +2 -2
- jettask/scheduler/unified_manager.py +5 -5
- jettask/scheduler/unified_scheduler_manager.py +1 -1
- jettask/schemas/__init__.py +166 -0
- jettask/schemas/alert.py +99 -0
- jettask/schemas/backlog.py +122 -0
- jettask/schemas/common.py +139 -0
- jettask/schemas/monitoring.py +181 -0
- jettask/schemas/namespace.py +168 -0
- jettask/schemas/queue.py +83 -0
- jettask/schemas/scheduled_task.py +128 -0
- jettask/schemas/task.py +70 -0
- jettask/services/__init__.py +24 -0
- jettask/services/alert_service.py +454 -0
- jettask/services/analytics_service.py +46 -0
- jettask/services/overview_service.py +978 -0
- jettask/services/queue_service.py +711 -0
- jettask/services/redis_monitor_service.py +151 -0
- jettask/services/scheduled_task_service.py +207 -0
- jettask/services/settings_service.py +758 -0
- jettask/services/task_service.py +157 -0
- jettask/{webui/task_center.py → task_center.py} +30 -8
- jettask/{webui/task_center_client.py → task_center_client.py} +1 -1
- jettask/{webui/config.py → webui_config.py} +6 -1
- jettask/webui_exceptions.py +67 -0
- jettask/webui_sql/verify_database.sql +72 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/METADATA +3 -1
- jettask-0.2.16.dist-info/RECORD +150 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/entry_points.txt +1 -1
- jettask/webui/backend/data_api.py +0 -3294
- jettask/webui/backend/namespace_api.py +0 -295
- jettask/webui/backend/queue_backlog_api.py +0 -727
- jettask/webui/backend/redis_monitor_api.py +0 -476
- jettask/webui/frontend/index.html +0 -13
- 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 -22
- 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 -810
- jettask/webui/frontend/src/pages/Settings.jsx +0 -801
- jettask/webui/frontend/src/pages/Workers.jsx +0 -12
- jettask/webui/frontend/src/services/api.js +0 -159
- jettask/webui/frontend/src/services/queueTrend.js +0 -166
- jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
- jettask/webui/frontend/src/utils/userPreferences.js +0 -154
- jettask/webui/frontend/vite.config.js +0 -26
- jettask/webui/sql/init_database.sql +0 -640
- jettask-0.2.14.dist-info/RECORD +0 -172
- /jettask/{webui/backend → backend}/__init__.py +0 -0
- /jettask/{webui/backend → backend}/api/__init__.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/__init__.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/monitoring.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/namespaces.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/queues.py +0 -0
- /jettask/{webui/backend → backend}/api/v1/tasks.py +0 -0
- /jettask/{webui/backend → backend}/config.py +0 -0
- /jettask/{webui/backend → backend}/core/__init__.py +0 -0
- /jettask/{webui/backend → backend}/core/cache.py +0 -0
- /jettask/{webui/backend → backend}/core/database.py +0 -0
- /jettask/{webui/backend → backend}/core/exceptions.py +0 -0
- /jettask/{webui/backend → backend}/data_access.py +0 -0
- /jettask/{webui/backend → backend}/dependencies.py +0 -0
- /jettask/{webui/backend → backend}/init_meta_db.py +0 -0
- /jettask/{webui/backend → backend}/main_v2.py +0 -0
- /jettask/{webui/backend → backend}/models/__init__.py +0 -0
- /jettask/{webui/backend → backend}/models/requests.py +0 -0
- /jettask/{webui/backend → backend}/models/responses.py +0 -0
- /jettask/{webui/backend → backend}/queue_stats_v2.py +0 -0
- /jettask/{webui/backend → backend}/services/__init__.py +0 -0
- /jettask/{webui/backend → backend}/start.py +0 -0
- /jettask/{webui/cleanup_deprecated_tables.sql → cleanup_deprecated_tables.sql} +0 -0
- /jettask/{webui/gradio_app.py → gradio_app.py} +0 -0
- /jettask/{webui/__init__.py → main.py} +0 -0
- /jettask/{webui/models.py → models.py} +0 -0
- /jettask/{webui/run_monitor.py → run_monitor.py} +0 -0
- /jettask/{webui/schema.sql → schema.sql} +0 -0
- /jettask/{webui/unified_consumer_manager.py → unified_consumer_manager.py} +0 -0
- /jettask/{webui/models → webui_models}/__init__.py +0 -0
- /jettask/{webui/models → webui_models}/namespace.py +0 -0
- /jettask/{webui/sql → webui_sql}/batch_upsert_functions.sql +0 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/WHEEL +0 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/licenses/LICENSE +0 -0
- {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,323 @@
|
|
1
|
+
"""
|
2
|
+
分析模块 - 数据分析、查询和报表
|
3
|
+
提供轻量级的路由入口,业务逻辑在 AnalyticsService 中实现
|
4
|
+
"""
|
5
|
+
from fastapi import APIRouter, HTTPException, Request, Query
|
6
|
+
from typing import List, Dict, Optional, Any
|
7
|
+
from datetime import datetime
|
8
|
+
import logging
|
9
|
+
|
10
|
+
from jettask.schemas import TimeRangeQuery
|
11
|
+
from jettask.services.analytics_service import AnalyticsService
|
12
|
+
from jettask.backend.namespace_data_access import get_namespace_data_access
|
13
|
+
|
14
|
+
router = APIRouter(prefix="/analytics", tags=["analytics"])
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
# 获取全局数据访问实例
|
18
|
+
data_access = get_namespace_data_access()
|
19
|
+
|
20
|
+
|
21
|
+
# ============ 命名空间管理 ============
|
22
|
+
|
23
|
+
@router.get("/namespaces")
|
24
|
+
async def get_all_namespaces():
|
25
|
+
"""
|
26
|
+
获取所有配置的命名空间列表
|
27
|
+
"""
|
28
|
+
try:
|
29
|
+
namespaces = await data_access.get_all_namespaces()
|
30
|
+
return {
|
31
|
+
"success": True,
|
32
|
+
"data": namespaces
|
33
|
+
}
|
34
|
+
except Exception as e:
|
35
|
+
logger.error(f"获取命名空间列表失败: {e}")
|
36
|
+
raise HTTPException(status_code=500, detail=str(e))
|
37
|
+
|
38
|
+
|
39
|
+
# ============ 队列统计 ============
|
40
|
+
|
41
|
+
@router.get("/queue-stats/{namespace}")
|
42
|
+
async def get_queue_stats(namespace: str):
|
43
|
+
"""
|
44
|
+
获取指定命名空间的队列统计信息
|
45
|
+
|
46
|
+
Args:
|
47
|
+
namespace: 命名空间名称
|
48
|
+
"""
|
49
|
+
try:
|
50
|
+
stats = await data_access.get_queue_stats(namespace)
|
51
|
+
return {
|
52
|
+
"success": True,
|
53
|
+
"data": stats
|
54
|
+
}
|
55
|
+
except Exception as e:
|
56
|
+
logger.error(f"获取队列统计失败: {e}")
|
57
|
+
raise HTTPException(status_code=500, detail=str(e))
|
58
|
+
|
59
|
+
|
60
|
+
@router.post("/queue-trends/{namespace}")
|
61
|
+
async def get_queue_trends(namespace: str, request: Request):
|
62
|
+
"""
|
63
|
+
获取队列趋势数据
|
64
|
+
|
65
|
+
Args:
|
66
|
+
namespace: 命名空间名称
|
67
|
+
"""
|
68
|
+
try:
|
69
|
+
# 解析请求体
|
70
|
+
body = await request.json() if await request.body() else {}
|
71
|
+
|
72
|
+
# 从请求体中获取参数
|
73
|
+
time_range = body.get('time_range', '1h')
|
74
|
+
queue_name = body.get('queue_name')
|
75
|
+
granularity = body.get('granularity', 'minute')
|
76
|
+
|
77
|
+
# TODO: 实现队列趋势分析
|
78
|
+
return {
|
79
|
+
"success": True,
|
80
|
+
"namespace": namespace,
|
81
|
+
"time_range": time_range,
|
82
|
+
"queue_name": queue_name,
|
83
|
+
"data": [],
|
84
|
+
"granularity": granularity
|
85
|
+
}
|
86
|
+
except Exception as e:
|
87
|
+
logger.error(f"获取队列趋势失败: {e}")
|
88
|
+
raise HTTPException(status_code=500, detail=str(e))
|
89
|
+
|
90
|
+
|
91
|
+
# ============ 任务分析 ============
|
92
|
+
|
93
|
+
@router.post("/task-analysis/{namespace}")
|
94
|
+
async def analyze_tasks(namespace: str, request: Request):
|
95
|
+
"""
|
96
|
+
分析任务执行情况
|
97
|
+
|
98
|
+
Args:
|
99
|
+
namespace: 命名空间名称
|
100
|
+
"""
|
101
|
+
try:
|
102
|
+
body = await request.json()
|
103
|
+
|
104
|
+
# TODO: 实现任务分析逻辑
|
105
|
+
return {
|
106
|
+
"success": True,
|
107
|
+
"data": {
|
108
|
+
"total_tasks": 0,
|
109
|
+
"success_rate": 0.0,
|
110
|
+
"avg_execution_time": 0.0,
|
111
|
+
"error_distribution": {}
|
112
|
+
}
|
113
|
+
}
|
114
|
+
except Exception as e:
|
115
|
+
logger.error(f"任务分析失败: {e}")
|
116
|
+
raise HTTPException(status_code=500, detail=str(e))
|
117
|
+
|
118
|
+
|
119
|
+
@router.get("/task-distribution/{namespace}")
|
120
|
+
async def get_task_distribution(
|
121
|
+
namespace: str,
|
122
|
+
time_range: str = Query("24h", description="时间范围")
|
123
|
+
):
|
124
|
+
"""
|
125
|
+
获取任务分布情况
|
126
|
+
|
127
|
+
Args:
|
128
|
+
namespace: 命名空间名称
|
129
|
+
time_range: 时间范围
|
130
|
+
"""
|
131
|
+
try:
|
132
|
+
# TODO: 实现任务分布统计
|
133
|
+
return {
|
134
|
+
"success": True,
|
135
|
+
"data": {
|
136
|
+
"by_queue": {},
|
137
|
+
"by_status": {},
|
138
|
+
"by_worker": {},
|
139
|
+
"by_hour": []
|
140
|
+
}
|
141
|
+
}
|
142
|
+
except Exception as e:
|
143
|
+
logger.error(f"获取任务分布失败: {e}")
|
144
|
+
raise HTTPException(status_code=500, detail=str(e))
|
145
|
+
|
146
|
+
|
147
|
+
# ============ 性能分析 ============
|
148
|
+
|
149
|
+
@router.get("/performance-metrics/{namespace}")
|
150
|
+
async def get_performance_metrics(
|
151
|
+
namespace: str,
|
152
|
+
time_range: str = Query("1h", description="时间范围")
|
153
|
+
):
|
154
|
+
"""
|
155
|
+
获取性能指标
|
156
|
+
|
157
|
+
Args:
|
158
|
+
namespace: 命名空间名称
|
159
|
+
time_range: 时间范围
|
160
|
+
"""
|
161
|
+
try:
|
162
|
+
# TODO: 实现性能指标收集
|
163
|
+
return {
|
164
|
+
"success": True,
|
165
|
+
"data": {
|
166
|
+
"throughput": 0,
|
167
|
+
"latency": {
|
168
|
+
"p50": 0,
|
169
|
+
"p95": 0,
|
170
|
+
"p99": 0
|
171
|
+
},
|
172
|
+
"error_rate": 0,
|
173
|
+
"queue_depth": 0
|
174
|
+
}
|
175
|
+
}
|
176
|
+
except Exception as e:
|
177
|
+
logger.error(f"获取性能指标失败: {e}")
|
178
|
+
raise HTTPException(status_code=500, detail=str(e))
|
179
|
+
|
180
|
+
|
181
|
+
@router.get("/bottlenecks/{namespace}")
|
182
|
+
async def identify_bottlenecks(namespace: str):
|
183
|
+
"""
|
184
|
+
识别系统瓶颈
|
185
|
+
|
186
|
+
Args:
|
187
|
+
namespace: 命名空间名称
|
188
|
+
"""
|
189
|
+
try:
|
190
|
+
# TODO: 实现瓶颈分析
|
191
|
+
return {
|
192
|
+
"success": True,
|
193
|
+
"data": {
|
194
|
+
"slow_queues": [],
|
195
|
+
"high_error_queues": [],
|
196
|
+
"high_latency_tasks": [],
|
197
|
+
"recommendations": []
|
198
|
+
}
|
199
|
+
}
|
200
|
+
except Exception as e:
|
201
|
+
logger.error(f"识别瓶颈失败: {e}")
|
202
|
+
raise HTTPException(status_code=500, detail=str(e))
|
203
|
+
|
204
|
+
|
205
|
+
# ============ 报表生成 ============
|
206
|
+
|
207
|
+
@router.post("/reports/generate/{namespace}")
|
208
|
+
async def generate_report(
|
209
|
+
namespace: str,
|
210
|
+
request: Request
|
211
|
+
):
|
212
|
+
"""
|
213
|
+
生成分析报表
|
214
|
+
|
215
|
+
Args:
|
216
|
+
namespace: 命名空间名称
|
217
|
+
"""
|
218
|
+
try:
|
219
|
+
body = await request.json()
|
220
|
+
report_type = body.get("report_type", "daily")
|
221
|
+
start_time = body.get("start_time")
|
222
|
+
end_time = body.get("end_time")
|
223
|
+
|
224
|
+
# TODO: 实现报表生成
|
225
|
+
return {
|
226
|
+
"success": True,
|
227
|
+
"data": {
|
228
|
+
"report_id": f"report_{namespace}_{datetime.now().strftime('%Y%m%d%H%M%S')}",
|
229
|
+
"report_type": report_type,
|
230
|
+
"status": "generated",
|
231
|
+
"url": None
|
232
|
+
}
|
233
|
+
}
|
234
|
+
except Exception as e:
|
235
|
+
logger.error(f"生成报表失败: {e}")
|
236
|
+
raise HTTPException(status_code=500, detail=str(e))
|
237
|
+
|
238
|
+
|
239
|
+
@router.get("/reports/{report_id}")
|
240
|
+
async def get_report(report_id: str):
|
241
|
+
"""
|
242
|
+
获取报表内容
|
243
|
+
|
244
|
+
Args:
|
245
|
+
report_id: 报表ID
|
246
|
+
"""
|
247
|
+
try:
|
248
|
+
# TODO: 实现报表获取
|
249
|
+
return {
|
250
|
+
"success": True,
|
251
|
+
"data": {
|
252
|
+
"report_id": report_id,
|
253
|
+
"content": {},
|
254
|
+
"generated_at": datetime.now().isoformat()
|
255
|
+
}
|
256
|
+
}
|
257
|
+
except Exception as e:
|
258
|
+
logger.error(f"获取报表失败: {e}")
|
259
|
+
raise HTTPException(status_code=500, detail=str(e))
|
260
|
+
|
261
|
+
|
262
|
+
# ============ 实时监控 ============
|
263
|
+
|
264
|
+
@router.get("/realtime/{namespace}/stats")
|
265
|
+
async def get_realtime_stats(namespace: str):
|
266
|
+
"""
|
267
|
+
获取实时统计数据
|
268
|
+
|
269
|
+
Args:
|
270
|
+
namespace: 命名空间名称
|
271
|
+
"""
|
272
|
+
try:
|
273
|
+
# TODO: 实现实时统计
|
274
|
+
return {
|
275
|
+
"success": True,
|
276
|
+
"data": {
|
277
|
+
"timestamp": datetime.now().isoformat(),
|
278
|
+
"active_tasks": 0,
|
279
|
+
"queued_tasks": 0,
|
280
|
+
"completed_last_minute": 0,
|
281
|
+
"failed_last_minute": 0,
|
282
|
+
"avg_processing_time": 0.0
|
283
|
+
}
|
284
|
+
}
|
285
|
+
except Exception as e:
|
286
|
+
logger.error(f"获取实时统计失败: {e}")
|
287
|
+
raise HTTPException(status_code=500, detail=str(e))
|
288
|
+
|
289
|
+
|
290
|
+
# ============ 数据导出 ============
|
291
|
+
|
292
|
+
@router.post("/export/{namespace}")
|
293
|
+
async def export_data(
|
294
|
+
namespace: str,
|
295
|
+
request: Request
|
296
|
+
):
|
297
|
+
"""
|
298
|
+
导出分析数据
|
299
|
+
|
300
|
+
Args:
|
301
|
+
namespace: 命名空间名称
|
302
|
+
"""
|
303
|
+
try:
|
304
|
+
body = await request.json()
|
305
|
+
export_format = body.get("format", "csv")
|
306
|
+
data_type = body.get("data_type", "tasks")
|
307
|
+
|
308
|
+
# TODO: 实现数据导出
|
309
|
+
return {
|
310
|
+
"success": True,
|
311
|
+
"data": {
|
312
|
+
"export_id": f"export_{namespace}_{datetime.now().strftime('%Y%m%d%H%M%S')}",
|
313
|
+
"format": export_format,
|
314
|
+
"status": "pending",
|
315
|
+
"url": None
|
316
|
+
}
|
317
|
+
}
|
318
|
+
except Exception as e:
|
319
|
+
logger.error(f"导出数据失败: {e}")
|
320
|
+
raise HTTPException(status_code=500, detail=str(e))
|
321
|
+
|
322
|
+
|
323
|
+
__all__ = ['router']
|
@@ -0,0 +1,134 @@
|
|
1
|
+
"""
|
2
|
+
命名空间管理路由
|
3
|
+
提供命名空间的增删改查和管理功能
|
4
|
+
"""
|
5
|
+
from fastapi import APIRouter, HTTPException, Query
|
6
|
+
from typing import Optional, List
|
7
|
+
import logging
|
8
|
+
import traceback
|
9
|
+
|
10
|
+
from jettask.schemas import NamespaceCreate, NamespaceUpdate, NamespaceResponse
|
11
|
+
from jettask.services.settings_service import SettingsService
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
# 创建独立的命名空间路由,设置 /namespaces 前缀
|
16
|
+
router = APIRouter(prefix="/namespaces", tags=["namespaces"])
|
17
|
+
|
18
|
+
|
19
|
+
@router.get("/", response_model=List[NamespaceResponse])
|
20
|
+
async def list_namespaces(
|
21
|
+
page: int = Query(1, ge=1),
|
22
|
+
page_size: int = Query(20, ge=1, le=100),
|
23
|
+
is_active: Optional[bool] = None
|
24
|
+
):
|
25
|
+
"""
|
26
|
+
列出所有命名空间
|
27
|
+
|
28
|
+
Args:
|
29
|
+
page: 页码(从1开始)
|
30
|
+
page_size: 每页数量
|
31
|
+
is_active: 是否只返回激活的命名空间
|
32
|
+
"""
|
33
|
+
try:
|
34
|
+
return await SettingsService.list_namespaces(page, page_size, is_active)
|
35
|
+
except Exception as e:
|
36
|
+
logger.error(f"获取命名空间列表失败: {e}")
|
37
|
+
traceback.print_exc()
|
38
|
+
raise HTTPException(status_code=500, detail=str(e))
|
39
|
+
|
40
|
+
|
41
|
+
@router.post("/", response_model=NamespaceResponse, status_code=201)
|
42
|
+
async def create_namespace(namespace: NamespaceCreate):
|
43
|
+
"""
|
44
|
+
创建新的命名空间
|
45
|
+
|
46
|
+
Args:
|
47
|
+
namespace: 命名空间创建信息
|
48
|
+
"""
|
49
|
+
try:
|
50
|
+
return await SettingsService.create_namespace(namespace)
|
51
|
+
except ValueError as e:
|
52
|
+
raise HTTPException(status_code=400, detail=str(e))
|
53
|
+
except Exception as e:
|
54
|
+
logger.error(f"创建命名空间失败: {e}")
|
55
|
+
traceback.print_exc()
|
56
|
+
raise HTTPException(status_code=500, detail=str(e))
|
57
|
+
|
58
|
+
|
59
|
+
@router.get("/{namespace_name}", response_model=NamespaceResponse)
|
60
|
+
async def get_namespace(namespace_name: str):
|
61
|
+
"""
|
62
|
+
获取指定命名空间的详细信息
|
63
|
+
|
64
|
+
Args:
|
65
|
+
namespace_name: 命名空间名称
|
66
|
+
"""
|
67
|
+
try:
|
68
|
+
return await SettingsService.get_namespace(namespace_name)
|
69
|
+
except ValueError as e:
|
70
|
+
raise HTTPException(status_code=404, detail=str(e))
|
71
|
+
except Exception as e:
|
72
|
+
logger.error(f"获取命名空间失败: {e}")
|
73
|
+
traceback.print_exc()
|
74
|
+
raise HTTPException(status_code=500, detail=str(e))
|
75
|
+
|
76
|
+
|
77
|
+
@router.put("/{namespace_name}", response_model=NamespaceResponse)
|
78
|
+
async def update_namespace(namespace_name: str, namespace: NamespaceUpdate):
|
79
|
+
"""
|
80
|
+
更新命名空间配置
|
81
|
+
|
82
|
+
Args:
|
83
|
+
namespace_name: 命名空间名称
|
84
|
+
namespace: 更新的配置信息
|
85
|
+
"""
|
86
|
+
try:
|
87
|
+
return await SettingsService.update_namespace(namespace_name, namespace)
|
88
|
+
except ValueError as e:
|
89
|
+
status_code = 404 if "不存在" in str(e) else 400
|
90
|
+
raise HTTPException(status_code=status_code, detail=str(e))
|
91
|
+
except Exception as e:
|
92
|
+
logger.error(f"更新命名空间失败: {e}")
|
93
|
+
traceback.print_exc()
|
94
|
+
raise HTTPException(status_code=500, detail=str(e))
|
95
|
+
|
96
|
+
|
97
|
+
@router.delete("/{namespace_name}")
|
98
|
+
async def delete_namespace(namespace_name: str):
|
99
|
+
"""
|
100
|
+
删除命名空间
|
101
|
+
|
102
|
+
Args:
|
103
|
+
namespace_name: 命名空间名称
|
104
|
+
"""
|
105
|
+
try:
|
106
|
+
return await SettingsService.delete_namespace(namespace_name)
|
107
|
+
except ValueError as e:
|
108
|
+
status_code = 400 if "默认命名空间" in str(e) else 404
|
109
|
+
raise HTTPException(status_code=status_code, detail=str(e))
|
110
|
+
except Exception as e:
|
111
|
+
logger.error(f"删除命名空间失败: {e}")
|
112
|
+
traceback.print_exc()
|
113
|
+
raise HTTPException(status_code=500, detail=str(e))
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
@router.get("/{namespace_name}/statistics")
|
118
|
+
async def get_namespace_statistics(namespace_name: str):
|
119
|
+
"""
|
120
|
+
获取命名空间统计信息
|
121
|
+
|
122
|
+
Args:
|
123
|
+
namespace_name: 命名空间名称
|
124
|
+
"""
|
125
|
+
try:
|
126
|
+
return await SettingsService.get_namespace_statistics(namespace_name)
|
127
|
+
except ValueError as e:
|
128
|
+
raise HTTPException(status_code=404, detail=str(e))
|
129
|
+
except Exception as e:
|
130
|
+
logger.error(f"获取命名空间统计信息失败: {e}")
|
131
|
+
traceback.print_exc()
|
132
|
+
raise HTTPException(status_code=500, detail=str(e))
|
133
|
+
|
134
|
+
__all__ = ['router']
|
@@ -0,0 +1,136 @@
|
|
1
|
+
"""
|
2
|
+
概览模块 - 系统总览、健康检查和仪表板统计
|
3
|
+
提供轻量级的路由入口,业务逻辑在 OverviewService 中实现
|
4
|
+
"""
|
5
|
+
from fastapi import APIRouter, HTTPException, Query
|
6
|
+
from typing import Optional
|
7
|
+
import logging
|
8
|
+
import traceback
|
9
|
+
|
10
|
+
from jettask.schemas import TimeRangeQuery
|
11
|
+
from jettask.services.overview_service import OverviewService
|
12
|
+
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
# 创建概览路由,添加 /overview 前缀
|
16
|
+
router = APIRouter(prefix="/overview", tags=["overview"])
|
17
|
+
|
18
|
+
|
19
|
+
# ============ 健康检查和根路径 ============
|
20
|
+
|
21
|
+
@router.get("/")
|
22
|
+
async def root():
|
23
|
+
"""根路径"""
|
24
|
+
return OverviewService.get_root_info()
|
25
|
+
|
26
|
+
|
27
|
+
@router.get("/health")
|
28
|
+
async def health_check():
|
29
|
+
"""健康检查"""
|
30
|
+
return OverviewService.get_health_status()
|
31
|
+
|
32
|
+
|
33
|
+
# ============ 系统统计 ============
|
34
|
+
|
35
|
+
@router.get("/system-stats/{namespace}")
|
36
|
+
async def get_system_stats(namespace: str):
|
37
|
+
"""
|
38
|
+
获取指定命名空间的系统统计信息
|
39
|
+
|
40
|
+
Args:
|
41
|
+
namespace: 命名空间名称
|
42
|
+
"""
|
43
|
+
try:
|
44
|
+
return await OverviewService.get_system_stats(namespace)
|
45
|
+
except Exception as e:
|
46
|
+
logger.error(f"获取系统统计信息失败: {e}")
|
47
|
+
traceback.print_exc()
|
48
|
+
raise HTTPException(status_code=500, detail=str(e))
|
49
|
+
|
50
|
+
|
51
|
+
# ============ 仪表板统计 ============
|
52
|
+
|
53
|
+
@router.get("/dashboard-stats/{namespace}")
|
54
|
+
async def get_dashboard_stats(
|
55
|
+
namespace: str,
|
56
|
+
time_range: str = "24h",
|
57
|
+
queues: Optional[str] = Query(None, description="逗号分隔的队列名称列表")
|
58
|
+
):
|
59
|
+
"""
|
60
|
+
获取仪表板统计数据(任务总数、成功数、失败数、成功率、吞吐量等)
|
61
|
+
|
62
|
+
Args:
|
63
|
+
namespace: 命名空间名称
|
64
|
+
time_range: 时间范围(如'1h', '24h', '7d')
|
65
|
+
queues: 逗号分隔的队列名称列表
|
66
|
+
"""
|
67
|
+
try:
|
68
|
+
return await OverviewService.get_dashboard_stats(namespace, time_range, queues)
|
69
|
+
except Exception as e:
|
70
|
+
logger.error(f"获取仪表板统计数据失败: {e}")
|
71
|
+
traceback.print_exc()
|
72
|
+
raise HTTPException(status_code=500, detail=str(e))
|
73
|
+
|
74
|
+
|
75
|
+
# ============ 队列排行榜 ============
|
76
|
+
|
77
|
+
@router.get("/top-queues/{namespace}")
|
78
|
+
async def get_top_queues(
|
79
|
+
namespace: str,
|
80
|
+
metric: str = Query("backlog", description="指标类型: backlog(积压) 或 error(错误率)"),
|
81
|
+
limit: int = 10,
|
82
|
+
time_range: str = "24h",
|
83
|
+
queues: Optional[str] = Query(None, description="逗号分隔的队列名称列表")
|
84
|
+
):
|
85
|
+
"""
|
86
|
+
获取队列排行榜 - 支持积压和错误率两种指标
|
87
|
+
|
88
|
+
Args:
|
89
|
+
namespace: 命名空间名称
|
90
|
+
metric: 指标类型 (backlog/error)
|
91
|
+
limit: 返回的队列数量限制
|
92
|
+
time_range: 时间范围
|
93
|
+
queues: 逗号分隔的队列名称列表
|
94
|
+
"""
|
95
|
+
try:
|
96
|
+
return await OverviewService.get_top_queues(namespace, metric, limit, time_range, queues)
|
97
|
+
except ValueError as e:
|
98
|
+
raise HTTPException(status_code=400, detail=str(e))
|
99
|
+
except Exception as e:
|
100
|
+
logger.error(f"获取队列排行榜失败: {e}")
|
101
|
+
traceback.print_exc()
|
102
|
+
raise HTTPException(status_code=500, detail=str(e))
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
# ============ 概览统计数据 ============
|
107
|
+
|
108
|
+
@router.post("/dashboard-overview-stats/{namespace}")
|
109
|
+
async def get_dashboard_overview_stats(namespace: str, query: TimeRangeQuery):
|
110
|
+
"""
|
111
|
+
获取概览页面的统一统计数据
|
112
|
+
包含:任务处理趋势、任务并发数量、任务处理时间、任务执行延时
|
113
|
+
|
114
|
+
Args:
|
115
|
+
namespace: 命名空间名称
|
116
|
+
query: 时间范围查询参数
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
统一的时间序列数据,包含所有概览图表需要的指标和granularity字段
|
120
|
+
"""
|
121
|
+
try:
|
122
|
+
return await OverviewService.get_dashboard_overview_stats(namespace, query)
|
123
|
+
except Exception as e:
|
124
|
+
logger.error(f"获取概览统计数据失败: {e}")
|
125
|
+
traceback.print_exc()
|
126
|
+
# 返回空数据而不是抛出异常
|
127
|
+
return {
|
128
|
+
"task_trend": [],
|
129
|
+
"concurrency": [],
|
130
|
+
"processing_time": [],
|
131
|
+
"creation_latency": [],
|
132
|
+
"granularity": "minute"
|
133
|
+
}
|
134
|
+
|
135
|
+
|
136
|
+
__all__ = ['router']
|