Files
Nexus/docs/design/plans/2026-05-17-server-add-optimize.md
T
2026-07-08 22:31:31 +08:00

6.1 KiB
Raw Blame History

servers.php 添加服务器优化 — 实现计划

⚠️ 已归档 — 2026-05-22 本文档引用的文件结构已过时:web/servers.php 已迁移到 web/app/servers.html (Tailwind CSS v4 + Alpine.js)server/services/ssh_direct.py 已被 asyncssh 连接池替代;server/database.py 已拆分为 server/domain/models/。功能已实现,保留本文档供历史参考。

基于设计: docs/superpowers/specs/2026-05-17-server-add-optimize-design.md 日期: 2026-05-17

任务 1 — 后端:create_server 自动生成密钥 + 目标路径入表单

文件: server/api/servers.py

# ServerCreate 模型:加 target_pathagent_api_key 改为 Optional(不从前端收)
class ServerCreate(BaseModel):
    ...
    target_path: str  # 新增必填
    agent_api_key: Optional[str] = None  # 自动生成

# create_server 路由:自动生成 agent_api_key
def create_server(server_data):
    import secrets
    data = server_data.model_dump()
    data["agent_api_key"] = data.get("agent_api_key") or secrets.token_hex(16)
    ...

验证: curl -X POST /api/servers/ -d '{"name":"t","domain":"1.1.1.1","target_path":"/tmp"}' 返回含 agent_api_key


任务 2 — 后端:异步 SSH 安装 Agent 服务

文件: 新建 server/services/agent_installer.py

async def install_agent_on_server(server_id: int):
    """SSH 到子服务器执行 Agent 安装脚本,通过 Redis pub/sub 推送进度"""
    # 1. 读 DB 获取 server
    # 2. 构造安装命令:curl install.sh | bash --url ... --key ... --dirs ...
    # 3. SSH exec_command 执行
    # 4. 逐行读取 stdout → Redis PUBLISH install:{id}:log
    # 5. 完成/失败 → 更新 server.is_online + Redis state

Redis 消息格式:

PUBLISH install:3:log  {"percent":45,"line":"[3/5] 下载 Agent..."}
PUBLISH install:3:log  {"percent":100,"line":"✅ 安装完成","status":"done"}

安装命令:

curl -fsSL https://${NEXUS_DOMAIN}/agent/install.sh | bash -s -- \
  --url http://${NEXUS_DOMAIN}:8600 \
  --key <agent_api_key> \
  --dirs <target_path>

验证: python -m py_compile server/services/agent_installer.py 通过


任务 3 — 后端:create_server 触发异步安装

文件: server/api/servers.py

def create_server():
    ...
    db.commit(); db.refresh(server)
    # 触发后台安装
    import asyncio
    from server.services.agent_installer import install_agent_on_server
    asyncio.create_task(install_agent_on_server(server.id))
    return server

验证: 创建服务器后,查看 Redis install:{id}:state 是否有进度


任务 4 — 后端:WebSocket 转发安装进度

文件: server/main.py

# 已有 broadcast_push_progress,新增 install 进度广播
async def broadcast_install_progress(server_id: int, data: dict):
    """将 Redis 安装进度转发到 WebSocket"""
    for ws in connected_ws:
        await ws.send_json({"type":"install_progress","server_id":server_id,**data})

验证: WebSocket 连接后能收到 {"type":"install_progress",...}


任务 5 — 前端:表单改造

文件: web/servers.php

变更:

  1. 移除 agent_api_key 输入框
  2. 隐藏 agent_port → 固定 8601
  3. 新增 target_path 必填输入框
  4. SSH 密码标记必填
  5. 提交按钮改为「保存并安装 Agent」
<!-- 目标路径 -->
<div class="form-group">
    <label>目标路径 <span class="required">*</span></label>
    <input type="text" name="target_path" class="form-control" required
           placeholder="/www/wwwroot/project">
</div>

PHP POST 处理: 加 target_path$formData

验证: 表单提交后服务器创建成功,密码可空→必填校验生效


任务 6 — 前端:安装进度显示 + 日志弹窗

文件: web/servers.php

变更:

  1. 服务器列表:离线且 install_state != done → 显示进度条
  2. WebSocket 监听 install_progress → 更新行内进度和日志弹窗
  3. 新增日志弹窗 DOM
// 日志弹窗
<div class="push-overlay" id="installLogOverlay">
  <div class="push-dialog">
    <h3>安装日志: <span id="installLogTitle"></span></h3>
    <div id="installLogContent" style="font-family:monospace;font-size:12px;padding:16px;max-height:400px;overflow-y:auto;background:#1e293b;color:#e2e8f0;border-radius:6px;"></div>
    <div class="push-footer"><button onclick="closeInstallLog()">关闭</button></div>
  </div>
</div>

ws.onmessage 处理:

if (msg.type === 'install_progress') {
    updateInstallProgress(msg.server_id, msg.percent, msg.line);
    if (msg.status === 'done') location.reload();
}

验证: 添加服务器后列表出现进度,点击弹出日志


任务 7 — 前端:目标路径快速编辑

文件: web/servers.php

变更:

  1. 列表每行 target_path 后加 ✏️ 按钮
  2. 点击 → td 内出现 <input> + 确认按钮
  3. 回车 → PUT /api/servers/{id} 更新 target_path
<td class="path-cell">
  <span id="path_<?=$s['id']?>"><?= htmlspecialchars($s['target_path']??'-') ?></span>
  <button class="btn btn-sm" onclick="editPath(<?=$s['id']?>)">✏️</button>
</td>
function editPath(id) {
    const span = document.getElementById('path_' + id);
    const old = span.textContent;
    span.innerHTML = '<input id="path_input_' + id + '" value="'+old+'" style="width:160px;">' +
        '<button onclick="savePath('+id+')">✓</button>';
}

function savePath(id) {
    const v = document.getElementById('path_input_' + id).value;
    fetch(API_BASE + '/api/servers/' + id, {method:'PUT',headers:{'Content-Type':'application/json','X-API-Key':API_KEY},
        body: JSON.stringify({target_path: v})})
    .then(() => location.reload());
}

验证: 点击 ✏️ → 修改路径 → 回车 → 刷新后路径更新


任务 8 — 端到端验证

  1. 访问 https://${NEXUS_DOMAIN}/servers.html (添加服务器页面)
  2. 填写表单、提交
  3. 列表出现安装进度
  4. 安装完成 → 绿点在线
  5. ✏️ 编辑目标路径生效

验证: mcp__multisync__run_test() 全部通过