@@ -39,6 +39,21 @@ export interface InternalStatus {
3939 database : any ;
4040}
4141
42+ interface HeartBeat {
43+ status : number ,
44+ time : string ,
45+ msg ?: string ,
46+ ping : number ,
47+ }
48+
49+ interface StatusMetrics {
50+ uptimePercent : number ;
51+ averagePing : number ;
52+ minPing : number ;
53+ maxPing : number ;
54+ }
55+
56+
4257@Component ( {
4358 standalone : true ,
4459 imports : [ CommonModule , PrimeNgModule , DomainFaviconComponent ] ,
@@ -56,6 +71,17 @@ export default class StatusPage {
5671
5772 public dlServicesToSetup : string [ ] = [ 'App' , 'API' , 'Database' , 'Auth' , 'Scheduler' ] ;
5873
74+ readonly uptimeServiceMap : Record < string , string > = {
75+ '5' : 'Landing' ,
76+ '6' : 'Main App' ,
77+ '7' : 'Docs' ,
78+ } ;
79+
80+ // public uptimeExtraInfo
81+
82+ public uptimeChartUrl : string = '' ;
83+ public currentStatuses : { id : string ; name : string ; status : boolean ; latestPing ?: number , extraInfo ?: any } [ ] = [ ] ;
84+
5985 constructor (
6086 private http : HttpClient ,
6187 private errorHandler : ErrorHandlerService ,
@@ -67,13 +93,15 @@ export default class StatusPage {
6793 const servicePieConfig = this . generatePieChartConfig ( data , 'service' , { title : 'Issues per Service' } ) ;
6894 this . pieChartUrl = 'https://quickchart.io/chart?c=' + encodeURIComponent ( JSON . stringify ( servicePieConfig ) ) ;
6995 } ) ;
96+
97+ this . internalStatusInfo$ . subscribe ( this . uptimeDataProcess . bind ( this ) ) ;
7098 }
7199
72100 private fetchInternalStatusData ( ) : Observable < any > {
73101 return this . http . get < any > ( '/api/internal-status-info' ) . pipe (
74102 map ( data => ( {
75103 scheduled : data . scheduledCrons || [ ] ,
76- supabase : data . supabaseStatus ?. healthy || { healthy : false , undetermined : true } ,
104+ supabase : data . supabaseStatus || { healthy : false , undetermined : true } ,
77105 uptime : data . uptimeStatus || { } ,
78106 database : data . databaseStatus || { } ,
79107 } ) ) ,
@@ -126,6 +154,22 @@ export default class StatusPage {
126154 return / ^ # ( [ 0 - 9 A - F ] { 3 } ) { 1 , 2 } $ / i. test ( value ) ? value : fallback ;
127155 }
128156
157+ private calculateStatusMetrics ( heartbeats : HeartBeat [ ] | undefined ) : StatusMetrics | null {
158+ if ( ! heartbeats || heartbeats . length === 0 ) return null ;
159+
160+ const successful = heartbeats . filter ( h => h . status === 1 ) ;
161+ const pings = successful . map ( h => h . ping ) . filter ( p => typeof p === 'number' ) ;
162+
163+ if ( pings . length === 0 ) return null ;
164+
165+ const uptimePercent = Math . round ( ( successful . length / heartbeats . length ) * 100 ) ;
166+ const averagePing = Math . round ( pings . reduce ( ( a , b ) => a + b , 0 ) / pings . length ) ;
167+ const minPing = Math . min ( ...pings ) ;
168+ const maxPing = Math . max ( ...pings ) ;
169+
170+ return { uptimePercent, averagePing, minPing, maxPing } ;
171+ }
172+
129173 /**
130174 * Generates a Chart.js configuration object for a stacked bar chart.
131175 *
@@ -387,4 +431,87 @@ export default class StatusPage {
387431 return { } ;
388432 }
389433
434+ private uptimeDataProcess ( internal : InternalStatus ) : void {
435+ const rawHeartbeats = internal . uptime ?. heartbeatList || { } ;
436+ const statusData : typeof this . currentStatuses = [ ] ;
437+
438+ const chartColors = [
439+ '--purple-400' , '--teal-400' , '--pink-400' , '--blue-400' , '--yellow-400' , '--green-400' ,
440+ ] ;
441+
442+ const datasets = Object . entries ( rawHeartbeats ) . map ( ( [ id , heartbeats ] , i ) => {
443+ const serviceName = this . uptimeServiceMap [ id ] || `Service ${ id } ` ;
444+ const latest = ( heartbeats as HeartBeat [ ] ) . at ( - 1 ) ;
445+
446+ statusData . push ( {
447+ id,
448+ name : serviceName ,
449+ status : latest ?. status === 1 ,
450+ latestPing : latest ?. ping ,
451+ extraInfo : this . calculateStatusMetrics ( heartbeats as HeartBeat [ ] ) ,
452+ } ) ;
453+
454+ return {
455+ label : serviceName ,
456+ data : ( heartbeats as HeartBeat [ ] ) . map ( h => ( {
457+ x : h . time . replace ( ' ' , 'T' ) + 'Z' ,
458+ y : h . ping
459+ } ) ) ,
460+ borderColor : this . getCssVariableColor ( chartColors [ i % chartColors . length ] , '#36A2EB' ) ,
461+ fill : false ,
462+ spanGaps : false ,
463+ tension : 0.4 ,
464+ } ;
465+ } ) ;
466+
467+ this . currentStatuses = statusData ;
468+
469+ // Just using the first dataset's timestamps to generate HH:mm labels
470+ const firstDataset = datasets [ 0 ] ?. data || [ ] ;
471+ const labels = firstDataset . map ( ( point : any ) => {
472+ const date = new Date ( point . x ) ;
473+ return `${ date . getHours ( ) . toString ( ) . padStart ( 2 , '0' ) } :${ date . getMinutes ( ) . toString ( ) . padStart ( 2 , '0' ) } ` ;
474+ } ) ;
475+
476+ const chartConfig = {
477+ type : 'line' ,
478+ data : {
479+ labels,
480+ datasets
481+ } ,
482+ options : {
483+ title : {
484+ display : true ,
485+ text : 'Domain Locker Uptime' ,
486+ color : this . getCssVariableColor ( '--text-color' , '#333' ) ,
487+ } ,
488+ responsive : true ,
489+ plugins : {
490+ title : {
491+ display : true ,
492+ text : 'Service Response Time' ,
493+ color : this . getCssVariableColor ( '--text-color' , '#333' ) ,
494+ }
495+ } ,
496+ scales : {
497+ x : {
498+ title : {
499+ display : true ,
500+ text : 'Time'
501+ }
502+ } ,
503+ y : {
504+ title : {
505+ display : true ,
506+ text : 'Ping (ms)'
507+ }
508+ }
509+ }
510+ }
511+ } ;
512+
513+ this . uptimeChartUrl = `https://quickchart.io/chart?c=${ encodeURIComponent ( JSON . stringify ( chartConfig ) ) } ` ;
514+ }
515+
516+
390517}
0 commit comments