feat: 左侧导航改版与 Python 镜像管理

- 应用外壳换成 stash-ui Layout:左侧导航(任务调度 / Python 镜像)+ 页面标题区,窄屏折叠为顶部导航
- 新增 python_mirrors 表与 CRUD/设默认接口,内置清华、阿里云、中科大、PyPI 官方
- Python 脚本任务运行时注入默认镜像的 PIP_INDEX_URL / UV_INDEX_URL,任务自身环境变量优先
- 新增镜像管理页与表单,支持新增、编辑、启用/停用、设默认、删除
- 重新生成 OpenAPI 与 TS 类型;补齐后端镜像测试、前端单测与 e2e,修复窄屏页面被宽表格撑开
This commit is contained in:
2026-11-03 22:12:11 +08:00
parent d7a6e17531
commit 29fc579275
25 changed files with 2491 additions and 47 deletions
+286
View File
@@ -52,6 +52,54 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/v1/python/mirrors": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["list_python_mirrors"];
put?: never;
post: operations["create_python_mirror"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/python/mirrors/{id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["get_python_mirror"];
put: operations["update_python_mirror"];
post?: never;
delete: operations["delete_python_mirror"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/python/mirrors/{id}/default": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["set_default_python_mirror"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/python/venv": {
parameters: {
query?: never;
@@ -298,6 +346,12 @@ export interface components {
schemas: {
/** @enum {string} */
ConcurrencyPolicy: "skip";
CreatePythonMirrorRequest: {
enabled?: boolean;
index_url: string;
name: string;
trusted_host?: string | null;
};
CreateTaskRequest: {
action: components["schemas"]["TaskAction"];
concurrency_policy?: components["schemas"]["ConcurrencyPolicy"];
@@ -360,6 +414,22 @@ export interface components {
/** Format: int64 */
next_offset: number;
};
/** @description 一个 Python 包索引镜像(pip / uv)。 */
PythonMirrorResponse: {
/** Format: date-time */
created_at: string;
enabled: boolean;
id: string;
/** @description pip 的 `--index-url`,运行时注入 `PIP_INDEX_URL` */
index_url: string;
/** @description 当前默认镜像;同一时间最多一个 */
is_default: boolean;
name: string;
/** @description pip 的 `--trusted-host`;https 源通常为空 */
trusted_host?: string | null;
/** Format: date-time */
updated_at: string;
};
RetryPolicy: {
/** Format: int64 */
delay_secs?: number;
@@ -501,6 +571,12 @@ export interface components {
*/
total: number;
};
UpdatePythonMirrorRequest: {
enabled: boolean;
index_url: string;
name: string;
trusted_host?: string | null;
};
UpdateTaskRequest: {
action: components["schemas"]["TaskAction"];
concurrency_policy?: components["schemas"]["ConcurrencyPolicy"];
@@ -620,6 +696,216 @@ export interface operations {
};
};
};
list_python_mirrors: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Python package mirrors */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PythonMirrorResponse"][];
};
};
};
};
create_python_mirror: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["CreatePythonMirrorRequest"];
};
};
responses: {
/** @description Mirror created */
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PythonMirrorResponse"];
};
};
/** @description Validation failed */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
/** @description Mirror URL already exists */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
};
};
get_python_mirror: {
parameters: {
query?: never;
header?: never;
path: {
/** @description Mirror identifier */
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Mirror detail */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PythonMirrorResponse"];
};
};
/** @description Mirror not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
};
};
update_python_mirror: {
parameters: {
query?: never;
header?: never;
path: {
/** @description Mirror identifier */
id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UpdatePythonMirrorRequest"];
};
};
responses: {
/** @description Mirror updated */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PythonMirrorResponse"];
};
};
/** @description Validation failed */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
/** @description Mirror not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
/** @description Mirror URL already exists */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
};
};
delete_python_mirror: {
parameters: {
query?: never;
header?: never;
path: {
/** @description Mirror identifier */
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Mirror deleted */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
/** @description Mirror not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
};
};
set_default_python_mirror: {
parameters: {
query?: never;
header?: never;
path: {
/** @description Mirror identifier */
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Default mirror updated */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PythonMirrorResponse"];
};
};
/** @description Mirror not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponse"];
};
};
};
};
probe_python_venv: {
parameters: {
query?: never;