README.md

KADT WebUI

KADT的可视化部署界面,支持OpenCode、OpenClaw和vLLM-Ascend三种部署方案的Web化管理。

核心特性

可视化部署

  • 清晰的步骤流程展示
  • 实时日志输出与滚动刷新

单步执行

  • 每个步骤可独立执行
  • 支持跳过、重试
  • 参数表单动态渲染
  • 服务器IP/密码配置

进程管理

  • 任务中止(SIGTERM → SIGKILL)
  • WebUI重启不杀运行中的任务
  • 任务状态持久化与恢复
  • 进程意外终止检测

配置驱动

  • YAML配置定义脚本流程
  • 无需修改Python代码即可扩展
  • 动态选项(如OS列表)实时获取
  • 多参数类型支持

快速启动

使用启动脚本(推荐)

cd kadt/webui
./launcher.sh

默认端口:8080

自定义端口

./launcher.sh 9000

launcher.sh 功能

启动脚本会自动完成以下操作:

  1. 检查 Python 环境:验证 Python 3 是否可用
  2. 自动安装依赖:检测并安装 Flask、PyYAML(如未安装)
  3. 获取本机 IP:自动获取服务器 IP 地址用于远程访问
  4. 启动服务:启动 Flask 应用

启动后显示访问地址:

访问地址:
  本机: http://127.0.0.1:8080
  远程: http://10.10.10.10:8080

直接启动(不推荐)

cd kadt/webui
python3 app.py --port 8080

此方式需要手动安装依赖:

pip3 install flask pyyaml

依赖

最小依赖设计,仅需:

  • Python 3.6+
  • Flask
  • PyYAML

使用 launcher.sh 启动时会自动检测并安装缺失的依赖。

手动安装:

pip3 install flask pyyaml

支持的部署方案

Profile 名称 步骤数 说明
opencode OpenCode 一体机 4步 AI 编程助手(下载→安装→LLM→应用)
openclaw OpenClaw 一体机 4步 AI Agent助手(下载→安装→LLM→应用)
vllm-ascend vLLM-Ascend 3步 纯推理服务(下载→安装→LLM)

使用流程

1. 选择部署方案

首页展示3个Profile卡片,点击选择进入工作流页面。

2. 查看部署流程

左侧显示完整步骤列表,右侧显示当前步骤详情。

3. 执行步骤

  • 点击「执行」进入参数填写页面
  • 填写必要参数(服务器、模型、端口等)
  • 点击「开始执行」,查看实时日志
  • 等待执行完成,查看结果状态

4. 继续后续步骤

按顺序执行或跳过某些步骤,所有步骤完成后部署成功。

参数类型

WebUI支持多种参数类型,自动渲染对应表单:

类型 说明 示例
text 文本输入 模型名称、路径
number 数字输入 端口、实例数量
password 密码输入 API Key、SSH密码
select 下拉选择 操作系统、模型
checkbox 复选框 布尔开关
servers 服务器配置 IP + 密码输入
software_list 软件列表 添加/删除软件组件

特殊参数

servers类型

  • 支持添加多个远程服务器
  • IP支持单IP或IP范围(10.10.10.1-10.10.10.9)
  • User固定为root,需填写密码
  • 自动更新ascend_deployer/inventory_file

software_list类型

  • 显示已选软件标签,点击×可删除
  • 输入框添加新软件名称
  • 逗号分隔传递给脚本(如sys_pkg,npu,docker_compose

select + bool_flag

  • 当options为["true", "false"]bool_flag: true
  • 渲染为开关,true时添加flag参数(如--dual-node

目录结构

webui/
├── app.py                   # Flask应用
├── script_runner.py         # 核心执行引擎
├── scripts_config.yaml      # YAML配置
├── launcher.sh              # 启动脚本(自动安装依赖)
├── templates/               # Jinja2模板
│   ├── base.html            # 基础模板
│   ├── index.html           # 首页(Profile卡片)
│   ├── workflow.html        # 工作流页面
│   ├── step.html            # 步骤执行页面
│   ├── logs.html            # 日志查看页面
│   ├── tasks.html           # 任务列表页面
│   └── error.html           # 错误页面
├── static/
│   ├── style.css            # CSS
│   └── app.js               # 前端交互逻辑
├── logs/                    # 任务日志与状态文件
│   ├── task_xxx.log         # 执行日志
│   └── task_xxx.status.json # 任务状态
└── README.md                # 本文件

技术架构

Flask应用 (app.py)

  • 6个页面路由:index, profile, step, logs, tasks, error
  • 8个API路由:profiles, workflow, step, execute, logs, task, tasks, kill, health
  • datetime模板过滤器
  • threaded=True支持并发请求

ScriptRunner (script_runner.py)

核心执行引擎,负责:

  1. 配置解析:加载YAML,提取profiles/workflow/params
  2. 命令构建:根据参数类型生成bash命令
  3. inventory更新:写入服务器列表到inventory_file
  4. 异步执行:subprocess.Popen + 独立进程组
  5. 日志管理:实时写入,支持增量读取
  6. 进程管理:SIGTERM/SIGKILL中止,进程存活检测
  7. 状态恢复:启动时扫描.status.json,恢复运行中任务
  8. 清理机制:退出时终止运行中任务,定时清理24小时前的日志

关键设计:

  • start_new_session=True:创建独立进程组,不受WebUI退出影响
  • stdin.write(b'y\n'):自动确认EULA
  • 状态持久化到JSON文件,支持跨重启恢复

配置驱动 (scripts_config.yaml)

每个profile定义workflow,每个step定义params:

profiles:
  opencode:
    display_name: "OpenCode 一体机"
    workflow:
      - name: download
        script: "opencode/opencode_download.sh"
        title: "软件下载"
        params:
          - name: os
            type: select
            source: dynamic
            source_cmd: "python3 -c '...'"

无需修改Python代码,只需修改YAML即可扩展新脚本。

API接口

REST API可用于集成或自动化:

路径 方法 说明
/api/profiles GET 获取所有profile列表
/api/workflow/<profile> GET 获取profile的workflow步骤
/api/step/<profile>/<step> GET 获取步骤表单信息(含动态选项)
/api/execute POST 执行步骤,返回task_id
/api/logs/<task_id>?pos=N GET 增量读取日志(从pos位置开始)
/api/task/<task_id> GET 获取任务状态
/api/tasks GET 获取所有任务列表
/api/kill/<task_id> POST 中止正在运行的任务
/api/health GET 健康检查

执行API示例

curl -X POST http://localhost:8080/api/execute \
  -H "Content-Type: application/json" \
  -d '{
    "profile": "opencode",
    "step": "download",
    "params": {
      "os": "openeuler22.03",
      "model": "Qwen/Qwen3-8B"
    }
  }'

返回:

{
  "task_id": "task_1234567890_1",
  "status": "started",
  "message": "任务 task_xxx 已启动"
}

进程生命周期

正常流程

  1. 用户点击「开始执行」
  2. ScriptRunner创建独立进程组(start_new_session)
  3. 进程运行,日志实时写入
  4. 进程结束,更新状态(success/failed)
  5. WebUI退出时不影响进程运行

任务中止

  1. 用户点击「中止任务」
  2. 发送SIGTERM,等待0.3秒
  3. 进程仍存活则发送SIGKILL
  4. 更新状态为killed,写入日志

WebUI重启恢复

  1. 启动时扫描logs/*.status.json
  2. 状态为running的检查进程是否存活
  3. 存活则恢复任务,继续监控
  4. 不存活则更新状态为failed

WebUI退出清理

  1. 注册atexit和SIGTERM/SIGINT信号
  2. 扫描所有running状态任务
  3. 发送SIGTERM终止进程
  4. 更新状态为killed,写入日志

测试

WebUI包含完整的测试套件(39个测试):

cd ../..
python3 -m pytest test/st/test_webui.py -v

测试覆盖:

  • JavaScript语法验证
  • Jinja2模板语法验证
  • Python导入测试
  • Flask路由测试
  • ScriptRunner功能测试
  • 配置文件解析测试
  • CSS语法检查
  • HTML渲染测试(新增)
    • JSON数据属性引号格式验证
    • help_panel元素渲染验证
    • dynamic_filter属性完整性验证
    • 参数默认值渲染验证
    • JSON可解析性验证

扩展新脚本

1. 添加脚本

kadt/your_profile/下创建脚本:

#!/bin/bash
# your_script.sh
while [[ $# -gt 0 ]]; do
    case $1 in
        --param-name)
            PARAM_VALUE="$2"
            shift 2
            ;;
        *)
            shift
            ;;
    esac
done

2. 添加配置

修改scripts_config.yaml

profiles:
  your_profile:
    display_name: "你的方案"
    description: "方案描述"
    icon: "server"
    workflow:
      - name: your_step
        script: "your_profile/your_script.sh"
        title: "步骤标题"
        description: "步骤描述"
        standalone: true
        skip_allowed: true
        params:
          - name: param-name
            type: text
            label: "参数名"
            required: true
            placeholder: "请输入"

3. 高级参数类型

dynamic_filter - 动态过滤下拉选项

根据其他参数值动态过滤可选项(如根据服务器类型过滤模型):

- name: model
  type: select
  label: 模型名称
  required: true
  dynamic_filter: true
  filter_depends:
    - server-type
    - dual-node
  filter_rules:
    a2_single:
      - Qwen3-8B
      - Qwen3-14B
    a2_multi:
      - MiniMax-M2.5
  options:
    - Qwen3-8B
    - Qwen3-14B
    - MiniMax-M2.5

重要:filter_depends和filter_rules在HTML中会转为JSON属性,模板必须使用单引号包裹:

data-filter-depends='["server-type", "dual-node"]'
data-filter-rules='{"a2_single": ["Qwen3-8B"]}'

help_panel - 可折叠帮助面板

显示兼容性说明等信息:

- name: model_help
  type: help_panel
  title: 模型兼容性说明
  collapsed: true
  content:
    a2_single:
      title: A2 单机
      models:
        - Qwen3-8B
        - Qwen3-14B
    a2_multi:
      title: A2 多机
      models:
        - MiniMax-M2.5

注意:help_panel不需要label字段,只需要title。

frontend_only - 前端专用参数

不传递给后端脚本,仅用于前端交互:

- name: server-type
  type: select
  label: 服务器类型
  frontend_only: true
  options:
    - a2
    - a3
  default: a2

frontend_only参数在ScriptRunner.build_command()中会被跳过。

无需修改任何Python代码,重启WebUI即可生效。

注意事项

  1. 执行权限:所有部署脚本需要root权限执行
  2. 重启提醒:base_install步骤执行后需要重启服务器(NPU驱动生效)
  3. 模型权重:下载的模型存储在model_weights/目录
  4. 日志清理:任务状态文件和日志默认保留24小时
  5. 端口占用:默认8080端口,如被占用请指定其他端口
  6. 浏览器兼容:推荐使用Chrome/Firefox/Safari现代浏览器
  7. HTML/JS交互:模板中JSON数据属性必须使用单引号包裹
    <!-- 正确:单引号包裹JSON -->
    data-filter-depends='["server-type", "dual-node"]'
    
    <!-- 错误:双引号会与JSON内部引号冲突 -->
    data-filter-depends="[\"server-type\"]"
    

故障排除

Flask启动失败

pip3 install flask --upgrade

YAML解析错误

pip3 install pyyaml --upgrade

权限问题

chmod +x app.py script_runner.py
chmod +x ../common/*.sh
chmod +x ../opencode/*.sh
chmod +x ../openclaw/*.sh
chmod +x ../vllm-ascend/*.sh

Ansible回调模块错误

参考主仓库AGENTS.md中的解决方案:

cd ascend_deployer/resources/sources/ansible_collections
ansible-galaxy collection install community-general-*.tar.gz

进程无法中止

检查进程状态:

ps aux | grep <task_id中的PID>
kill -9 <PID>

版本历史

v1.0 (2026-05)

  • 基础WebUI框架
  • 3个部署方案支持
  • 单步执行与实时日志
  • 任务中止功能
  • 服务器配置
  • 软件列表编辑
  • 任务状态恢复机制
  • 模型兼容性过滤
  • help_panel帮助面板
  • frontend_only参数支持
  • 39个自动化测试(含HTML渲染测试)