- Remember history task and stdout/stderr terminal open state across the 60s refresh so they no longer collapse on every poll. - Mark GPUs with allocated memory but near-zero utilization as `abnormal` (orange) — covers stuck processes that previously rendered as busy. - Bump dashboard auto-refresh from 30s to 60s. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Local Kanban
本地 Web 看板,用来汇总 SSH 可访问开发机的 GPU 使用情况、IPADS API 额度、本地 Gitea 项目,并从选定 project/branch 准备 workspace 后启动 agent。
启动
npm start
默认地址是 http://127.0.0.1:5180,生产访问通常走 https://gahow-pc.ipads-lab.se.sjtu.edu.cn:8443/ 的本地 Nginx 反代。
首次启动会生成访问 token,并保存到 ~/.config/local-kanban/state.json。登录页需要输入这个 token:
jq -r .authToken ~/.config/local-kanban/state.json
也可以用固定环境变量覆盖:
KANBAN_AUTH_TOKEN='long-random-token' npm start
如果通过 HTTPS 反代访问,可设置 KANBAN_AUTH_SECURE_COOKIE=1 让浏览器只在 HTTPS 连接发送 cookie。纯 HTTP 暴露在局域网时,token 登录能阻止未授权用户直接访问服务,但链路本身不能抵御同网段抓包;需要“强隔离”时应配合 HTTPS、VPN 或 SSH tunnel。
HTTPS 反代推荐让 Node 只监听本机:
KANBAN_HOST=127.0.0.1 \
KANBAN_PORT=5180 \
KANBAN_AUTH_SECURE_COOKIE=1 \
KANBAN_REQUIRE_HTTPS=1 \
npm start
Nginx 示例在 deploy/nginx.local-kanban.conf。当前设计是:
https://gahow-pc.ipads-lab.se.sjtu.edu.cn/是入口首页- 每个服务仍然保留自己的端口,例如 Kanban 是
https://gahow-pc.ipads-lab.se.sjtu.edu.cn:8443/ - 入口首页展示 Gitea、Kanban、Stock Agent 三个常用服务
- 后端 API 链接默认折叠,需要时展开
系统 Nginx 负责:
- TLS 终止
- 在 443 提供静态入口首页
- 在 3443 反代 Gitea 的 3300
- 在 5443 反代 Stock Agent 前端的 5174,并把
/api转发到 8787 - 在 8788 直接反代 Stock Agent API 的 8787
- 默认不监听 80,避免和现有本地服务冲突
没有 sudo 时,可以使用当前仓库里的用户级 Nginx 配置监听 8443,详见 deploy/README.md。
如果需要持久运行 Node app 和用户级 Nginx,可以安装用户级 systemd 服务:
./scripts/install-user-systemd.sh
配置
可以用环境变量快速配置:
KANBAN_GPU_HOSTS=dash0,dash1 \
KANBAN_GITEA_BASE_URL="http://gahow-pc.ipads-lab.se.sjtu.edu.cn:3300" \
npm start
也可以创建 ~/.config/local-kanban/config.json:
{
"gpuHosts": ["dash0", "dash1"],
"gitea": {
"baseUrl": "http://gahow-pc.ipads-lab.se.sjtu.edu.cn:3300",
"tokenEnv": "GITEA_TOKEN"
},
"workspaceRoot": "/home/gahow/.local/share/local-kanban/workspaces",
"authSecureCookie": true,
"requireHttps": true,
"quotaSources": [
{
"id": "ipads",
"label": "IPADS API",
"type": "command",
"command": "bash",
"args": ["-lc", "curl -sS -H \"Authorization: Bearer $IPADS_API_KEY\" http://tianx.ipads-lab.se.sjtu.edu.cn:8319/v1/usage | jq '{remaining, usage}'"],
"timeoutMs": 15000
}
],
"agentProfiles": [
{
"id": "codex-full",
"label": "Codex Full Permission",
"engine": "codex",
"command": "codex",
"args": [
"--dangerously-bypass-approvals-and-sandbox",
"exec",
"--sandbox",
"danger-full-access",
"--cd",
"{workspace_path}"
]
},
{
"id": "claude-bypass",
"label": "Claude Code Bypass",
"engine": "claude",
"command": "claude",
"args": [
"--permission-mode",
"bypassPermissions",
"-p",
"--output-format",
"stream-json",
"--verbose"
]
},
{
"id": "qoder-yolo",
"label": "Qoder YOLO",
"engine": "qoder",
"command": "qodercli",
"args": [
"--yolo",
"-p",
"--output-format",
"stream-json"
]
}
]
}
GPU 机器列表可以在 Web 页面里编辑保存,保存位置是 ~/.config/local-kanban/state.json。默认只读取当前项目目录的 /home/gahow/projects/kanban/.env。需要的 key 可以参考 .env.example:
GITEA_TOKEN=replace-with-your-gitea-token
IPADS_API_KEY=replace-with-your-ipads-api-key
也可以用逗号分隔的 KANBAN_ENV_FILE 指向其他 env 文件。
API
GET /api/gpus: 查询ssh <host> nvidia-smi --query-gpu=index,name,memory.used,memory.total,utilization.gpu --format=csv,noheader,nounitsPUT /api/settings/gpu-hosts: 保存需要监控的 GPU 机器列表POST /api/auth/login: 使用本地 token 登录并设置 HttpOnly cookiePOST /api/auth/logout: 清除登录 cookieGET /api/quotas: 执行默认的 IPADS API 额度命令GET /api/repos: 从 Gitea/user/repos读取仓库GET /api/branches?repo=owner/name: 从 Gitea 读取 branch listGET /api/agent-profiles: 返回可选 AI 配置POST /api/tasks: 准备选定 project/branch 的 workspace 后启动 agent 命令GET /api/dashboard: 聚合以上数据,供 Web 和后续 iOS 复用
Agent Session 历史
任务历史会保存到 ~/.config/local-kanban/state.json。每次 agent 启动后,服务会从输出中提取 session id 并记录到对应的 project/branch。之后在同一个 project/branch 下,可以在 Web UI 的 “历史 Session” 下拉框里选择之前的任务,点击 “继续 Session” 发送新指令。
默认内置三个 agent profile,可在 Agent 配置下拉框里切换:
codex-full— Codex,完全权限模式claude-bypass— Claude Code,等价于本地alias ccx='claude --permission-mode bypassPermissions',但跑在 headless 模式下(-p --output-format stream-json --verbose),方便提取session_idqoder-yolo— Qoder CLI,等价于本地alias qxx='qodercli --yolo',headless 模式下加-p --output-format stream-json
继续会话使用(按 engine):
# codex
codex --dangerously-bypass-approvals-and-sandbox exec --sandbox danger-full-access --cd <workspace> resume <session_id> <prompt>
# claude
claude --permission-mode bypassPermissions -p --output-format stream-json --verbose --resume <session_id> <prompt>
# qoder
qodercli --yolo -p --output-format stream-json --resume <session_id> <prompt>
Profile 自定义时可设 engine: "codex" / "claude" / "qoder" 来选择参数构造与 session id 解析方式;省略时根据 command 推断。Claude 和 Qoder 的 stream-json 输出 schema 一致,前端展示时都只渲染 assistant 文本和最终 [done] 行。