@@ -8,7 +8,7 @@ use serde::Serialize;
88use super :: get_admin_client;
99use crate :: exit_code:: ExitCode ;
1010use crate :: output:: Formatter ;
11- use rc_core:: admin:: { AdminApi , HealScanMode , HealStartRequest , HealStatus } ;
11+ use rc_core:: admin:: { AdminApi , HealScanMode , HealStartRequest , HealStatus , HealTaskRequest } ;
1212
1313const HEAL_STOP_SUCCESS_MESSAGE : & str = "Heal operation stopped successfully" ;
1414const HEAL_STOP_STILL_RUNNING_MESSAGE : & str =
@@ -31,6 +31,18 @@ pub enum HealCommands {
3131pub struct StatusArgs {
3232 /// Alias name of the server
3333 pub alias : String ,
34+
35+ /// Bucket for token-scoped manual heal task status
36+ #[ arg( short, long) ]
37+ pub bucket : Option < String > ,
38+
39+ /// Object prefix for token-scoped manual heal task status
40+ #[ arg( short, long) ]
41+ pub prefix : Option < String > ,
42+
43+ /// Client token returned by heal start
44+ #[ arg( long) ]
45+ pub client_token : Option < String > ,
3446}
3547
3648#[ derive( clap:: Args , Debug ) ]
@@ -67,6 +79,18 @@ pub struct StartArgs {
6779pub struct StopArgs {
6880 /// Alias name of the server
6981 pub alias : String ,
82+
83+ /// Bucket for token-scoped manual heal task stop
84+ #[ arg( short, long) ]
85+ pub bucket : Option < String > ,
86+
87+ /// Object prefix for token-scoped manual heal task stop
88+ #[ arg( short, long) ]
89+ pub prefix : Option < String > ,
90+
91+ /// Client token returned by heal start
92+ #[ arg( long) ]
93+ pub client_token : Option < String > ,
7094}
7195
7296/// JSON output for heal status
@@ -75,6 +99,10 @@ pub struct StopArgs {
7599struct HealStatusOutput {
76100 heal_id : String ,
77101 healing : bool ,
102+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
103+ summary : Option < String > ,
104+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
105+ detail : Option < String > ,
78106 bucket : String ,
79107 object : String ,
80108 #[ serde( skip_serializing_if = "Option::is_none" ) ]
@@ -98,6 +126,8 @@ impl From<&HealStatus> for HealStatusOutput {
98126 Self {
99127 heal_id : status. heal_id . clone ( ) ,
100128 healing : status. healing ,
129+ summary : status. summary . clone ( ) ,
130+ detail : status. detail . clone ( ) ,
101131 bucket : status. bucket . clone ( ) ,
102132 object : status. object . clone ( ) ,
103133 scan_mode : status. scan_mode ,
@@ -118,6 +148,8 @@ impl From<&HealStatus> for HealStatusOutput {
118148fn has_heal_status_details ( status : & HealStatus ) -> bool {
119149 status. healing
120150 || !status. heal_id . is_empty ( )
151+ || status. summary . is_some ( )
152+ || status. detail . is_some ( )
121153 || !status. bucket . is_empty ( )
122154 || !status. object . is_empty ( )
123155 || status. scan_mode . is_some ( )
@@ -138,6 +170,8 @@ fn has_heal_status_details(status: &HealStatus) -> bool {
138170struct HealOperationOutput {
139171 success : bool ,
140172 message : String ,
173+ #[ serde( skip_serializing_if = "Option::is_none" , rename = "clientToken" ) ]
174+ client_token : Option < String > ,
141175 #[ serde( skip_serializing_if = "Option::is_none" ) ]
142176 status : Option < HealStatusOutput > ,
143177}
@@ -159,6 +193,7 @@ fn heal_stop_output(status: &HealStatus) -> (ExitCode, HealOperationOutput) {
159193 HealOperationOutput {
160194 success,
161195 message : message. to_string ( ) ,
196+ client_token : None ,
162197 status : has_heal_status_details ( status) . then ( || HealStatusOutput :: from ( status) ) ,
163198 } ,
164199 )
@@ -179,7 +214,23 @@ async fn execute_status(args: StatusArgs, formatter: &Formatter) -> ExitCode {
179214 Err ( code) => return code,
180215 } ;
181216
182- match client. heal_status ( ) . await {
217+ let task_request = match heal_task_request (
218+ args. bucket . clone ( ) ,
219+ args. prefix . clone ( ) ,
220+ args. client_token . clone ( ) ,
221+ formatter,
222+ ) {
223+ Ok ( request) => request,
224+ Err ( code) => return code,
225+ } ;
226+
227+ let result = if let Some ( request) = task_request {
228+ client. heal_task_status ( request) . await
229+ } else {
230+ client. heal_status ( ) . await
231+ } ;
232+
233+ match result {
183234 Ok ( status) => {
184235 if formatter. is_json ( ) {
185236 formatter. json ( & HealStatusOutput :: from ( & status) ) ;
@@ -196,10 +247,13 @@ async fn execute_status(args: StatusArgs, formatter: &Formatter) -> ExitCode {
196247}
197248
198249fn print_heal_status ( status : & HealStatus , formatter : & Formatter ) {
199- let healing_status = if status. healing {
200- formatter. style_size ( "In Progress" )
201- } else {
202- formatter. style_date ( "Idle" )
250+ let healing_status = match status. summary . as_deref ( ) {
251+ Some ( "running" ) => formatter. style_size ( "In Progress" ) ,
252+ Some ( "finished" ) => formatter. style_size ( "Finished" ) ,
253+ Some ( "stopped" ) => formatter. style_date ( "Stopped" ) ,
254+ Some ( "notFound" ) => formatter. style_date ( "Not Found" ) ,
255+ _ if status. healing => formatter. style_size ( "In Progress" ) ,
256+ _ => formatter. style_date ( "Idle" ) ,
203257 } ;
204258
205259 formatter. println ( & format ! (
@@ -213,7 +267,14 @@ fn print_heal_status(status: &HealStatus, formatter: &Formatter) {
213267 formatter. println ( & format ! ( " Heal ID: {}" , status. heal_id) ) ;
214268 }
215269
216- if status. healing {
270+ if let Some ( ref summary) = status. summary {
271+ formatter. println ( & format ! ( " Summary: {}" , summary) ) ;
272+ }
273+ if let Some ( ref detail) = status. detail {
274+ formatter. println ( & format ! ( " Detail: {}" , detail) ) ;
275+ }
276+
277+ if status. healing || status. summary . is_some ( ) {
217278 if !status. bucket . is_empty ( ) {
218279 formatter. println ( & format ! (
219280 " Current: {}/{}" ,
@@ -289,6 +350,7 @@ async fn execute_start(args: StartArgs, formatter: &Formatter) -> ExitCode {
289350 let output = HealOperationOutput {
290351 success : true ,
291352 message : "Heal operation started successfully" . to_string ( ) ,
353+ client_token : ( !status. heal_id . is_empty ( ) ) . then ( || status. heal_id . clone ( ) ) ,
292354 status : status_output,
293355 } ;
294356 formatter. json ( & output) ;
@@ -298,9 +360,11 @@ async fn execute_start(args: StartArgs, formatter: &Formatter) -> ExitCode {
298360 } else {
299361 formatter. success ( "Heal operation started successfully." ) ;
300362 }
301- if has_heal_status_details ( & status) {
302- formatter. println ( "" ) ;
303- print_heal_status ( & status, formatter) ;
363+ if !status. heal_id . is_empty ( ) {
364+ formatter. println ( & format ! ( " Client Token: {}" , status. heal_id) ) ;
365+ }
366+ if let Some ( ref started) = status. started {
367+ formatter. println ( & format ! ( " Started: {}" , started) ) ;
304368 }
305369 }
306370 ExitCode :: Success
@@ -318,6 +382,40 @@ async fn execute_stop(args: StopArgs, formatter: &Formatter) -> ExitCode {
318382 Err ( code) => return code,
319383 } ;
320384
385+ let task_request = match heal_task_request (
386+ args. bucket . clone ( ) ,
387+ args. prefix . clone ( ) ,
388+ args. client_token . clone ( ) ,
389+ formatter,
390+ ) {
391+ Ok ( request) => request,
392+ Err ( code) => return code,
393+ } ;
394+
395+ if let Some ( request) = task_request {
396+ return match client. heal_task_stop ( request) . await {
397+ Ok ( status) => {
398+ let ( exit_code, mut output) = heal_stop_output ( & status) ;
399+ output. client_token = ( !status. heal_id . is_empty ( ) ) . then ( || status. heal_id . clone ( ) ) ;
400+
401+ if formatter. is_json ( ) {
402+ formatter. json ( & output) ;
403+ } else if output. success {
404+ formatter. success ( & format ! ( "{}." , output. message) ) ;
405+ } else {
406+ formatter. error ( & format ! ( "{}." , output. message) ) ;
407+ formatter. println ( "" ) ;
408+ print_heal_status ( & status, formatter) ;
409+ }
410+ exit_code
411+ }
412+ Err ( e) => {
413+ formatter. error ( & format ! ( "Failed to stop heal operation: {e}" ) ) ;
414+ ExitCode :: GeneralError
415+ }
416+ } ;
417+ }
418+
321419 match client. heal_stop ( ) . await {
322420 Ok ( ( ) ) => {
323421 let status = match client. heal_status ( ) . await {
@@ -349,6 +447,42 @@ async fn execute_stop(args: StopArgs, formatter: &Formatter) -> ExitCode {
349447 }
350448}
351449
450+ fn heal_task_request (
451+ bucket : Option < String > ,
452+ prefix : Option < String > ,
453+ client_token : Option < String > ,
454+ formatter : & Formatter ,
455+ ) -> Result < Option < HealTaskRequest > , ExitCode > {
456+ if prefix. as_deref ( ) . is_some_and ( |prefix| !prefix. is_empty ( ) )
457+ && bucket. as_deref ( ) . is_none_or ( |bucket| bucket. is_empty ( ) )
458+ {
459+ formatter. error ( "Heal task prefix requires --bucket." ) ;
460+ return Err ( ExitCode :: UsageError ) ;
461+ }
462+
463+ let has_target = bucket. as_deref ( ) . is_some_and ( |bucket| !bucket. is_empty ( ) ) ;
464+ let has_token = client_token
465+ . as_deref ( )
466+ . is_some_and ( |client_token| !client_token. is_empty ( ) ) ;
467+
468+ match ( has_target, has_token) {
469+ ( false , false ) => Ok ( None ) ,
470+ ( true , true ) => Ok ( Some ( HealTaskRequest {
471+ bucket : bucket. expect ( "bucket is present" ) ,
472+ prefix,
473+ client_token : client_token. expect ( "client token is present" ) ,
474+ } ) ) ,
475+ ( true , false ) => {
476+ formatter. error ( "Heal task request requires --client-token when --bucket is set." ) ;
477+ Err ( ExitCode :: UsageError )
478+ }
479+ ( false , true ) => {
480+ formatter. error ( "Heal task request requires --bucket when --client-token is set." ) ;
481+ Err ( ExitCode :: UsageError )
482+ }
483+ }
484+ }
485+
352486/// Format bytes into human-readable form
353487fn format_bytes ( bytes : u64 ) -> String {
354488 const KB : u64 = 1024 ;
@@ -399,15 +533,18 @@ mod tests {
399533 bytes_healed : 1024 * 1024 * 5 ,
400534 started : Some ( "2024-01-01T10:00:00Z" . to_string ( ) ) ,
401535 last_update : Some ( "2024-01-01T10:30:00Z" . to_string ( ) ) ,
536+ ..Default :: default ( )
402537 } ;
403538
404539 let output = HealOperationOutput {
405540 success : true ,
406541 message : "Heal operation started successfully" . to_string ( ) ,
542+ client_token : Some ( "heal-123" . to_string ( ) ) ,
407543 status : Some ( HealStatusOutput :: from ( & status) ) ,
408544 } ;
409545
410546 let value = serde_json:: to_value ( & output) . expect ( "serialize heal operation output" ) ;
547+ assert_eq ! ( value[ "clientToken" ] , "heal-123" ) ;
411548 let status_value = value
412549 . get ( "status" )
413550 . expect ( "status field exists" )
@@ -467,6 +604,7 @@ mod tests {
467604 bytes_healed : 1024 * 1024 * 5 ,
468605 started : Some ( "2024-01-01T10:00:00Z" . to_string ( ) ) ,
469606 last_update : Some ( "2024-01-01T10:30:00Z" . to_string ( ) ) ,
607+ ..Default :: default ( )
470608 } ;
471609
472610 let output = HealStatusOutput :: from ( & status) ;
0 commit comments