1mod app;
2mod event;
3mod ui;
4
5use crate::ipc::client::IpcClient;
6use crate::Result;
7use crossterm::{
8 event::{DisableMouseCapture, EnableMouseCapture},
9 execute,
10 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
11};
12use log::LevelFilter;
13use miette::IntoDiagnostic;
14use ratatui::prelude::*;
15use std::io;
16use std::time::Duration;
17
18pub use app::App;
19
20const REFRESH_RATE: Duration = Duration::from_secs(2);
21const TICK_RATE: Duration = Duration::from_millis(100);
22
23pub async fn run() -> Result<()> {
24 let prev_log_level = log::max_level();
26 log::set_max_level(LevelFilter::Off);
27
28 enable_raw_mode().into_diagnostic()?;
30 let mut stdout = io::stdout();
31 execute!(stdout, EnterAlternateScreen, EnableMouseCapture).into_diagnostic()?;
32 let backend = CrosstermBackend::new(stdout);
33 let mut terminal = Terminal::new(backend).into_diagnostic()?;
34
35 let result = run_with_cleanup(&mut terminal).await;
37
38 let _ = disable_raw_mode();
40 let _ = execute!(
41 terminal.backend_mut(),
42 LeaveAlternateScreen,
43 DisableMouseCapture
44 );
45 let _ = terminal.show_cursor();
46
47 log::set_max_level(prev_log_level);
49
50 result
51}
52
53async fn run_with_cleanup<B: Backend>(terminal: &mut Terminal<B>) -> Result<()> {
54 let client = IpcClient::connect(true).await?;
56
57 let mut app = App::new();
59 app.refresh(&client).await?;
60
61 run_app(terminal, &mut app, &client).await
63}
64
65async fn run_app<B: Backend>(
66 terminal: &mut Terminal<B>,
67 app: &mut App,
68 client: &IpcClient,
69) -> Result<()> {
70 let mut last_refresh = std::time::Instant::now();
71
72 loop {
73 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
75
76 if crossterm::event::poll(TICK_RATE).into_diagnostic()? {
78 if let Some(action) = event::handle_event(app)? {
79 match action {
80 event::Action::Quit => break,
81 event::Action::Start(id) => {
82 app.start_loading(format!("Starting {}...", id));
83 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
84 if let Err(e) = app.start_daemon(client, &id).await {
86 app.stop_loading();
87 app.set_message(format!("Failed to start {}: {}", id, e));
88 } else {
89 app.stop_loading();
90 }
91 app.refresh(client).await?;
92 }
93 event::Action::Enable(id) => {
94 app.start_loading(format!("Enabling {}...", id));
95 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
96 client.enable(id.clone()).await?;
97 app.stop_loading();
98 app.set_message(format!("Enabled {}", id));
99 app.refresh(client).await?;
100 }
101 event::Action::BatchStart(ids) => {
102 let count = ids.len();
103 app.start_loading(format!("Starting {} daemons...", count));
104 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
105 let mut started = 0;
106 for id in &ids {
107 if app.start_daemon(client, id).await.is_ok() {
108 started += 1;
109 }
110 }
111 app.stop_loading();
112 app.clear_selection();
113 app.set_message(format!("Started {}/{} daemons", started, count));
114 app.refresh(client).await?;
115 }
116 event::Action::BatchEnable(ids) => {
117 let count = ids.len();
118 app.start_loading(format!("Enabling {} daemons...", count));
119 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
120 for id in &ids {
121 let _ = client.enable(id.clone()).await;
122 }
123 app.stop_loading();
124 app.clear_selection();
125 app.set_message(format!("Enabled {} daemons", count));
126 app.refresh(client).await?;
127 }
128 event::Action::Refresh => {
129 app.start_loading("Refreshing...");
130 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
131 app.refresh(client).await?;
132 app.stop_loading();
133 }
134 event::Action::ConfirmPending => {
135 if let Some(pending) = app.take_pending_action() {
136 match pending {
137 app::PendingAction::Stop(id) => {
138 app.start_loading(format!("Stopping {}...", id));
139 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
140 client.stop(id.clone()).await?;
141 app.stop_loading();
142 app.set_message(format!("Stopped {}", id));
143 }
144 app::PendingAction::Restart(id) => {
145 app.start_loading(format!("Restarting {}...", id));
146 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
147 client.stop(id.clone()).await?;
148 tokio::time::sleep(Duration::from_millis(500)).await;
149 if let Err(e) = app.start_daemon(client, &id).await {
151 app.stop_loading();
152 app.set_message(format!(
153 "Stopped {} but failed to restart: {}",
154 id, e
155 ));
156 } else {
157 app.stop_loading();
158 app.set_message(format!("Restarted {}", id));
159 }
160 }
161 app::PendingAction::Disable(id) => {
162 app.start_loading(format!("Disabling {}...", id));
163 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
164 client.disable(id.clone()).await?;
165 app.stop_loading();
166 app.set_message(format!("Disabled {}", id));
167 }
168 app::PendingAction::BatchStop(ids) => {
169 let count = ids.len();
170 app.start_loading(format!("Stopping {} daemons...", count));
171 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
172 for id in &ids {
173 let _ = client.stop(id.clone()).await;
174 }
175 app.stop_loading();
176 app.clear_selection();
177 app.set_message(format!("Stopped {} daemons", count));
178 }
179 app::PendingAction::BatchRestart(ids) => {
180 let count = ids.len();
181 app.start_loading(format!("Restarting {} daemons...", count));
182 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
183 for id in &ids {
185 let _ = client.stop(id.clone()).await;
186 }
187 tokio::time::sleep(Duration::from_millis(500)).await;
188 let mut started = 0;
190 for id in &ids {
191 if app.start_daemon(client, id).await.is_ok() {
192 started += 1;
193 }
194 }
195 app.stop_loading();
196 app.clear_selection();
197 app.set_message(format!(
198 "Restarted {}/{} daemons",
199 started, count
200 ));
201 }
202 app::PendingAction::BatchDisable(ids) => {
203 let count = ids.len();
204 app.start_loading(format!("Disabling {} daemons...", count));
205 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
206 for id in &ids {
207 let _ = client.disable(id.clone()).await;
208 }
209 app.stop_loading();
210 app.clear_selection();
211 app.set_message(format!("Disabled {} daemons", count));
212 }
213 }
214 app.refresh(client).await?;
215 }
216 }
217 }
218 }
219 }
220
221 if last_refresh.elapsed() >= REFRESH_RATE {
223 app.refresh(client).await?;
224 last_refresh = std::time::Instant::now();
225 }
226 }
227
228 Ok(())
229}