pitchfork_cli/
daemon_list.rs1use crate::Result;
2use crate::daemon::Daemon;
3use crate::daemon_id::DaemonId;
4use crate::daemon_status::DaemonStatus;
5use crate::ipc::client::IpcClient;
6use crate::pitchfork_toml::PitchforkToml;
7use std::collections::HashSet;
8
9#[derive(Debug, Clone)]
11pub struct DaemonListEntry {
12 pub id: DaemonId,
13 pub daemon: Daemon,
14 pub is_disabled: bool,
15 pub is_available: bool, }
17
18pub async fn get_all_daemons(client: &IpcClient) -> Result<Vec<DaemonListEntry>> {
34 let config = PitchforkToml::all_merged()?;
35
36 let state_file = crate::state_file::StateFile::read(&*crate::env::PITCHFORK_STATE_FILE)?;
38 let state_daemons: Vec<Daemon> = state_file.daemons.values().cloned().collect();
39
40 let disabled_daemons = client.get_disabled_daemons().await?;
41 let disabled_set: HashSet<DaemonId> = disabled_daemons.into_iter().collect();
42
43 build_daemon_list(state_daemons, disabled_set, config)
44}
45
46pub async fn get_all_daemons_direct(
57 supervisor: &crate::supervisor::Supervisor,
58) -> Result<Vec<DaemonListEntry>> {
59 let config = PitchforkToml::all_merged()?;
60
61 let state_file = supervisor.state_file.lock().await;
63 let state_daemons: Vec<Daemon> = state_file.daemons.values().cloned().collect();
64 let disabled_set: HashSet<DaemonId> = state_file.disabled.clone().into_iter().collect();
65 drop(state_file); build_daemon_list(state_daemons, disabled_set, config)
68}
69
70pub async fn get_daemon_direct(
75 supervisor: &crate::supervisor::Supervisor,
76 id: &DaemonId,
77) -> Result<Option<DaemonListEntry>> {
78 let pitchfork_id = DaemonId::pitchfork();
79 if *id == pitchfork_id {
80 return Ok(None);
81 }
82
83 let state_file = supervisor.state_file.lock().await;
85 if let Some(daemon) = state_file.daemons.get(id).cloned() {
86 let is_disabled = state_file.disabled.contains(id);
87 drop(state_file);
88 return Ok(Some(DaemonListEntry {
89 id: id.clone(),
90 daemon,
91 is_disabled,
92 is_available: false,
93 }));
94 }
95 let is_disabled = state_file.disabled.contains(id);
96 drop(state_file);
97
98 let config = PitchforkToml::all_merged()?;
100 if let Some(daemon_config) = config.daemons.get(id) {
101 return Ok(Some(DaemonListEntry {
102 id: id.clone(),
103 daemon: build_placeholder_daemon(id, daemon_config),
104 is_disabled,
105 is_available: true,
106 }));
107 }
108
109 let namespaces = PitchforkToml::read_global_namespaces();
111 for (_, entry) in namespaces {
112 match PitchforkToml::all_merged_from(&entry.dir) {
113 Ok(ns_config) => {
114 if let Some(daemon_config) = ns_config.daemons.get(id) {
115 return Ok(Some(DaemonListEntry {
116 id: id.clone(),
117 daemon: build_placeholder_daemon(id, daemon_config),
118 is_disabled,
119 is_available: true,
120 }));
121 }
122 }
123 Err(e) => {
124 log::warn!("Failed to load namespace from {}: {e}", entry.dir.display());
125 }
126 }
127 }
128
129 Ok(None)
130}
131
132fn build_placeholder_daemon(
134 id: &DaemonId,
135 daemon_config: &crate::pitchfork_toml::PitchforkTomlDaemon,
136) -> Daemon {
137 Daemon {
138 id: id.clone(),
139 status: DaemonStatus::Stopped,
140 port: daemon_config.port.clone(),
141 depends: vec![],
142 env: None,
143 watch: vec![],
144 watch_mode: daemon_config.watch_mode,
145 watch_base_dir: None,
146 mise: daemon_config.mise,
147 user: daemon_config.user.clone(),
148 active_port: None,
149 slug: None,
150 proxy: None,
151 memory_limit: daemon_config.memory_limit,
152 cpu_limit: daemon_config.cpu_limit,
153 ..Daemon::default()
154 }
155}
156
157fn build_daemon_list(
159 state_daemons: Vec<Daemon>,
160 disabled_set: HashSet<DaemonId>,
161 config: PitchforkToml,
162) -> Result<Vec<DaemonListEntry>> {
163 let mut entries = Vec::new();
164 let mut seen_ids = HashSet::new();
165
166 let pitchfork_id = DaemonId::pitchfork();
168
169 for daemon in state_daemons {
171 if daemon.id == pitchfork_id {
172 continue; }
174
175 seen_ids.insert(daemon.id.clone());
180 entries.push(DaemonListEntry {
181 id: daemon.id.clone(),
182 is_disabled: disabled_set.contains(&daemon.id),
183 is_available: false,
184 daemon,
185 });
186 }
187
188 for (daemon_id, daemon_config) in &config.daemons {
190 if *daemon_id == pitchfork_id || seen_ids.contains(daemon_id) {
191 continue;
192 }
193
194 let placeholder = build_placeholder_daemon(daemon_id, daemon_config);
195
196 entries.push(DaemonListEntry {
197 id: daemon_id.clone(),
198 daemon: placeholder,
199 is_disabled: disabled_set.contains(daemon_id),
200 is_available: true,
201 });
202 seen_ids.insert(daemon_id.clone());
203 }
204
205 let namespaces = PitchforkToml::read_global_namespaces();
207 for (ns_name, entry) in namespaces {
208 match PitchforkToml::all_merged_from(&entry.dir) {
209 Ok(ns_config) => {
210 for (daemon_id, daemon_config) in &ns_config.daemons {
211 if *daemon_id == pitchfork_id || seen_ids.contains(daemon_id) {
212 continue;
213 }
214 let placeholder = build_placeholder_daemon(daemon_id, daemon_config);
215 entries.push(DaemonListEntry {
216 id: daemon_id.clone(),
217 daemon: placeholder,
218 is_disabled: disabled_set.contains(daemon_id),
219 is_available: true,
220 });
221 seen_ids.insert(daemon_id.clone());
222 }
223 }
224 Err(e) => {
225 log::warn!(
226 "Failed to load namespace '{ns_name}' from {}: {e}",
227 entry.dir.display()
228 );
229 }
230 }
231 }
232
233 Ok(entries)
234}