add graceful checkbox to restart confirm dialog
Restart now offers the same graceful-vs-hard choice stop already has: single-agent menu item and the bulk-select action bar both grow a 'restart gracefully' checkbox that routes through the existing submit::graceful_restart DAG (signal -> drain -> stop -> reconcile) instead of a hard restart. Backend gains a ?graceful=true query param on POST /api/restart/<name>, mirroring post_kill's shape (renamed KillParams -> GracefulParams since it's now shared).
This commit is contained in:
parent
207b66e35d
commit
d8567fc546
2 changed files with 27 additions and 8 deletions
|
|
@ -244,7 +244,7 @@ function buildAgentMenu(c, forgeBase) {
|
||||||
danger: true,
|
danger: true,
|
||||||
confirmLabel: opts.confirmLabel || 'confirm',
|
confirmLabel: opts.confirmLabel || 'confirm',
|
||||||
checkboxes: opts.graceful
|
checkboxes: opts.graceful
|
||||||
? [{ name: 'graceful', label: 'stop gracefully — let the agent finish its turn and flush state before the container stops' }]
|
? [{ name: 'graceful', label: opts.gracefulLabel || 'stop gracefully — let the agent finish its turn and flush state before the container stops' }]
|
||||||
: [],
|
: [],
|
||||||
});
|
});
|
||||||
if (!r) return;
|
if (!r) return;
|
||||||
|
|
@ -277,7 +277,12 @@ function buildAgentMenu(c, forgeBase) {
|
||||||
// Show only actions that are applicable in the current state.
|
// Show only actions that are applicable in the current state.
|
||||||
if (c.running) {
|
if (c.running) {
|
||||||
dropdown.append(
|
dropdown.append(
|
||||||
menuItem('↺ R3ST4RT', { action: '/api/restart/', confirm: `restart ${c.name}?` }),
|
menuItem('↺ R3ST4RT', {
|
||||||
|
action: '/api/restart/',
|
||||||
|
confirm: `restart ${c.name}?`,
|
||||||
|
graceful: true,
|
||||||
|
gracefulLabel: 'restart gracefully — let the agent finish its turn and flush state before the container restarts',
|
||||||
|
}),
|
||||||
menuItem('■ ST0P', { action: '/api/kill/', confirm: `stop ${c.name}?`, confirmLabel: '■ stop', graceful: true }),
|
menuItem('■ ST0P', { action: '/api/kill/', confirm: `stop ${c.name}?`, confirmLabel: '■ stop', graceful: true }),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -929,6 +934,8 @@ export function renderSelectionBar(containers) {
|
||||||
addBulkButton(actions, 'btn-restart', '↺ R3ST4RT', allRunning, selected, {
|
addBulkButton(actions, 'btn-restart', '↺ R3ST4RT', allRunning, selected, {
|
||||||
action: '/api/restart/',
|
action: '/api/restart/',
|
||||||
confirm: (names) => `restart ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`,
|
confirm: (names) => `restart ${names.length} agent${names.length === 1 ? '' : 's'} (${names.join(', ')})?`,
|
||||||
|
graceful: true,
|
||||||
|
gracefulLabel: 'restart gracefully — let each agent finish its turn and flush state before the container restarts',
|
||||||
disabledTitle: why('↺ R3ST4RT', stoppedNames.map((n) => `\`${n}\` is stopped`)),
|
disabledTitle: why('↺ R3ST4RT', stoppedNames.map((n) => `\`${n}\` is stopped`)),
|
||||||
});
|
});
|
||||||
addBulkButton(actions, 'btn-stop', '■ ST0P', allRunning, selected, {
|
addBulkButton(actions, 'btn-stop', '■ ST0P', allRunning, selected, {
|
||||||
|
|
@ -1106,7 +1113,7 @@ function addBulkButton(parent, btnClass, label, enabled, selected, opts) {
|
||||||
danger: true,
|
danger: true,
|
||||||
confirmLabel: opts.confirmLabel || 'confirm',
|
confirmLabel: opts.confirmLabel || 'confirm',
|
||||||
checkboxes: opts.graceful
|
checkboxes: opts.graceful
|
||||||
? [{ name: 'graceful', label: 'stop gracefully — let each agent finish its turn and flush state before the container stops' }]
|
? [{ name: 'graceful', label: opts.gracefulLabel || 'stop gracefully — let each agent finish its turn and flush state before the container stops' }]
|
||||||
: [],
|
: [],
|
||||||
});
|
});
|
||||||
if (!r) return;
|
if (!r) return;
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,12 @@ use axum::{
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
/// Query params for `post_kill`. `?graceful=1` routes to the graceful-stop
|
/// Query params for `post_kill` / `post_restart`. `?graceful=1` routes to
|
||||||
/// orchestration (quiesce the harness, flush `/state`, then container stop)
|
/// the graceful-stop/-restart orchestration (quiesce the harness, flush
|
||||||
/// instead of an immediate hard stop. Defaults false → today's hard kill.
|
/// `/state`, then container stop/restart) instead of an immediate hard
|
||||||
|
/// action. Defaults false → today's hard kill/restart.
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub(super) struct KillParams {
|
pub(super) struct GracefulParams {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
graceful: bool,
|
graceful: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +49,7 @@ pub(super) async fn post_rebuild(
|
||||||
pub(super) async fn post_kill(
|
pub(super) async fn post_kill(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
AxumPath(name): AxumPath<String>,
|
AxumPath(name): AxumPath<String>,
|
||||||
Query(params): Query<KillParams>,
|
Query(params): Query<GracefulParams>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let logical = strip_container_prefix(&name);
|
let logical = strip_container_prefix(&name);
|
||||||
if let Some(reject) = guard_agent_name(&state, &logical).await {
|
if let Some(reject) = guard_agent_name(&state, &logical).await {
|
||||||
|
|
@ -92,11 +93,22 @@ pub(super) async fn post_kill(
|
||||||
pub(super) async fn post_restart(
|
pub(super) async fn post_restart(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
AxumPath(name): AxumPath<String>,
|
AxumPath(name): AxumPath<String>,
|
||||||
|
Query(params): Query<GracefulParams>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let logical = strip_container_prefix(&name);
|
let logical = strip_container_prefix(&name);
|
||||||
if let Some(reject) = guard_agent_name(&state, &logical).await {
|
if let Some(reject) = guard_agent_name(&state, &logical).await {
|
||||||
return reject;
|
return reject;
|
||||||
}
|
}
|
||||||
|
if params.graceful {
|
||||||
|
submit::graceful_restart(
|
||||||
|
&state.coord,
|
||||||
|
&logical,
|
||||||
|
Source::Manual,
|
||||||
|
"manual via dashboard graceful restart".to_owned(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
return (StatusCode::OK, "ok").into_response();
|
||||||
|
}
|
||||||
submit::restart(
|
submit::restart(
|
||||||
&state.coord,
|
&state.coord,
|
||||||
&logical,
|
&logical,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue