@@ -69,7 +69,7 @@ async fn download_docker_compose() -> Result<(), Box<dyn std::error::Error>> {
6969 Ok ( ( ) )
7070}
7171
72- pub async fn handle_start ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
72+ pub async fn handle_start ( port : u16 ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
7373 if check_docker_compose ( ) . is_err ( ) {
7474 println ! (
7575 "{} Docker Compose not found. Install Docker Desktop: {}" ,
@@ -86,11 +86,13 @@ pub async fn handle_start() -> Result<(), Box<dyn std::error::Error>> {
8686
8787 let status = Command :: new ( "docker" )
8888 . args ( [ "compose" , "up" , "-d" ] )
89+ . env ( "PORT" , port. to_string ( ) )
8990 . current_dir ( local_dir ( ) )
9091 . status ( ) ?;
9192
9293 if status. success ( ) {
9394 println ! ( "{} Local Canine environment started" , "✓" . green( ) ) ;
95+ println ! ( "\n Open {} in your browser" , format!( "http://localhost:{}" , port) . cyan( ) ) ;
9496 } else {
9597 println ! ( "{} Failed to start local Canine environment" , "✗" . red( ) ) ;
9698 std:: process:: exit ( 1 ) ;
@@ -106,11 +108,80 @@ pub async fn handle_status() -> Result<(), Box<dyn std::error::Error>> {
106108 std:: process:: exit ( 1 ) ;
107109 }
108110
109- Command :: new ( "docker" )
110- . args ( [ "compose" , "ps" ] )
111+ // Get structured JSON output from docker compose
112+ let output = Command :: new ( "docker" )
113+ . args ( [ "compose" , "ps" , "--format" , "json" ] )
111114 . current_dir ( local_dir ( ) )
112- . status ( ) ?;
115+ . output ( ) ?;
116+
117+ if !output. status . success ( ) {
118+ println ! ( "{} Failed to get container status" , "✗" . red( ) ) ;
119+ std:: process:: exit ( 1 ) ;
120+ }
121+
122+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
123+
124+ // Parse each line as a JSON object (docker outputs one JSON object per line)
125+ let services: Vec < serde_json:: Value > = stdout
126+ . lines ( )
127+ . filter_map ( |line| serde_json:: from_str ( line) . ok ( ) )
128+ . collect ( ) ;
129+
130+ if services. is_empty ( ) {
131+ println ! ( "{} No services running" , "✗" . yellow( ) ) ;
132+ println ! ( " Run {} to start" , "canine local start" . cyan( ) ) ;
133+ return Ok ( ( ) ) ;
134+ }
135+
136+ println ! ( "\n {}" , "Local Canine Services" . bold( ) ) ;
137+ println ! ( "{}" , "─" . repeat( 50 ) ) ;
138+
139+ for svc in & services {
140+ let name = svc[ "Service" ] . as_str ( ) . unwrap_or ( "unknown" ) ;
141+ let state = svc[ "State" ] . as_str ( ) . unwrap_or ( "unknown" ) ;
142+ let health = svc[ "Health" ] . as_str ( ) . unwrap_or ( "" ) ;
143+ let ports = svc[ "Publishers" ] . as_array ( ) ;
144+
145+ let status_icon = match state {
146+ "running" => "✓" . green ( ) ,
147+ "exited" => "✗" . red ( ) ,
148+ _ => "?" . yellow ( ) ,
149+ } ;
150+
151+ let health_str = if !health. is_empty ( ) {
152+ format ! ( " ({})" , health)
153+ } else {
154+ String :: new ( )
155+ } ;
156+
157+ // Extract published ports
158+ let port_str = ports
159+ . map ( |p| {
160+ p. iter ( )
161+ . filter_map ( |pub_info| {
162+ let published = pub_info[ "PublishedPort" ] . as_u64 ( ) ?;
163+ if published > 0 {
164+ Some ( format ! ( ":{}" , published) )
165+ } else {
166+ None
167+ }
168+ } )
169+ . collect :: < Vec < _ > > ( )
170+ . join ( ", " )
171+ } )
172+ . unwrap_or_default ( ) ;
173+
174+ println ! (
175+ "{} {:<20} {:<10}{} {}" ,
176+ status_icon,
177+ name,
178+ state,
179+ health_str,
180+ port_str. cyan( )
181+ ) ;
182+ }
113183
184+ println ! ( ) ;
114185 Ok ( ( ) )
115186}
116187
0 commit comments