dashboard: operator-direct schedule submit + cancel endpoints (#444 step 3)
This commit is contained in:
parent
aa7d8d9c9a
commit
a31cd8bb15
2 changed files with 87 additions and 0 deletions
|
|
@ -74,6 +74,8 @@ pub async fn serve(port: u16, coord: Arc<Coordinator>) -> Result<()> {
|
||||||
.route("/request-spawn", post(post_request_spawn))
|
.route("/request-spawn", post(post_request_spawn))
|
||||||
.route("/op-send", post(post_op_send))
|
.route("/op-send", post(post_op_send))
|
||||||
.route("/meta-update", post(post_meta_update))
|
.route("/meta-update", post(post_meta_update))
|
||||||
|
.route("/api/schedules", get(api_schedules).post(post_schedule_new))
|
||||||
|
.route("/api/schedules/{id}/cancel", post(post_schedule_cancel))
|
||||||
.route("/dashboard/stream", get(dashboard_stream))
|
.route("/dashboard/stream", get(dashboard_stream))
|
||||||
.route("/dashboard/history", get(dashboard_history))
|
.route("/dashboard/history", get(dashboard_history))
|
||||||
// Anything not matched by the dynamic routes above falls
|
// Anything not matched by the dynamic routes above falls
|
||||||
|
|
@ -1389,6 +1391,84 @@ async fn api_reminders(State(state): State<AppState>) -> Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `GET /api/schedules` — snapshot of every schedule for the
|
||||||
|
/// scheduled-prompts tab (#444). Returns the wire shape directly
|
||||||
|
/// so the frontend can render without an extra translation layer.
|
||||||
|
async fn api_schedules(State(state): State<AppState>) -> Response {
|
||||||
|
match state.coord.scheduled_prompts.list() {
|
||||||
|
Ok(rows) => axum::Json(
|
||||||
|
rows.into_iter()
|
||||||
|
.map(crate::manager_server::schedule_to_wire_public)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
Err(e) => error_response(&format!("scheduled_prompts list: {e:#}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `POST /api/schedules` — operator-direct schedule creation
|
||||||
|
/// (mara: "user can add them manually"). Accepts the same
|
||||||
|
/// `SchedulePromptPayload` shape as the manager request flow but
|
||||||
|
/// skips the approval gate — the operator click *is* the
|
||||||
|
/// approval. The schedule lands directly with
|
||||||
|
/// `source = Operator` and the worker picks it up at fire time.
|
||||||
|
async fn post_schedule_new(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
axum::Json(payload): axum::Json<hive_sh4re::SchedulePromptPayload>,
|
||||||
|
) -> Response {
|
||||||
|
if payload.targets.is_empty() {
|
||||||
|
return error_response("schedule must have at least one target");
|
||||||
|
}
|
||||||
|
if payload.body.trim().is_empty() {
|
||||||
|
return error_response("schedule body must be non-empty");
|
||||||
|
}
|
||||||
|
if let Some(0) = payload.interval_seconds {
|
||||||
|
return error_response("interval_seconds must be > 0 (use None for one-shot)");
|
||||||
|
}
|
||||||
|
let new = crate::scheduled_prompts::NewSchedule {
|
||||||
|
owner: hive_sh4re::OPERATOR_RECIPIENT.to_owned(),
|
||||||
|
targets: payload.targets,
|
||||||
|
body: payload.body,
|
||||||
|
first_fire_at_unix: payload.first_fire_at_unix,
|
||||||
|
interval_seconds: payload.interval_seconds,
|
||||||
|
description: payload.description,
|
||||||
|
source: crate::scheduled_prompts::ScheduleSource::Operator,
|
||||||
|
};
|
||||||
|
match state.coord.scheduled_prompts.submit(new) {
|
||||||
|
Ok(id) => axum::Json(serde_json::json!({"id": id})).into_response(),
|
||||||
|
Err(e) => error_response(&format!("schedule submit: {e:#}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Default)]
|
||||||
|
struct CancelScheduleForm {
|
||||||
|
/// `None` / absent / empty array → cancel whole schedule.
|
||||||
|
#[serde(default)]
|
||||||
|
targets: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `POST /api/schedules/{id}/cancel` — operator-side cancel
|
||||||
|
/// (whole schedule when no `targets` field, partial when one is
|
||||||
|
/// provided). Operator bypasses the topology check; the manager
|
||||||
|
/// surface enforces it for agent callers.
|
||||||
|
async fn post_schedule_cancel(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
AxumPath(id): AxumPath<i64>,
|
||||||
|
body: Option<axum::Json<CancelScheduleForm>>,
|
||||||
|
) -> Response {
|
||||||
|
let targets = body
|
||||||
|
.and_then(|axum::Json(b)| b.targets)
|
||||||
|
.filter(|t| !t.is_empty());
|
||||||
|
let result = match targets.as_deref() {
|
||||||
|
Some(list) => state.coord.scheduled_prompts.cancel_targets(id, list),
|
||||||
|
None => state.coord.scheduled_prompts.cancel_all(id),
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
Ok(()) => (StatusCode::OK, "ok").into_response(),
|
||||||
|
Err(e) => error_response(&format!("cancel schedule {id}: {e:#}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Same-origin proxy that fetches the named agent's
|
/// Same-origin proxy that fetches the named agent's
|
||||||
/// `GET /api/state` and forwards only the `links` field to the
|
/// `GET /api/state` and forwards only the `links` field to the
|
||||||
/// dashboard JS (issue #262). Lets the agent backend stay the
|
/// dashboard JS (issue #262). Lets the agent backend stay the
|
||||||
|
|
|
||||||
|
|
@ -841,6 +841,13 @@ fn cancel_authorized(requester: &str, owner: &str) -> bool {
|
||||||
/// Map a `scheduled_prompts::Schedule` to its public wire shape.
|
/// Map a `scheduled_prompts::Schedule` to its public wire shape.
|
||||||
/// Field-by-field copy — the two types are intentionally identical;
|
/// Field-by-field copy — the two types are intentionally identical;
|
||||||
/// the separation keeps hive-sh4re free of hive-c0re-internal types.
|
/// the separation keeps hive-sh4re free of hive-c0re-internal types.
|
||||||
|
/// Public alias `schedule_to_wire_public` re-exports for
|
||||||
|
/// `dashboard.rs::api_schedules` without crossing the module
|
||||||
|
/// boundary into the manager-server file.
|
||||||
|
pub fn schedule_to_wire_public(s: crate::scheduled_prompts::Schedule) -> hive_sh4re::WireSchedule {
|
||||||
|
schedule_to_wire(s)
|
||||||
|
}
|
||||||
|
|
||||||
fn schedule_to_wire(s: crate::scheduled_prompts::Schedule) -> hive_sh4re::WireSchedule {
|
fn schedule_to_wire(s: crate::scheduled_prompts::Schedule) -> hive_sh4re::WireSchedule {
|
||||||
hive_sh4re::WireSchedule {
|
hive_sh4re::WireSchedule {
|
||||||
id: s.id,
|
id: s.id,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue