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,758 @@
|
|
|
1
|
+
"""
|
|
2
|
+
设置服务
|
|
3
|
+
提供系统设置、配置管理等功能
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Optional, Dict, Any
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from sqlalchemy import text
|
|
10
|
+
from urllib.parse import urlparse
|
|
11
|
+
|
|
12
|
+
from jettask.core.db_manager import get_db_manager
|
|
13
|
+
from jettask.schemas import (
|
|
14
|
+
ConfigMode,
|
|
15
|
+
NamespaceCreate,
|
|
16
|
+
NamespaceUpdate,
|
|
17
|
+
NamespaceResponse
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SettingsService:
|
|
24
|
+
"""设置服务类"""
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def validate_redis_url(redis_url: str) -> bool:
|
|
28
|
+
"""
|
|
29
|
+
验证 Redis URL 格式
|
|
30
|
+
格式: redis://[password@]host:port/db
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
parsed = urlparse(redis_url)
|
|
34
|
+
return parsed.scheme in ['redis', 'rediss']
|
|
35
|
+
except:
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def validate_pg_url(pg_url: str) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
验证 PostgreSQL URL 格式
|
|
42
|
+
支持格式:
|
|
43
|
+
- postgresql://user:password@host:port/database
|
|
44
|
+
- postgresql+asyncpg://user:password@host:port/database
|
|
45
|
+
- postgres://user:password@host:port/database
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
parsed = urlparse(pg_url)
|
|
49
|
+
return parsed.scheme in ['postgresql', 'postgres', 'postgresql+asyncpg']
|
|
50
|
+
except:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def mask_url_password(url: str) -> str:
|
|
55
|
+
"""
|
|
56
|
+
将URL中的密码部分替换为星号
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
parsed = urlparse(url)
|
|
60
|
+
if parsed.password:
|
|
61
|
+
# 构建脱敏后的URL
|
|
62
|
+
if parsed.username:
|
|
63
|
+
netloc = f"{parsed.username}:***@{parsed.hostname}"
|
|
64
|
+
else:
|
|
65
|
+
netloc = f"***@{parsed.hostname}"
|
|
66
|
+
|
|
67
|
+
if parsed.port:
|
|
68
|
+
netloc += f":{parsed.port}"
|
|
69
|
+
|
|
70
|
+
masked_url = f"{parsed.scheme}://{netloc}{parsed.path}"
|
|
71
|
+
if parsed.query:
|
|
72
|
+
masked_url += f"?{parsed.query}"
|
|
73
|
+
if parsed.fragment:
|
|
74
|
+
masked_url += f"#{parsed.fragment}"
|
|
75
|
+
|
|
76
|
+
return masked_url
|
|
77
|
+
return url
|
|
78
|
+
except:
|
|
79
|
+
return url
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
async def get_config_from_nacos(key: str) -> str:
|
|
83
|
+
"""
|
|
84
|
+
从 Nacos 获取配置值(应该返回URL格式的字符串)
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
from jettask.config.nacos_config import config
|
|
88
|
+
value = config.config.get(key)
|
|
89
|
+
if not value:
|
|
90
|
+
raise ValueError(f"Nacos配置键 '{key}' 不存在或为空")
|
|
91
|
+
return value
|
|
92
|
+
except ImportError:
|
|
93
|
+
raise ValueError("无法加载Nacos配置模块")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
raise ValueError(f"从Nacos获取配置失败: {str(e)}")
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
async def list_namespaces(
|
|
99
|
+
page: int = 1,
|
|
100
|
+
page_size: int = 20,
|
|
101
|
+
is_active: Optional[bool] = None
|
|
102
|
+
) -> List[NamespaceResponse]:
|
|
103
|
+
"""
|
|
104
|
+
列出所有命名空间
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
page: 页码(从1开始)
|
|
108
|
+
page_size: 每页数量
|
|
109
|
+
is_active: 是否只返回激活的命名空间
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
命名空间列表
|
|
113
|
+
"""
|
|
114
|
+
db_manager = get_db_manager()
|
|
115
|
+
async with db_manager.get_master_session() as session:
|
|
116
|
+
query = """
|
|
117
|
+
SELECT id, name, description, redis_config, pg_config,
|
|
118
|
+
is_active, version, created_at, updated_at
|
|
119
|
+
FROM namespaces
|
|
120
|
+
"""
|
|
121
|
+
params = {}
|
|
122
|
+
|
|
123
|
+
if is_active is not None:
|
|
124
|
+
query += " WHERE is_active = :is_active"
|
|
125
|
+
params['is_active'] = is_active
|
|
126
|
+
|
|
127
|
+
query += " ORDER BY created_at DESC"
|
|
128
|
+
query += " LIMIT :limit OFFSET :offset"
|
|
129
|
+
params['limit'] = page_size
|
|
130
|
+
params['offset'] = (page - 1) * page_size
|
|
131
|
+
|
|
132
|
+
result = await session.execute(text(query), params)
|
|
133
|
+
rows = result.fetchall()
|
|
134
|
+
|
|
135
|
+
namespaces = []
|
|
136
|
+
for row in rows:
|
|
137
|
+
# 构建响应
|
|
138
|
+
redis_config_dict = row.redis_config if row.redis_config else {}
|
|
139
|
+
pg_config_dict = row.pg_config if row.pg_config else {}
|
|
140
|
+
|
|
141
|
+
# 根据配置模式获取实际的URL
|
|
142
|
+
config_mode = redis_config_dict.get('config_mode', 'direct')
|
|
143
|
+
|
|
144
|
+
# 获取Redis URL
|
|
145
|
+
if config_mode == 'nacos' and redis_config_dict.get('nacos_key'):
|
|
146
|
+
try:
|
|
147
|
+
redis_url = await SettingsService.get_config_from_nacos(redis_config_dict['nacos_key'])
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(f"从Nacos获取Redis URL失败: {e} {row.name}")
|
|
150
|
+
redis_url = redis_config_dict.get('url', '')
|
|
151
|
+
else:
|
|
152
|
+
redis_url = redis_config_dict.get('url', '')
|
|
153
|
+
|
|
154
|
+
# 获取PostgreSQL URL
|
|
155
|
+
pg_url = None
|
|
156
|
+
if config_mode == 'nacos' and pg_config_dict.get('nacos_key'):
|
|
157
|
+
try:
|
|
158
|
+
pg_url = await SettingsService.get_config_from_nacos(pg_config_dict['nacos_key'])
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"从Nacos获取PostgreSQL URL失败: {e} {row.name}")
|
|
161
|
+
pg_url = pg_config_dict.get('url')
|
|
162
|
+
else:
|
|
163
|
+
pg_url = pg_config_dict.get('url')
|
|
164
|
+
|
|
165
|
+
response = NamespaceResponse(
|
|
166
|
+
name=row.name,
|
|
167
|
+
description=row.description,
|
|
168
|
+
redis_url=redis_url,
|
|
169
|
+
pg_url=pg_url,
|
|
170
|
+
connection_url=f"/api/v1/namespaces/{row.name}",
|
|
171
|
+
version=row.version or 1,
|
|
172
|
+
enabled=row.is_active,
|
|
173
|
+
created_at=row.created_at,
|
|
174
|
+
updated_at=row.updated_at
|
|
175
|
+
)
|
|
176
|
+
namespaces.append(response)
|
|
177
|
+
|
|
178
|
+
return namespaces
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
async def create_namespace(namespace: NamespaceCreate) -> NamespaceResponse:
|
|
182
|
+
"""
|
|
183
|
+
创建新的命名空间
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
namespace: 命名空间创建信息
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
创建的命名空间信息
|
|
190
|
+
"""
|
|
191
|
+
db_manager = get_db_manager()
|
|
192
|
+
async with db_manager.get_master_session() as session:
|
|
193
|
+
# 检查命名空间是否已存在
|
|
194
|
+
check_query = text("SELECT COUNT(*) FROM namespaces WHERE name = :name")
|
|
195
|
+
result = await session.execute(check_query, {'name': namespace.name})
|
|
196
|
+
if result.scalar() > 0:
|
|
197
|
+
raise ValueError(f"命名空间 '{namespace.name}' 已存在")
|
|
198
|
+
|
|
199
|
+
# 准备配置
|
|
200
|
+
redis_config = {'config_mode': namespace.config_mode.value}
|
|
201
|
+
pg_config = {'config_mode': namespace.config_mode.value}
|
|
202
|
+
|
|
203
|
+
# 根据配置模式处理
|
|
204
|
+
if namespace.config_mode == ConfigMode.DIRECT:
|
|
205
|
+
# 直接配置模式
|
|
206
|
+
if not namespace.redis_url:
|
|
207
|
+
raise ValueError("直接配置模式下,redis_url是必需的")
|
|
208
|
+
if namespace.redis_nacos_key:
|
|
209
|
+
raise ValueError("直接配置模式下不应提供redis_nacos_key")
|
|
210
|
+
|
|
211
|
+
# 验证URL格式
|
|
212
|
+
if not SettingsService.validate_redis_url(namespace.redis_url):
|
|
213
|
+
raise ValueError("无效的Redis URL格式")
|
|
214
|
+
|
|
215
|
+
redis_config['url'] = namespace.redis_url
|
|
216
|
+
|
|
217
|
+
# 处理PostgreSQL配置(可选)
|
|
218
|
+
if namespace.pg_url:
|
|
219
|
+
if not SettingsService.validate_pg_url(namespace.pg_url):
|
|
220
|
+
raise ValueError("无效的PostgreSQL URL格式")
|
|
221
|
+
pg_config['url'] = namespace.pg_url
|
|
222
|
+
if namespace.pg_nacos_key:
|
|
223
|
+
raise ValueError("直接配置模式下不应提供pg_nacos_key")
|
|
224
|
+
|
|
225
|
+
elif namespace.config_mode == ConfigMode.NACOS:
|
|
226
|
+
# Nacos配置模式
|
|
227
|
+
if not namespace.redis_nacos_key:
|
|
228
|
+
raise ValueError("Nacos配置模式下,redis_nacos_key是必需的")
|
|
229
|
+
if namespace.redis_url:
|
|
230
|
+
raise ValueError("Nacos配置模式下不应提供redis_url")
|
|
231
|
+
|
|
232
|
+
# 从Nacos获取Redis URL并验证
|
|
233
|
+
redis_url = await SettingsService.get_config_from_nacos(namespace.redis_nacos_key)
|
|
234
|
+
if not SettingsService.validate_redis_url(redis_url):
|
|
235
|
+
raise ValueError(f"从Nacos获取的Redis URL格式无效: {redis_url}")
|
|
236
|
+
|
|
237
|
+
redis_config['url'] = redis_url
|
|
238
|
+
redis_config['nacos_key'] = namespace.redis_nacos_key
|
|
239
|
+
|
|
240
|
+
# 从Nacos获取PostgreSQL URL(可选)
|
|
241
|
+
if namespace.pg_nacos_key:
|
|
242
|
+
pg_url = await SettingsService.get_config_from_nacos(namespace.pg_nacos_key)
|
|
243
|
+
if not SettingsService.validate_pg_url(pg_url):
|
|
244
|
+
raise ValueError(f"从Nacos获取的PostgreSQL URL格式无效: {pg_url}")
|
|
245
|
+
pg_config['url'] = pg_url
|
|
246
|
+
pg_config['nacos_key'] = namespace.pg_nacos_key
|
|
247
|
+
if namespace.pg_url:
|
|
248
|
+
raise ValueError("Nacos配置模式下不应提供pg_url")
|
|
249
|
+
|
|
250
|
+
# 创建命名空间
|
|
251
|
+
insert_query = text("""
|
|
252
|
+
INSERT INTO namespaces (name, description, redis_config, pg_config, version)
|
|
253
|
+
VALUES (:name, :description, :redis_config, :pg_config, 1)
|
|
254
|
+
RETURNING id, name, description, redis_config, pg_config,
|
|
255
|
+
is_active, version, created_at, updated_at
|
|
256
|
+
""")
|
|
257
|
+
|
|
258
|
+
result = await session.execute(insert_query, {
|
|
259
|
+
'name': namespace.name,
|
|
260
|
+
'description': namespace.description,
|
|
261
|
+
'redis_config': json.dumps(redis_config),
|
|
262
|
+
'pg_config': json.dumps(pg_config)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
row = result.fetchone()
|
|
266
|
+
await session.commit()
|
|
267
|
+
|
|
268
|
+
# 构建响应 - 只返回最终的URL,不返回配置细节
|
|
269
|
+
response = NamespaceResponse(
|
|
270
|
+
name=row.name,
|
|
271
|
+
description=row.description,
|
|
272
|
+
redis_url=redis_config['url'], # 这里已经是最终的URL
|
|
273
|
+
pg_url=pg_config.get('url', '') if pg_config.get('url') else None,
|
|
274
|
+
connection_url=f"/api/v1/namespaces/{row.name}",
|
|
275
|
+
version=row.version or 1,
|
|
276
|
+
enabled=row.is_active,
|
|
277
|
+
created_at=row.created_at,
|
|
278
|
+
updated_at=row.updated_at
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
logger.info(f"成功创建命名空间: {namespace.name}")
|
|
282
|
+
return response
|
|
283
|
+
|
|
284
|
+
@staticmethod
|
|
285
|
+
async def get_namespace(namespace_name: str) -> NamespaceResponse:
|
|
286
|
+
"""
|
|
287
|
+
获取指定命名空间的详细信息
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
namespace_name: 命名空间名称
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
命名空间信息
|
|
294
|
+
"""
|
|
295
|
+
db_manager = get_db_manager()
|
|
296
|
+
async with db_manager.get_master_session() as session:
|
|
297
|
+
query = text("""
|
|
298
|
+
SELECT id, name, description, redis_config, pg_config,
|
|
299
|
+
is_active, version, created_at, updated_at
|
|
300
|
+
FROM namespaces
|
|
301
|
+
WHERE name = :name
|
|
302
|
+
""")
|
|
303
|
+
|
|
304
|
+
result = await session.execute(query, {'name': namespace_name})
|
|
305
|
+
row = result.fetchone()
|
|
306
|
+
|
|
307
|
+
if not row:
|
|
308
|
+
raise ValueError(f"命名空间 '{namespace_name}' 不存在")
|
|
309
|
+
|
|
310
|
+
# 构建响应
|
|
311
|
+
redis_config_dict = row.redis_config if row.redis_config else {}
|
|
312
|
+
pg_config_dict = row.pg_config if row.pg_config else {}
|
|
313
|
+
|
|
314
|
+
# 根据配置模式获取实际的URL
|
|
315
|
+
config_mode = redis_config_dict.get('config_mode', 'direct')
|
|
316
|
+
|
|
317
|
+
# 获取Redis URL
|
|
318
|
+
if config_mode == 'nacos' and redis_config_dict.get('nacos_key'):
|
|
319
|
+
try:
|
|
320
|
+
redis_url = await SettingsService.get_config_from_nacos(redis_config_dict['nacos_key'])
|
|
321
|
+
except Exception as e:
|
|
322
|
+
logger.error(f"从Nacos获取Redis URL失败: {e}")
|
|
323
|
+
redis_url = redis_config_dict.get('url', '')
|
|
324
|
+
else:
|
|
325
|
+
redis_url = redis_config_dict.get('url', '')
|
|
326
|
+
|
|
327
|
+
# 获取PostgreSQL URL
|
|
328
|
+
pg_url = None
|
|
329
|
+
if config_mode == 'nacos' and pg_config_dict.get('nacos_key'):
|
|
330
|
+
try:
|
|
331
|
+
pg_url = await SettingsService.get_config_from_nacos(pg_config_dict['nacos_key'])
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.error(f"从Nacos获取PostgreSQL URL失败: {e}")
|
|
334
|
+
pg_url = pg_config_dict.get('url')
|
|
335
|
+
else:
|
|
336
|
+
pg_url = pg_config_dict.get('url')
|
|
337
|
+
|
|
338
|
+
response = NamespaceResponse(
|
|
339
|
+
name=row.name,
|
|
340
|
+
description=row.description,
|
|
341
|
+
redis_url=redis_url,
|
|
342
|
+
pg_url=pg_url,
|
|
343
|
+
connection_url=f"/api/v1/namespaces/{row.name}",
|
|
344
|
+
version=row.version or 1,
|
|
345
|
+
enabled=row.is_active,
|
|
346
|
+
created_at=row.created_at,
|
|
347
|
+
updated_at=row.updated_at
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return response
|
|
351
|
+
|
|
352
|
+
@staticmethod
|
|
353
|
+
async def update_namespace(namespace_name: str, namespace: NamespaceUpdate) -> NamespaceResponse:
|
|
354
|
+
"""
|
|
355
|
+
更新命名空间配置
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
namespace_name: 命名空间名称
|
|
359
|
+
namespace: 更新的配置信息
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
更新后的命名空间信息
|
|
363
|
+
"""
|
|
364
|
+
db_manager = get_db_manager()
|
|
365
|
+
async with db_manager.get_master_session() as session:
|
|
366
|
+
# 检查命名空间是否存在
|
|
367
|
+
check_query = text("""
|
|
368
|
+
SELECT id, redis_config, pg_config FROM namespaces WHERE name = :name
|
|
369
|
+
""")
|
|
370
|
+
result = await session.execute(check_query, {'name': namespace_name})
|
|
371
|
+
row = result.fetchone()
|
|
372
|
+
|
|
373
|
+
if not row:
|
|
374
|
+
raise ValueError(f"命名空间 '{namespace_name}' 不存在")
|
|
375
|
+
|
|
376
|
+
# 获取当前配置
|
|
377
|
+
current_redis_config = row.redis_config if row.redis_config else {}
|
|
378
|
+
current_pg_config = row.pg_config if row.pg_config else {}
|
|
379
|
+
|
|
380
|
+
# 更新配置
|
|
381
|
+
updates = []
|
|
382
|
+
params = {'name': namespace_name}
|
|
383
|
+
|
|
384
|
+
if namespace.description is not None:
|
|
385
|
+
updates.append("description = :description")
|
|
386
|
+
params['description'] = namespace.description
|
|
387
|
+
|
|
388
|
+
# 如果提供了config_mode,根据新模式处理配置
|
|
389
|
+
if namespace.config_mode is not None:
|
|
390
|
+
redis_config = {'config_mode': namespace.config_mode.value}
|
|
391
|
+
pg_config = {'config_mode': namespace.config_mode.value}
|
|
392
|
+
|
|
393
|
+
if namespace.config_mode == ConfigMode.DIRECT:
|
|
394
|
+
# 直接配置模式
|
|
395
|
+
if namespace.redis_nacos_key or namespace.pg_nacos_key:
|
|
396
|
+
raise ValueError("直接配置模式下不应提供nacos_key")
|
|
397
|
+
|
|
398
|
+
if namespace.redis_url:
|
|
399
|
+
if not SettingsService.validate_redis_url(namespace.redis_url):
|
|
400
|
+
raise ValueError("无效的Redis URL格式")
|
|
401
|
+
redis_config['url'] = namespace.redis_url
|
|
402
|
+
else:
|
|
403
|
+
# 保留原有URL
|
|
404
|
+
redis_config['url'] = current_redis_config.get('url', '')
|
|
405
|
+
|
|
406
|
+
if namespace.pg_url:
|
|
407
|
+
if not SettingsService.validate_pg_url(namespace.pg_url):
|
|
408
|
+
raise ValueError("无效的PostgreSQL URL格式")
|
|
409
|
+
pg_config['url'] = namespace.pg_url
|
|
410
|
+
elif current_pg_config.get('url'):
|
|
411
|
+
pg_config['url'] = current_pg_config.get('url')
|
|
412
|
+
|
|
413
|
+
elif namespace.config_mode == ConfigMode.NACOS:
|
|
414
|
+
# Nacos配置模式
|
|
415
|
+
if namespace.redis_url or namespace.pg_url:
|
|
416
|
+
raise ValueError("Nacos配置模式下不应提供直接URL")
|
|
417
|
+
|
|
418
|
+
if namespace.redis_nacos_key:
|
|
419
|
+
redis_url = await SettingsService.get_config_from_nacos(namespace.redis_nacos_key)
|
|
420
|
+
if not SettingsService.validate_redis_url(redis_url):
|
|
421
|
+
raise ValueError(f"从Nacos获取的Redis URL格式无效: {redis_url}")
|
|
422
|
+
redis_config['url'] = redis_url
|
|
423
|
+
redis_config['nacos_key'] = namespace.redis_nacos_key
|
|
424
|
+
else:
|
|
425
|
+
# 保留原有配置
|
|
426
|
+
redis_config['url'] = current_redis_config.get('url', '')
|
|
427
|
+
if current_redis_config.get('nacos_key'):
|
|
428
|
+
redis_config['nacos_key'] = current_redis_config.get('nacos_key')
|
|
429
|
+
|
|
430
|
+
if namespace.pg_nacos_key:
|
|
431
|
+
pg_url = await SettingsService.get_config_from_nacos(namespace.pg_nacos_key)
|
|
432
|
+
if not SettingsService.validate_pg_url(pg_url):
|
|
433
|
+
raise ValueError(f"从Nacos获取的PostgreSQL URL格式无效: {pg_url}")
|
|
434
|
+
pg_config['url'] = pg_url
|
|
435
|
+
pg_config['nacos_key'] = namespace.pg_nacos_key
|
|
436
|
+
elif current_pg_config.get('url'):
|
|
437
|
+
pg_config['url'] = current_pg_config.get('url')
|
|
438
|
+
if current_pg_config.get('nacos_key'):
|
|
439
|
+
pg_config['nacos_key'] = current_pg_config.get('nacos_key')
|
|
440
|
+
|
|
441
|
+
updates.append("redis_config = :redis_config")
|
|
442
|
+
params['redis_config'] = json.dumps(redis_config)
|
|
443
|
+
updates.append("pg_config = :pg_config")
|
|
444
|
+
params['pg_config'] = json.dumps(pg_config)
|
|
445
|
+
|
|
446
|
+
else:
|
|
447
|
+
# 没有提供config_mode,保持当前模式并更新相应字段
|
|
448
|
+
current_mode = current_redis_config.get('config_mode', 'direct')
|
|
449
|
+
|
|
450
|
+
if current_mode == 'direct':
|
|
451
|
+
if namespace.redis_nacos_key or namespace.pg_nacos_key:
|
|
452
|
+
raise ValueError("当前为直接配置模式,不能提供nacos_key,请先切换到nacos模式")
|
|
453
|
+
|
|
454
|
+
if namespace.redis_url:
|
|
455
|
+
if not SettingsService.validate_redis_url(namespace.redis_url):
|
|
456
|
+
raise ValueError("无效的Redis URL格式")
|
|
457
|
+
current_redis_config['url'] = namespace.redis_url
|
|
458
|
+
updates.append("redis_config = :redis_config")
|
|
459
|
+
params['redis_config'] = json.dumps(current_redis_config)
|
|
460
|
+
|
|
461
|
+
if namespace.pg_url:
|
|
462
|
+
if not SettingsService.validate_pg_url(namespace.pg_url):
|
|
463
|
+
raise ValueError("无效的PostgreSQL URL格式")
|
|
464
|
+
current_pg_config['url'] = namespace.pg_url
|
|
465
|
+
updates.append("pg_config = :pg_config")
|
|
466
|
+
params['pg_config'] = json.dumps(current_pg_config)
|
|
467
|
+
|
|
468
|
+
else: # nacos mode
|
|
469
|
+
if namespace.redis_url or namespace.pg_url:
|
|
470
|
+
raise ValueError("当前为nacos配置模式,不能提供直接URL,请先切换到direct模式")
|
|
471
|
+
|
|
472
|
+
if namespace.redis_nacos_key:
|
|
473
|
+
redis_url = await SettingsService.get_config_from_nacos(namespace.redis_nacos_key)
|
|
474
|
+
if not SettingsService.validate_redis_url(redis_url):
|
|
475
|
+
raise ValueError(f"从Nacos获取的Redis URL格式无效: {redis_url}")
|
|
476
|
+
current_redis_config['url'] = redis_url
|
|
477
|
+
current_redis_config['nacos_key'] = namespace.redis_nacos_key
|
|
478
|
+
updates.append("redis_config = :redis_config")
|
|
479
|
+
params['redis_config'] = json.dumps(current_redis_config)
|
|
480
|
+
|
|
481
|
+
if namespace.pg_nacos_key:
|
|
482
|
+
pg_url = await SettingsService.get_config_from_nacos(namespace.pg_nacos_key)
|
|
483
|
+
if not SettingsService.validate_pg_url(pg_url):
|
|
484
|
+
raise ValueError(f"从Nacos获取的PostgreSQL URL格式无效: {pg_url}")
|
|
485
|
+
current_pg_config['url'] = pg_url
|
|
486
|
+
current_pg_config['nacos_key'] = namespace.pg_nacos_key
|
|
487
|
+
updates.append("pg_config = :pg_config")
|
|
488
|
+
params['pg_config'] = json.dumps(current_pg_config)
|
|
489
|
+
|
|
490
|
+
if namespace.enabled is not None:
|
|
491
|
+
updates.append("is_active = :is_active")
|
|
492
|
+
params['is_active'] = namespace.enabled
|
|
493
|
+
|
|
494
|
+
if not updates:
|
|
495
|
+
raise ValueError("没有提供要更新的字段")
|
|
496
|
+
|
|
497
|
+
# 更新版本号和时间戳
|
|
498
|
+
updates.append("version = version + 1")
|
|
499
|
+
updates.append("updated_at = CURRENT_TIMESTAMP")
|
|
500
|
+
|
|
501
|
+
# 执行更新
|
|
502
|
+
update_query = text(f"""
|
|
503
|
+
UPDATE namespaces
|
|
504
|
+
SET {', '.join(updates)}
|
|
505
|
+
WHERE name = :name
|
|
506
|
+
RETURNING id, name, description, redis_config, pg_config,
|
|
507
|
+
is_active, version, created_at, updated_at
|
|
508
|
+
""")
|
|
509
|
+
|
|
510
|
+
result = await session.execute(update_query, params)
|
|
511
|
+
updated_row = result.fetchone()
|
|
512
|
+
await session.commit()
|
|
513
|
+
|
|
514
|
+
# 构建响应
|
|
515
|
+
redis_config_dict = updated_row.redis_config
|
|
516
|
+
pg_config_dict = updated_row.pg_config if updated_row.pg_config else {}
|
|
517
|
+
|
|
518
|
+
# 根据配置模式获取实际的URL
|
|
519
|
+
config_mode = redis_config_dict.get('config_mode', 'direct')
|
|
520
|
+
|
|
521
|
+
# 获取Redis URL
|
|
522
|
+
if config_mode == 'nacos' and redis_config_dict.get('nacos_key'):
|
|
523
|
+
try:
|
|
524
|
+
redis_url = await SettingsService.get_config_from_nacos(redis_config_dict['nacos_key'])
|
|
525
|
+
except Exception as e:
|
|
526
|
+
logger.error(f"从Nacos获取Redis URL失败: {e}")
|
|
527
|
+
redis_url = redis_config_dict.get('url', '')
|
|
528
|
+
else:
|
|
529
|
+
redis_url = redis_config_dict.get('url', '')
|
|
530
|
+
|
|
531
|
+
# 获取PostgreSQL URL
|
|
532
|
+
pg_url = None
|
|
533
|
+
if config_mode == 'nacos' and pg_config_dict.get('nacos_key'):
|
|
534
|
+
try:
|
|
535
|
+
pg_url = await SettingsService.get_config_from_nacos(pg_config_dict['nacos_key'])
|
|
536
|
+
except Exception as e:
|
|
537
|
+
logger.error(f"从Nacos获取PostgreSQL URL失败: {e}")
|
|
538
|
+
pg_url = pg_config_dict.get('url')
|
|
539
|
+
else:
|
|
540
|
+
pg_url = pg_config_dict.get('url')
|
|
541
|
+
|
|
542
|
+
response = NamespaceResponse(
|
|
543
|
+
name=updated_row.name,
|
|
544
|
+
description=updated_row.description,
|
|
545
|
+
redis_url=redis_url,
|
|
546
|
+
pg_url=pg_url,
|
|
547
|
+
connection_url=f"/api/v1/namespaces/{updated_row.name}",
|
|
548
|
+
version=updated_row.version or 1,
|
|
549
|
+
enabled=updated_row.is_active,
|
|
550
|
+
created_at=updated_row.created_at,
|
|
551
|
+
updated_at=updated_row.updated_at
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
logger.info(f"成功更新命名空间: {namespace_name}")
|
|
555
|
+
return response
|
|
556
|
+
|
|
557
|
+
@staticmethod
|
|
558
|
+
async def delete_namespace(namespace_name: str) -> Dict[str, str]:
|
|
559
|
+
"""
|
|
560
|
+
删除命名空间
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
namespace_name: 命名空间名称
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
删除结果
|
|
567
|
+
"""
|
|
568
|
+
if namespace_name == 'default':
|
|
569
|
+
raise ValueError("不能删除默认命名空间")
|
|
570
|
+
|
|
571
|
+
db_manager = get_db_manager()
|
|
572
|
+
async with db_manager.get_master_session() as session:
|
|
573
|
+
# 检查命名空间是否存在
|
|
574
|
+
check_query = text("SELECT id FROM namespaces WHERE name = :name")
|
|
575
|
+
result = await session.execute(check_query, {'name': namespace_name})
|
|
576
|
+
|
|
577
|
+
if not result.fetchone():
|
|
578
|
+
raise ValueError(f"命名空间 '{namespace_name}' 不存在")
|
|
579
|
+
|
|
580
|
+
# 删除命名空间
|
|
581
|
+
delete_query = text("DELETE FROM namespaces WHERE name = :name")
|
|
582
|
+
await session.execute(delete_query, {'name': namespace_name})
|
|
583
|
+
await session.commit()
|
|
584
|
+
|
|
585
|
+
logger.info(f"成功删除命名空间: {namespace_name}")
|
|
586
|
+
return {"message": f"命名空间 '{namespace_name}' 已删除"}
|
|
587
|
+
|
|
588
|
+
@staticmethod
|
|
589
|
+
async def activate_namespace(namespace_name: str) -> Dict[str, str]:
|
|
590
|
+
"""
|
|
591
|
+
激活命名空间
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
namespace_name: 命名空间名称
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
激活结果
|
|
598
|
+
"""
|
|
599
|
+
db_manager = get_db_manager()
|
|
600
|
+
async with db_manager.get_master_session() as session:
|
|
601
|
+
# 检查并激活命名空间
|
|
602
|
+
update_query = text("""
|
|
603
|
+
UPDATE namespaces
|
|
604
|
+
SET is_active = true, updated_at = CURRENT_TIMESTAMP
|
|
605
|
+
WHERE name = :name
|
|
606
|
+
""")
|
|
607
|
+
|
|
608
|
+
result = await session.execute(update_query, {'name': namespace_name})
|
|
609
|
+
|
|
610
|
+
if result.rowcount == 0:
|
|
611
|
+
raise ValueError(f"命名空间 '{namespace_name}' 不存在")
|
|
612
|
+
|
|
613
|
+
await session.commit()
|
|
614
|
+
|
|
615
|
+
logger.info(f"成功激活命名空间: {namespace_name}")
|
|
616
|
+
return {"message": f"命名空间 '{namespace_name}' 已激活"}
|
|
617
|
+
|
|
618
|
+
@staticmethod
|
|
619
|
+
async def deactivate_namespace(namespace_name: str) -> Dict[str, str]:
|
|
620
|
+
"""
|
|
621
|
+
停用命名空间
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
namespace_name: 命名空间名称
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
停用结果
|
|
628
|
+
"""
|
|
629
|
+
if namespace_name == 'default':
|
|
630
|
+
raise ValueError("不能停用默认命名空间")
|
|
631
|
+
|
|
632
|
+
db_manager = get_db_manager()
|
|
633
|
+
async with db_manager.get_master_session() as session:
|
|
634
|
+
# 检查并停用命名空间
|
|
635
|
+
update_query = text("""
|
|
636
|
+
UPDATE namespaces
|
|
637
|
+
SET is_active = false, updated_at = CURRENT_TIMESTAMP
|
|
638
|
+
WHERE name = :name
|
|
639
|
+
""")
|
|
640
|
+
|
|
641
|
+
result = await session.execute(update_query, {'name': namespace_name})
|
|
642
|
+
|
|
643
|
+
if result.rowcount == 0:
|
|
644
|
+
raise ValueError(f"命名空间 '{namespace_name}' 不存在")
|
|
645
|
+
|
|
646
|
+
await session.commit()
|
|
647
|
+
|
|
648
|
+
logger.info(f"成功停用命名空间: {namespace_name}")
|
|
649
|
+
return {"message": f"命名空间 '{namespace_name}' 已停用"}
|
|
650
|
+
|
|
651
|
+
@staticmethod
|
|
652
|
+
async def get_namespace_statistics(namespace_name: str) -> Dict[str, Any]:
|
|
653
|
+
"""
|
|
654
|
+
获取命名空间统计信息
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
namespace_name: 命名空间名称
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
统计信息
|
|
661
|
+
"""
|
|
662
|
+
# 这里可以根据实际需求实现统计逻辑
|
|
663
|
+
# 暂时返回模拟数据
|
|
664
|
+
return {
|
|
665
|
+
"namespace": namespace_name,
|
|
666
|
+
"statistics": {
|
|
667
|
+
"total_queues": 0,
|
|
668
|
+
"total_tasks": 0,
|
|
669
|
+
"active_workers": 0,
|
|
670
|
+
"pending_tasks": 0,
|
|
671
|
+
"processing_tasks": 0,
|
|
672
|
+
"completed_tasks": 0,
|
|
673
|
+
"failed_tasks": 0
|
|
674
|
+
},
|
|
675
|
+
"timestamp": datetime.now().isoformat()
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
@staticmethod
|
|
679
|
+
async def batch_activate_namespaces(namespace_names: List[str]) -> Dict[str, Any]:
|
|
680
|
+
"""
|
|
681
|
+
批量激活命名空间
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
namespace_names: 命名空间名称列表
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
激活结果
|
|
688
|
+
"""
|
|
689
|
+
db_manager = get_db_manager()
|
|
690
|
+
async with db_manager.get_master_session() as session:
|
|
691
|
+
# 批量激活
|
|
692
|
+
update_query = text("""
|
|
693
|
+
UPDATE namespaces
|
|
694
|
+
SET is_active = true, updated_at = CURRENT_TIMESTAMP
|
|
695
|
+
WHERE name = ANY(:names)
|
|
696
|
+
""")
|
|
697
|
+
|
|
698
|
+
result = await session.execute(update_query, {'names': namespace_names})
|
|
699
|
+
await session.commit()
|
|
700
|
+
|
|
701
|
+
activated_count = result.rowcount
|
|
702
|
+
logger.info(f"批量激活了 {activated_count} 个命名空间")
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
"activated": activated_count,
|
|
706
|
+
"namespaces": namespace_names[:activated_count]
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
@staticmethod
|
|
710
|
+
async def batch_deactivate_namespaces(namespace_names: List[str]) -> Dict[str, Any]:
|
|
711
|
+
"""
|
|
712
|
+
批量停用命名空间
|
|
713
|
+
|
|
714
|
+
Args:
|
|
715
|
+
namespace_names: 命名空间名称列表
|
|
716
|
+
|
|
717
|
+
Returns:
|
|
718
|
+
停用结果
|
|
719
|
+
"""
|
|
720
|
+
# 过滤掉默认命名空间
|
|
721
|
+
filtered_names = [name for name in namespace_names if name != 'default']
|
|
722
|
+
|
|
723
|
+
if not filtered_names:
|
|
724
|
+
return {
|
|
725
|
+
"deactivated": 0,
|
|
726
|
+
"namespaces": [],
|
|
727
|
+
"skipped": ["default"]
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
db_manager = get_db_manager()
|
|
731
|
+
async with db_manager.get_master_session() as session:
|
|
732
|
+
# 批量停用
|
|
733
|
+
update_query = text("""
|
|
734
|
+
UPDATE namespaces
|
|
735
|
+
SET is_active = false, updated_at = CURRENT_TIMESTAMP
|
|
736
|
+
WHERE name = ANY(:names)
|
|
737
|
+
""")
|
|
738
|
+
|
|
739
|
+
result = await session.execute(update_query, {'names': filtered_names})
|
|
740
|
+
await session.commit()
|
|
741
|
+
|
|
742
|
+
deactivated_count = result.rowcount
|
|
743
|
+
logger.info(f"批量停用了 {deactivated_count} 个命名空间")
|
|
744
|
+
|
|
745
|
+
response = {
|
|
746
|
+
"deactivated": deactivated_count,
|
|
747
|
+
"namespaces": filtered_names[:deactivated_count]
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
# 如果有被跳过的
|
|
751
|
+
skipped = [name for name in namespace_names if name not in filtered_names]
|
|
752
|
+
if skipped:
|
|
753
|
+
response["skipped"] = skipped
|
|
754
|
+
|
|
755
|
+
return response
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
__all__ = ['SettingsService']
|