use crate::control::command::{CommandResult, CurrentState};
use crate::control::outcome::{
ChildAttemptStatus, ChildControlFailure, ChildControlFailurePhase, ChildControlOperation,
ChildControlResult as RuntimeChildControlResult, ChildLivenessState, ChildRuntimeRecord,
ChildStopState, GenerationFenceDecision, GenerationFenceOutcome, GenerationFencePhase,
PendingRestartSummary, RestartLimitState,
};
use crate::readiness::signal::ReadinessState;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SupportedCommand {
pub name: String,
pub idempotent: bool,
pub timeout_seconds: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TargetProcessRegistration {
pub target_id: String,
pub display_name: String,
pub ipc_path: String,
pub lease_seconds: u64,
pub supported_commands: Vec<SupportedCommand>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum RegistrationState {
Active,
Rejected,
Expired,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TargetConnectionState {
Registered,
Connecting,
Connected,
Reconnecting,
Unavailable,
Expired,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TargetProcessIdentity {
pub target_id: String,
pub display_name: String,
pub registration_state: RegistrationState,
pub connection_state: TargetConnectionState,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardState {
pub target: TargetProcessIdentity,
pub topology: SupervisorTopology,
pub runtime_state: Vec<RuntimeState>,
pub child_runtime_records: Vec<DashboardChildRuntimeRecord>,
pub recent_events: Vec<EventRecord>,
pub recent_logs: Vec<LogRecord>,
pub dropped_event_count: u64,
pub dropped_log_count: u64,
pub config_version: String,
pub generated_at_unix_nanos: u128,
pub state_generation: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SupervisorTopology {
pub root: SupervisorNode,
pub nodes: Vec<SupervisorNode>,
pub edges: Vec<SupervisorEdge>,
pub declaration_order: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SupervisorNodeKind {
RootSupervisor,
ChildTask,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardCriticality {
Critical,
Standard,
BestEffort,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SupervisorNode {
pub node_id: String,
pub child_id: Option<String>,
pub path: String,
pub name: String,
pub kind: SupervisorNodeKind,
pub tags: Vec<String>,
pub criticality: DashboardCriticality,
pub state_summary: String,
pub diagnostics: BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SupervisorEdgeKind {
ParentChild,
Dependency,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SupervisorEdge {
pub edge_id: String,
pub source_path: String,
pub target_path: String,
pub kind: SupervisorEdgeKind,
pub order: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct RuntimeState {
pub child_path: String,
pub lifecycle_state: String,
pub health: String,
pub readiness: String,
pub generation: u64,
pub child_start_count: u64,
pub restart_count: u64,
pub last_failure: Option<String>,
pub last_policy_decision: Option<String>,
pub shutdown_state: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardManagedChildState {
Running,
Paused,
Quarantined,
Removed,
}
impl From<ChildControlOperation> for DashboardManagedChildState {
fn from(value: ChildControlOperation) -> Self {
match value {
ChildControlOperation::Active => Self::Running,
ChildControlOperation::Paused => Self::Paused,
ChildControlOperation::Quarantined => Self::Quarantined,
ChildControlOperation::Removed => Self::Removed,
}
}
}
impl DashboardManagedChildState {
pub fn as_label(&self) -> &'static str {
match self {
Self::Running => "running",
Self::Paused => "paused",
Self::Quarantined => "quarantined",
Self::Removed => "removed",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardChildControlOperation {
Active,
Paused,
Quarantined,
Removed,
}
impl From<ChildControlOperation> for DashboardChildControlOperation {
fn from(value: ChildControlOperation) -> Self {
match value {
ChildControlOperation::Active => Self::Active,
ChildControlOperation::Paused => Self::Paused,
ChildControlOperation::Quarantined => Self::Quarantined,
ChildControlOperation::Removed => Self::Removed,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardChildAttemptStatus {
Starting,
Running,
Ready,
Cancelling,
Stopped,
}
impl From<ChildAttemptStatus> for DashboardChildAttemptStatus {
fn from(value: ChildAttemptStatus) -> Self {
match value {
ChildAttemptStatus::Starting => Self::Starting,
ChildAttemptStatus::Running => Self::Running,
ChildAttemptStatus::Ready => Self::Ready,
ChildAttemptStatus::Cancelling => Self::Cancelling,
ChildAttemptStatus::Stopped => Self::Stopped,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardChildStopState {
Idle,
NoActiveAttempt,
CancelDelivered,
Completed,
Failed,
}
impl From<ChildStopState> for DashboardChildStopState {
fn from(value: ChildStopState) -> Self {
match value {
ChildStopState::Idle => Self::Idle,
ChildStopState::NoActiveAttempt => Self::NoActiveAttempt,
ChildStopState::CancelDelivered => Self::CancelDelivered,
ChildStopState::Completed => Self::Completed,
ChildStopState::Failed => Self::Failed,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardReadinessState {
Unreported,
Ready,
NotReady,
}
impl From<ReadinessState> for DashboardReadinessState {
fn from(value: ReadinessState) -> Self {
match value {
ReadinessState::Unreported => Self::Unreported,
ReadinessState::Ready => Self::Ready,
ReadinessState::NotReady => Self::NotReady,
}
}
}
impl DashboardReadinessState {
pub fn as_label(&self) -> &'static str {
match self {
Self::Unreported => "unreported",
Self::Ready => "ready",
Self::NotReady => "not_ready",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardChildControlFailurePhase {
WaitCompletion,
}
impl From<ChildControlFailurePhase> for DashboardChildControlFailurePhase {
fn from(value: ChildControlFailurePhase) -> Self {
match value {
ChildControlFailurePhase::WaitCompletion => Self::WaitCompletion,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardChildLivenessState {
pub last_heartbeat_at_unix_nanos: Option<u128>,
pub heartbeat_stale: bool,
pub readiness: DashboardReadinessState,
}
impl DashboardChildLivenessState {
pub fn from_liveness(value: &ChildLivenessState) -> Self {
Self {
last_heartbeat_at_unix_nanos: value.last_heartbeat_at_unix_nanos,
heartbeat_stale: value.heartbeat_stale,
readiness: DashboardReadinessState::from(value.readiness),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardRestartLimitState {
pub window_millis: u128,
pub limit: u32,
pub used: u32,
pub remaining: u32,
pub exhausted: bool,
pub updated_at_unix_nanos: u128,
}
impl DashboardRestartLimitState {
pub fn from_restart_limit(value: &RestartLimitState) -> Self {
Self {
window_millis: value.window.as_millis(),
limit: value.limit,
used: value.used,
remaining: value.remaining,
exhausted: value.exhausted,
updated_at_unix_nanos: value.updated_at_unix_nanos,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardChildControlFailure {
pub phase: DashboardChildControlFailurePhase,
pub reason: String,
pub recoverable: bool,
}
impl DashboardChildControlFailure {
pub fn from_failure(value: &ChildControlFailure) -> Self {
Self {
phase: DashboardChildControlFailurePhase::from(value.phase),
reason: value.reason.clone(),
recoverable: value.recoverable,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardGenerationFencePhase {
Open,
WaitingForOldStop,
AbortingOld,
ReadyToStart,
Closed,
}
impl From<GenerationFencePhase> for DashboardGenerationFencePhase {
fn from(value: GenerationFencePhase) -> Self {
match value {
GenerationFencePhase::Open => Self::Open,
GenerationFencePhase::WaitingForOldStop => Self::WaitingForOldStop,
GenerationFencePhase::AbortingOld => Self::AbortingOld,
GenerationFencePhase::ReadyToStart => Self::ReadyToStart,
GenerationFencePhase::Closed => Self::Closed,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum DashboardGenerationFenceDecision {
StartedImmediately,
QueuedAfterStop,
AlreadyPending,
BlockedByShutdown,
Rejected,
}
impl From<GenerationFenceDecision> for DashboardGenerationFenceDecision {
fn from(value: GenerationFenceDecision) -> Self {
match value {
GenerationFenceDecision::StartedImmediately => Self::StartedImmediately,
GenerationFenceDecision::QueuedAfterStop => Self::QueuedAfterStop,
GenerationFenceDecision::AlreadyPending => Self::AlreadyPending,
GenerationFenceDecision::BlockedByShutdown => Self::BlockedByShutdown,
GenerationFenceDecision::Rejected => Self::Rejected,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardPendingRestartSummary {
pub old_generation: u64,
pub old_attempt: u64,
pub target_generation: u64,
}
impl From<&PendingRestartSummary> for DashboardPendingRestartSummary {
fn from(value: &PendingRestartSummary) -> Self {
Self {
old_generation: value.old_generation.value,
old_attempt: value.old_attempt.value,
target_generation: value.target_generation.value,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardGenerationFenceOutcome {
pub decision: DashboardGenerationFenceDecision,
pub old_generation: Option<u64>,
pub old_attempt: Option<u64>,
pub target_generation: Option<u64>,
pub cancel_delivered: bool,
pub abort_requested: bool,
pub conflict: Option<DashboardChildControlFailure>,
}
impl From<&GenerationFenceOutcome> for DashboardGenerationFenceOutcome {
fn from(outcome: &GenerationFenceOutcome) -> Self {
Self {
decision: outcome.decision.into(),
old_generation: outcome.old_generation.map(|generation| generation.value),
old_attempt: outcome.old_attempt.map(|attempt| attempt.value),
target_generation: outcome.target_generation.map(|generation| generation.value),
cancel_delivered: outcome.cancel_delivered,
abort_requested: outcome.abort_requested,
conflict: outcome
.conflict
.as_ref()
.map(DashboardChildControlFailure::from_failure),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardChildRuntimeRecord {
pub child_id: String,
pub child_path: String,
pub generation: Option<u64>,
pub attempt: Option<u64>,
pub status: Option<DashboardChildAttemptStatus>,
pub operation: DashboardChildControlOperation,
pub managed_child_state: DashboardManagedChildState,
pub liveness: DashboardChildLivenessState,
pub restart_limit: DashboardRestartLimitState,
pub stop_state: DashboardChildStopState,
pub failure: Option<DashboardChildControlFailure>,
pub generation_fence_phase: DashboardGenerationFencePhase,
pub pending_restart: Option<DashboardPendingRestartSummary>,
}
impl DashboardChildRuntimeRecord {
pub fn from_runtime_record(record: &ChildRuntimeRecord) -> Self {
Self {
child_id: record.child_id.to_string(),
child_path: record.path.to_string(),
generation: record.generation.map(|generation| generation.value),
attempt: record.attempt.map(|attempt| attempt.value),
status: record.status.map(DashboardChildAttemptStatus::from),
operation: DashboardChildControlOperation::from(record.operation),
managed_child_state: DashboardManagedChildState::from(record.operation),
liveness: DashboardChildLivenessState::from_liveness(&record.liveness),
restart_limit: DashboardRestartLimitState::from_restart_limit(&record.restart_limit),
stop_state: DashboardChildStopState::from(record.stop_state),
failure: record
.failure
.as_ref()
.map(DashboardChildControlFailure::from_failure),
generation_fence_phase: record.generation_fence_phase.into(),
pending_restart: record
.pending_restart
.as_ref()
.map(DashboardPendingRestartSummary::from),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardCurrentState {
pub child_count: usize,
pub shutdown_completed: bool,
pub child_runtime_records: Vec<DashboardChildRuntimeRecord>,
}
impl DashboardCurrentState {
pub fn from_current_state(state: &CurrentState) -> Self {
Self {
child_count: state.child_count,
shutdown_completed: state.shutdown_completed,
child_runtime_records: state
.child_runtime_records
.iter()
.map(DashboardChildRuntimeRecord::from_runtime_record)
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct DashboardChildControlResult {
pub child_id: String,
pub attempt: Option<u64>,
pub generation: Option<u64>,
pub operation_before: DashboardChildControlOperation,
pub operation_after: DashboardChildControlOperation,
pub managed_child_state_before: DashboardManagedChildState,
pub managed_child_state_after: DashboardManagedChildState,
pub status: Option<DashboardChildAttemptStatus>,
pub cancel_delivered: bool,
pub stop_state: DashboardChildStopState,
pub restart_limit: DashboardRestartLimitState,
pub liveness: DashboardChildLivenessState,
pub idempotent: bool,
pub failure: Option<DashboardChildControlFailure>,
pub generation_fence: Option<DashboardGenerationFenceOutcome>,
}
impl DashboardChildControlResult {
pub fn from_child_control_result(outcome: &RuntimeChildControlResult) -> Self {
Self {
child_id: outcome.child_id.to_string(),
attempt: outcome.attempt.map(|attempt| attempt.value),
generation: outcome.generation.map(|generation| generation.value),
operation_before: DashboardChildControlOperation::from(outcome.operation_before),
operation_after: DashboardChildControlOperation::from(outcome.operation_after),
managed_child_state_before: DashboardManagedChildState::from(outcome.operation_before),
managed_child_state_after: DashboardManagedChildState::from(outcome.operation_after),
status: outcome.status.map(DashboardChildAttemptStatus::from),
cancel_delivered: outcome.cancel_delivered,
stop_state: DashboardChildStopState::from(outcome.stop_state),
restart_limit: DashboardRestartLimitState::from_restart_limit(&outcome.restart_limit),
liveness: DashboardChildLivenessState::from_liveness(&outcome.liveness),
idempotent: outcome.idempotent,
failure: outcome
.failure
.as_ref()
.map(DashboardChildControlFailure::from_failure),
generation_fence: outcome
.generation_fence
.as_ref()
.map(DashboardGenerationFenceOutcome::from),
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DashboardCommandResult {
ChildAdded {
child_manifest: String,
},
ChildControl {
outcome: DashboardChildControlResult,
},
CurrentState {
state: DashboardCurrentState,
},
Shutdown {
result: Value,
},
}
impl DashboardCommandResult {
pub fn from_command_result(result: &CommandResult) -> Result<Self, serde_json::Error> {
match result {
CommandResult::ChildAdded { child_manifest } => Ok(Self::ChildAdded {
child_manifest: child_manifest.clone(),
}),
CommandResult::ChildControl { outcome } => Ok(Self::ChildControl {
outcome: DashboardChildControlResult::from_child_control_result(outcome),
}),
CommandResult::CurrentState { state } => Ok(Self::CurrentState {
state: DashboardCurrentState::from_current_state(state),
}),
CommandResult::Shutdown { result } => Ok(Self::Shutdown {
result: serde_json::to_value(result)?,
}),
}
}
}
pub fn dashboard_command_result_value(result: &CommandResult) -> Result<Value, serde_json::Error> {
serde_json::to_value(DashboardCommandResult::from_command_result(result)?)
}
pub fn runtime_state_from_child_runtime_record(
record: &ChildRuntimeRecord,
shutdown_completed: bool,
) -> RuntimeState {
let managed_child_state = DashboardManagedChildState::from(record.operation);
let readiness = DashboardReadinessState::from(record.liveness.readiness);
RuntimeState {
child_path: record.path.to_string(),
lifecycle_state: managed_child_state.as_label().to_owned(),
health: if record.liveness.heartbeat_stale {
"stale".to_owned()
} else {
"healthy".to_owned()
},
readiness: readiness.as_label().to_owned(),
generation: record
.generation
.map(|generation| generation.value)
.unwrap_or(0),
child_start_count: record.attempt.map(|attempt| attempt.value).unwrap_or(0),
restart_count: u64::from(record.restart_limit.used),
last_failure: record
.failure
.as_ref()
.map(|failure| failure.reason.clone()),
last_policy_decision: Some(format!(
"restart_limit_remaining={}",
record.restart_limit.remaining
)),
shutdown_state: if shutdown_completed {
"completed".to_owned()
} else {
"running".to_owned()
},
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct EventRecord {
pub target_id: String,
pub sequence: u64,
pub correlation_id: String,
pub event_type: String,
pub severity: String,
pub target_path: String,
pub child_id: Option<String>,
pub occurred_at_unix_nanos: u128,
pub config_version: String,
pub payload: Value,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct LogRecord {
pub target_id: String,
pub sequence: Option<u64>,
pub correlation_id: Option<String>,
pub severity: String,
pub message: String,
pub fields: BTreeMap<String, String>,
pub occurred_at_unix_nanos: u128,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ControlCommandKind {
RestartChild,
PauseChild,
ResumeChild,
QuarantineChild,
RemoveChild,
AddChild,
ShutdownTree,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ControlCommandTarget {
pub child_path: Option<String>,
pub child_manifest: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ControlCommandRequest {
pub command_id: String,
pub target_id: String,
pub command: ControlCommandKind,
pub target: ControlCommandTarget,
pub reason: String,
pub requested_by: String,
pub confirmed: bool,
pub requested_at_unix_nanos: u128,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ControlCommandResult {
pub command_id: String,
pub target_id: String,
pub accepted: bool,
pub status: String,
pub error: Option<crate::dashboard::error::DashboardError>,
pub state_delta: Option<Value>,
pub completed_at_unix_nanos: Option<u128>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AuditEvent {
pub audit_id: String,
pub identity: String,
pub target_id: String,
pub command_id: String,
pub command: ControlCommandKind,
pub target: ControlCommandTarget,
pub reason: String,
pub result: String,
pub occurred_at_unix_nanos: u128,
}