Add machine deletion API with uninstall command support

- Add AdminCommand message to protobuf (uninstall, restart, update)
- Add DELETE /api/machines/:agent_id endpoint with options:
  - ?uninstall=true - send uninstall command to online agent
  - ?export=true - return session history before deletion
- Add GET /api/machines/:agent_id/history endpoint for history export
- Add GET /api/machines endpoint to list all machines
- Handle AdminCommand in agent session handler
- Handle ADMIN_UNINSTALL error in agent main loop to trigger uninstall

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
AZ Computer Guru
2025-12-29 19:15:16 -07:00
parent 05ab8a8bf4
commit dc7b7427ce
8 changed files with 380 additions and 6 deletions

View File

@@ -1,13 +1,14 @@
//! REST API endpoints
use axum::{
extract::{Path, State},
extract::{Path, State, Query},
Json,
};
use serde::Serialize;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::session::SessionManager;
use crate::db;
/// Viewer info returned by API
#[derive(Debug, Serialize)]
@@ -88,3 +89,120 @@ pub async fn get_session(
Ok(Json(SessionInfo::from(session)))
}
// ============================================================================
// Machine API Types
// ============================================================================
/// Machine info returned by API
#[derive(Debug, Serialize)]
pub struct MachineInfo {
pub id: String,
pub agent_id: String,
pub hostname: String,
pub os_version: Option<String>,
pub is_elevated: bool,
pub is_persistent: bool,
pub first_seen: String,
pub last_seen: String,
pub status: String,
}
impl From<db::machines::Machine> for MachineInfo {
fn from(m: db::machines::Machine) -> Self {
Self {
id: m.id.to_string(),
agent_id: m.agent_id,
hostname: m.hostname,
os_version: m.os_version,
is_elevated: m.is_elevated,
is_persistent: m.is_persistent,
first_seen: m.first_seen.to_rfc3339(),
last_seen: m.last_seen.to_rfc3339(),
status: m.status,
}
}
}
/// Session record for history
#[derive(Debug, Serialize)]
pub struct SessionRecord {
pub id: String,
pub started_at: String,
pub ended_at: Option<String>,
pub duration_secs: Option<i32>,
pub is_support_session: bool,
pub support_code: Option<String>,
pub status: String,
}
impl From<db::sessions::DbSession> for SessionRecord {
fn from(s: db::sessions::DbSession) -> Self {
Self {
id: s.id.to_string(),
started_at: s.started_at.to_rfc3339(),
ended_at: s.ended_at.map(|t| t.to_rfc3339()),
duration_secs: s.duration_secs,
is_support_session: s.is_support_session,
support_code: s.support_code,
status: s.status,
}
}
}
/// Event record for history
#[derive(Debug, Serialize)]
pub struct EventRecord {
pub id: i64,
pub session_id: String,
pub event_type: String,
pub timestamp: String,
pub viewer_id: Option<String>,
pub viewer_name: Option<String>,
pub details: Option<serde_json::Value>,
pub ip_address: Option<String>,
}
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(),
event_type: e.event_type,
timestamp: e.timestamp.to_rfc3339(),
viewer_id: e.viewer_id,
viewer_name: e.viewer_name,
details: e.details,
ip_address: e.ip_address,
}
}
}
/// Full machine history (for export)
#[derive(Debug, Serialize)]
pub struct MachineHistory {
pub machine: MachineInfo,
pub sessions: Vec<SessionRecord>,
pub events: Vec<EventRecord>,
pub exported_at: String,
}
/// Query parameters for machine deletion
#[derive(Debug, Deserialize)]
pub struct DeleteMachineParams {
/// If true, send uninstall command to agent (if online)
#[serde(default)]
pub uninstall: bool,
/// If true, include history in response before deletion
#[serde(default)]
pub export: bool,
}
/// Response for machine deletion
#[derive(Debug, Serialize)]
pub struct DeleteMachineResponse {
pub success: bool,
pub message: String,
pub uninstall_sent: bool,
pub history: Option<MachineHistory>,
}