1mod app;
2mod event;
3mod ui;
4
5use crate::Result;
6use crate::ipc::client::IpcClient;
7use crossterm::{
8 event::{DisableMouseCapture, EnableMouseCapture},
9 execute,
10 terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
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 && let Some(action) = event::handle_event(app)?
79 {
80 match action {
81 event::Action::Quit => break,
82 event::Action::Start(id) => {
83 app.start_loading(format!("Starting {}...", id));
84 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
85 if let Err(e) = app.start_daemon(client, &id).await {
87 app.stop_loading();
88 app.set_message(format!("Failed to start {}: {}", id, e));
89 } else {
90 app.stop_loading();
91 }
92 app.refresh(client).await?;
93 }
94 event::Action::Enable(id) => {
95 app.start_loading(format!("Enabling {}...", id));
96 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
97 client.enable(id.clone()).await?;
98 app.stop_loading();
99 app.set_message(format!("Enabled {}", id));
100 app.refresh(client).await?;
101 }
102 event::Action::BatchStart(ids) => {
103 let count = ids.len();
104 app.start_loading(format!("Starting {} daemons...", count));
105 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
106 let mut started = 0;
107 for id in &ids {
108 if app.start_daemon(client, id).await.is_ok() {
109 started += 1;
110 }
111 }
112 app.stop_loading();
113 app.clear_selection();
114 app.set_message(format!("Started {}/{} daemons", started, count));
115 app.refresh(client).await?;
116 }
117 event::Action::BatchEnable(ids) => {
118 let count = ids.len();
119 app.start_loading(format!("Enabling {} daemons...", count));
120 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
121 for id in &ids {
122 let _ = client.enable(id.clone()).await;
123 }
124 app.stop_loading();
125 app.clear_selection();
126 app.set_message(format!("Enabled {} daemons", count));
127 app.refresh(client).await?;
128 }
129 event::Action::Refresh => {
130 app.start_loading("Refreshing...");
131 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
132 app.refresh(client).await?;
133 app.stop_loading();
134 }
135 event::Action::ConfirmPending => {
136 if let Some(pending) = app.take_pending_action() {
137 match pending {
138 app::PendingAction::Stop(id) => {
139 app.start_loading(format!("Stopping {}...", id));
140 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
141 client.stop(id.clone()).await?;
142 app.stop_loading();
143 app.set_message(format!("Stopped {}", id));
144 }
145 app::PendingAction::Restart(id) => {
146 app.start_loading(format!("Restarting {}...", id));
147 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
148 client.stop(id.clone()).await?;
149 tokio::time::sleep(Duration::from_millis(500)).await;
150 if let Err(e) = app.start_daemon(client, &id).await {
152 app.stop_loading();
153 app.set_message(format!(
154 "Stopped {} but failed to restart: {}",
155 id, e
156 ));
157 } else {
158 app.stop_loading();
159 app.set_message(format!("Restarted {}", id));
160 }
161 }
162 app::PendingAction::Disable(id) => {
163 app.start_loading(format!("Disabling {}...", id));
164 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
165 client.disable(id.clone()).await?;
166 app.stop_loading();
167 app.set_message(format!("Disabled {}", id));
168 }
169 app::PendingAction::BatchStop(ids) => {
170 let count = ids.len();
171 app.start_loading(format!("Stopping {} daemons...", count));
172 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
173 for id in &ids {
174 let _ = client.stop(id.clone()).await;
175 }
176 app.stop_loading();
177 app.clear_selection();
178 app.set_message(format!("Stopped {} daemons", count));
179 }
180 app::PendingAction::BatchRestart(ids) => {
181 let count = ids.len();
182 app.start_loading(format!("Restarting {} daemons...", count));
183 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
184 for id in &ids {
186 let _ = client.stop(id.clone()).await;
187 }
188 tokio::time::sleep(Duration::from_millis(500)).await;
189 let mut started = 0;
191 for id in &ids {
192 if app.start_daemon(client, id).await.is_ok() {
193 started += 1;
194 }
195 }
196 app.stop_loading();
197 app.clear_selection();
198 app.set_message(format!("Restarted {}/{} daemons", started, count));
199 }
200 app::PendingAction::BatchDisable(ids) => {
201 let count = ids.len();
202 app.start_loading(format!("Disabling {} daemons...", count));
203 terminal.draw(|f| ui::draw(f, app)).into_diagnostic()?;
204 for id in &ids {
205 let _ = client.disable(id.clone()).await;
206 }
207 app.stop_loading();
208 app.clear_selection();
209 app.set_message(format!("Disabled {} daemons", count));
210 }
211 }
212 app.refresh(client).await?;
213 }
214 }
215 }
216 }
217
218 if last_refresh.elapsed() >= REFRESH_RATE {
220 app.refresh(client).await?;
221 last_refresh = std::time::Instant::now();
222 }
223 }
224
225 Ok(())
226}