From b78504c31e5523b560ddc0017afbc73a821c1296 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Sat, 25 Jul 2026 12:27:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E4=BF=AE=E5=A4=8D=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E9=9D=A2=E6=9D=BF=E5=88=9B=E5=BB=BA=E6=97=A7=E5=BC=8F?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E5=AD=A4=E5=84=BF=E5=AE=B9=E5=99=A8=EF=BC=8C?= =?UTF-8?q?=E5=88=87=E9=99=A4=20app=5Flegacy=20=E6=97=A7=E6=9E=B6=E6=9E=84?= =?UTF-8?q?=E6=AD=BB=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/admin.py | 14 +++- server/app_legacy.py | 172 ------------------------------------------- 2 files changed, 12 insertions(+), 174 deletions(-) diff --git a/server/admin.py b/server/admin.py index 3df3f823..42d7c134 100644 --- a/server/admin.py +++ b/server/admin.py @@ -209,13 +209,23 @@ def admin_dashboard_snapshot_api(): return guard try: # 若当前管理员没有容器句柄,主动确保容器存在,避免面板始终显示“宿主机模式” + # 注意:容器 key 必须带工作区后缀({username}::{workspace_id}), + # 否则会创建出旧式命名的 agent-term-{username} 孤儿容器。 try: record = get_current_user_record() uname = record.username if record else None if uname: handles = state.container_manager.list_containers() - if uname not in handles: - state.container_manager.ensure_container(uname, str(state.user_manager.ensure_user_workspace(uname).project_path), preferred_mode="docker") + default_ws_id = "default" + container_key = f"{uname}::{default_ws_id}" + if container_key not in handles: + workspace = state.user_manager.ensure_user_workspace(uname, default_ws_id) + state.container_manager.ensure_container( + uname, + str(workspace.project_path), + container_key=container_key, + preferred_mode="docker", + ) except Exception as ensure_exc: logging.getLogger(__name__).warning("ensure_container for admin failed: %s", ensure_exc) diff --git a/server/app_legacy.py b/server/app_legacy.py index 34c9e0e9..ee18bc62 100644 --- a/server/app_legacy.py +++ b/server/app_legacy.py @@ -881,178 +881,6 @@ def admin_api_required(view_func): return wrapped -def make_terminal_callback(username: str): - """生成面向指定用户的广播函数""" - def _callback(event_type, data): - try: - socketio.emit(event_type, data, room=f"user_{username}") - except Exception as exc: - debug_log(f"广播事件失败 ({username}): {event_type} - {exc}") - return _callback - - -def attach_user_broadcast(terminal: WebTerminal, username: str): - """确保终端的广播函数指向当前用户的房间""" - callback = make_terminal_callback(username) - terminal.message_callback = callback - if terminal.terminal_manager: - terminal.terminal_manager.broadcast = callback - - -def get_user_resources(username: Optional[str] = None) -> Tuple[Optional[WebTerminal], Optional[UserWorkspace]]: - username = (username or get_current_username()) - if not username: - return None, None - record = get_current_user_record() - workspace = user_manager.ensure_user_workspace(username) - container_handle = container_manager.ensure_container(username, str(workspace.project_path), preferred_mode="docker") - usage_tracker = get_or_create_usage_tracker(username, workspace) - terminal = user_terminals.get(username) - if not terminal: - run_mode = session.get('run_mode') - thinking_mode_flag = session.get('thinking_mode') - if run_mode not in {"fast", "thinking", "deep"}: - preferred_run_mode = None - try: - personal_config = load_personalization_config(workspace.data_dir) - candidate_mode = (personal_config or {}).get('default_run_mode') - if isinstance(candidate_mode, str) and candidate_mode.lower() in {"fast", "thinking", "deep"}: - preferred_run_mode = candidate_mode.lower() - except Exception as exc: - debug_log(f"[UserInit] 加载个性化偏好失败: {exc}") - - if preferred_run_mode: - run_mode = preferred_run_mode - thinking_mode_flag = preferred_run_mode != "fast" - elif thinking_mode_flag: - run_mode = "deep" - else: - run_mode = "fast" - thinking_mode = run_mode != "fast" - terminal = WebTerminal( - project_path=str(workspace.project_path), - thinking_mode=thinking_mode, - run_mode=run_mode, - message_callback=make_terminal_callback(username), - data_dir=str(workspace.data_dir), - container_session=container_handle, - usage_tracker=usage_tracker - ) - if terminal.terminal_manager: - terminal.terminal_manager.broadcast = terminal.message_callback - user_terminals[username] = terminal - terminal.username = username - terminal.user_role = get_current_user_role(record) - terminal.quota_update_callback = lambda metric=None: emit_user_quota_update(username) - session['run_mode'] = terminal.run_mode - session['thinking_mode'] = terminal.thinking_mode - else: - terminal.update_container_session(container_handle) - attach_user_broadcast(terminal, username) - terminal.username = username - terminal.user_role = get_current_user_role(record) - terminal.quota_update_callback = lambda metric=None: emit_user_quota_update(username) - - # 应用管理员策略(工具分类、强制开关、模型禁用) - try: - from core.tool_config import ToolCategory - - policy = resolve_admin_policy(user_manager.get_user(username)) - categories_map = { - cid: ToolCategory( - label=cat.get("label") or cid, - tools=list(cat.get("tools") or []), - default_enabled=bool(cat.get("default_enabled", True)), - silent_when_disabled=bool(cat.get("silent_when_disabled", False)), - ) - for cid, cat in policy.get("categories", {}).items() - } - forced_states = policy.get("forced_category_states") or {} - disabled_models = policy.get("disabled_models") or [] - terminal.set_admin_policy(categories_map, forced_states, disabled_models) - terminal.admin_policy_ui_blocks = policy.get("ui_blocks") or {} - terminal.admin_policy_version = policy.get("updated_at") - # 若当前模型被禁用,则回退到第一个可用模型 - if terminal.model_key in disabled_models: - for candidate in get_registered_model_keys(visible_only=True): - if candidate not in disabled_models: - try: - terminal.set_model(candidate) - session["model_key"] = terminal.model_key - break - except Exception: - continue - except Exception as exc: - debug_log(f"[admin_policy] 应用失败: {exc}") - return terminal, workspace - - -def get_or_create_usage_tracker(username: Optional[str], workspace: Optional[UserWorkspace] = None) -> Optional[UsageTracker]: - if not username: - return None - tracker = usage_trackers.get(username) - if tracker: - return tracker - if workspace is None: - workspace = user_manager.ensure_user_workspace(username) - record = user_manager.get_user(username) - role = getattr(record, "role", "user") if record else "user" - tracker = UsageTracker(str(workspace.data_dir), role=role or "user") - usage_trackers[username] = tracker - return tracker - - -def emit_user_quota_update(username: Optional[str]): - if not username: - return - tracker = get_or_create_usage_tracker(username) - if not tracker: - return - try: - snapshot = tracker.get_quota_snapshot() - socketio.emit('quota_update', { - 'quotas': snapshot - }, room=f"user_{username}") - except Exception: - pass - - -def with_terminal(func): - """注入用户专属终端和工作区""" - @wraps(func) - def wrapper(*args, **kwargs): - username = get_current_username() - try: - terminal, workspace = get_user_resources(username) - except RuntimeError as exc: - return jsonify({"error": str(exc), "code": "resource_busy"}), 503 - if not terminal or not workspace: - return jsonify({"error": "System not initialized"}), 503 - kwargs.update({ - 'terminal': terminal, - 'workspace': workspace, - 'username': username - }) - return func(*args, **kwargs) - return wrapper - - -def get_terminal_for_sid(sid: str) -> Tuple[Optional[str], Optional[WebTerminal], Optional[UserWorkspace]]: - username = connection_users.get(sid) - if not username: - return None, None, None - try: - terminal, workspace = get_user_resources(username) - except RuntimeError: - return username, None, None - return username, terminal, workspace - - -def get_gui_manager(workspace: UserWorkspace) -> GuiFileManager: - """构建 GUI 文件管理器""" - return GuiFileManager(str(workspace.project_path)) - - def get_upload_guard(workspace: UserWorkspace) -> UploadQuarantineManager: """构建上传隔离管理器""" return UploadQuarantineManager(workspace)