feat: 首页改版与 uv / 镜像配置收敛,精简任务数据结构
- 页头只保留品牌与操作图标,三栏改为 1:2:1、容器最宽 1320 - 任务卡片:状态标签移入标题行并缩小,隐藏任务标识,操作行贴内容底边 - uv 配置弹窗:删掉与首页重复的状态条,三段配置改成标签页 - 镜像源:弹窗去掉默认镜像汇总,首页卡片改为展示默认镜像与运行时注入的变量 - 后端:迁移合并为单一基线 0001_init,删掉未生效的 concurrency_policy / retry_*, 运行触发只剩 schedule / manual;contracts、前端生成类型、README / PLAN 同步
This commit is contained in:
@@ -114,14 +114,14 @@ uv run --no-project tools/verify.py --base-url http://127.0.0.1:3000
|
||||
|
||||
| 分组 | 能力 |
|
||||
| --- | --- |
|
||||
| 任务管理 | 增删改查、启用/停用、按 ID/名称搜索、按启用状态与 Action 类型筛选、分页 |
|
||||
| 任务管理 | 增删改查、启用/停用、按 ID/名称搜索、按启用状态与 Action 类型筛选 |
|
||||
| 调度 | 5/6 位 Cron(支持秒级、别名、月份/星期名)、IANA 时区、启动恢复调度、下次触发时间 |
|
||||
| 执行 | 手动立即运行;同一任务重叠触发记为 `skipped`;启动时对账遗留运行 |
|
||||
| 进程治理 | Windows Job Object 管理进程树;超时或取消时终止整棵树 |
|
||||
| 观测 | 运行历史(状态/触发方式筛选)、退出码、耗时、错误信息、日志下载 |
|
||||
| 日志 | SSE 实时推送 + 增量轮询 + 断线重连;编码自动识别;敏感值脱敏;容量与保留期清理 |
|
||||
| Python | 自动关联 `.venv`/`venv`/`env`、解释器可选、用 uv 从 `pyproject.toml` / `requirements*.txt` 创建环境(新任务保存时自动创建)、uv 只用程序目录副本或自定义路径(不读系统 PATH)、没有 uv 时可从科大镜像一键下载、`UV_*` 环境变量可配置、注入 PATH/`VIRTUAL_ENV`/`PYTHONIOENCODING`、装包时走默认 PyPI 镜像 |
|
||||
| 界面 | 顶部品牌栏 + 三栏工作台(用户/概览/调度 · 任务分组 · uv/镜像/环境卡片)、任务类型 + 脚本语言联动表单、Cron 预览、虚拟环境状态条、页面内目录浏览器、深色日志弹窗 |
|
||||
| 界面 | 顶部品牌栏 + 三栏工作台(用户/概览/调度 · 任务卡片 · uv/镜像/环境卡片)、任务类型 + 脚本语言联动表单、Cron 预览、虚拟环境状态条、页面内目录浏览器、深色日志弹窗 |
|
||||
| 安全 | 仅回环监听、Origin 白名单、写操作令牌、常量时间比较、请求体上限 |
|
||||
| 交付 | 单进程静态托管、SPA 回退、Release 打包、OpenAPI 契约、可选离线字体 |
|
||||
|
||||
@@ -135,12 +135,11 @@ uv run --no-project tools/verify.py --base-url http://127.0.0.1:3000
|
||||
| --- | --- |
|
||||
| `id` | 稳定标识,`[A-Za-z0-9_-]`,1–64 字符;同时决定日志目录名 |
|
||||
| `name` | 展示名,1–120 字符 |
|
||||
| `description` | 一句话说明,≤500 字符;显示在任务卡片副标题和详情里,可留空 |
|
||||
| `cron` | 5 或 6 位表达式,例:`0 0 2 * * *`、`*/30 * * * * *`、`@daily` |
|
||||
| `timezone` | IANA 时区,例:`Asia/Shanghai` |
|
||||
| `enabled` | 是否注册调度 |
|
||||
| `timeout_secs` | 1–86400;超时终止整个进程树 |
|
||||
| `concurrency_policy` | 目前仅 `skip`(同任务重叠触发被跳过) |
|
||||
| `retry_policy` | `max_attempts` ≤ 10、`delay_secs` ≤ 3600;**当前只持久化,不自动重试** |
|
||||
| `action` | 见下 |
|
||||
|
||||
### 三类 Action
|
||||
@@ -187,7 +186,6 @@ queued ──► running ──► succeeded / failed / timed_out / cancelled
|
||||
- **取消后立即重跑**:允许;旧进程终止与新进程启动可能短暂重叠。
|
||||
- **错过的触发不补跑**:下次触发始终从"现在"开始计算。
|
||||
- **下次触发时间**:调度器保存在内存中,`GET /api/v1/schedule` 暴露给前端。
|
||||
- **重试**:字段已持久化但执行器尚未实现自动重试(见「已知限制」)。
|
||||
|
||||
---
|
||||
|
||||
@@ -231,12 +229,21 @@ queued ──► running ──► succeeded / failed / timed_out / cancelled
|
||||
- 创建时会使用当前默认 PyPI 镜像:注入 `UV_DEFAULT_INDEX`(实际生效的是它)以及兼容用的 `UV_INDEX_URL` / `PIP_INDEX_URL`,不会绕过镜像设置。
|
||||
- uv 只用平台认的两份:程序目录下的 `uv\uv.exe`(「程序目录 uv」,默认),或你指定的 `uv.exe`(「指定路径」)。
|
||||
**不读系统 PATH**——PATH 上的 uv 版本会随启动方式变化,不适合当调度器的隐式依赖。
|
||||
没有 uv 时在 uv 配置弹窗点「下载 uv」,从科大镜像
|
||||
`mirrors.ustc.edu.cn/github-release/astral-sh/uv/LatestRelease` 下载当前架构的压缩包,
|
||||
校验 sha256 后解压到 `uv\uv.exe` 并自动切换过去;本机已经装了 uv 的话,把它填进「指定路径」即可。
|
||||
没有 uv 时在 uv 配置弹窗点「下载 uv」,从「下载镜像」(默认科大
|
||||
`mirrors.ustc.edu.cn/github-release`)下载当前架构的压缩包,
|
||||
校验 sha256 后解压到 `uv\uv.exe` 并自动切换过去(弹窗状态条里会显示 uv 与解释器的下载地址);
|
||||
本机已经装了 uv 的话,把它填进「指定路径」即可。
|
||||
- **uv 的下载镜像不跟 PyPI 包索引混用**:它配在 uv 配置弹窗的「下载镜像」里(GitHub Release
|
||||
镜像根),uv 本体与 Python 解释器都从它派生(uv 地址 = `镜像根/astral-sh/uv/LatestRelease`,
|
||||
解释器地址 = `镜像根/astral-sh/python-build-standalone/releases/download`)。
|
||||
解释器这一步也可以单独覆盖:环境变量里就有一条 `UV_PYTHON_INSTALL_MIRROR`(默认跟随镜像根,
|
||||
改它的值即可换成别的发行源),它和 `UV_PYTHON_INSTALL_DIR`、`UV_CACHE_DIR` 一样是固定项,
|
||||
只能改值、不能删除。
|
||||
- uv 配置弹窗里还能配置 `UV_*` 环境变量(如 `UV_CACHE_DIR`、`UV_PYTHON_INSTALL_DIR`、`UV_HTTP_TIMEOUT`),
|
||||
会注入到每一次 uv 调用,并优先于镜像注入与进程环境。两个路径变量默认是**相对程序目录**的
|
||||
`python` / `uv-cache`(随包移动),也可以改成绝对路径。
|
||||
- Python 解释器的下载与查看也在 uv 配置弹窗里(`uv python install` / `uv python list`)——
|
||||
解释器属于 uv,只是建虚拟环境时要用;虚拟环境弹窗只从这份已下载列表里挑版本。
|
||||
- uv 不可用:保存任务时不会自动建环境,运行时会失败并提示先配置 uv。
|
||||
- 运行任务时会把平台 uv 目录加到 PATH 前面,并注入 `UV_PYTHON_INSTALL_DIR`、弹窗里配置的 `UV_*` 和默认 `UV_DEFAULT_INDEX`
|
||||
(任务自己配置的环境变量优先)。这样脚本里直接写 `uv ...` 用的也是平台这一份,装包默认走镜像;不会再注入 `PIP_*`。
|
||||
@@ -260,23 +267,30 @@ queued ──► running ──► succeeded / failed / timed_out / cancelled
|
||||
|
||||
## Web 界面
|
||||
|
||||
- **顶部品牌栏**:品牌 + 右侧 Rust 引擎在线状态与版本;页面级导航已去掉,配置都收到任务调度页的卡片里。
|
||||
- **三栏工作台**(任务调度页):左栏用户卡片 + 概览指标 + 调度摘要;中栏搜索筛选 + 按类型(脚本 / HTTP / 二进制)分组的任务卡片与分页;
|
||||
右栏 uv(版本号 / 缓存目录 / Python 目录)、镜像源、托管虚拟环境摘要——点「配置 / 管理」打开对应**弹窗**。
|
||||
- **任务卡片**:名称、任务标识、状态、Cron、下次触发,以及运行 / 日志 / 编辑 / 启停 / 删除。
|
||||
- **顶部品牌栏**:只保留品牌与副标题;引擎在线状态显示在左栏用户卡片底部(跟随健康检查)。
|
||||
页面级导航已去掉,配置都收到任务调度页的卡片里。
|
||||
- **三栏工作台**(任务调度页):左栏用户卡片 + 概览指标 + 调度摘要;中栏搜索筛选 + **任务卡片**(每个任务一张卡片,一次取最多 100 条,不分页);
|
||||
右栏 uv(版本号 / 解释器数量 / 缓存目录 / Python 目录 / 下载镜像)、镜像源、托管虚拟环境摘要——点「配置 / 管理」打开对应**弹窗**。
|
||||
- **任务卡片**:卡片顶部是任务名称(左侧主色竖线 + 整行标题,右上角是「详情」);内容区里是任务标识 + 类型标签、任务说明(没填时回退成执行目标)、Cron 与下次触发时间,以及状态和运行 / 日志 / 编辑 / 启停 / 删除。点「详情」打开只读详情弹窗(基本信息 / 执行配置 / 时间)。
|
||||
- **编辑器**:先选任务类型(脚本 / HTTP / 二进制),脚本类型再选语言(Python / Shell / Node / Lua),
|
||||
非 Python 语言会自动隐藏虚拟环境字段并调整脚本占位符;Cron 预览(实时计算未来 3/5/8/10 次)、
|
||||
时区下拉、虚拟环境状态条、调用预览;探测到虚拟环境会自动回填「虚拟环境目录」,仍可手动修改;
|
||||
缺少虚拟环境但项目有依赖文件时保存会自动用 uv 创建。解释器由语言自动选择,界面不再要求手填。
|
||||
- **目录选择**:页面内目录浏览器(盘符 → 逐级进入 → 使用此目录),同时保留手动输入。
|
||||
- **目录选择**:页面内目录浏览器(盘符 → 逐级进入 → 使用此目录),同时保留手动输入;
|
||||
uv 的「指定路径」用同一个浏览器选文件(`include_files=true`,只列 `.exe`)。
|
||||
- **日志弹窗**:运行历史筛选、SSE 实时日志、取消运行、下载日志、Escape/焦点陷阱。
|
||||
- **uv 配置弹窗**:图标标题 + 右侧规则提示 + 选项 chip 的版式;「程序目录 uv」与「指定路径」二选一,
|
||||
程序目录下没有可用 uv 时才出现「下载 uv」;`UV_*` 环境变量键值列表,底部是关闭(图标)/ 保存(整宽)。
|
||||
uv 来源不读系统 PATH。
|
||||
- **镜像源弹窗**:内置清华 TUNA / 阿里云 / 中科大 / PyPI 官方;可新增、编辑、启用停用、设为默认。
|
||||
只在**装包**(建环境 / 预装依赖)时生效,任务运行时不注入镜像变量。
|
||||
- **虚拟环境弹窗**:Python 版本(uv 下载到 `python\` 的解释器,可填版本下载)+ 托管环境
|
||||
(`data\envs\<名称>`,从已下载版本里选 Python、可选预装包、查看已装包)。
|
||||
- **uv 配置弹窗**:顶部状态条(版本 / 可执行文件 / uv 下载与解释器镜像地址 / 解释器与环境变量数量)+ 按配置顺序排的三段。
|
||||
① **uv 可执行文件**:单选「程序目录 uv / 指定路径」——程序目录只展示路径、不可编辑,缺失时才出现「下载 uv」;
|
||||
指定路径是可编辑输入框,右侧「浏览」(页面内文件浏览器挑 `uv.exe`,保存时校验能否运行);下面是「下载镜像」
|
||||
(GitHub Release 镜像根,uv 与解释器共用);② **环境变量**:`UV_PYTHON_INSTALL_DIR` / `UV_CACHE_DIR` 决定目录,
|
||||
`UV_PYTHON_INSTALL_MIRROR` 决定解释器下载源,这三项只能改值、不能删除(清空或重复会拦下保存),
|
||||
其余 `UV_*` 变量可增删;③ **Python 解释器**:按上面配置的目录列出已下载版本,填版本号即下载。
|
||||
底部关闭 / 保存设置;uv 来源不读系统 PATH。
|
||||
- **镜像源弹窗**:只放 PyPI 包索引镜像:内置清华 TUNA / 阿里云 / 中科大 / PyPI 官方;
|
||||
可新增、编辑、启用停用、设为默认。包索引只在**装包**(建环境 / 预装依赖)时生效,任务运行时不注入镜像变量。
|
||||
uv 自己的下载镜像不在这里,在 uv 配置弹窗里。
|
||||
- **虚拟环境弹窗**:只负责**使用**已下载的解释器创建托管环境
|
||||
(`data\envs\<名称>`,从已下载版本里选 Python、可选预装包、查看已装包);解释器下载入口在 uv 配置弹窗。
|
||||
- **设计系统**:按钮、输入框、表格、标签、分页、弹窗、Toast、排版全部来自
|
||||
`@stash/ui`(同级目录 `../stash-ui`,`file:` 依赖);本仓库只保留页面级布局与
|
||||
业务块(三栏工作台、任务卡片、日志控制台、表单栅格)。
|
||||
@@ -332,7 +346,7 @@ queued ──► running ──► succeeded / failed / timed_out / cancelled
|
||||
| `POST` | `/api/v1/cron/preview` | 计算未来若干次触发时间 |
|
||||
| `POST` | `/api/v1/python/venv` | 探测工作目录会关联的虚拟环境、uv 可用性与依赖文件 |
|
||||
| `POST` | `/api/v1/python/venv/create` | 用 uv 创建/同步项目 `.venv`(优先复用已有环境) |
|
||||
| `GET` | `/api/v1/system/directories?path=` | 列盘符或子文件夹(目录浏览器) |
|
||||
| `GET` | `/api/v1/system/directories?path=` | 列盘符或子文件夹(目录浏览器);`include_files=true` 时一并返回文件 |
|
||||
| `GET` | `/api/v1/stats` | 仪表盘计数(总数/启用/运行中/三类数量) |
|
||||
| `GET` | `/api/v1/schedule` | 已启用任务的下次触发时间 |
|
||||
|
||||
@@ -352,8 +366,8 @@ queued ──► running ──► succeeded / failed / timed_out / cancelled
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `/api/v1/uv` | 当前 uv 来源、可执行文件、版本、`UV_*` 环境变量与解析后的 `python_dir` / `cache_dir`、两个路径变量的相对默认值 |
|
||||
| `PUT` | `/api/v1/uv` | 保存 uv 来源(`managed` / `custom`)、自定义路径和 `extra_env`;`custom` 会校验可运行 |
|
||||
| `POST` | `/api/v1/uv/download` | 从科大镜像下载当前架构的 uv、校验 sha256、安装到程序目录 `uv\` 并切换过去 |
|
||||
| `PUT` | `/api/v1/uv` | 保存 uv 来源(`managed` / `custom`)、自定义路径、`extra_env` 和下载镜像根(`mirror_url`,空 = 内置默认);`custom` 会校验可运行 |
|
||||
| `POST` | `/api/v1/uv/download` | 从下载镜像取当前架构的 uv、校验 sha256、安装到程序目录 `uv\` 并切换过去 |
|
||||
|
||||
### 托管虚拟环境
|
||||
|
||||
@@ -397,6 +411,7 @@ Content-Type: application/json
|
||||
{
|
||||
"id": "sync_data",
|
||||
"name": "数据同步",
|
||||
"description": "把上游数据同步到本地仓库,失败时记录日志",
|
||||
"cron": "0 0 2 * * *",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"enabled": true,
|
||||
@@ -447,7 +462,7 @@ Content-Type: application/json
|
||||
| `cron` | 可解析且存在未来触发点 |
|
||||
| `timezone` | 有效 IANA 名称 |
|
||||
| `timeout_secs` | 1–86400 |
|
||||
| `retry_policy` | `max_attempts ≤ 10`,`delay_secs ≤ 3600` |
|
||||
| `description` | ≤500 字符,可留空 |
|
||||
| `action.working_dir` / `script` | 非空 |
|
||||
| `action.language` | `python` / `shell` / `node` / `lua`;可省略,省略时按解释器推断 |
|
||||
| `action.interpreter` | 可空;空时按语言默认(Python 为 `python.exe`,Shell 为 `cmd.exe`,Node 为 `node.exe`,Lua 为 `lua.exe`) |
|
||||
@@ -509,7 +524,7 @@ React (Vite) ──fetch──► Axum router ──security::api_guard(Origin +
|
||||
| `python_envs.rs` | 托管虚拟环境与 uv Python 版本:`data/envs` 扫描、创建/删除、已装包 |
|
||||
| `uv.rs` | uv 来源解析与校验、镜像下载(sha256)、`UV_*` 环境变量与路径默认值 |
|
||||
| `python_mirrors/` | PyPI 镜像的模型、校验、CRUD 与默认项事务 |
|
||||
| `directory_browser.rs` | 盘符与目录列举(页面内目录选择器) |
|
||||
| `directory_browser.rs` | 盘符与目录列举(页面内目录选择器,可选返回文件) |
|
||||
| `security.rs` | Origin 白名单、令牌校验、常量时间比较 |
|
||||
| `database.rs` | 连接池与迁移(WAL/busy_timeout) |
|
||||
|
||||
@@ -518,15 +533,14 @@ React (Vite) ──fetch──► Axum router ──security::api_guard(Origin +
|
||||
| 模块 | 职责 |
|
||||
| --- | --- |
|
||||
| `@stash/ui`(同级 `../stash-ui`) | 设计令牌与通用组件:按钮、输入、表格、标签、弹窗、分页、Toast、排版 |
|
||||
| `components/AppHeader.tsx` | 顶部品牌栏、引擎状态 |
|
||||
| `pages/TaskDashboard.tsx` | 三栏工作台:概览/调度、任务分组、uv/镜像/环境卡片与三个配置弹窗 |
|
||||
| `components/UvSettingsSection.tsx` | uv 弹窗内容:来源 chip、下载、`UV_*` 环境变量 |
|
||||
| `components/PythonRuntimeSection.tsx` | 虚拟环境弹窗内容:Python 版本列表与下载 |
|
||||
| `components/PythonEnvsSection.tsx` | 虚拟环境弹窗内容:环境列表、创建、已装包 |
|
||||
| `components/AppHeader.tsx` | 顶部品牌栏 |
|
||||
| `pages/TaskDashboard.tsx` | 三栏工作台:概览/调度、任务卡片、uv/镜像/环境卡片与三个配置弹窗 |
|
||||
| `components/UvSettingsModal.tsx` | uv 弹窗:状态条、来源选择、下载镜像、Python 解释器下载与列表、`UV_*` 环境变量 |
|
||||
| `components/PythonEnvsSection.tsx` | 虚拟环境弹窗:用已下载解释器创建环境、环境列表、已装包 |
|
||||
| `components/PythonMirrorsSection.tsx` | 镜像源弹窗内容:列表、默认项、新增 / 编辑 / 删除 |
|
||||
| `components/PythonMirrorModal.tsx` | 镜像表单(名称 / 索引地址 / 信任主机 / 启用) |
|
||||
| `components/TaskEditorModal.tsx` | 三类动态表单、Cron 预览、虚拟环境状态条 |
|
||||
| `components/DirectoryPickerDialog.tsx` | 页面内目录浏览器 |
|
||||
| `components/DirectoryPickerDialog.tsx` | 页面内目录浏览器(`mode="file"` 时为文件选择器,用于挑 `uv.exe`) |
|
||||
| `components/RunLogDrawer.tsx` | 运行历史、日志、取消、下载、SSE |
|
||||
| `components/LogViewer.tsx` | 日志虚拟滚动、跟随底部/回到最新 |
|
||||
| `lib/logLines.ts` | 增量日志的行缓冲(残行拼接、CRLF 归一) |
|
||||
@@ -592,15 +606,12 @@ uv run --with fonttools --with brotli tools/build-fonts.py
|
||||
|
||||
| 层 | 覆盖 |
|
||||
| --- | --- |
|
||||
| Rust 单元(73) | Cron、虚拟环境解析与探测、项目依赖文件扫描、uv 来源解析/校验/压缩包解压/`UV_*` 环境变量校验、托管环境名校验、PATH 注入去重、执行器(编码/截断/清理/句柄归属/日志合并推送)、安全、目录列举 |
|
||||
| Rust 集成(48) | 任务 CRUD 与校验、运行生命周期、取消与超时、日志分块、调度恢复、统计与调度接口、重启对账、镜像 CRUD、uv 设置读写与下载鉴权、托管环境校验与 404、venv 探测/创建、无环境运行提示、Shell 脚本执行 |
|
||||
| 前端 Vitest(37) | 三栏工作台渲染、保存 uv 来源与 `UV_*` 环境变量、镜像下载、Python 版本下载、托管环境创建与包列表、新建任务前先建虚拟环境、表单载荷与脚本语言、虚拟环境状态、uv 创建入口、目录浏览器、下次触发时间、日志行切分/窗口计算/虚拟滚动 |
|
||||
| Rust 单元(78) | Cron、虚拟环境解析与探测、项目依赖文件扫描、uv 来源解析/校验/压缩包解压/镜像地址派生/`UV_*` 环境变量校验、托管环境名校验、PATH 注入去重、执行器(编码/截断/清理/句柄归属/日志合并推送)、安全、目录列举 |
|
||||
| Rust 集成(50) | 任务 CRUD 与校验、运行生命周期、取消与超时、日志分块、调度恢复、统计与调度接口、重启对账、镜像 CRUD、uv 设置(含下载镜像)读写与下载鉴权、托管环境校验与 404、venv 探测/创建、无环境运行提示、Shell 脚本执行 |
|
||||
| 前端 Vitest(40) | 三栏工作台渲染、任务详情弹窗、uv 弹窗(保存来源与 `UV_*`、下载镜像、下载 uv、浏览选 `uv.exe`、下载 Python 版本)、镜像下载、托管环境创建与包列表、新建任务前先建虚拟环境、表单载荷与脚本语言、虚拟环境状态、uv 创建入口、目录浏览器、下次触发时间、日志行切分/窗口计算/虚拟滚动 |
|
||||
| Playwright(8) | 真实任务执行与日志、编辑/停用/删除、脚本语言切换、目录切换单状态、窄屏、**无外部网络渲染**、1500 行日志的虚拟滚动、镜像增删与设默认 |
|
||||
| Python 自检(14 项) | `tools/verify.py` 覆盖 API 全链路 |
|
||||
|
||||
> 前端 Vitest 里 6 个用例仍在按已删除的顶部导航点击(uv / 虚拟环境相关),
|
||||
> 需要改成点右栏卡片并断言弹窗内容后才能全绿;改动停在 UI 调整阶段,尚未修。
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
@@ -621,8 +632,7 @@ uv run --with fonttools --with brotli tools/build-fonts.py
|
||||
|
||||
## 已知限制
|
||||
|
||||
- 自动重试尚未实现:`retry_policy` 只校验与存储。
|
||||
- 并发策略只有 `skip`;没有全局并发上限与队列。
|
||||
- 同一任务重叠触发时直接记为 `skipped`;没有全局并发上限与队列。
|
||||
- HTTP Action 只校验状态码,不支持响应体断言、认证方案或 Header 断言。
|
||||
- 敏感值脱敏基于变量名关键词与值替换,不是凭据保险箱。
|
||||
- Conda 环境不会自动激活,需要显式填写解释器路径。
|
||||
|
||||
Reference in New Issue
Block a user