66//! mode) or standalone via `vz stack dashboard <name>`.
77
88use std:: collections:: HashMap ;
9- use std:: io:: { self , IsTerminal , Stdout } ;
9+ use std:: io:: { self , BufRead , IsTerminal , Stdout , Write as _} ;
10+ use std:: os:: unix:: net:: UnixStream ;
1011use std:: path:: PathBuf ;
1112use std:: time:: { Duration , Instant } ;
1213
@@ -82,6 +83,7 @@ pub struct App {
8283 logs : HashMap < String , String > ,
8384 selected_log_service : usize ,
8485 log_scroll : usize ,
86+ sock_path : Option < PathBuf > ,
8587
8688 // UI state
8789 show_help : bool ,
@@ -93,7 +95,7 @@ pub struct App {
9395
9496impl App {
9597 /// Create a new TUI application.
96- pub fn new ( stack_name : String , spec : StackSpec , store : StateStore ) -> Self {
98+ pub fn new ( stack_name : String , spec : StackSpec , store : StateStore , sock_path : Option < PathBuf > ) -> Self {
9799 let service_names: Vec < String > = spec. services . iter ( ) . map ( |s| s. name . clone ( ) ) . collect ( ) ;
98100 let logs: HashMap < String , String > = service_names
99101 . iter ( )
@@ -114,6 +116,7 @@ impl App {
114116 logs,
115117 selected_log_service : 0 ,
116118 log_scroll : 0 ,
119+ sock_path,
117120 show_help : false ,
118121 should_quit : false ,
119122 recent_events : Vec :: new ( ) ,
@@ -146,6 +149,55 @@ impl App {
146149 self . event_scroll = self . events . len ( ) . saturating_sub ( 1 ) ;
147150 }
148151 }
152+
153+ // Fetch logs only when viewing the Logs tab (avoid unnecessary socket calls).
154+ if self . active_tab == Tab :: Logs {
155+ self . refresh_logs ( ) ;
156+ }
157+ }
158+
159+ /// Fetch logs for the currently-selected service via the control socket.
160+ fn refresh_logs ( & mut self ) {
161+ let sock_path = match & self . sock_path {
162+ Some ( p) if p. exists ( ) => p. clone ( ) ,
163+ _ => return ,
164+ } ;
165+
166+ let service_name = match self . current_log_service_name ( ) {
167+ Some ( n) => n,
168+ None => return ,
169+ } ;
170+
171+ let stream = match UnixStream :: connect ( & sock_path) {
172+ Ok ( s) => s,
173+ Err ( _) => return ,
174+ } ;
175+ let _ = stream. set_read_timeout ( Some ( Duration :: from_millis ( 500 ) ) ) ;
176+ let _ = stream. set_write_timeout ( Some ( Duration :: from_millis ( 200 ) ) ) ;
177+
178+ let request = serde_json:: json!( {
179+ "action" : "exec" ,
180+ "service" : service_name,
181+ "cmd" : [ "tail" , "-n" , "200" , "/var/log/vz-oci/output.log" ]
182+ } ) ;
183+
184+ let mut request_bytes = serde_json:: to_vec ( & request) . unwrap_or_default ( ) ;
185+ request_bytes. push ( b'\n' ) ;
186+
187+ if ( & stream) . write_all ( & request_bytes) . is_err ( ) {
188+ return ;
189+ }
190+
191+ let mut reader = io:: BufReader :: new ( & stream) ;
192+ let mut line = String :: new ( ) ;
193+ if reader. read_line ( & mut line) . is_ok ( ) && !line. is_empty ( ) {
194+ if let Ok ( resp) = serde_json:: from_str :: < serde_json:: Value > ( & line) {
195+ let stdout = resp. get ( "stdout" ) . and_then ( |v| v. as_str ( ) ) . unwrap_or ( "" ) ;
196+ if !stdout. is_empty ( ) {
197+ self . logs . insert ( service_name, stdout. to_string ( ) ) ;
198+ }
199+ }
200+ }
149201 }
150202
151203 /// Handle a key press event.
@@ -402,7 +454,12 @@ pub fn is_tty() -> bool {
402454/// This takes over the terminal and presents a dashboard that polls
403455/// the state store at `db_path` every 500ms. Returns when the user
404456/// presses `q` or `Ctrl-C`.
405- pub fn run_tui ( stack_name : String , spec : StackSpec , db_path : PathBuf ) -> anyhow:: Result < ( ) > {
457+ pub fn run_tui (
458+ stack_name : String ,
459+ spec : StackSpec ,
460+ db_path : PathBuf ,
461+ sock_path : Option < PathBuf > ,
462+ ) -> anyhow:: Result < ( ) > {
406463 enable_raw_mode ( ) . context ( "failed to enable raw terminal mode" ) ?;
407464 let mut stdout = io:: stdout ( ) ;
408465 execute ! ( stdout, EnterAlternateScreen ) . context ( "failed to enter alternate screen" ) ?;
@@ -414,7 +471,7 @@ pub fn run_tui(stack_name: String, spec: StackSpec, db_path: PathBuf) -> anyhow:
414471
415472 let store =
416473 StateStore :: open ( & db_path) . context ( "failed to open state store for TUI dashboard" ) ?;
417- let mut app = App :: new ( stack_name, spec, store) ;
474+ let mut app = App :: new ( stack_name, spec, store, sock_path ) ;
418475
419476 // Initial data load.
420477 app. refresh_data ( ) ;
@@ -1152,7 +1209,7 @@ mod tests {
11521209 fn app_new_initializes_correctly ( ) {
11531210 let spec = make_test_spec ( ) ;
11541211 let store = StateStore :: in_memory ( ) . unwrap ( ) ;
1155- let app = App :: new ( "test" . into ( ) , spec, store) ;
1212+ let app = App :: new ( "test" . into ( ) , spec, store, None ) ;
11561213
11571214 assert_eq ! ( app. stack_name, "test" ) ;
11581215 assert_eq ! ( app. active_tab, Tab :: Services ) ;
@@ -1167,7 +1224,7 @@ mod tests {
11671224 fn handle_key_quit ( ) {
11681225 let spec = make_test_spec ( ) ;
11691226 let store = StateStore :: in_memory ( ) . unwrap ( ) ;
1170- let mut app = App :: new ( "test" . into ( ) , spec, store) ;
1227+ let mut app = App :: new ( "test" . into ( ) , spec, store, None ) ;
11711228
11721229 app. handle_key ( event:: KeyEvent :: new ( KeyCode :: Char ( 'q' ) , KeyModifiers :: NONE ) ) ;
11731230 assert ! ( app. should_quit) ;
@@ -1177,7 +1234,7 @@ mod tests {
11771234 fn handle_key_ctrl_c ( ) {
11781235 let spec = make_test_spec ( ) ;
11791236 let store = StateStore :: in_memory ( ) . unwrap ( ) ;
1180- let mut app = App :: new ( "test" . into ( ) , spec, store) ;
1237+ let mut app = App :: new ( "test" . into ( ) , spec, store, None ) ;
11811238
11821239 app. handle_key ( event:: KeyEvent :: new (
11831240 KeyCode :: Char ( 'c' ) ,
@@ -1190,7 +1247,7 @@ mod tests {
11901247 fn handle_key_tab_switch ( ) {
11911248 let spec = make_test_spec ( ) ;
11921249 let store = StateStore :: in_memory ( ) . unwrap ( ) ;
1193- let mut app = App :: new ( "test" . into ( ) , spec, store) ;
1250+ let mut app = App :: new ( "test" . into ( ) , spec, store, None ) ;
11941251
11951252 assert_eq ! ( app. active_tab, Tab :: Services ) ;
11961253
@@ -1208,7 +1265,7 @@ mod tests {
12081265 fn handle_key_number_tabs ( ) {
12091266 let spec = make_test_spec ( ) ;
12101267 let store = StateStore :: in_memory ( ) . unwrap ( ) ;
1211- let mut app = App :: new ( "test" . into ( ) , spec, store) ;
1268+ let mut app = App :: new ( "test" . into ( ) , spec, store, None ) ;
12121269
12131270 app. handle_key ( event:: KeyEvent :: new ( KeyCode :: Char ( '2' ) , KeyModifiers :: NONE ) ) ;
12141271 assert_eq ! ( app. active_tab, Tab :: Events ) ;
@@ -1224,7 +1281,7 @@ mod tests {
12241281 fn handle_key_help_toggle ( ) {
12251282 let spec = make_test_spec ( ) ;
12261283 let store = StateStore :: in_memory ( ) . unwrap ( ) ;
1227- let mut app = App :: new ( "test" . into ( ) , spec, store) ;
1284+ let mut app = App :: new ( "test" . into ( ) , spec, store, None ) ;
12281285
12291286 app. handle_key ( event:: KeyEvent :: new ( KeyCode :: Char ( '?' ) , KeyModifiers :: NONE ) ) ;
12301287 assert ! ( app. show_help) ;
@@ -1238,7 +1295,7 @@ mod tests {
12381295 fn navigate_services ( ) {
12391296 let spec = make_test_spec ( ) ;
12401297 let store = StateStore :: in_memory ( ) . unwrap ( ) ;
1241- let mut app = App :: new ( "test" . into ( ) , spec, store) ;
1298+ let mut app = App :: new ( "test" . into ( ) , spec, store, None ) ;
12421299
12431300 // Add some observed services.
12441301 app. services = vec ! [
@@ -1279,7 +1336,7 @@ mod tests {
12791336 fn navigate_jump_top_bottom ( ) {
12801337 let spec = make_test_spec ( ) ;
12811338 let store = StateStore :: in_memory ( ) . unwrap ( ) ;
1282- let mut app = App :: new ( "test" . into ( ) , spec, store) ;
1339+ let mut app = App :: new ( "test" . into ( ) , spec, store, None ) ;
12831340
12841341 app. services = vec ! [
12851342 ServiceObservedState {
@@ -1338,7 +1395,7 @@ mod tests {
13381395 )
13391396 . unwrap ( ) ;
13401397
1341- let mut app = App :: new ( "test" . into ( ) , spec, store) ;
1398+ let mut app = App :: new ( "test" . into ( ) , spec, store, None ) ;
13421399 app. refresh_data ( ) ;
13431400
13441401 assert_eq ! ( app. services. len( ) , 1 ) ;
0 commit comments