Skip to content

Commit e523e40

Browse files
committed
feat: Adds scheduled tasks and db to status page
1 parent fe0c871 commit e523e40

File tree

5 files changed

+236
-67
lines changed

5 files changed

+236
-67
lines changed

src/app/pages/advanced/diagnostic-actions.page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export default class ErrorPage implements OnInit {
150150
{
151151
label: 'Status Info',
152152
description: 'Ensures that services relied upon by the Domain Locker public instance are running well.',
153-
url: '/api/status-info',
153+
url: '/api/external-status-info',
154154
loading: false,
155155
success: null,
156156
},

src/app/pages/advanced/status.page.html

Lines changed: 102 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,49 +18,115 @@ <h1 class="mb-4">Service Statuses</h1>
1818

1919
<p-divider class="my-4"></p-divider>
2020

21-
<div *ngIf="statusInfo$ | async as statusInfo; else loading" class="flex flex-col gap-4 mt-4">
21+
<div class="flex flex-col gap-4 mt-4">
2222

2323
<!-- Current Status -->
2424
<div class="p-card px-4 py-3">
2525
<h2 class="mb-4">Current Service Status</h2>
26-
<p-divider class="my-4" />
27-
<h3>Domain Locker Internal</h3>
28-
<ul class="list-none p-0 mt-2">
29-
<li *ngFor="let s of dlServicesToSetup" class="flex">
30-
<span class="font-semibold flex-shrink-0 w-32 flex items-center space-x-2">{{s}}</span>
31-
<span class="flex items-center"><i class="pi pi-check-circle text-green-400 mr-1"></i> Operational</span>
32-
</li>
33-
</ul>
26+
<!--
27+
<div *ngIf="statusInfo$ | async">
28+
<p-divider class="my-4" />
29+
<h3>Domain Locker Internal</h3>
30+
<ul class="list-none p-0 mt-2">
31+
<li *ngFor="let s of dlServicesToSetup" class="flex">
32+
<span class="font-medium flex-shrink-0 w-56 flex items-center space-x-2">{{s}}</span>
33+
<span class="flex items-center"><i class="pi pi-check-circle text-green-400 mr-1"></i> Operational</span>
34+
</li>
35+
</ul>
36+
</div> -->
37+
38+
<div *ngIf="internalStatusInfo$ | async as internal">
39+
<!-- <p-divider class="my-4" /> -->
40+
<h3>Database</h3>
41+
<ul class="list-none p-0 mt-2">
42+
<li class="flex">
43+
<span class="font-medium flex-shrink-0 w-56 flex items-center space-x-2">Platform</span>
44+
<span *ngIf="internal.supabase.undetermined" class="flex items-center"><i class="pi pi-exclamation-circle text-yellow-400 mr-1"></i> Undetermined</span>
45+
<span *ngIf="internal.supabase.healthy === true" class="flex items-center"><i class="pi pi-check-circle text-green-400 mr-1"></i> Operational</span>
46+
<span *ngIf="!internal.supabase.undetermined && !internal.supabase.healthy" class="flex items-center"><i class="pi pi-times-circle text-red-400 mr-1"></i> Unavailable</span>
47+
</li>
48+
<li class="flex">
49+
<span class="font-medium flex-shrink-0 w-56 flex items-center space-x-2">Serverless Functions</span>
50+
<span *ngIf="internal.database.up; else notOperational" class="flex items-center"><i class="pi pi-check-circle text-green-400 mr-1"></i> Operational</span>
51+
<ng-template #notOperational>
52+
<span class="flex items-center"><i class="pi pi-times-circle text-red-400 mr-1"></i> Unavailable</span>
53+
</ng-template>
54+
</li>
55+
<li class="flex">
56+
<span class="font-medium flex-shrink-0 w-56 flex items-center space-x-2">Database</span>
57+
<span *ngIf="internal.database.db; else notOperational" class="flex items-center"><i class="pi pi-check-circle text-green-400 mr-1"></i> Operational</span>
58+
<ng-template #notOperational>
59+
<span class="flex items-center"><i class="pi pi-times-circle text-red-400 mr-1"></i> Unavailable</span>
60+
</ng-template>
61+
</li>
62+
<li class="flex">
63+
<span class="font-medium flex-shrink-0 w-56 flex items-center space-x-2">Environment Config</span>
64+
<span *ngIf="internal.database.env; else notOperational" class="flex items-center"><i class="pi pi-check-circle text-green-400 mr-1"></i> Operational</span>
65+
<ng-template #notOperational>
66+
<span class="flex items-center"><i class="pi pi-times-circle text-red-400 mr-1"></i> Unavailable</span>
67+
</ng-template>
68+
</li>
69+
</ul>
70+
</div>
3471

35-
<p-divider class="my-4"></p-divider>
36-
<h3>Third-Party Services</h3>
37-
<div class="flex flex-col">
38-
<div *ngFor="let item of statusInfo.externalServices.summary" class="flex flex-col sm:flex-row mb-1">
39-
<div class="flex-shrink-0 w-32 flex items-center space-x-2">
40-
<app-domain-favicon *ngIf="item.serviceMeta"
41-
[domain]="item.serviceMeta.upstreamSite || item.serviceMeta.url"
42-
[size]="20" />
43-
<span class="font-semibold">{{ item.service }}</span>
44-
</div>
45-
<div class="flex-grow">
46-
<i class="pi pi-circle" [ngClass]="{
47-
'text-red-400 pi-times-circle': item.status === 'critical',
48-
'text-yellow-400 pi-exclamation-circle': item.status === 'minor',
49-
'text-blue-400 pi-info-circle': item.status === 'info',
50-
'text-green-400 pi-check-circle': item.status === 'operational',
51-
'text-gray-400 pi-minus-circle': item.status === 'unknown'
52-
}"></i>
53-
{{ item.details }}
54-
<a *ngIf="item.link" title="View incident details" [href]="item.link" target="_blank">
55-
<i class="pi pi-external-link text-[10px]"></i>
56-
</a>
72+
<div *ngIf="internalStatusInfo$ | async as internal">
73+
<p-divider class="my-4" />
74+
<h3>Scheduled Tasks</h3>
75+
<ul class="list-none p-0 mt-2">
76+
<li *ngFor="let s of internal.scheduled" class="flex" [title]="s.desc">
77+
<details>
78+
<summary class="flex cursor-pointer hover:bg-surface-100 rounded pr-2">
79+
<span class="font-medium flex-shrink-0 w-56 flex items-center space-x-2">{{s.name}}</span>
80+
<span class="flex items-center">
81+
<div *ngIf="s.status === 'up'; else downStatus">
82+
<i class="pi pi-check-circle text-green-400 mr-1"></i> Operational
83+
</div>
84+
<ng-template #downStatus>
85+
<i class="pi pi-times-circle text-red-400 mr-1"></i> Down
86+
</ng-template>
87+
</span>
88+
</summary>
89+
<p class="mt-0 text-sm opacity-70 italic">
90+
Last ping was at {{ s.last_ping | date:"d MMM 'at' HH:mm" }}, and took {{ s.last_duration }}ms.
91+
The next check is scheduled to be completed by {{ s.next_ping ? (s.next_ping | date:"d MMM 'at' HH:mm") : 'tonight' }}.
92+
There have been {{ s.n_pings}} pings.
93+
</p>
94+
</details>
95+
</li>
96+
</ul>
97+
</div>
98+
99+
<div *ngIf="statusInfo$ | async as statusInfo">
100+
<p-divider class="my-4"></p-divider>
101+
<h3>Third-Party Services</h3>
102+
<div class="flex flex-col">
103+
<div *ngFor="let item of statusInfo.summary" class="flex flex-col sm:flex-row mb-1">
104+
<div class="flex-shrink-0 w-56 flex items-center space-x-2">
105+
<app-domain-favicon *ngIf="item.serviceMeta"
106+
[domain]="item.serviceMeta.upstreamSite || item.serviceMeta.url"
107+
[size]="20" />
108+
<span class="font-medium">{{ item.service }}</span>
109+
</div>
110+
<div class="flex-grow">
111+
<i class="pi pi-circle" [ngClass]="{
112+
'text-red-400 pi-times-circle': item.status === 'critical',
113+
'text-yellow-400 pi-exclamation-circle': item.status === 'minor',
114+
'text-blue-400 pi-info-circle': item.status === 'info',
115+
'text-green-400 pi-check-circle': item.status === 'operational',
116+
'text-gray-400 pi-minus-circle': item.status === 'unknown'
117+
}"></i>
118+
{{ item.details }}
119+
<a *ngIf="item.link" title="View incident details" [href]="item.link" target="_blank">
120+
<i class="pi pi-external-link text-[10px]"></i>
121+
</a>
122+
</div>
57123
</div>
58124
</div>
59125
</div>
60126
</div>
61127

62128
<!-- Recent Incidents -->
63-
<div class="p-card px-4 py-3">
129+
<div class="p-card px-4 py-3" *ngIf="statusInfo$ | async as statusInfo; else loading" >
64130
<h2 class="mb-4">Third-Party Disruptions</h2>
65131
<div class="flex items-center justifty-between justify-around gap-4">
66132
<img *ngIf="historyChartUrl" [src]="historyChartUrl" alt="Incident History Chart" class="max-w-96" />
@@ -69,7 +135,7 @@ <h2 class="mb-4">Third-Party Disruptions</h2>
69135
<p-divider class="my-4"></p-divider>
70136
<h3>Recent Incidents</h3>
71137
<ul class="list-none p-0 transition-all duration-300">
72-
<li *ngFor="let item of statusInfo.externalServices.history | slice:0:(showAllHistory ? statusInfo.externalServices.history.length : 10)"
138+
<li *ngFor="let item of statusInfo.history | slice:0:(showAllHistory ? statusInfo.history.length : 10)"
73139
class="flex items-between sm:items-center gap-1 sm:gap-2 mb-1 flex-col sm:flex-row">
74140
<span class="flex-shrink-0 whitespace-nowrap">
75141
<i class="pi pi-circle" [ngClass]="{
@@ -81,7 +147,7 @@ <h3>Recent Incidents</h3>
81147
}"></i>
82148
{{ item.date | date:"d MMM 'at' HH:mm" }}
83149
</span>
84-
<span class="flex-shrink-0 whitespace-nowrap font-semibold">
150+
<span class="flex-shrink-0 whitespace-nowrap font-medium">
85151
<app-domain-favicon *ngIf="item.serviceMeta"
86152
[domain]="item.serviceMeta.upstreamSite || item.serviceMeta.url"
87153
[size]="16" />
@@ -110,13 +176,13 @@ <h3>Recent Incidents</h3>
110176

111177
<h3>Scheduled Maintenance</h3>
112178
<ul class="list-none p-0 transition-all duration-300">
113-
<li *ngFor="let item of statusInfo.externalServices.scheduled | slice:0:(showAllScheduled ? statusInfo.externalServices.scheduled.length : 10)"
179+
<li *ngFor="let item of statusInfo.scheduled | slice:0:(showAllScheduled ? statusInfo.scheduled.length : 10)"
114180
class="flex items-between sm:items-center gap-1 sm:gap-2 mb-1 flex-col sm:flex-row">
115181
<span class="flex-shrink-0 whitespace-nowrap">
116182
<i class="pi pi-history text-purple-400"></i>
117183
{{ item.date | date:"d MMM 'at' HH:mm" }}
118184
</span>
119-
<span class="flex-shrink-0 whitespace-nowrap font-semibold">
185+
<span class="flex-shrink-0 whitespace-nowrap font-medium">
120186
<app-domain-favicon *ngIf="item.serviceMeta"
121187
[domain]="item.serviceMeta.upstreamSite || item.serviceMeta.url"
122188
[size]="16" />

src/app/pages/advanced/status.page.ts

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,16 @@ export interface StatusLog {
2727
}
2828

2929
export interface StatusData {
30-
externalServices: {
31-
summary: StatusSummary[];
32-
history: StatusLog[];
33-
scheduled: StatusLog[];
34-
}
30+
summary: StatusSummary[];
31+
history: StatusLog[];
32+
scheduled: StatusLog[];
33+
}
34+
35+
export interface InternalStatus {
36+
scheduled: any[];
37+
supabase: { healthy: boolean, undetermined?: boolean };
38+
uptime: any;
39+
database: any;
3540
}
3641

3742
@Component({
@@ -41,6 +46,7 @@ export interface StatusData {
4146
})
4247
export default class StatusPage {
4348
readonly statusInfo$: Observable<StatusData> = this.fetchStatusData();
49+
readonly internalStatusInfo$: Observable<InternalStatus> = this.fetchInternalStatusData();
4450
public historyChartUrl: string = '';
4551
public pieChartUrl: string = '';
4652

@@ -63,14 +69,32 @@ export default class StatusPage {
6369
});
6470
}
6571

72+
private fetchInternalStatusData(): Observable<any> {
73+
return this.http.get<any>('/api/internal-status-info').pipe(
74+
map(data => ({
75+
scheduled: data.scheduledCrons || [],
76+
supabase: data.supabaseStatus?.healthy || { healthy: false, undetermined: true },
77+
uptime: data.uptimeStatus || {},
78+
database: data.databaseStatus || {},
79+
})),
80+
catchError(error => {
81+
this.errorHandler.handleError({
82+
error,
83+
message: 'Failed to load internal status data',
84+
location: 'internal-status-info',
85+
showToast: true,
86+
});
87+
return of({ scheduled: [], supabase: { healthy: false }, uptime: {} });
88+
})
89+
)
90+
}
91+
6692
private fetchStatusData(): Observable<StatusData> {
67-
return this.http.get<StatusData>('/api/status-info').pipe(
93+
return this.http.get<StatusData>('/api/external-status-info').pipe(
6894
map(data => ({
69-
externalServices: {
70-
summary: this.enrichList(data.externalServices.summary),
71-
history: this.enrichList(data.externalServices.history),
72-
scheduled: this.enrichList(data.externalServices.scheduled),
73-
}
95+
summary: this.enrichList(data.summary),
96+
history: this.enrichList(data.history),
97+
scheduled: this.enrichList(data.scheduled),
7498
})),
7599
catchError(error => {
76100
this.errorHandler.handleError({
@@ -79,15 +103,7 @@ export default class StatusPage {
79103
location: 'status-info',
80104
showToast: true,
81105
});
82-
return of(
83-
{
84-
externalServices: {
85-
summary: [],
86-
history: [],
87-
scheduled: [],
88-
}
89-
},
90-
);
106+
return of({ summary: [], history: [], scheduled: [] });
91107
})
92108
);
93109
}
@@ -163,7 +179,7 @@ export default class StatusPage {
163179
};
164180

165181
// Process history items (past and present only).
166-
statusData.externalServices.history.forEach(item => {
182+
statusData.history.forEach(item => {
167183
const d = new Date(item.date);
168184
d.setHours(0, 0, 0, 0);
169185
const idx = getDayIndex(d);
@@ -180,7 +196,7 @@ export default class StatusPage {
180196
});
181197

182198
// Process scheduled items (future only).
183-
statusData.externalServices.scheduled.forEach(item => {
199+
statusData.scheduled.forEach(item => {
184200
const d = new Date(item.date);
185201
d.setHours(0, 0, 0, 0);
186202
const idx = getDayIndex(d);
@@ -286,7 +302,7 @@ export default class StatusPage {
286302
options?: { title?: string }
287303
): any {
288304
// Combine both history and scheduled items.
289-
const allItems = [...statusData.externalServices.history, ...statusData.externalServices.scheduled];
305+
const allItems = [...statusData.history, ...statusData.scheduled];
290306
const counts: { [key: string]: number } = {};
291307

292308
if (breakdownType === 'service') {

src/server/routes/status-info.ts renamed to src/server/routes/external-status-info.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ const services: Service[] = [
6161
service: 'GitHub',
6262
rss: 'https://www.githubstatus.com/history.rss',
6363
},
64-
{
65-
service: 'Healthchecks.io',
66-
rss: 'https://status.healthchecks.io/updates.rss',
67-
},
64+
// {
65+
// service: 'GitHub',
66+
// rss: 'https://www.githubstatus.com/history.rss',
67+
// },
6868
];
6969

7070
/**
@@ -242,13 +242,13 @@ export default defineEventHandler(async (event) => {
242242
}
243243

244244
// Prepare the final results object
245-
const externalServices = { summary, history, scheduled };
245+
const results = { summary, history, scheduled };
246246

247247
// Update cache
248248
cache.timestamp = now;
249-
cache.data = { externalServices };
249+
cache.data = results;
250250

251251
// And then return results, and go to bed
252-
return cache.data;
252+
return results;
253253

254254
});

0 commit comments

Comments
 (0)