1use crate::control::command::{CommandResult, CurrentState};
8use crate::control::outcome::{
9 ChildAttemptStatus, ChildControlFailure, ChildControlFailurePhase, ChildControlOperation,
10 ChildControlResult as RuntimeChildControlResult, ChildLivenessState, ChildRuntimeRecord,
11 ChildStopState, GenerationFenceDecision, GenerationFenceOutcome, GenerationFencePhase,
12 PendingRestartSummary, RestartLimitState,
13};
14use crate::readiness::signal::ReadinessState;
15use schemars::JsonSchema;
16use serde::{Deserialize, Deserializer, Serialize, Serializer};
17use serde_json::Value;
18use std::collections::BTreeMap;
19
20pub fn serialize_nanos<S>(value: &u128, serializer: S) -> Result<S::Ok, S::Error>
24where
25 S: Serializer,
26{
27 serializer.serialize_str(&value.to_string())
28}
29
30pub fn deserialize_nanos<'de, D>(deserializer: D) -> Result<u128, D::Error>
32where
33 D: Deserializer<'de>,
34{
35 use serde::de::Error;
36
37 struct NanosVisitor;
38 impl<'de> serde::de::Visitor<'de> for NanosVisitor {
39 type Value = u128;
40
41 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 formatter.write_str("a nanosecond timestamp as string or integer")
44 }
45
46 fn visit_str<E: Error>(self, s: &str) -> Result<u128, E> {
48 s.parse::<u128>()
49 .map_err(|e| E::custom(format!("invalid nanos string: {e}")))
50 }
51
52 fn visit_u64<E: Error>(self, v: u64) -> Result<u128, E> {
54 Ok(v as u128)
55 }
56
57 fn visit_u128<E: Error>(self, v: u128) -> Result<u128, E> {
59 Ok(v)
60 }
61
62 fn visit_i64<E: Error>(self, v: i64) -> Result<u128, E> {
64 u128::try_from(v).map_err(|_| E::custom("negative timestamp is not valid for nanos"))
65 }
66 }
67
68 deserializer.deserialize_any(NanosVisitor)
69}
70
71pub fn serialize_nanos_opt<S>(value: &Option<u128>, serializer: S) -> Result<S::Ok, S::Error>
73where
74 S: Serializer,
75{
76 match value {
77 Some(v) => serializer.serialize_str(&v.to_string()),
78 None => serializer.serialize_none(),
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
84pub struct SupportedCommand {
85 pub name: String,
87 pub idempotent: bool,
89 pub timeout_seconds: u64,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
95pub struct TargetProcessRegistration {
96 pub target_id: String,
98 pub display_name: String,
100 pub ipc_path: String,
102 pub lease_seconds: u64,
104 pub supported_commands: Vec<SupportedCommand>,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
110#[serde(rename_all = "snake_case")]
111pub enum RegistrationState {
112 Active,
114 Rejected,
116 Expired,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
122#[serde(rename_all = "snake_case")]
123pub enum TargetConnectionState {
124 Registered,
126 Connecting,
128 Connected,
130 Reconnecting,
132 Unavailable,
134 Expired,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
140pub struct TargetProcessIdentity {
141 pub target_id: String,
143 pub display_name: String,
145 pub registration_state: RegistrationState,
147 pub connection_state: TargetConnectionState,
149}
150
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
153pub struct DashboardState {
154 pub target: TargetProcessIdentity,
156 pub topology: SupervisorTopology,
158 pub runtime_state: Vec<RuntimeState>,
160 pub child_runtime_records: Vec<DashboardChildRuntimeRecord>,
162 pub recent_events: Vec<EventRecord>,
164 pub recent_logs: Vec<LogRecord>,
166 pub dropped_event_count: u64,
168 pub dropped_log_count: u64,
170 pub config_version: String,
172 #[serde(serialize_with = "serialize_nanos")]
174 pub generated_at_unix_nanos: u128,
175 pub state_generation: u64,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
181pub struct SupervisorTopology {
182 pub root: SupervisorNode,
184 pub nodes: Vec<SupervisorNode>,
186 pub edges: Vec<SupervisorEdge>,
188 pub declaration_order: Vec<String>,
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
194#[serde(rename_all = "snake_case")]
195pub enum SupervisorNodeKind {
196 RootSupervisor,
198 ChildTask,
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
204#[serde(rename_all = "snake_case")]
205pub enum DashboardCriticality {
206 Critical,
208 Standard,
210 BestEffort,
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
216pub struct SupervisorNode {
217 pub node_id: String,
219 pub child_id: Option<String>,
221 pub path: String,
223 pub name: String,
225 pub kind: SupervisorNodeKind,
227 pub tags: Vec<String>,
229 pub criticality: DashboardCriticality,
231 pub state_summary: String,
233 pub diagnostics: BTreeMap<String, String>,
235}
236
237#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
239#[serde(rename_all = "snake_case")]
240pub enum SupervisorEdgeKind {
241 ParentChild,
243 Dependency,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
249pub struct SupervisorEdge {
250 pub edge_id: String,
252 pub source_path: String,
254 pub target_path: String,
256 pub kind: SupervisorEdgeKind,
258 pub order: usize,
260}
261
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
264pub struct RuntimeState {
265 pub child_path: String,
267 pub lifecycle_state: String,
269 pub health: String,
271 pub readiness: String,
273 pub generation: u64,
275 pub child_start_count: u64,
277 pub restart_count: u64,
279 pub last_failure: Option<String>,
281 pub last_policy_decision: Option<String>,
283 pub shutdown_state: String,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
289#[serde(rename_all = "snake_case")]
290pub enum DashboardManagedChildState {
291 Running,
293 Paused,
295 Quarantined,
297 Removed,
299}
300
301impl From<ChildControlOperation> for DashboardManagedChildState {
302 fn from(value: ChildControlOperation) -> Self {
304 match value {
305 ChildControlOperation::Active => Self::Running,
306 ChildControlOperation::Paused => Self::Paused,
307 ChildControlOperation::Quarantined => Self::Quarantined,
308 ChildControlOperation::Removed => Self::Removed,
309 }
310 }
311}
312
313impl DashboardManagedChildState {
314 pub fn as_label(&self) -> &'static str {
324 match self {
325 Self::Running => "running",
326 Self::Paused => "paused",
327 Self::Quarantined => "quarantined",
328 Self::Removed => "removed",
329 }
330 }
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
335#[serde(rename_all = "snake_case")]
336pub enum DashboardChildControlOperation {
337 Active,
339 Paused,
341 Quarantined,
343 Removed,
345}
346
347impl From<ChildControlOperation> for DashboardChildControlOperation {
348 fn from(value: ChildControlOperation) -> Self {
350 match value {
351 ChildControlOperation::Active => Self::Active,
352 ChildControlOperation::Paused => Self::Paused,
353 ChildControlOperation::Quarantined => Self::Quarantined,
354 ChildControlOperation::Removed => Self::Removed,
355 }
356 }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
361#[serde(rename_all = "snake_case")]
362pub enum DashboardChildAttemptStatus {
363 Starting,
365 Running,
367 Ready,
369 Cancelling,
371 Stopped,
373}
374
375impl From<ChildAttemptStatus> for DashboardChildAttemptStatus {
376 fn from(value: ChildAttemptStatus) -> Self {
378 match value {
379 ChildAttemptStatus::Starting => Self::Starting,
380 ChildAttemptStatus::Running => Self::Running,
381 ChildAttemptStatus::Ready => Self::Ready,
382 ChildAttemptStatus::Cancelling => Self::Cancelling,
383 ChildAttemptStatus::Stopped => Self::Stopped,
384 }
385 }
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
390#[serde(rename_all = "snake_case")]
391pub enum DashboardChildStopState {
392 Idle,
394 NoActiveAttempt,
396 CancelDelivered,
398 Completed,
400 Failed,
402}
403
404impl From<ChildStopState> for DashboardChildStopState {
405 fn from(value: ChildStopState) -> Self {
407 match value {
408 ChildStopState::Idle => Self::Idle,
409 ChildStopState::NoActiveAttempt => Self::NoActiveAttempt,
410 ChildStopState::CancelDelivered => Self::CancelDelivered,
411 ChildStopState::Completed => Self::Completed,
412 ChildStopState::Failed => Self::Failed,
413 }
414 }
415}
416
417#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
419#[serde(rename_all = "snake_case")]
420pub enum DashboardReadinessState {
421 Unreported,
423 Ready,
425 NotReady,
427}
428
429impl From<ReadinessState> for DashboardReadinessState {
430 fn from(value: ReadinessState) -> Self {
432 match value {
433 ReadinessState::Unreported => Self::Unreported,
434 ReadinessState::Ready => Self::Ready,
435 ReadinessState::NotReady => Self::NotReady,
436 }
437 }
438}
439
440impl DashboardReadinessState {
441 pub fn as_label(&self) -> &'static str {
451 match self {
452 Self::Unreported => "unreported",
453 Self::Ready => "ready",
454 Self::NotReady => "not_ready",
455 }
456 }
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
461#[serde(rename_all = "snake_case")]
462pub enum DashboardChildControlFailurePhase {
463 WaitCompletion,
465}
466
467impl From<ChildControlFailurePhase> for DashboardChildControlFailurePhase {
468 fn from(value: ChildControlFailurePhase) -> Self {
470 match value {
471 ChildControlFailurePhase::WaitCompletion => Self::WaitCompletion,
472 }
473 }
474}
475
476#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
478pub struct DashboardChildLivenessState {
479 #[serde(
481 default,
482 skip_serializing_if = "Option::is_none",
483 serialize_with = "serialize_nanos_opt"
484 )]
485 pub last_heartbeat_at_unix_nanos: Option<u128>,
488 pub heartbeat_stale: bool,
490 pub readiness: DashboardReadinessState,
492}
493
494impl DashboardChildLivenessState {
495 pub fn from_liveness(value: &ChildLivenessState) -> Self {
505 Self {
506 last_heartbeat_at_unix_nanos: value.last_heartbeat_at_unix_nanos,
507 heartbeat_stale: value.heartbeat_stale,
508 readiness: DashboardReadinessState::from(value.readiness),
509 }
510 }
511}
512
513#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
515pub struct DashboardRestartLimitState {
516 #[serde(serialize_with = "serialize_nanos")]
518 pub window_millis: u128,
519 pub limit: u32,
521 pub used: u32,
523 pub remaining: u32,
525 pub exhausted: bool,
527 #[serde(serialize_with = "serialize_nanos")]
529 pub updated_at_unix_nanos: u128,
530}
531
532impl DashboardRestartLimitState {
533 pub fn from_restart_limit(value: &RestartLimitState) -> Self {
543 Self {
544 window_millis: value.window.as_millis(),
545 limit: value.limit,
546 used: value.used,
547 remaining: value.remaining,
548 exhausted: value.exhausted,
549 updated_at_unix_nanos: value.updated_at_unix_nanos,
550 }
551 }
552}
553
554#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
556pub struct DashboardChildControlFailure {
557 pub phase: DashboardChildControlFailurePhase,
559 pub reason: String,
561 pub recoverable: bool,
563}
564
565impl DashboardChildControlFailure {
566 pub fn from_failure(value: &ChildControlFailure) -> Self {
576 Self {
577 phase: DashboardChildControlFailurePhase::from(value.phase),
578 reason: value.reason.clone(),
579 recoverable: value.recoverable,
580 }
581 }
582}
583
584#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
586#[serde(rename_all = "snake_case")]
587pub enum DashboardGenerationFencePhase {
588 Open,
590 WaitingForOldStop,
592 AbortingOld,
594 ReadyToStart,
596 Closed,
598}
599
600impl From<GenerationFencePhase> for DashboardGenerationFencePhase {
601 fn from(value: GenerationFencePhase) -> Self {
603 match value {
604 GenerationFencePhase::Open => Self::Open,
605 GenerationFencePhase::WaitingForOldStop => Self::WaitingForOldStop,
606 GenerationFencePhase::AbortingOld => Self::AbortingOld,
607 GenerationFencePhase::ReadyToStart => Self::ReadyToStart,
608 GenerationFencePhase::Closed => Self::Closed,
609 }
610 }
611}
612
613#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
615#[serde(rename_all = "snake_case")]
616pub enum DashboardGenerationFenceDecision {
617 StartedImmediately,
619 QueuedAfterStop,
621 AlreadyPending,
623 BlockedByShutdown,
625 Rejected,
627}
628
629impl From<GenerationFenceDecision> for DashboardGenerationFenceDecision {
630 fn from(value: GenerationFenceDecision) -> Self {
632 match value {
633 GenerationFenceDecision::StartedImmediately => Self::StartedImmediately,
634 GenerationFenceDecision::QueuedAfterStop => Self::QueuedAfterStop,
635 GenerationFenceDecision::AlreadyPending => Self::AlreadyPending,
636 GenerationFenceDecision::BlockedByShutdown => Self::BlockedByShutdown,
637 GenerationFenceDecision::Rejected => Self::Rejected,
638 }
639 }
640}
641
642#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
644pub struct DashboardPendingRestartSummary {
645 pub old_generation: u64,
647 pub old_attempt: u64,
649 pub target_generation: u64,
651}
652
653impl From<&PendingRestartSummary> for DashboardPendingRestartSummary {
654 fn from(value: &PendingRestartSummary) -> Self {
656 Self {
657 old_generation: value.old_generation.value,
658 old_attempt: value.old_attempt.value,
659 target_generation: value.target_generation.value,
660 }
661 }
662}
663
664#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
666pub struct DashboardGenerationFenceOutcome {
667 pub decision: DashboardGenerationFenceDecision,
669 pub old_generation: Option<u64>,
671 pub old_attempt: Option<u64>,
673 pub target_generation: Option<u64>,
675 pub cancel_delivered: bool,
677 pub abort_requested: bool,
679 pub conflict: Option<DashboardChildControlFailure>,
681}
682
683impl From<&GenerationFenceOutcome> for DashboardGenerationFenceOutcome {
684 fn from(outcome: &GenerationFenceOutcome) -> Self {
686 Self {
687 decision: outcome.decision.into(),
688 old_generation: outcome.old_generation.map(|generation| generation.value),
689 old_attempt: outcome.old_attempt.map(|attempt| attempt.value),
690 target_generation: outcome.target_generation.map(|generation| generation.value),
691 cancel_delivered: outcome.cancel_delivered,
692 abort_requested: outcome.abort_requested,
693 conflict: outcome
694 .conflict
695 .as_ref()
696 .map(DashboardChildControlFailure::from_failure),
697 }
698 }
699}
700
701#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
703pub struct DashboardChildRuntimeRecord {
704 pub child_id: String,
706 pub child_path: String,
708 pub generation: Option<u64>,
710 pub attempt: Option<u64>,
712 pub status: Option<DashboardChildAttemptStatus>,
714 pub operation: DashboardChildControlOperation,
716 pub managed_child_state: DashboardManagedChildState,
718 pub liveness: DashboardChildLivenessState,
720 pub restart_limit: DashboardRestartLimitState,
722 pub stop_state: DashboardChildStopState,
724 pub failure: Option<DashboardChildControlFailure>,
726 pub generation_fence_phase: DashboardGenerationFencePhase,
728 pub pending_restart: Option<DashboardPendingRestartSummary>,
730}
731
732impl DashboardChildRuntimeRecord {
733 pub fn from_runtime_record(record: &ChildRuntimeRecord) -> Self {
743 Self {
744 child_id: record.child_id.to_string(),
745 child_path: record.path.to_string(),
746 generation: record.generation.map(|generation| generation.value),
747 attempt: record.attempt.map(|attempt| attempt.value),
748 status: record.status.map(DashboardChildAttemptStatus::from),
749 operation: DashboardChildControlOperation::from(record.operation),
750 managed_child_state: DashboardManagedChildState::from(record.operation),
751 liveness: DashboardChildLivenessState::from_liveness(&record.liveness),
752 restart_limit: DashboardRestartLimitState::from_restart_limit(&record.restart_limit),
753 stop_state: DashboardChildStopState::from(record.stop_state),
754 failure: record
755 .failure
756 .as_ref()
757 .map(DashboardChildControlFailure::from_failure),
758 generation_fence_phase: record.generation_fence_phase.into(),
759 pending_restart: record
760 .pending_restart
761 .as_ref()
762 .map(DashboardPendingRestartSummary::from),
763 }
764 }
765}
766
767#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
769pub struct DashboardCurrentState {
770 pub child_count: usize,
772 pub shutdown_completed: bool,
774 pub child_runtime_records: Vec<DashboardChildRuntimeRecord>,
776}
777
778impl DashboardCurrentState {
779 pub fn from_current_state(state: &CurrentState) -> Self {
789 Self {
790 child_count: state.child_count,
791 shutdown_completed: state.shutdown_completed,
792 child_runtime_records: state
793 .child_runtime_records
794 .iter()
795 .map(DashboardChildRuntimeRecord::from_runtime_record)
796 .collect(),
797 }
798 }
799}
800
801#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
803pub struct DashboardChildControlResult {
804 pub child_id: String,
806 pub attempt: Option<u64>,
808 pub generation: Option<u64>,
810 pub operation_before: DashboardChildControlOperation,
812 pub operation_after: DashboardChildControlOperation,
814 pub managed_child_state_before: DashboardManagedChildState,
816 pub managed_child_state_after: DashboardManagedChildState,
818 pub status: Option<DashboardChildAttemptStatus>,
820 pub cancel_delivered: bool,
822 pub stop_state: DashboardChildStopState,
824 pub restart_limit: DashboardRestartLimitState,
826 pub liveness: DashboardChildLivenessState,
828 pub idempotent: bool,
830 pub failure: Option<DashboardChildControlFailure>,
832 pub generation_fence: Option<DashboardGenerationFenceOutcome>,
834}
835
836impl DashboardChildControlResult {
837 pub fn from_child_control_result(outcome: &RuntimeChildControlResult) -> Self {
847 Self {
848 child_id: outcome.child_id.to_string(),
849 attempt: outcome.attempt.map(|attempt| attempt.value),
850 generation: outcome.generation.map(|generation| generation.value),
851 operation_before: DashboardChildControlOperation::from(outcome.operation_before),
852 operation_after: DashboardChildControlOperation::from(outcome.operation_after),
853 managed_child_state_before: DashboardManagedChildState::from(outcome.operation_before),
854 managed_child_state_after: DashboardManagedChildState::from(outcome.operation_after),
855 status: outcome.status.map(DashboardChildAttemptStatus::from),
856 cancel_delivered: outcome.cancel_delivered,
857 stop_state: DashboardChildStopState::from(outcome.stop_state),
858 restart_limit: DashboardRestartLimitState::from_restart_limit(&outcome.restart_limit),
859 liveness: DashboardChildLivenessState::from_liveness(&outcome.liveness),
860 idempotent: outcome.idempotent,
861 failure: outcome
862 .failure
863 .as_ref()
864 .map(DashboardChildControlFailure::from_failure),
865 generation_fence: outcome
866 .generation_fence
867 .as_ref()
868 .map(DashboardGenerationFenceOutcome::from),
869 }
870 }
871}
872
873#[allow(clippy::large_enum_variant)]
875#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
876#[serde(tag = "type", rename_all = "snake_case")]
877pub enum DashboardCommandResult {
878 ChildAdded {
880 child_manifest: String,
882 },
883 ChildControl {
885 outcome: DashboardChildControlResult,
887 },
888 CurrentState {
890 state: DashboardCurrentState,
892 },
893 Shutdown {
895 result: Value,
897 },
898}
899
900impl DashboardCommandResult {
901 pub fn from_command_result(result: &CommandResult) -> Result<Self, serde_json::Error> {
911 match result {
912 CommandResult::ChildAdded { child_manifest } => Ok(Self::ChildAdded {
913 child_manifest: child_manifest.clone(),
914 }),
915 CommandResult::ChildControl { outcome } => Ok(Self::ChildControl {
916 outcome: DashboardChildControlResult::from_child_control_result(outcome),
917 }),
918 CommandResult::CurrentState { state } => Ok(Self::CurrentState {
919 state: DashboardCurrentState::from_current_state(state),
920 }),
921 CommandResult::Shutdown { result } => Ok(Self::Shutdown {
922 result: serde_json::to_value(result)?,
923 }),
924 }
925 }
926}
927
928pub fn dashboard_command_result_value(result: &CommandResult) -> Result<Value, serde_json::Error> {
938 serde_json::to_value(DashboardCommandResult::from_command_result(result)?)
939}
940
941pub fn runtime_state_from_child_runtime_record(
958 record: &ChildRuntimeRecord,
959 shutdown_completed: bool,
960) -> RuntimeState {
961 let managed_child_state = DashboardManagedChildState::from(record.operation);
962 let readiness = DashboardReadinessState::from(record.liveness.readiness);
963 RuntimeState {
964 child_path: record.path.to_string(),
965 lifecycle_state: managed_child_state.as_label().to_owned(),
966 health: if record.liveness.heartbeat_stale {
967 "stale".to_owned()
968 } else {
969 "healthy".to_owned()
970 },
971 readiness: readiness.as_label().to_owned(),
972 generation: record
973 .generation
974 .map(|generation| generation.value)
975 .unwrap_or(0),
976 child_start_count: record.attempt.map(|attempt| attempt.value).unwrap_or(0),
977 restart_count: u64::from(record.restart_limit.used),
978 last_failure: record
979 .failure
980 .as_ref()
981 .map(|failure| failure.reason.clone()),
982 last_policy_decision: Some(format!(
983 "restart_limit_remaining={}",
984 record.restart_limit.remaining
985 )),
986 shutdown_state: if shutdown_completed {
987 "completed".to_owned()
988 } else {
989 "running".to_owned()
990 },
991 }
992}
993
994#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
996pub struct EventRecord {
997 pub target_id: String,
999 pub sequence: u64,
1001 pub correlation_id: String,
1003 pub event_type: String,
1005 pub severity: String,
1007 pub target_path: String,
1009 pub child_id: Option<String>,
1011 #[serde(serialize_with = "serialize_nanos")]
1013 pub occurred_at_unix_nanos: u128,
1014 pub config_version: String,
1016 pub payload: Value,
1018}
1019
1020#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1022pub struct LogRecord {
1023 pub target_id: String,
1025 pub sequence: Option<u64>,
1027 pub correlation_id: Option<String>,
1029 pub severity: String,
1031 pub message: String,
1033 pub fields: BTreeMap<String, String>,
1035 #[serde(serialize_with = "serialize_nanos")]
1037 pub occurred_at_unix_nanos: u128,
1038}
1039
1040#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1042#[serde(rename_all = "snake_case")]
1043pub enum ControlCommandKind {
1044 RestartChild,
1046 PauseChild,
1048 ResumeChild,
1050 QuarantineChild,
1052 RemoveChild,
1054 AddChild,
1056 ShutdownTree,
1058}
1059
1060#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1062pub struct ControlCommandTarget {
1063 pub child_path: Option<String>,
1065 pub child_manifest: Option<String>,
1067}
1068
1069#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1071pub struct ControlCommandRequest {
1072 pub command_id: String,
1074 pub target_id: String,
1076 pub command: ControlCommandKind,
1078 pub target: ControlCommandTarget,
1080 pub reason: String,
1082 pub requested_by: String,
1084 pub confirmed: bool,
1086 #[serde(
1087 serialize_with = "serialize_nanos",
1088 deserialize_with = "deserialize_nanos"
1089 )]
1090 pub requested_at_unix_nanos: u128,
1092}
1093
1094#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1096pub struct ControlCommandResult {
1097 pub command_id: String,
1099 pub target_id: String,
1101 pub accepted: bool,
1103 pub status: String,
1105 pub error: Option<crate::dashboard::error::DashboardError>,
1107 pub state_delta: Option<Value>,
1109 #[serde(
1111 default,
1112 skip_serializing_if = "Option::is_none",
1113 serialize_with = "serialize_nanos_opt"
1114 )]
1115 pub completed_at_unix_nanos: Option<u128>,
1117}
1118
1119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1121pub struct AuditEvent {
1122 pub audit_id: String,
1124 pub identity: String,
1126 pub target_id: String,
1128 pub command_id: String,
1130 pub command: ControlCommandKind,
1132 pub target: ControlCommandTarget,
1134 pub reason: String,
1136 pub result: String,
1138 #[serde(serialize_with = "serialize_nanos")]
1140 pub occurred_at_unix_nanos: u128,
1141}