feat(server): operator removal of stale sessions/machines (SPEC-004 Task 5, server)
All checks were successful
All checks were successful
Admin-gated soft-delete + purge so operators can clear ghost machines/sessions (the ~15-rows-for-one-host accumulation) from the console. - migration 009: deleted_at on connect_sessions + connect_machines, with partial indexes WHERE deleted_at IS NULL. - DELETE /api/machines/:agent_id?purge=true and DELETE /api/sessions/:id?purge=true soft-delete the row and purge the in-memory session (remove_session); the non-purge path keeps the legacy hard-delete / live-only disconnect. POST /api/machines/bulk-remove handles multi-select (batch cap 500). All admin-gated (AdminUser -> 403; tightens the prior any-user delete) and audited to connect_session_events (actor + target + trusted client IP). - list/get queries filter deleted_at IS NULL so removed units leave the console; upsert revives (deleted_at = NULL) a genuinely-reconnecting machine. The keyed-reattach identity resolver (get_machine_by_id) is intentionally unfiltered. Dashboard removal UI is the A3b follow-up. 86 server tests pass; fmt/clippy/test clean. Implements specs/v2-stable-identity/plan.md Task 5 (server portion). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ pub mod changelog;
|
||||
pub mod downloads;
|
||||
pub mod machine_keys;
|
||||
pub mod releases;
|
||||
pub mod removal;
|
||||
pub mod sessions;
|
||||
pub mod users;
|
||||
|
||||
@@ -172,7 +173,7 @@ impl From<db::sessions::DbSession> for SessionRecord {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct EventRecord {
|
||||
pub id: i64,
|
||||
pub session_id: String,
|
||||
pub session_id: Option<String>,
|
||||
pub event_type: String,
|
||||
pub timestamp: String,
|
||||
pub viewer_id: Option<String>,
|
||||
@@ -185,7 +186,7 @@ impl From<db::events::SessionEvent> for EventRecord {
|
||||
fn from(e: db::events::SessionEvent) -> Self {
|
||||
Self {
|
||||
id: e.id,
|
||||
session_id: e.session_id.to_string(),
|
||||
session_id: e.session_id.map(|id| id.to_string()),
|
||||
event_type: e.event_type,
|
||||
timestamp: e.timestamp.to_rfc3339(),
|
||||
viewer_id: e.viewer_id,
|
||||
@@ -208,12 +209,17 @@ pub struct MachineHistory {
|
||||
/// Query parameters for machine deletion
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DeleteMachineParams {
|
||||
/// If true, send uninstall command to agent (if online)
|
||||
/// If true, send uninstall command to agent (if online). Legacy (non-purge) path.
|
||||
#[serde(default)]
|
||||
pub uninstall: bool,
|
||||
/// If true, include history in response before deletion
|
||||
/// If true, include history in response before deletion. Legacy (non-purge) path.
|
||||
#[serde(default)]
|
||||
pub export: bool,
|
||||
/// If true, take the Task-5 SOFT-DELETE path: set `connect_machines.deleted_at`,
|
||||
/// drop the live in-memory session, and audit the removal — instead of the legacy
|
||||
/// hard delete. This is the operator-removal mechanism that purges ghost rows.
|
||||
#[serde(default)]
|
||||
pub purge: bool,
|
||||
}
|
||||
|
||||
/// Response for machine deletion
|
||||
|
||||
Reference in New Issue
Block a user