jettask 0.2.5__py3-none-any.whl → 0.2.6__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 (93) hide show
  1. jettask/monitor/run_backlog_collector.py +96 -0
  2. jettask/monitor/stream_backlog_monitor.py +362 -0
  3. jettask/pg_consumer/pg_consumer_v2.py +403 -0
  4. jettask/pg_consumer/sql_utils.py +182 -0
  5. jettask/scheduler/__init__.py +17 -0
  6. jettask/scheduler/add_execution_count.sql +11 -0
  7. jettask/scheduler/add_priority_field.sql +26 -0
  8. jettask/scheduler/add_scheduler_id.sql +25 -0
  9. jettask/scheduler/add_scheduler_id_index.sql +10 -0
  10. jettask/scheduler/loader.py +249 -0
  11. jettask/scheduler/make_scheduler_id_required.sql +28 -0
  12. jettask/scheduler/manager.py +696 -0
  13. jettask/scheduler/migrate_interval_seconds.sql +9 -0
  14. jettask/scheduler/models.py +200 -0
  15. jettask/scheduler/multi_namespace_scheduler.py +294 -0
  16. jettask/scheduler/performance_optimization.sql +45 -0
  17. jettask/scheduler/run_scheduler.py +186 -0
  18. jettask/scheduler/scheduler.py +715 -0
  19. jettask/scheduler/schema.sql +84 -0
  20. jettask/scheduler/unified_manager.py +450 -0
  21. jettask/scheduler/unified_scheduler_manager.py +280 -0
  22. jettask/webui/backend/api/__init__.py +3 -0
  23. jettask/webui/backend/api/v1/__init__.py +17 -0
  24. jettask/webui/backend/api/v1/monitoring.py +431 -0
  25. jettask/webui/backend/api/v1/namespaces.py +504 -0
  26. jettask/webui/backend/api/v1/queues.py +342 -0
  27. jettask/webui/backend/api/v1/tasks.py +367 -0
  28. jettask/webui/backend/core/__init__.py +3 -0
  29. jettask/webui/backend/core/cache.py +221 -0
  30. jettask/webui/backend/core/database.py +200 -0
  31. jettask/webui/backend/core/exceptions.py +102 -0
  32. jettask/webui/backend/models/__init__.py +3 -0
  33. jettask/webui/backend/models/requests.py +236 -0
  34. jettask/webui/backend/models/responses.py +230 -0
  35. jettask/webui/backend/services/__init__.py +3 -0
  36. jettask/webui/frontend/index.html +13 -0
  37. jettask/webui/models/__init__.py +3 -0
  38. jettask/webui/models/namespace.py +63 -0
  39. jettask/webui/sql/batch_upsert_functions.sql +178 -0
  40. jettask/webui/sql/init_database.sql +640 -0
  41. {jettask-0.2.5.dist-info → jettask-0.2.6.dist-info}/METADATA +11 -9
  42. {jettask-0.2.5.dist-info → jettask-0.2.6.dist-info}/RECORD +46 -53
  43. jettask/webui/frontend/package-lock.json +0 -4833
  44. jettask/webui/frontend/package.json +0 -30
  45. jettask/webui/frontend/src/App.css +0 -109
  46. jettask/webui/frontend/src/App.jsx +0 -66
  47. jettask/webui/frontend/src/components/NamespaceSelector.jsx +0 -166
  48. jettask/webui/frontend/src/components/QueueBacklogChart.jsx +0 -298
  49. jettask/webui/frontend/src/components/QueueBacklogTrend.jsx +0 -638
  50. jettask/webui/frontend/src/components/QueueDetailsTable.css +0 -65
  51. jettask/webui/frontend/src/components/QueueDetailsTable.jsx +0 -487
  52. jettask/webui/frontend/src/components/QueueDetailsTableV2.jsx +0 -465
  53. jettask/webui/frontend/src/components/ScheduledTaskFilter.jsx +0 -423
  54. jettask/webui/frontend/src/components/TaskFilter.jsx +0 -425
  55. jettask/webui/frontend/src/components/TimeRangeSelector.css +0 -21
  56. jettask/webui/frontend/src/components/TimeRangeSelector.jsx +0 -160
  57. jettask/webui/frontend/src/components/charts/QueueChart.jsx +0 -111
  58. jettask/webui/frontend/src/components/charts/QueueTrendChart.jsx +0 -115
  59. jettask/webui/frontend/src/components/charts/WorkerChart.jsx +0 -40
  60. jettask/webui/frontend/src/components/common/StatsCard.jsx +0 -18
  61. jettask/webui/frontend/src/components/layout/AppLayout.css +0 -95
  62. jettask/webui/frontend/src/components/layout/AppLayout.jsx +0 -49
  63. jettask/webui/frontend/src/components/layout/Header.css +0 -106
  64. jettask/webui/frontend/src/components/layout/Header.jsx +0 -106
  65. jettask/webui/frontend/src/components/layout/SideMenu.css +0 -137
  66. jettask/webui/frontend/src/components/layout/SideMenu.jsx +0 -209
  67. jettask/webui/frontend/src/components/layout/TabsNav.css +0 -244
  68. jettask/webui/frontend/src/components/layout/TabsNav.jsx +0 -206
  69. jettask/webui/frontend/src/components/layout/UserInfo.css +0 -197
  70. jettask/webui/frontend/src/components/layout/UserInfo.jsx +0 -197
  71. jettask/webui/frontend/src/contexts/LoadingContext.jsx +0 -27
  72. jettask/webui/frontend/src/contexts/NamespaceContext.jsx +0 -72
  73. jettask/webui/frontend/src/contexts/TabsContext.backup.jsx +0 -245
  74. jettask/webui/frontend/src/index.css +0 -114
  75. jettask/webui/frontend/src/main.jsx +0 -20
  76. jettask/webui/frontend/src/pages/Alerts.jsx +0 -684
  77. jettask/webui/frontend/src/pages/Dashboard/index.css +0 -35
  78. jettask/webui/frontend/src/pages/Dashboard/index.jsx +0 -281
  79. jettask/webui/frontend/src/pages/Dashboard.jsx +0 -1330
  80. jettask/webui/frontend/src/pages/QueueDetail.jsx +0 -1117
  81. jettask/webui/frontend/src/pages/QueueMonitor.jsx +0 -527
  82. jettask/webui/frontend/src/pages/Queues.jsx +0 -12
  83. jettask/webui/frontend/src/pages/ScheduledTasks.jsx +0 -809
  84. jettask/webui/frontend/src/pages/Settings.jsx +0 -800
  85. jettask/webui/frontend/src/pages/Workers.jsx +0 -12
  86. jettask/webui/frontend/src/services/api.js +0 -114
  87. jettask/webui/frontend/src/services/queueTrend.js +0 -152
  88. jettask/webui/frontend/src/utils/suppressWarnings.js +0 -22
  89. jettask/webui/frontend/src/utils/userPreferences.js +0 -154
  90. {jettask-0.2.5.dist-info → jettask-0.2.6.dist-info}/WHEEL +0 -0
  91. {jettask-0.2.5.dist-info → jettask-0.2.6.dist-info}/entry_points.txt +0 -0
  92. {jettask-0.2.5.dist-info → jettask-0.2.6.dist-info}/licenses/LICENSE +0 -0
  93. {jettask-0.2.5.dist-info → jettask-0.2.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,200 @@
1
+ """
2
+ Database connection management and utilities
3
+ """
4
+ import asyncio
5
+ from contextlib import asynccontextmanager
6
+ from typing import Optional, Dict, Any, AsyncGenerator
7
+ import asyncpg
8
+ import redis.asyncio as redis
9
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
10
+ from sqlalchemy.orm import DeclarativeBase
11
+ import logging
12
+
13
+ from .exceptions import DatabaseConnectionError
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class DatabaseManager:
19
+ """数据库连接管理器"""
20
+
21
+ def __init__(self):
22
+ self._pg_pools: Dict[str, asyncpg.Pool] = {}
23
+ self._redis_pools: Dict[str, redis.ConnectionPool] = {}
24
+ self._sqlalchemy_engines: Dict[str, Any] = {}
25
+ self._session_makers: Dict[str, async_sessionmaker] = {}
26
+
27
+ async def get_pg_pool(self, connection_string: str, pool_name: str = "default") -> asyncpg.Pool:
28
+ """获取PostgreSQL连接池"""
29
+ if pool_name not in self._pg_pools:
30
+ try:
31
+ # 解析连接字符串
32
+ if connection_string.startswith('postgresql://'):
33
+ connection_string = connection_string.replace('postgresql://', '')
34
+ elif connection_string.startswith('postgresql+asyncpg://'):
35
+ connection_string = connection_string.replace('postgresql+asyncpg://', '')
36
+
37
+ # 分离认证和主机信息
38
+ auth, host_info = connection_string.split('@')
39
+ user, password = auth.split(':')
40
+ host_port, database = host_info.split('/')
41
+ host, port = host_port.split(':')
42
+
43
+ pool = await asyncpg.create_pool(
44
+ host=host,
45
+ port=int(port),
46
+ user=user,
47
+ password=password,
48
+ database=database,
49
+ min_size=2,
50
+ max_size=10,
51
+ command_timeout=30
52
+ )
53
+
54
+ self._pg_pools[pool_name] = pool
55
+ logger.info(f"PostgreSQL连接池已创建: {pool_name}")
56
+
57
+ except Exception as e:
58
+ logger.error(f"创建PostgreSQL连接池失败: {e}")
59
+ raise DatabaseConnectionError(f"PostgreSQL connection failed: {e}")
60
+
61
+ return self._pg_pools[pool_name]
62
+
63
+ async def get_redis_client(self, connection_string: str, pool_name: str = "default") -> redis.Redis:
64
+ """获取Redis客户端"""
65
+ if pool_name not in self._redis_pools:
66
+ try:
67
+ pool = redis.ConnectionPool.from_url(
68
+ connection_string,
69
+ max_connections=20,
70
+ decode_responses=False # 保持原始字节格式
71
+ )
72
+ self._redis_pools[pool_name] = pool
73
+ logger.info(f"Redis连接池已创建: {pool_name}")
74
+
75
+ except Exception as e:
76
+ logger.error(f"创建Redis连接池失败: {e}")
77
+ raise DatabaseConnectionError(f"Redis connection failed: {e}")
78
+
79
+ return redis.Redis(connection_pool=self._redis_pools[pool_name])
80
+
81
+ def get_sqlalchemy_session_maker(
82
+ self,
83
+ connection_string: str,
84
+ pool_name: str = "default"
85
+ ) -> async_sessionmaker[AsyncSession]:
86
+ """获取SQLAlchemy会话工厂"""
87
+ if pool_name not in self._session_makers:
88
+ try:
89
+ # 确保使用asyncpg驱动
90
+ if connection_string.startswith('postgresql://'):
91
+ connection_string = connection_string.replace('postgresql://', 'postgresql+asyncpg://')
92
+
93
+ engine = create_async_engine(
94
+ connection_string,
95
+ pool_size=5,
96
+ max_overflow=10,
97
+ pool_timeout=30,
98
+ pool_recycle=3600,
99
+ echo=False # 生产环境关闭SQL日志
100
+ )
101
+
102
+ session_maker = async_sessionmaker(
103
+ engine,
104
+ class_=AsyncSession,
105
+ expire_on_commit=False
106
+ )
107
+
108
+ self._sqlalchemy_engines[pool_name] = engine
109
+ self._session_makers[pool_name] = session_maker
110
+ logger.info(f"SQLAlchemy会话工厂已创建: {pool_name}")
111
+
112
+ except Exception as e:
113
+ logger.error(f"创建SQLAlchemy会话工厂失败: {e}")
114
+ raise DatabaseConnectionError(f"SQLAlchemy connection failed: {e}")
115
+
116
+ return self._session_makers[pool_name]
117
+
118
+ @asynccontextmanager
119
+ async def get_pg_connection(self, pool_name: str = "default") -> AsyncGenerator[asyncpg.Connection, None]:
120
+ """获取PostgreSQL连接上下文管理器"""
121
+ if pool_name not in self._pg_pools:
122
+ raise DatabaseConnectionError(f"PostgreSQL pool '{pool_name}' not found")
123
+
124
+ pool = self._pg_pools[pool_name]
125
+ async with pool.acquire() as conn:
126
+ yield conn
127
+
128
+ @asynccontextmanager
129
+ async def get_sqlalchemy_session(self, pool_name: str = "default") -> AsyncGenerator[AsyncSession, None]:
130
+ """获取SQLAlchemy会话上下文管理器"""
131
+ if pool_name not in self._session_makers:
132
+ raise DatabaseConnectionError(f"SQLAlchemy session maker '{pool_name}' not found")
133
+
134
+ session_maker = self._session_makers[pool_name]
135
+ async with session_maker() as session:
136
+ try:
137
+ yield session
138
+ await session.commit()
139
+ except Exception:
140
+ await session.rollback()
141
+ raise
142
+ finally:
143
+ await session.close()
144
+
145
+ async def close_all(self):
146
+ """关闭所有连接"""
147
+ # 关闭PostgreSQL连接池
148
+ for name, pool in self._pg_pools.items():
149
+ try:
150
+ await pool.close()
151
+ logger.info(f"PostgreSQL连接池已关闭: {name}")
152
+ except Exception as e:
153
+ logger.error(f"关闭PostgreSQL连接池失败 {name}: {e}")
154
+
155
+ # 关闭Redis连接池
156
+ for name, pool in self._redis_pools.items():
157
+ try:
158
+ await pool.disconnect()
159
+ logger.info(f"Redis连接池已关闭: {name}")
160
+ except Exception as e:
161
+ logger.error(f"关闭Redis连接池失败 {name}: {e}")
162
+
163
+ # 关闭SQLAlchemy引擎
164
+ for name, engine in self._sqlalchemy_engines.items():
165
+ try:
166
+ await engine.dispose()
167
+ logger.info(f"SQLAlchemy引擎已关闭: {name}")
168
+ except Exception as e:
169
+ logger.error(f"关闭SQLAlchemy引擎失败 {name}: {e}")
170
+
171
+ # 清空缓存
172
+ self._pg_pools.clear()
173
+ self._redis_pools.clear()
174
+ self._sqlalchemy_engines.clear()
175
+ self._session_makers.clear()
176
+
177
+
178
+ # 全局数据库管理器实例
179
+ db_manager = DatabaseManager()
180
+
181
+
182
+ # 数据库模型基类
183
+ class Base(DeclarativeBase):
184
+ pass
185
+
186
+
187
+ # 便捷函数
188
+ async def get_pg_pool(connection_string: str, pool_name: str = "default") -> asyncpg.Pool:
189
+ """便捷函数:获取PostgreSQL连接池"""
190
+ return await db_manager.get_pg_pool(connection_string, pool_name)
191
+
192
+
193
+ async def get_redis_client(connection_string: str, pool_name: str = "default") -> redis.Redis:
194
+ """便捷函数:获取Redis客户端"""
195
+ return await db_manager.get_redis_client(connection_string, pool_name)
196
+
197
+
198
+ def get_session_maker(connection_string: str, pool_name: str = "default") -> async_sessionmaker[AsyncSession]:
199
+ """便捷函数:获取SQLAlchemy会话工厂"""
200
+ return db_manager.get_sqlalchemy_session_maker(connection_string, pool_name)
@@ -0,0 +1,102 @@
1
+ """
2
+ Custom exceptions for JetTask WebUI Backend
3
+ """
4
+ from typing import Optional, Dict, Any
5
+ from fastapi import HTTPException
6
+
7
+
8
+ class JetTaskAPIException(HTTPException):
9
+ """Base exception for JetTask API"""
10
+
11
+ def __init__(
12
+ self,
13
+ status_code: int,
14
+ detail: str,
15
+ error_code: Optional[str] = None,
16
+ extra_data: Optional[Dict[str, Any]] = None
17
+ ):
18
+ super().__init__(status_code=status_code, detail=detail)
19
+ self.error_code = error_code
20
+ self.extra_data = extra_data or {}
21
+
22
+
23
+ class NamespaceNotFoundError(JetTaskAPIException):
24
+ """Namespace not found error"""
25
+
26
+ def __init__(self, namespace: str):
27
+ super().__init__(
28
+ status_code=404,
29
+ detail=f"Namespace '{namespace}' not found",
30
+ error_code="NAMESPACE_NOT_FOUND",
31
+ extra_data={"namespace": namespace}
32
+ )
33
+
34
+
35
+ class QueueNotFoundError(JetTaskAPIException):
36
+ """Queue not found error"""
37
+
38
+ def __init__(self, queue_name: str, namespace: str = "default"):
39
+ super().__init__(
40
+ status_code=404,
41
+ detail=f"Queue '{queue_name}' not found in namespace '{namespace}'",
42
+ error_code="QUEUE_NOT_FOUND",
43
+ extra_data={"queue_name": queue_name, "namespace": namespace}
44
+ )
45
+
46
+
47
+ class TaskNotFoundError(JetTaskAPIException):
48
+ """Task not found error"""
49
+
50
+ def __init__(self, task_id: str):
51
+ super().__init__(
52
+ status_code=404,
53
+ detail=f"Task '{task_id}' not found",
54
+ error_code="TASK_NOT_FOUND",
55
+ extra_data={"task_id": task_id}
56
+ )
57
+
58
+
59
+ class ValidationError(JetTaskAPIException):
60
+ """Validation error"""
61
+
62
+ def __init__(self, message: str, field: Optional[str] = None):
63
+ super().__init__(
64
+ status_code=400,
65
+ detail=message,
66
+ error_code="VALIDATION_ERROR",
67
+ extra_data={"field": field} if field else {}
68
+ )
69
+
70
+
71
+ class DatabaseConnectionError(JetTaskAPIException):
72
+ """Database connection error"""
73
+
74
+ def __init__(self, message: str = "Database connection failed"):
75
+ super().__init__(
76
+ status_code=503,
77
+ detail=message,
78
+ error_code="DATABASE_CONNECTION_ERROR"
79
+ )
80
+
81
+
82
+ class RateLimitError(JetTaskAPIException):
83
+ """Rate limit exceeded error"""
84
+
85
+ def __init__(self, limit: int, window: int):
86
+ super().__init__(
87
+ status_code=429,
88
+ detail=f"Rate limit exceeded: {limit} requests per {window} seconds",
89
+ error_code="RATE_LIMIT_EXCEEDED",
90
+ extra_data={"limit": limit, "window": window}
91
+ )
92
+
93
+
94
+ class InternalServerError(JetTaskAPIException):
95
+ """Internal server error"""
96
+
97
+ def __init__(self, message: str = "Internal server error"):
98
+ super().__init__(
99
+ status_code=500,
100
+ detail=message,
101
+ error_code="INTERNAL_SERVER_ERROR"
102
+ )
@@ -0,0 +1,3 @@
1
+ """
2
+ Data models for JetTask WebUI Backend
3
+ """
@@ -0,0 +1,236 @@
1
+ """
2
+ Request models for JetTask WebUI Backend
3
+ """
4
+ from typing import List, Dict, Any, Optional, Union
5
+ from datetime import datetime
6
+ from pydantic import BaseModel, Field, validator
7
+ from enum import Enum
8
+
9
+
10
+ class TimeRange(str, Enum):
11
+ """时间范围枚举"""
12
+ FIFTEEN_MINUTES = "15m"
13
+ THIRTY_MINUTES = "30m"
14
+ ONE_HOUR = "1h"
15
+ THREE_HOURS = "3h"
16
+ SIX_HOURS = "6h"
17
+ TWELVE_HOURS = "12h"
18
+ ONE_DAY = "24h"
19
+ THREE_DAYS = "3d"
20
+ ONE_WEEK = "7d"
21
+ ONE_MONTH = "30d"
22
+
23
+
24
+ class SortOrder(str, Enum):
25
+ """排序方向"""
26
+ ASC = "asc"
27
+ DESC = "desc"
28
+
29
+
30
+ class FilterOperator(str, Enum):
31
+ """筛选操作符"""
32
+ EQ = "eq" # 等于
33
+ NE = "ne" # 不等于
34
+ GT = "gt" # 大于
35
+ GTE = "gte" # 大于等于
36
+ LT = "lt" # 小于
37
+ LTE = "lte" # 小于等于
38
+ IN = "in" # 包含
39
+ NOT_IN = "not_in" # 不包含
40
+ LIKE = "like" # 模糊匹配
41
+ REGEX = "regex" # 正则表达式
42
+ IS_NULL = "is_null" # 为空
43
+ IS_NOT_NULL = "is_not_null" # 不为空
44
+
45
+
46
+ class FilterCondition(BaseModel):
47
+ """筛选条件"""
48
+ field: str = Field(description="字段名")
49
+ operator: FilterOperator = Field(description="操作符")
50
+ value: Optional[Any] = Field(default=None, description="筛选值")
51
+
52
+ @validator('value')
53
+ def validate_value(cls, v, values):
54
+ operator = values.get('operator')
55
+ if operator in [FilterOperator.IS_NULL, FilterOperator.IS_NOT_NULL]:
56
+ return None
57
+ if operator in [FilterOperator.IN, FilterOperator.NOT_IN] and not isinstance(v, list):
58
+ raise ValueError(f"Operator {operator} requires a list value")
59
+ return v
60
+
61
+
62
+ class BaseListRequest(BaseModel):
63
+ """基础列表查询请求"""
64
+ page: int = Field(default=1, ge=1, description="页码")
65
+ page_size: int = Field(default=20, ge=1, le=100, description="每页大小")
66
+ sort_field: Optional[str] = Field(default=None, description="排序字段")
67
+ sort_order: SortOrder = Field(default=SortOrder.DESC, description="排序方向")
68
+ filters: List[FilterCondition] = Field(default_factory=list, description="筛选条件")
69
+ search: Optional[str] = Field(default=None, description="搜索关键词")
70
+
71
+
72
+ class TimeRangeRequest(BaseModel):
73
+ """时间范围查询请求"""
74
+ start_time: Optional[datetime] = Field(default=None, description="开始时间")
75
+ end_time: Optional[datetime] = Field(default=None, description="结束时间")
76
+ time_range: Optional[TimeRange] = Field(default=None, description="预设时间范围")
77
+ granularity: Optional[str] = Field(default=None, description="数据粒度")
78
+
79
+ @validator('end_time')
80
+ def validate_time_range(cls, v, values):
81
+ start_time = values.get('start_time')
82
+ if start_time and v and start_time >= v:
83
+ raise ValueError("end_time must be after start_time")
84
+ return v
85
+
86
+
87
+ # 队列相关请求模型
88
+ class QueueListRequest(BaseListRequest):
89
+ """队列列表查询请求"""
90
+ namespace: str = Field(default="default", description="命名空间")
91
+ status: Optional[str] = Field(default=None, description="队列状态筛选")
92
+ include_stats: bool = Field(default=True, description="是否包含统计信息")
93
+
94
+
95
+ class QueueMetricsRequest(TimeRangeRequest):
96
+ """队列指标查询请求"""
97
+ namespace: str = Field(default="default", description="命名空间")
98
+ queue_name: str = Field(description="队列名称")
99
+ metrics: List[str] = Field(default_factory=lambda: ["pending", "processing", "completed"], description="指标类型")
100
+ include_consumer_groups: bool = Field(default=False, description="是否包含消费者组数据")
101
+
102
+
103
+ class QueueActionRequest(BaseModel):
104
+ """队列操作请求"""
105
+ action: str = Field(description="操作类型")
106
+ parameters: Dict[str, Any] = Field(default_factory=dict, description="操作参数")
107
+
108
+
109
+ # 任务相关请求模型
110
+ class TaskListRequest(BaseListRequest, TimeRangeRequest):
111
+ """任务列表查询请求"""
112
+ namespace: str = Field(default="default", description="命名空间")
113
+ queue_name: Optional[str] = Field(default=None, description="队列名称")
114
+ status: Optional[str] = Field(default=None, description="任务状态")
115
+ consumer_group: Optional[str] = Field(default=None, description="消费者组")
116
+ worker_id: Optional[str] = Field(default=None, description="工作者ID")
117
+ task_name: Optional[str] = Field(default=None, description="任务名称")
118
+
119
+
120
+ class TaskActionRequest(BaseModel):
121
+ """任务操作请求"""
122
+ task_ids: List[str] = Field(description="任务ID列表")
123
+ action: str = Field(description="操作类型")
124
+ parameters: Dict[str, Any] = Field(default_factory=dict, description="操作参数")
125
+
126
+
127
+ # 监控相关请求模型
128
+ class MonitoringRequest(TimeRangeRequest):
129
+ """监控数据请求"""
130
+ namespace: str = Field(default="default", description="命名空间")
131
+ queues: Optional[List[str]] = Field(default=None, description="队列列表")
132
+ metrics: List[str] = Field(default_factory=list, description="指标类型")
133
+ include_groups: bool = Field(default=False, description="是否包含消费者组")
134
+
135
+
136
+ class BacklogTrendRequest(TimeRangeRequest):
137
+ """队列积压趋势请求"""
138
+ namespace: str = Field(default="default", description="命名空间")
139
+ queues: Optional[List[str]] = Field(default=None, description="队列列表")
140
+ include_groups: bool = Field(default=False, description="是否包含消费者组级别数据")
141
+
142
+
143
+ # 分析相关请求模型
144
+ class AnalyticsRequest(TimeRangeRequest):
145
+ """分析查询请求"""
146
+ namespace: str = Field(default="default", description="命名空间")
147
+ analysis_type: str = Field(description="分析类型")
148
+ dimensions: List[str] = Field(default_factory=list, description="分析维度")
149
+ metrics: List[str] = Field(default_factory=list, description="分析指标")
150
+ filters: List[FilterCondition] = Field(default_factory=list, description="筛选条件")
151
+
152
+
153
+ # 定时任务相关请求模型
154
+ class ScheduleConfig(BaseModel):
155
+ """调度配置"""
156
+ cron_expression: Optional[str] = Field(default=None, description="Cron表达式")
157
+ interval_seconds: Optional[int] = Field(default=None, description="间隔秒数")
158
+ interval_minutes: Optional[int] = Field(default=None, description="间隔分钟数")
159
+ interval_hours: Optional[int] = Field(default=None, description="间隔小时数")
160
+ start_date: Optional[datetime] = Field(default=None, description="开始时间")
161
+ end_date: Optional[datetime] = Field(default=None, description="结束时间")
162
+ max_runs: Optional[int] = Field(default=None, description="最大运行次数")
163
+
164
+ @validator('interval_seconds')
165
+ def validate_interval_seconds(cls, v):
166
+ if v is not None and v < 1:
167
+ raise ValueError("interval_seconds must be at least 1")
168
+ return v
169
+
170
+
171
+ class ScheduledTaskCreateRequest(BaseModel):
172
+ """创建定时任务请求"""
173
+ name: str = Field(description="任务名称")
174
+ namespace: str = Field(default="default", description="命名空间")
175
+ queue_name: str = Field(description="队列名称")
176
+ task_name: str = Field(description="任务函数名")
177
+ task_data: Dict[str, Any] = Field(default_factory=dict, description="任务数据")
178
+ schedule_type: str = Field(description="调度类型")
179
+ schedule_config: ScheduleConfig = Field(description="调度配置")
180
+ is_active: bool = Field(default=True, description="是否启用")
181
+ description: Optional[str] = Field(default=None, description="任务描述")
182
+ max_retry: int = Field(default=3, ge=0, description="最大重试次数")
183
+ timeout: Optional[int] = Field(default=None, ge=1, description="超时时间(秒)")
184
+
185
+
186
+ class ScheduledTaskUpdateRequest(ScheduledTaskCreateRequest):
187
+ """更新定时任务请求"""
188
+ pass
189
+
190
+
191
+ class ScheduledTaskListRequest(BaseListRequest):
192
+ """定时任务列表查询请求"""
193
+ namespace: str = Field(default="default", description="命名空间")
194
+ is_active: Optional[bool] = Field(default=None, description="是否启用")
195
+ schedule_type: Optional[str] = Field(default=None, description="调度类型")
196
+
197
+
198
+ # 命名空间相关请求模型
199
+ class NamespaceCreateRequest(BaseModel):
200
+ """创建命名空间请求"""
201
+ name: str = Field(description="命名空间名称", pattern="^[a-zA-Z0-9_-]+$")
202
+ display_name: str = Field(description="显示名称")
203
+ description: Optional[str] = Field(default=None, description="描述")
204
+ redis_url: str = Field(description="Redis连接URL")
205
+ pg_url: Optional[str] = Field(default=None, description="PostgreSQL连接URL")
206
+ redis_prefix: str = Field(default="jettask", description="Redis键前缀")
207
+
208
+
209
+ class NamespaceUpdateRequest(BaseModel):
210
+ """更新命名空间请求"""
211
+ display_name: Optional[str] = Field(default=None, description="显示名称")
212
+ description: Optional[str] = Field(default=None, description="描述")
213
+ redis_url: Optional[str] = Field(default=None, description="Redis连接URL")
214
+ pg_url: Optional[str] = Field(default=None, description="PostgreSQL连接URL")
215
+ is_active: Optional[bool] = Field(default=None, description="是否启用")
216
+
217
+
218
+ class NamespaceListRequest(BaseListRequest):
219
+ """命名空间列表查询请求"""
220
+ is_active: Optional[bool] = Field(default=None, description="是否启用")
221
+
222
+
223
+ # 系统管理相关请求模型
224
+ class SystemConfigUpdateRequest(BaseModel):
225
+ """系统配置更新请求"""
226
+ config_key: str = Field(description="配置键")
227
+ config_value: Any = Field(description="配置值")
228
+ description: Optional[str] = Field(default=None, description="配置描述")
229
+
230
+
231
+ class BatchOperationRequest(BaseModel):
232
+ """批量操作请求"""
233
+ operation: str = Field(description="操作类型")
234
+ target_type: str = Field(description="目标类型")
235
+ target_ids: List[str] = Field(description="目标ID列表")
236
+ parameters: Dict[str, Any] = Field(default_factory=dict, description="操作参数")