1use serde::{Deserialize, Serialize};
2use uuid::Uuid;
3
4use chrono::{DateTime, Utc};
5
6use crate::{Lifecycle, LogAvailability, LostEvidence, VersionInfo};
7
8#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
10#[non_exhaustive]
11#[serde(rename_all = "snake_case")]
12pub enum KillOutcome {
13 Signalled,
15 AlreadyExited,
17}
18
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20pub struct KillByPidRequest {
21 pub pid: u32,
22 pub signal: i32,
23 pub grace_secs: u64,
24}
25
26#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
27pub struct KillByPidResponse {
28 pub pid: u32,
29 pub signal: i32,
30 pub killed_after_grace: bool,
31 pub outcome: KillOutcome,
32}
33
34#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
35pub struct StatusFilter {
36 pub session_id: Option<Uuid>,
37 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 pub session_ids: Vec<Uuid>,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub updated_since: Option<DateTime<Utc>>,
41 pub runtime: Option<String>,
42 pub state: Option<String>,
43}
44
45impl StatusFilter {
46 pub const fn empty() -> Self {
47 Self {
48 session_id: None,
49 session_ids: Vec::new(),
50 updated_since: None,
51 runtime: None,
52 state: None,
53 }
54 }
55
56 pub fn requested_session_ids(&self) -> Vec<Uuid> {
57 let mut ids = self.session_ids.clone();
58 if let Some(session_id) = self.session_id
59 && !ids.contains(&session_id)
60 {
61 ids.push(session_id);
62 }
63 ids
64 }
65}
66
67impl Default for StatusFilter {
68 fn default() -> Self {
69 Self::empty()
70 }
71}
72
73#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
74pub struct StatusResponse {
75 pub lifecycles: Vec<Lifecycle>,
76}
77
78#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
79pub struct WatcherCounts {
80 pub process_exit_watchers: usize,
81 pub shim_sockets: usize,
82 pub event_waiters: usize,
83}
84
85#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
86pub struct LifecycleCounts {
87 pub forking: u64,
88 pub running: u64,
89 pub exited: u64,
90 pub lost: u64,
91}
92
93#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
94pub struct MigrationState {
95 pub applied: usize,
96 pub total: usize,
97 pub applied_descriptions: Vec<String>,
98 pub pending_descriptions: Vec<String>,
99}
100
101#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
102pub struct RecentLostEvent {
103 pub session_id: Uuid,
104 pub evidence: LostEvidence,
105 pub occurred_at: DateTime<Utc>,
106}
107
108#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
109pub struct LauncherStatus {
110 pub runtime: String,
111 pub command: Option<String>,
112 pub error: Option<String>,
113}
114
115#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
116pub struct TmuxStatus {
117 pub available: bool,
118 pub version: Option<String>,
119 pub error: Option<String>,
120}
121
122#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
123pub struct DockerReadiness {
124 pub ready: bool,
125 pub detail: Option<String>,
126 pub error: Option<String>,
127}
128
129impl DockerReadiness {
130 pub fn ready(detail: impl Into<String>) -> Self {
131 Self {
132 ready: true,
133 detail: Some(detail.into()),
134 error: None,
135 }
136 }
137
138 pub fn unavailable(error: impl Into<String>) -> Self {
139 Self {
140 ready: false,
141 detail: None,
142 error: Some(error.into()),
143 }
144 }
145}
146
147#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
148pub struct DockerIsolationStatus {
149 pub supported: bool,
150 pub default_workspace: String,
151 pub experimental: bool,
152}
153
154#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
155pub struct DockerStatus {
156 pub cli: DockerReadiness,
157 pub daemon: DockerReadiness,
158 pub manifest_validation: DockerReadiness,
159 pub isolation: DockerIsolationStatus,
160}
161
162impl DockerStatus {
163 pub fn legacy_missing() -> Self {
164 Self {
165 cli: DockerReadiness::unavailable("not reported by this daemon"),
166 daemon: DockerReadiness::unavailable("not reported by this daemon"),
167 manifest_validation: DockerReadiness::unavailable("not reported by this daemon"),
168 isolation: DockerIsolationStatus {
169 supported: false,
170 default_workspace: String::new(),
171 experimental: false,
172 },
173 }
174 }
175
176 pub fn is_legacy_missing(&self) -> bool {
177 self == &Self::legacy_missing()
178 }
179}
180
181fn legacy_missing_docker_status() -> Box<DockerStatus> {
182 Box::new(DockerStatus::legacy_missing())
183}
184
185fn is_legacy_missing_docker_status(status: &DockerStatus) -> bool {
186 status.is_legacy_missing()
187}
188
189#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
190pub struct LifecycleLogAvailability {
191 pub session_id: Uuid,
192 pub log_availability: LogAvailability,
193}
194
195#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
200pub struct DoctorResponse {
201 pub version: VersionInfo,
202 pub socket_path: String,
203 pub uptime_secs: u64,
204 pub sqlite: MigrationState,
205 pub lifecycles: LifecycleCounts,
206 pub watchers: WatcherCounts,
207 pub launchers: Vec<LauncherStatus>,
208 pub tmux: TmuxStatus,
209 #[serde(
210 default = "legacy_missing_docker_status",
211 skip_serializing_if = "is_legacy_missing_docker_status"
212 )]
213 pub docker: Box<DockerStatus>,
214 pub log_availability: Vec<LifecycleLogAvailability>,
215 pub last_probe_sweep: Option<DateTime<Utc>>,
216 pub recent_lost: Vec<RecentLostEvent>,
217}