@@ -4381,24 +4381,37 @@ pub async fn provider_refresh_status(
43814381 . into_inner ( ) ;
43824382
43834383 if response. credentials . is_empty ( ) {
4384- println ! ( "No refresh configuration found for provider '{name}'." ) ;
4384+ if let Some ( credential_key) = credential_key {
4385+ println ! (
4386+ "No refresh configuration found for provider '{name}' credential '{credential_key}'."
4387+ ) ;
4388+ } else {
4389+ println ! ( "No refresh configurations found for provider '{name}'." ) ;
4390+ }
43854391 return Ok ( ( ) ) ;
43864392 }
43874393
4388- println ! (
4389- "{:<28} {:<28} {:<18} {:<20} {}" ,
4390- "PROVIDER" . bold( ) ,
4391- "CREDENTIAL_KEY" . bold( ) ,
4392- "STRATEGY" . bold( ) ,
4393- "EXPIRES_AT" . bold( ) ,
4394- "STATUS" . bold( ) ,
4395- ) ;
4394+ println ! ( "{}" , refresh_status_header( ) ) ;
43964395 for status in response. credentials {
43974396 print_refresh_status_row ( & status) ;
43984397 }
43994398 Ok ( ( ) )
44004399}
44014400
4401+ fn refresh_status_header ( ) -> String {
4402+ format ! (
4403+ "{:<24} {:<28} {:<28} {:<18} {:<20} {:<20} {:<20} {}" ,
4404+ "PROVIDER" . bold( ) ,
4405+ "CREDENTIAL_KEY" . bold( ) ,
4406+ "STRATEGY" . bold( ) ,
4407+ "STATUS" . bold( ) ,
4408+ "EXPIRES_AT" . bold( ) ,
4409+ "NEXT_REFRESH" . bold( ) ,
4410+ "LAST_REFRESH" . bold( ) ,
4411+ "LAST_ERROR" . bold( ) ,
4412+ )
4413+ }
4414+
44024415pub struct ProviderRefreshConfigInput < ' a > {
44034416 pub name : & ' a str ,
44044417 pub credential_key : & ' a str ,
@@ -4518,20 +4531,44 @@ fn provider_refresh_strategy(strategy: &str) -> Result<ProviderCredentialRefresh
45184531}
45194532
45204533fn print_refresh_status_row ( status : & ProviderCredentialRefreshStatus ) {
4534+ println ! ( "{}" , refresh_status_row( status) ) ;
4535+ }
4536+
4537+ fn refresh_status_row ( status : & ProviderCredentialRefreshStatus ) -> String {
45214538 let strategy = ProviderCredentialRefreshStrategy :: try_from ( status. strategy )
45224539 . unwrap_or ( ProviderCredentialRefreshStrategy :: Unspecified ) ;
4523- println ! (
4524- "{:<28} {:<28} {:<18} {:<20} {}" ,
4540+ format ! (
4541+ "{:<24} {:< 28} {:<28} {:<18} {:<20} {:<20 } {:<20} {}" ,
45254542 status. provider_name,
45264543 status. credential_key,
45274544 provider_refresh_strategy_name( strategy) ,
4528- if status. expires_at_ms > 0 {
4529- format_epoch_ms( status. expires_at_ms)
4530- } else {
4531- "-" . to_string( )
4532- } ,
4533- status. status
4534- ) ;
4545+ status. status,
4546+ format_optional_epoch_ms( status. expires_at_ms) ,
4547+ format_optional_epoch_ms( status. next_refresh_at_ms) ,
4548+ format_optional_epoch_ms( status. last_refresh_at_ms) ,
4549+ truncate_status_field( & status. last_error, 72 ) ,
4550+ )
4551+ }
4552+
4553+ fn format_optional_epoch_ms ( ms : i64 ) -> String {
4554+ if ms > 0 {
4555+ format_epoch_ms ( ms)
4556+ } else {
4557+ "-" . to_string ( )
4558+ }
4559+ }
4560+
4561+ fn truncate_status_field ( value : & str , max_chars : usize ) -> String {
4562+ if value. is_empty ( ) {
4563+ return "-" . to_string ( ) ;
4564+ }
4565+ let mut chars = value. chars ( ) ;
4566+ let truncated = chars. by_ref ( ) . take ( max_chars) . collect :: < String > ( ) ;
4567+ if chars. next ( ) . is_some ( ) {
4568+ format ! ( "{truncated}..." )
4569+ } else {
4570+ truncated
4571+ }
45354572}
45364573
45374574fn provider_refresh_strategy_name ( strategy : ProviderCredentialRefreshStrategy ) -> & ' static str {
@@ -6556,8 +6593,9 @@ mod tests {
65566593 git_sync_files, http_health_check, image_requests_gpu, import_local_package_mtls_bundle,
65576594 inferred_provider_type, package_managed_tls_dirs, parse_cli_setting_value,
65586595 parse_credential_pairs, plaintext_gateway_is_remote, progress_step_from_metadata,
6559- provisioning_timeout_message, ready_false_condition_message, resolve_from,
6560- sandbox_should_persist, service_expose_status_error, service_url_for_gateway,
6596+ provisioning_timeout_message, ready_false_condition_message, refresh_status_header,
6597+ refresh_status_row, resolve_from, sandbox_should_persist, service_expose_status_error,
6598+ service_url_for_gateway,
65616599 } ;
65626600 use crate :: TEST_ENV_LOCK ;
65636601 use hyper:: StatusCode ;
@@ -6576,7 +6614,8 @@ mod tests {
65766614 PROGRESS_STEP_STARTING_SANDBOX ,
65776615 } ;
65786616 use openshell_core:: proto:: {
6579- Provider , SandboxCondition , SandboxStatus , datamodel:: v1:: ObjectMeta ,
6617+ Provider , ProviderCredentialRefreshStatus , ProviderCredentialRefreshStrategy ,
6618+ SandboxCondition , SandboxStatus , datamodel:: v1:: ObjectMeta ,
65806619 } ;
65816620
65826621 struct EnvVarGuard {
@@ -6732,6 +6771,34 @@ mod tests {
67326771 assert_eq ! ( progress_step_from_metadata( "driver-private-step" ) , None ) ;
67336772 }
67346773
6774+ #[ test]
6775+ fn refresh_status_table_includes_operational_fields ( ) {
6776+ let header = refresh_status_header ( ) ;
6777+ assert ! ( header. contains( "NEXT_REFRESH" ) ) ;
6778+ assert ! ( header. contains( "LAST_REFRESH" ) ) ;
6779+ assert ! ( header. contains( "LAST_ERROR" ) ) ;
6780+
6781+ let row = refresh_status_row ( & ProviderCredentialRefreshStatus {
6782+ provider_name : "my-graph" . to_string ( ) ,
6783+ provider_id : "provider-id" . to_string ( ) ,
6784+ credential_key : "MS_GRAPH_ACCESS_TOKEN" . to_string ( ) ,
6785+ strategy : ProviderCredentialRefreshStrategy :: Oauth2ClientCredentials as i32 ,
6786+ status : "error" . to_string ( ) ,
6787+ expires_at_ms : 1_767_225_600_000 ,
6788+ next_refresh_at_ms : 1_767_225_660_000 ,
6789+ last_refresh_at_ms : 1_767_225_000_000 ,
6790+ last_error : "token endpoint returned a very long error message that should be truncated for table readability"
6791+ . to_string ( ) ,
6792+ } ) ;
6793+
6794+ assert ! ( row. contains( "my-graph" ) ) ;
6795+ assert ! ( row. contains( "MS_GRAPH_ACCESS_TOKEN" ) ) ;
6796+ assert ! ( row. contains( "oauth2_client_credentials" ) ) ;
6797+ assert ! ( row. contains( "error" ) ) ;
6798+ assert ! ( row. contains( "2026-01-01 00:00:00" ) ) ;
6799+ assert ! ( row. contains( "..." ) ) ;
6800+ }
6801+
67356802 #[ cfg( feature = "dev-settings" ) ]
67366803 #[ test]
67376804 fn parse_cli_setting_value_parses_bool_aliases ( ) {
0 commit comments