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.
Files changed (148) hide show
  1. jettask/__init__.py +14 -35
  2. jettask/{webui/__main__.py → __main__.py} +4 -4
  3. jettask/api/__init__.py +103 -0
  4. jettask/api/v1/__init__.py +29 -0
  5. jettask/api/v1/alerts.py +226 -0
  6. jettask/api/v1/analytics.py +323 -0
  7. jettask/api/v1/namespaces.py +134 -0
  8. jettask/api/v1/overview.py +136 -0
  9. jettask/api/v1/queues.py +530 -0
  10. jettask/api/v1/scheduled.py +420 -0
  11. jettask/api/v1/settings.py +44 -0
  12. jettask/{webui/api.py → api.py} +4 -46
  13. jettask/{webui/backend → backend}/main.py +21 -109
  14. jettask/{webui/backend → backend}/main_unified.py +1 -1
  15. jettask/{webui/backend → backend}/namespace_api_old.py +3 -30
  16. jettask/{webui/backend → backend}/namespace_data_access.py +2 -1
  17. jettask/{webui/backend → backend}/unified_api_router.py +14 -74
  18. jettask/{core/cli.py → cli.py} +106 -26
  19. jettask/config/nacos_config.py +386 -0
  20. jettask/core/app.py +8 -100
  21. jettask/core/db_manager.py +515 -0
  22. jettask/core/event_pool.py +5 -2
  23. jettask/core/unified_manager_base.py +47 -14
  24. jettask/{webui/db_init.py → db_init.py} +1 -1
  25. jettask/executors/asyncio.py +2 -2
  26. jettask/{webui/integrated_gradio_app.py → integrated_gradio_app.py} +1 -1
  27. jettask/{webui/multi_namespace_consumer.py → multi_namespace_consumer.py} +5 -2
  28. jettask/{webui/pg_consumer.py → pg_consumer.py} +137 -69
  29. jettask/{webui/run.py → run.py} +1 -1
  30. jettask/{webui/run_webui.py → run_webui.py} +4 -4
  31. jettask/scheduler/multi_namespace_scheduler.py +2 -2
  32. jettask/scheduler/unified_manager.py +5 -5
  33. jettask/scheduler/unified_scheduler_manager.py +1 -1
  34. jettask/schemas/__init__.py +166 -0
  35. jettask/schemas/alert.py +99 -0
  36. jettask/schemas/backlog.py +122 -0
  37. jettask/schemas/common.py +139 -0
  38. jettask/schemas/monitoring.py +181 -0
  39. jettask/schemas/namespace.py +168 -0
  40. jettask/schemas/queue.py +83 -0
  41. jettask/schemas/scheduled_task.py +128 -0
  42. jettask/schemas/task.py +70 -0
  43. jettask/services/__init__.py +24 -0
  44. jettask/services/alert_service.py +454 -0
  45. jettask/services/analytics_service.py +46 -0
  46. jettask/services/overview_service.py +978 -0
  47. jettask/services/queue_service.py +711 -0
  48. jettask/services/redis_monitor_service.py +151 -0
  49. jettask/services/scheduled_task_service.py +207 -0
  50. jettask/services/settings_service.py +758 -0
  51. jettask/services/task_service.py +157 -0
  52. jettask/{webui/task_center.py → task_center.py} +30 -8
  53. jettask/{webui/task_center_client.py → task_center_client.py} +1 -1
  54. jettask/{webui/config.py → webui_config.py} +6 -1
  55. jettask/webui_exceptions.py +67 -0
  56. jettask/webui_sql/verify_database.sql +72 -0
  57. {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/METADATA +3 -1
  58. jettask-0.2.16.dist-info/RECORD +150 -0
  59. {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/entry_points.txt +1 -1
  60. jettask/webui/backend/data_api.py +0 -3294
  61. jettask/webui/backend/namespace_api.py +0 -295
  62. jettask/webui/backend/queue_backlog_api.py +0 -727
  63. jettask/webui/backend/redis_monitor_api.py +0 -476
  64. jettask/webui/frontend/index.html +0 -13
  65. jettask/webui/frontend/package.json +0 -30
  66. jettask/webui/frontend/src/App.css +0 -109
  67. jettask/webui/frontend/src/App.jsx +0 -66
  68. jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -166
  69. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -298
  70. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -638
  71. jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -65
  72. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -487
  73. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -465
  74. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -423
  75. jettask/webui/frontend/src/components/TaskFilter.jsx +0 -425
  76. jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -21
  77. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -160
  78. jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -111
  79. jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -115
  80. jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -40
  81. jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -18
  82. jettask/webui/frontend/src/components/layout/AppLayout.css +0 -95
  83. jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -49
  84. jettask/webui/frontend/src/components/layout/Header.css +0 -106
  85. jettask/webui/frontend/src/components/layout/Header.jsx +0 -106
  86. jettask/webui/frontend/src/components/layout/SideMenu.css +0 -137
  87. jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -209
  88. jettask/webui/frontend/src/components/layout/TabsNav.css +0 -244
  89. jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -206
  90. jettask/webui/frontend/src/components/layout/UserInfo.css +0 -197
  91. jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -197
  92. jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -27
  93. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -72
  94. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -245
  95. jettask/webui/frontend/src/index.css +0 -114
  96. jettask/webui/frontend/src/main.jsx +0 -22
  97. jettask/webui/frontend/src/pages/Alerts.jsx +0 -684
  98. jettask/webui/frontend/src/pages/Dashboard/index.css +0 -35
  99. jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -281
  100. jettask/webui/frontend/src/pages/Dashboard.jsx +0 -1330
  101. jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -1117
  102. jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -527
  103. jettask/webui/frontend/src/pages/Queues.jsx +0 -12
  104. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +0 -810
  105. jettask/webui/frontend/src/pages/Settings.jsx +0 -801
  106. jettask/webui/frontend/src/pages/Workers.jsx +0 -12
  107. jettask/webui/frontend/src/services/api.js +0 -159
  108. jettask/webui/frontend/src/services/queueTrend.js +0 -166
  109. jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
  110. jettask/webui/frontend/src/utils/userPreferences.js +0 -154
  111. jettask/webui/frontend/vite.config.js +0 -26
  112. jettask/webui/sql/init_database.sql +0 -640
  113. jettask-0.2.14.dist-info/RECORD +0 -172
  114. /jettask/{webui/backend → backend}/__init__.py +0 -0
  115. /jettask/{webui/backend → backend}/api/__init__.py +0 -0
  116. /jettask/{webui/backend → backend}/api/v1/__init__.py +0 -0
  117. /jettask/{webui/backend → backend}/api/v1/monitoring.py +0 -0
  118. /jettask/{webui/backend → backend}/api/v1/namespaces.py +0 -0
  119. /jettask/{webui/backend → backend}/api/v1/queues.py +0 -0
  120. /jettask/{webui/backend → backend}/api/v1/tasks.py +0 -0
  121. /jettask/{webui/backend → backend}/config.py +0 -0
  122. /jettask/{webui/backend → backend}/core/__init__.py +0 -0
  123. /jettask/{webui/backend → backend}/core/cache.py +0 -0
  124. /jettask/{webui/backend → backend}/core/database.py +0 -0
  125. /jettask/{webui/backend → backend}/core/exceptions.py +0 -0
  126. /jettask/{webui/backend → backend}/data_access.py +0 -0
  127. /jettask/{webui/backend → backend}/dependencies.py +0 -0
  128. /jettask/{webui/backend → backend}/init_meta_db.py +0 -0
  129. /jettask/{webui/backend → backend}/main_v2.py +0 -0
  130. /jettask/{webui/backend → backend}/models/__init__.py +0 -0
  131. /jettask/{webui/backend → backend}/models/requests.py +0 -0
  132. /jettask/{webui/backend → backend}/models/responses.py +0 -0
  133. /jettask/{webui/backend → backend}/queue_stats_v2.py +0 -0
  134. /jettask/{webui/backend → backend}/services/__init__.py +0 -0
  135. /jettask/{webui/backend → backend}/start.py +0 -0
  136. /jettask/{webui/cleanup_deprecated_tables.sql → cleanup_deprecated_tables.sql} +0 -0
  137. /jettask/{webui/gradio_app.py → gradio_app.py} +0 -0
  138. /jettask/{webui/__init__.py → main.py} +0 -0
  139. /jettask/{webui/models.py → models.py} +0 -0
  140. /jettask/{webui/run_monitor.py → run_monitor.py} +0 -0
  141. /jettask/{webui/schema.sql → schema.sql} +0 -0
  142. /jettask/{webui/unified_consumer_manager.py → unified_consumer_manager.py} +0 -0
  143. /jettask/{webui/models → webui_models}/__init__.py +0 -0
  144. /jettask/{webui/models → webui_models}/namespace.py +0 -0
  145. /jettask/{webui/sql → webui_sql}/batch_upsert_functions.sql +0 -0
  146. {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/WHEEL +0 -0
  147. {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/licenses/LICENSE +0 -0
  148. {jettask-0.2.14.dist-info → jettask-0.2.16.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,530 @@
1
+ """
2
+ 队列模块 - 队列管理、任务处理、队列统计和监控
3
+ 提供轻量级的路由入口,业务逻辑在 QueueService 中实现
4
+ """
5
+ from fastapi import APIRouter, HTTPException, Request, Query, Depends
6
+ from typing import Optional, Dict, Any
7
+ from datetime import datetime
8
+ import logging
9
+
10
+ from jettask.schemas import (
11
+ TimeRangeQuery,
12
+ TrimQueueRequest,
13
+ TasksRequest,
14
+ TaskActionRequest,
15
+ BacklogTrendRequest
16
+ )
17
+ from jettask.services.queue_service import QueueService
18
+ from jettask.services.task_service import TaskService
19
+ from jettask.services.redis_monitor_service import RedisMonitorService
20
+
21
+ router = APIRouter(prefix="/queues", tags=["queues"])
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ # ============ 队列基础管理 ============
26
+
27
+ @router.get("/{namespace}")
28
+ async def get_queues(request: Request, namespace: str):
29
+ """
30
+ 获取指定命名空间的队列列表
31
+
32
+ Args:
33
+ namespace: 命名空间名称
34
+ """
35
+ try:
36
+ app = request.app
37
+ if not app or not hasattr(app.state, 'namespace_data_access'):
38
+ raise HTTPException(status_code=500, detail="Namespace data access not initialized")
39
+
40
+ namespace_data_access = app.state.namespace_data_access
41
+ return await QueueService.get_queues_by_namespace(namespace_data_access, namespace)
42
+ except Exception as e:
43
+ logger.error(f"获取队列列表失败: {e}")
44
+ raise HTTPException(status_code=500, detail=str(e))
45
+
46
+
47
+ @router.get("/detail")
48
+ async def get_queues_detail(request: Request):
49
+ """获取队列详细信息"""
50
+ try:
51
+ app = request.app
52
+ if not app or not hasattr(app.state, 'data_access'):
53
+ raise HTTPException(status_code=500, detail="Data access not initialized")
54
+
55
+ data_access = app.state.data_access
56
+ return await QueueService.get_queues_detail(data_access)
57
+ except Exception as e:
58
+ logger.error(f"获取队列详细信息失败: {e}")
59
+ raise HTTPException(status_code=500, detail=str(e))
60
+
61
+
62
+ @router.delete("/{queue_name}")
63
+ async def delete_queue(queue_name: str):
64
+ """
65
+ 删除队列
66
+
67
+ Args:
68
+ queue_name: 队列名称
69
+ """
70
+ try:
71
+ return await QueueService.delete_queue(queue_name)
72
+ except Exception as e:
73
+ logger.error(f"删除队列失败: {e}")
74
+ raise HTTPException(status_code=500, detail=str(e))
75
+
76
+
77
+ @router.post("/{queue_name}/trim")
78
+ async def trim_queue(queue_name: str, request: TrimQueueRequest):
79
+ """
80
+ 裁剪队列到指定长度
81
+
82
+ Args:
83
+ queue_name: 队列名称
84
+ request: 裁剪请求参数
85
+ """
86
+ try:
87
+ return await QueueService.trim_queue(queue_name, request.max_length)
88
+ except Exception as e:
89
+ logger.error(f"裁剪队列失败: {e}")
90
+ raise HTTPException(status_code=500, detail=str(e))
91
+
92
+
93
+ # ============ 队列流量和统计 ============
94
+
95
+ @router.post("/flow-rates")
96
+ async def get_queue_flow_rates(request: Request):
97
+ """
98
+ 获取单个队列的流量速率(入队、开始执行、完成)
99
+
100
+ Args:
101
+ request: FastAPI request对象,包含JSON body
102
+ """
103
+ try:
104
+ app = request.app
105
+ if not app or not hasattr(app.state, 'data_access'):
106
+ raise HTTPException(status_code=500, detail="Data access not initialized")
107
+
108
+ # 解析请求体
109
+ body = await request.json()
110
+
111
+ # 创建TimeRangeQuery对象
112
+ from jettask.schemas import TimeRangeQuery
113
+ query = TimeRangeQuery(**body)
114
+
115
+ data_access = app.state.data_access
116
+ return await QueueService.get_queue_flow_rates(data_access, query)
117
+ except Exception as e:
118
+ logger.error(f"获取队列流量速率失败: {e}")
119
+ import traceback
120
+ traceback.print_exc()
121
+ raise HTTPException(status_code=500, detail=str(e))
122
+
123
+
124
+ @router.get("/stats")
125
+ async def get_global_stats(request: Request):
126
+ """获取全局统计信息"""
127
+ try:
128
+ app = request.app
129
+ if not app or not hasattr(app.state, 'data_access'):
130
+ raise HTTPException(status_code=500, detail="Data access not initialized")
131
+
132
+ data_access = app.state.data_access
133
+ return await QueueService.get_global_stats(data_access)
134
+ except Exception as e:
135
+ logger.error(f"获取全局统计信息失败: {e}")
136
+ raise HTTPException(status_code=500, detail=str(e))
137
+
138
+
139
+ @router.get("/stats-v2/{namespace}")
140
+ async def get_queue_stats_v2(
141
+ request: Request,
142
+ namespace: str,
143
+ queue: Optional[str] = Query(None, description="队列名称"),
144
+ start_time: Optional[datetime] = None,
145
+ end_time: Optional[datetime] = None,
146
+ time_range: Optional[str] = None
147
+ ):
148
+ """
149
+ 获取队列统计信息v2 - 支持消费者组详情和优先级队列
150
+
151
+ Args:
152
+ namespace: 命名空间
153
+ queue: 可选,筛选特定队列
154
+ start_time: 开始时间
155
+ end_time: 结束时间
156
+ time_range: 时间范围
157
+ """
158
+ try:
159
+ app = request.app
160
+ if not app or not hasattr(app.state, 'namespace_data_access'):
161
+ raise HTTPException(status_code=500, detail="Namespace data access not initialized")
162
+
163
+ namespace_data_access = app.state.namespace_data_access
164
+ return await QueueService.get_queue_stats_v2(
165
+ namespace_data_access, namespace, queue, start_time, end_time, time_range
166
+ )
167
+ except Exception as e:
168
+ logger.error(f"获取队列统计v2失败: {e}")
169
+ raise HTTPException(status_code=500, detail=str(e))
170
+
171
+
172
+ # ============ 消费者组统计 ============
173
+
174
+ @router.get("/consumer-groups/{namespace}/{group_name}/stats")
175
+ async def get_consumer_group_stats(request: Request, namespace: str, group_name: str):
176
+ """
177
+ 获取特定消费者组的详细统计
178
+
179
+ Args:
180
+ namespace: 命名空间
181
+ group_name: 消费者组名称
182
+ """
183
+ try:
184
+ app = request.app
185
+ if not app or not hasattr(app.state, 'namespace_data_access'):
186
+ raise HTTPException(status_code=500, detail="Namespace data access not initialized")
187
+
188
+ namespace_data_access = app.state.namespace_data_access
189
+ return await QueueService.get_consumer_group_stats(namespace_data_access, namespace, group_name)
190
+ except ValueError as e:
191
+ raise HTTPException(status_code=400, detail=str(e))
192
+ except Exception as e:
193
+ logger.error(f"获取消费者组统计失败: {e}")
194
+ raise HTTPException(status_code=500, detail=str(e))
195
+
196
+
197
+ # ============ Stream积压监控 ============
198
+
199
+ @router.get("/stream-backlog/{namespace}")
200
+ async def get_stream_backlog(
201
+ request: Request,
202
+ namespace: str,
203
+ stream_name: Optional[str] = Query(None, description="Stream名称"),
204
+ hours: int = Query(24, description="查询最近多少小时的数据")
205
+ ):
206
+ """
207
+ 获取Stream积压监控数据
208
+
209
+ Args:
210
+ namespace: 命名空间
211
+ stream_name: 可选,指定stream名称
212
+ hours: 查询最近多少小时的数据
213
+ """
214
+ try:
215
+ app = request.app
216
+ if not app or not hasattr(app.state, 'data_access'):
217
+ raise HTTPException(status_code=500, detail="Data access not initialized")
218
+
219
+ data_access = app.state.data_access
220
+ return await QueueService.get_stream_backlog(data_access, namespace, stream_name, hours)
221
+ except Exception as e:
222
+ logger.error(f"获取Stream积压监控数据失败: {e}")
223
+ raise HTTPException(status_code=500, detail=str(e))
224
+
225
+
226
+ @router.get("/stream-backlog/{namespace}/summary")
227
+ async def get_stream_backlog_summary(request: Request, namespace: str):
228
+ """
229
+ 获取Stream积压监控汇总数据
230
+
231
+ Args:
232
+ namespace: 命名空间
233
+ """
234
+ try:
235
+ app = request.app
236
+ if not app or not hasattr(app.state, 'data_access'):
237
+ raise HTTPException(status_code=500, detail="Data access not initialized")
238
+
239
+ data_access = app.state.data_access
240
+ return await QueueService.get_stream_backlog_summary(data_access, namespace)
241
+ except Exception as e:
242
+ logger.error(f"获取Stream积压监控汇总失败: {e}")
243
+ raise HTTPException(status_code=500, detail=str(e))
244
+
245
+
246
+ # ============ 队列积压监控 ============
247
+
248
+ @router.post("/backlog/latest/{namespace}")
249
+ async def get_latest_backlog(request: Request, namespace: str):
250
+ """
251
+ 获取最新的队列积压数据快照
252
+
253
+ Args:
254
+ namespace: 命名空间
255
+ """
256
+ try:
257
+ app = request.app
258
+ if not app or not hasattr(app.state, 'data_access'):
259
+ raise HTTPException(status_code=500, detail="Data access not initialized")
260
+
261
+ data_access = app.state.data_access
262
+ body = await request.json() if await request.body() else {}
263
+ queues = body.get('queues', [])
264
+
265
+ # TODO: 调用QueueService的积压监控方法
266
+ return {
267
+ "success": True,
268
+ "namespace": namespace,
269
+ "queues": queues,
270
+ "data": [],
271
+ "timestamp": datetime.now().isoformat(),
272
+ "message": "Backlog monitoring endpoint placeholder"
273
+ }
274
+ except Exception as e:
275
+ logger.error(f"获取最新积压数据失败: {e}")
276
+ raise HTTPException(status_code=500, detail=str(e))
277
+
278
+
279
+ @router.post("/backlog/trend/{namespace}")
280
+ async def get_backlog_trend(request: Request, namespace: str):
281
+ """
282
+ 获取队列积压趋势数据
283
+
284
+ Args:
285
+ namespace: 命名空间
286
+ """
287
+ try:
288
+ app = request.app
289
+ if not app or not hasattr(app.state, 'data_access'):
290
+ raise HTTPException(status_code=500, detail="Data access not initialized")
291
+
292
+ # 解析请求体
293
+ body = await request.json() if await request.body() else {}
294
+
295
+ # 从请求体中获取参数,namespace从路径参数获取
296
+ time_range = body.get('time_range', '1h')
297
+ queue_name = body.get('queue_name')
298
+ interval = body.get('interval', '5m')
299
+ metrics = body.get('metrics', ["pending", "processing", "completed", "failed"])
300
+
301
+ # TODO: 调用QueueService的积压趋势方法
302
+ return {
303
+ "success": True,
304
+ "namespace": namespace,
305
+ "time_range": time_range,
306
+ "queue_name": queue_name,
307
+ "interval": interval,
308
+ "data": [],
309
+ "statistics": {},
310
+ "granularity": "minute"
311
+ }
312
+ except Exception as e:
313
+ logger.error(f"获取积压趋势失败: {e}")
314
+ raise HTTPException(status_code=500, detail=str(e))
315
+
316
+
317
+ # ============ 任务管理 ============
318
+
319
+ def get_task_service(request: Request) -> TaskService:
320
+ """获取任务服务实例"""
321
+ if not hasattr(request.app.state, 'data_access'):
322
+ raise HTTPException(status_code=500, detail="Data access not initialized")
323
+ return TaskService(request.app.state.data_access)
324
+
325
+
326
+ @router.post("/tasks")
327
+ async def get_tasks(
328
+ request: TasksRequest,
329
+ service: TaskService = Depends(get_task_service)
330
+ ) -> Dict[str, Any]:
331
+ """
332
+ 获取队列的任务列表
333
+
334
+ 支持灵活筛选和时间范围查询
335
+ """
336
+ try:
337
+ return await service.get_tasks_with_filters(
338
+ queue_name=request.queue_name,
339
+ page=request.page,
340
+ page_size=request.page_size,
341
+ filters=request.filters,
342
+ time_range=request.time_range,
343
+ start_time=request.start_time,
344
+ end_time=request.end_time
345
+ )
346
+ except ValueError as e:
347
+ raise HTTPException(status_code=400, detail=str(e))
348
+ except Exception as e:
349
+ raise HTTPException(status_code=500, detail=str(e))
350
+
351
+
352
+ @router.get("/tasks")
353
+ async def get_tasks_legacy(
354
+ queue_name: str,
355
+ page: int = 1,
356
+ page_size: int = 20,
357
+ status: Optional[str] = None,
358
+ task_id: Optional[str] = None,
359
+ worker_id: Optional[str] = None,
360
+ service: TaskService = Depends(get_task_service)
361
+ ) -> Dict[str, Any]:
362
+ """
363
+ 获取队列的任务列表(向后兼容旧版本)
364
+ """
365
+ # 构建筛选条件
366
+ filters = []
367
+ if status:
368
+ filters.append({'field': 'status', 'operator': 'eq', 'value': status})
369
+ if task_id:
370
+ filters.append({'field': 'id', 'operator': 'eq', 'value': task_id})
371
+ if worker_id:
372
+ filters.append({'field': 'worker_id', 'operator': 'eq', 'value': worker_id})
373
+
374
+ try:
375
+ return await service.get_tasks_with_filters(
376
+ queue_name=queue_name,
377
+ page=page,
378
+ page_size=page_size,
379
+ filters=filters
380
+ )
381
+ except Exception as e:
382
+ raise HTTPException(status_code=500, detail=str(e))
383
+
384
+
385
+ @router.get("/task/{task_id}/details")
386
+ async def get_task_details(
387
+ task_id: str,
388
+ consumer_group: Optional[str] = None,
389
+ service: TaskService = Depends(get_task_service)
390
+ ) -> Dict[str, Any]:
391
+ """
392
+ 获取单个任务的详细数据
393
+
394
+ 包括task_data和result
395
+ """
396
+ try:
397
+ task_details = await service.get_task_details(task_id, consumer_group)
398
+ return {
399
+ "success": True,
400
+ "data": task_details
401
+ }
402
+ except ValueError as e:
403
+ raise HTTPException(status_code=404, detail=str(e))
404
+ except Exception as e:
405
+ raise HTTPException(status_code=500, detail=str(e))
406
+
407
+
408
+ @router.post("/tasks-v2/{namespace}")
409
+ async def get_tasks_v2(request: Request, namespace: str):
410
+ """
411
+ 获取任务列表v2 - 支持tasks和task_runs表连表查询
412
+
413
+ Args:
414
+ namespace: 命名空间
415
+ """
416
+ try:
417
+ app = request.app
418
+ if not app or not hasattr(app.state, 'namespace_data_access'):
419
+ raise HTTPException(status_code=500, detail="Namespace data access not initialized")
420
+
421
+ namespace_data_access = app.state.namespace_data_access
422
+
423
+ # 解析请求体
424
+ body = await request.json()
425
+
426
+ return await QueueService.get_tasks_v2(namespace_data_access, namespace, body)
427
+ except ValueError as e:
428
+ raise HTTPException(status_code=400, detail=str(e))
429
+ except Exception as e:
430
+ logger.error(f"获取任务列表v2失败: {e}")
431
+ raise HTTPException(status_code=500, detail=str(e))
432
+
433
+
434
+ # ============ Redis监控 ============
435
+
436
+ @router.get("/redis/monitor/{namespace}")
437
+ async def get_redis_monitor(request: Request, namespace: str):
438
+ """
439
+ 获取Redis性能监控数据
440
+
441
+ Args:
442
+ namespace: 命名空间
443
+ """
444
+ try:
445
+ app = request.app
446
+ if not app or not hasattr(app.state, 'namespace_data_access'):
447
+ raise HTTPException(status_code=500, detail="Namespace data access not initialized")
448
+
449
+ namespace_data_access = app.state.namespace_data_access
450
+ redis_monitor_service = RedisMonitorService(namespace_data_access)
451
+ return await redis_monitor_service.get_redis_monitor_data(namespace)
452
+ except Exception as e:
453
+ logger.error(f"获取Redis监控数据失败: {e}")
454
+ raise HTTPException(status_code=500, detail=str(e))
455
+
456
+
457
+ @router.get("/redis/slow-log/{namespace}")
458
+ async def get_redis_slow_log(
459
+ request: Request,
460
+ namespace: str,
461
+ limit: int = Query(10, description="返回记录数")
462
+ ):
463
+ """
464
+ 获取Redis慢查询日志
465
+
466
+ Args:
467
+ namespace: 命名空间
468
+ limit: 返回记录数
469
+ """
470
+ try:
471
+ app = request.app
472
+ if not app or not hasattr(app.state, 'namespace_data_access'):
473
+ raise HTTPException(status_code=500, detail="Namespace data access not initialized")
474
+
475
+ namespace_data_access = app.state.namespace_data_access
476
+ redis_monitor_service = RedisMonitorService(namespace_data_access)
477
+ return await redis_monitor_service.get_slow_log(namespace, limit)
478
+ except Exception as e:
479
+ logger.error(f"获取Redis慢查询日志失败: {e}")
480
+ raise HTTPException(status_code=500, detail=str(e))
481
+
482
+
483
+ @router.get("/redis/command-stats/{namespace}")
484
+ async def get_redis_command_stats(request: Request, namespace: str):
485
+ """
486
+ 获取Redis命令统计
487
+
488
+ Args:
489
+ namespace: 命名空间
490
+ """
491
+ try:
492
+ app = request.app
493
+ if not app or not hasattr(app.state, 'namespace_data_access'):
494
+ raise HTTPException(status_code=500, detail="Namespace data access not initialized")
495
+
496
+ namespace_data_access = app.state.namespace_data_access
497
+ redis_monitor_service = RedisMonitorService(namespace_data_access)
498
+ return await redis_monitor_service.get_command_stats(namespace)
499
+ except Exception as e:
500
+ logger.error(f"获取Redis命令统计失败: {e}")
501
+ raise HTTPException(status_code=500, detail=str(e))
502
+
503
+
504
+ @router.get("/redis/stream-stats/{namespace}")
505
+ async def get_redis_stream_stats(
506
+ request: Request,
507
+ namespace: str,
508
+ stream_name: Optional[str] = Query(None, description="Stream名称")
509
+ ):
510
+ """
511
+ 获取Redis Stream统计
512
+
513
+ Args:
514
+ namespace: 命名空间
515
+ stream_name: 可选,指定stream名称
516
+ """
517
+ try:
518
+ app = request.app
519
+ if not app or not hasattr(app.state, 'namespace_data_access'):
520
+ raise HTTPException(status_code=500, detail="Namespace data access not initialized")
521
+
522
+ namespace_data_access = app.state.namespace_data_access
523
+ redis_monitor_service = RedisMonitorService(namespace_data_access)
524
+ return await redis_monitor_service.get_stream_stats(namespace, stream_name)
525
+ except Exception as e:
526
+ logger.error(f"获取Redis Stream统计失败: {e}")
527
+ raise HTTPException(status_code=500, detail=str(e))
528
+
529
+
530
+ __all__ = ['router']