fix(admin): 修复管理面板创建旧式命名孤儿容器,切除 app_legacy 旧架构死代码

This commit is contained in:
JOJO 2026-07-25 12:27:06 +08:00
parent cf51ab0555
commit b78504c31e
2 changed files with 12 additions and 174 deletions

View File

@ -209,13 +209,23 @@ def admin_dashboard_snapshot_api():
return guard return guard
try: try:
# 若当前管理员没有容器句柄,主动确保容器存在,避免面板始终显示“宿主机模式” # 若当前管理员没有容器句柄,主动确保容器存在,避免面板始终显示“宿主机模式”
# 注意:容器 key 必须带工作区后缀({username}::{workspace_id}
# 否则会创建出旧式命名的 agent-term-{username} 孤儿容器。
try: try:
record = get_current_user_record() record = get_current_user_record()
uname = record.username if record else None uname = record.username if record else None
if uname: if uname:
handles = state.container_manager.list_containers() handles = state.container_manager.list_containers()
if uname not in handles: default_ws_id = "default"
state.container_manager.ensure_container(uname, str(state.user_manager.ensure_user_workspace(uname).project_path), preferred_mode="docker") 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: except Exception as ensure_exc:
logging.getLogger(__name__).warning("ensure_container for admin failed: %s", ensure_exc) logging.getLogger(__name__).warning("ensure_container for admin failed: %s", ensure_exc)

View File

@ -881,178 +881,6 @@ def admin_api_required(view_func):
return wrapped 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: def get_upload_guard(workspace: UserWorkspace) -> UploadQuarantineManager:
"""构建上传隔离管理器""" """构建上传隔离管理器"""
return UploadQuarantineManager(workspace) return UploadQuarantineManager(workspace)