feat: 加固运行期可靠性并补齐调度与观测接口
- 运行句柄按 run_id 归属,修复取消后重跑时闸门被旧运行删除的竞态 - 调度循环改用取消令牌退出,启动时对账遗留运行,校验 rows_affected - 三类任务统一参与调度,新增 /api/v1/stats 与 /api/v1/schedule - SQLite 启用 WAL 与 busy_timeout,日志清理跳过活跃文件,单行上限 256 KiB - 前端缓存管理令牌并在 403 时重试一次,仪表盘改用统计接口
This commit is contained in:
+53
-3
@@ -1,6 +1,6 @@
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::State,
|
||||
extract::{DefaultBodyLimit, State},
|
||||
http::{HeaderValue, Method, header},
|
||||
middleware,
|
||||
response::IntoResponse,
|
||||
@@ -31,8 +31,9 @@ use crate::{
|
||||
state::AppState,
|
||||
tasks::{
|
||||
model::{
|
||||
ConcurrencyPolicy, CreateTaskRequest, HttpMethod, RetryPolicy, TaskAction,
|
||||
TaskActionType, TaskListResponse, TaskResponse, UpdateTaskRequest,
|
||||
ConcurrencyPolicy, CreateTaskRequest, HttpMethod, RetryPolicy, ScheduledTaskResponse,
|
||||
TaskAction, TaskActionType, TaskListResponse, TaskResponse, TaskStatsResponse,
|
||||
UpdateTaskRequest,
|
||||
},
|
||||
routes::{
|
||||
__path_create_task, __path_delete_task, __path_disable_task, __path_enable_task,
|
||||
@@ -162,6 +163,47 @@ pub async fn list_directories(
|
||||
})
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/stats",
|
||||
tag = "tasks",
|
||||
responses((status = 200, description = "Dashboard counters", body = TaskStatsResponse))
|
||||
)]
|
||||
pub async fn task_stats(State(state): State<AppState>) -> ApiResult<Json<TaskStatsResponse>> {
|
||||
let counts = crate::tasks::repository::counts(&state.pool).await?;
|
||||
Ok(Json(TaskStatsResponse {
|
||||
total: counts.total,
|
||||
enabled: counts.enabled,
|
||||
running: u64::try_from(state.executor.running_count()).unwrap_or_default(),
|
||||
script: counts.script,
|
||||
http: counts.http,
|
||||
process: counts.process,
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/schedule",
|
||||
tag = "tasks",
|
||||
responses(
|
||||
(status = 200, description = "Next trigger time per enabled task", body = Vec<ScheduledTaskResponse>)
|
||||
)
|
||||
)]
|
||||
pub async fn list_schedule(
|
||||
State(state): State<AppState>,
|
||||
) -> ApiResult<Json<Vec<ScheduledTaskResponse>>> {
|
||||
let tasks = crate::tasks::repository::list_enabled_tasks(&state.pool).await?;
|
||||
Ok(Json(
|
||||
tasks
|
||||
.into_iter()
|
||||
.map(|task| ScheduledTaskResponse {
|
||||
next_run_at: state.scheduler.next_run_at(&task.id),
|
||||
task_id: task.id,
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
info(
|
||||
@@ -175,6 +217,8 @@ pub async fn list_directories(
|
||||
preview_cron,
|
||||
probe_python_venv,
|
||||
list_directories,
|
||||
task_stats,
|
||||
list_schedule,
|
||||
list_tasks,
|
||||
create_task,
|
||||
get_task,
|
||||
@@ -202,6 +246,8 @@ pub async fn list_directories(
|
||||
InterpreterKind,
|
||||
DirectoryEntry,
|
||||
DirectoryListingResponse,
|
||||
TaskStatsResponse,
|
||||
ScheduledTaskResponse,
|
||||
ErrorResponse,
|
||||
TaskActionType,
|
||||
HttpMethod,
|
||||
@@ -252,10 +298,14 @@ pub fn router(state: AppState) -> Router {
|
||||
axum::routing::post(probe_python_venv),
|
||||
)
|
||||
.route("/api/v1/system/directories", get(list_directories))
|
||||
.route("/api/v1/stats", get(task_stats))
|
||||
.route("/api/v1/schedule", get(list_schedule))
|
||||
.route("/api/v1/openapi.json", get(openapi_json))
|
||||
.merge(crate::tasks::routes::router())
|
||||
.merge(crate::runs::routes::router())
|
||||
.route("/api/{*path}", any(api_not_found))
|
||||
// 本地工具也不需要接受超大请求体;任务载荷与日志上传都用不到 2 MiB 以上。
|
||||
.layer(DefaultBodyLimit::max(2 * 1024 * 1024))
|
||||
.layer(cors)
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
|
||||
Reference in New Issue
Block a user