11import {
22 readAdminSystemHealthStatus ,
3+ runAdminSystemHealthAction ,
34 runAdminSystemHealthStorageConnectivityAction ,
45} from "../../../src/api/admin-system-health-api-client.js" ;
56import {
@@ -15,6 +16,7 @@ const STORAGE_DIAGNOSTIC_ACTIONS = Object.freeze([
1516 Object . freeze ( { actionId : "storage-read-test-object" , key : "read" } ) ,
1617 Object . freeze ( { actionId : "storage-delete-test-object" , key : "delete" } ) ,
1718] ) ;
19+ const STORAGE_DIAGNOSTIC_ACTION_KEY_BY_ID = new Map ( STORAGE_DIAGNOSTIC_ACTIONS . map ( ( action ) => [ action . actionId , action . key ] ) ) ;
1820
1921function asText ( value , fallback = "not available" ) {
2022 return statusText ( value , fallback ) ;
@@ -61,6 +63,8 @@ class AdminSystemHealthController {
6163 node ,
6264 ] ) ) ;
6365 this . historyRows = root . querySelector ( "[data-admin-system-health-history-rows]" ) ;
66+ this . actionRows = root . querySelector ( "[data-admin-system-health-action-rows]" ) ;
67+ this . actionButtons = Array . from ( root . querySelectorAll ( "[data-admin-system-health-action]" ) ) ;
6468 this . configurationRows = root . querySelector ( "[data-admin-system-health-configuration-rows]" ) ;
6569 this . serviceCards = root . querySelector ( "[data-admin-system-health-service-cards]" ) ;
6670 this . startupRows = root . querySelector ( "[data-admin-system-health-startup-rows]" ) ;
@@ -71,6 +75,7 @@ class AdminSystemHealthController {
7175 if ( ( ! this . environmentValues . size && ! this . dbValues . size && ! this . storageValues . size ) || document . querySelector ( "[data-session-access-blocked='admin']" ) || window . GameFoundrySessionGuard ?. blocked === true ) {
7276 return ;
7377 }
78+ this . bindManualActions ( ) ;
7479 this . load ( ) ;
7580 }
7681
@@ -141,6 +146,20 @@ class AdminSystemHealthController {
141146 this . renderHistoryPending ( reason ) ;
142147 }
143148
149+ bindManualActions ( ) {
150+ this . actionButtons . forEach ( ( button ) => {
151+ button . addEventListener ( "click" , ( ) => {
152+ this . runManualHealthAction ( button . dataset . adminSystemHealthAction ) ;
153+ } ) ;
154+ } ) ;
155+ }
156+
157+ setManualActionsDisabled ( disabled ) {
158+ this . actionButtons . forEach ( ( button ) => {
159+ button . disabled = disabled ;
160+ } ) ;
161+ }
162+
144163 renderEnvironmentIdentity ( environmentIdentity = { } ) {
145164 const reason = environmentIdentity . message || "Current deployment environment identity returned by the safe Admin System Health API." ;
146165 this . setEnvironmentValue ( "name" , environmentIdentity . name , "Unknown" ) ;
@@ -447,6 +466,71 @@ class AdminSystemHealthController {
447466 return cell ;
448467 }
449468
469+ renderManualActionResult ( result = { } ) {
470+ if ( ! this . actionRows ) {
471+ return ;
472+ }
473+ const blocked = result ?. secretsExposed === true || result ?. secretEditingAllowed === true ;
474+ const row = document . createElement ( "tr" ) ;
475+ row . append (
476+ this . createCell ( result . label || "Manual health action" ) ,
477+ this . createCell ( result . checkedAt || result . lastChecked || "not available" ) ,
478+ this . createStatusCell ( blocked ? "PENDING" : result . status , blocked ? "Safe manual action response was blocked because it exposed secret controls." : result . message ) ,
479+ this . createCell ( blocked ? "Safe manual action response was blocked." : result . message ) ,
480+ ) ;
481+ this . actionRows . replaceChildren ( row ) ;
482+ }
483+
484+ applyManualActionResult ( result = { } ) {
485+ if ( result . statusSnapshot ) {
486+ this . renderStatusData ( result . statusSnapshot ) ;
487+ }
488+ if ( result . runtimeHealth ) {
489+ this . renderRuntimeHealth ( result . runtimeHealth ) ;
490+ }
491+ if ( result . databaseStatus ) {
492+ this . renderPostgresStatus ( result . databaseStatus ) ;
493+ }
494+ if ( result . storageStatus ) {
495+ this . renderStorageStatus ( result . storageStatus ) ;
496+ }
497+ const storageDiagnostics = Array . isArray ( result . storageDiagnostics ) ? result . storageDiagnostics : [ ] ;
498+ storageDiagnostics . forEach ( ( storageResult ) => {
499+ const key = STORAGE_DIAGNOSTIC_ACTION_KEY_BY_ID . get ( storageResult . actionId ) ;
500+ if ( key ) {
501+ this . renderStorageResult ( key , storageResult ) ;
502+ }
503+ } ) ;
504+ }
505+
506+ runManualHealthAction ( actionId ) {
507+ if ( ! actionId ) {
508+ return ;
509+ }
510+ this . setManualActionsDisabled ( true ) ;
511+ this . renderManualActionResult ( {
512+ checkedAt : new Date ( ) . toISOString ( ) ,
513+ label : "Manual health action" ,
514+ message : "Manual health action is running through the safe Admin System Health API." ,
515+ status : "PENDING" ,
516+ } ) ;
517+ try {
518+ const result = runAdminSystemHealthAction ( actionId ) ;
519+ this . renderManualActionResult ( result ) ;
520+ this . applyManualActionResult ( result ) ;
521+ } catch ( error ) {
522+ const message = error instanceof Error ? error . message : "Safe Admin System Health action API is unavailable." ;
523+ this . renderManualActionResult ( {
524+ checkedAt : new Date ( ) . toISOString ( ) ,
525+ label : "Manual health action" ,
526+ message,
527+ status : "FAIL" ,
528+ } ) ;
529+ } finally {
530+ this . setManualActionsDisabled ( false ) ;
531+ }
532+ }
533+
450534 renderRuntimePending ( reason ) {
451535 if ( ! this . runtimeRows ) {
452536 return ;
@@ -488,23 +572,27 @@ class AdminSystemHealthController {
488572 this . runtimeRows . replaceChildren ( fragment ) ;
489573 }
490574
575+ renderStatusData ( data = { } ) {
576+ if ( data ?. secretsExposed === true || data ?. secretEditingAllowed === true ) {
577+ this . renderPending ( "Safe Admin System Health API refused to render because the response exposed secret controls." ) ;
578+ return ;
579+ }
580+ this . renderEnvironmentIdentity ( data ?. environmentIdentity || { } ) ;
581+ this . renderPostgresStatus ( data ?. databaseStatus || { } ) ;
582+ this . renderStartupDiagnostics ( data ?. localApiStartup || { } ) ;
583+ this . renderStorageStatus ( data ?. storageStatus || { } ) ;
584+ this . runStorageDiagnostics ( ) ;
585+ this . renderRuntimeHealth ( data ?. runtimeHealth || { } ) ;
586+ this . renderServiceHealth ( data ?. serviceHealth || { } ) ;
587+ this . renderConfigurationSummary ( data ?. configurationSummary || { } ) ;
588+ this . renderHealthCheckHistory ( data ?. healthCheckHistory || [ ] ) ;
589+ this . renderRuntimeEnvironment ( data ?. runtimeEnvironment || { } ) ;
590+ }
591+
491592 load ( ) {
492593 try {
493594 const data = readAdminSystemHealthStatus ( ) ;
494- if ( data ?. secretsExposed === true || data ?. secretEditingAllowed === true ) {
495- this . renderPending ( "Safe Admin System Health API refused to render because the response exposed secret controls." ) ;
496- return ;
497- }
498- this . renderEnvironmentIdentity ( data ?. environmentIdentity || { } ) ;
499- this . renderPostgresStatus ( data ?. databaseStatus || { } ) ;
500- this . renderStartupDiagnostics ( data ?. localApiStartup || { } ) ;
501- this . renderStorageStatus ( data ?. storageStatus || { } ) ;
502- this . runStorageDiagnostics ( ) ;
503- this . renderRuntimeHealth ( data ?. runtimeHealth || { } ) ;
504- this . renderServiceHealth ( data ?. serviceHealth || { } ) ;
505- this . renderConfigurationSummary ( data ?. configurationSummary || { } ) ;
506- this . renderHealthCheckHistory ( data ?. healthCheckHistory || [ ] ) ;
507- this . renderRuntimeEnvironment ( data ?. runtimeEnvironment || { } ) ;
595+ this . renderStatusData ( data ) ;
508596 } catch ( error ) {
509597 const message = error instanceof Error ? error . message : "Safe Admin System Health API is unavailable." ;
510598 this . renderPending ( message ) ;
0 commit comments