@oneuptime/common 7.0.3156 → 7.0.3162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/StatusPageDomain.ts +86 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1728472625805-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/StatusPageDomainService.ts +14 -0
- package/Types/WebsiteRequest.ts +5 -0
- package/Utils/API.ts +5 -0
- package/Utils/StatusPage/ResourceUptime.ts +301 -0
- package/Utils/Uptime/UptimeUtil.ts +17 -134
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +92 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1728472625805-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1728472625805-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +9 -0
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Types/WebsiteRequest.js +3 -0
- package/build/dist/Types/WebsiteRequest.js.map +1 -1
- package/build/dist/Utils/API.js +3 -0
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceUptime.js +182 -0
- package/build/dist/Utils/StatusPage/ResourceUptime.js.map +1 -0
- package/build/dist/Utils/Uptime/UptimeUtil.js +15 -91
- package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
- package/package.json +2 -2
|
@@ -538,4 +538,90 @@ export default class StatusPageDomain extends BaseModel {
|
|
|
538
538
|
transformer: ObjectID.getDatabaseTransformer(),
|
|
539
539
|
})
|
|
540
540
|
public deletedByUserId?: ObjectID = undefined;
|
|
541
|
+
|
|
542
|
+
@ColumnAccessControl({
|
|
543
|
+
create: [
|
|
544
|
+
Permission.ProjectOwner,
|
|
545
|
+
Permission.ProjectAdmin,
|
|
546
|
+
Permission.ProjectMember,
|
|
547
|
+
Permission.CreateStatusPageDomain,
|
|
548
|
+
],
|
|
549
|
+
read: [
|
|
550
|
+
Permission.ProjectOwner,
|
|
551
|
+
Permission.ProjectAdmin,
|
|
552
|
+
Permission.ProjectMember,
|
|
553
|
+
Permission.ReadStatusPageDomain,
|
|
554
|
+
],
|
|
555
|
+
update: [
|
|
556
|
+
Permission.ProjectOwner,
|
|
557
|
+
Permission.ProjectAdmin,
|
|
558
|
+
Permission.ProjectMember,
|
|
559
|
+
Permission.EditStatusPageDomain,
|
|
560
|
+
],
|
|
561
|
+
})
|
|
562
|
+
@TableColumn({ type: TableColumnType.VeryLongText })
|
|
563
|
+
@Column({
|
|
564
|
+
type: ColumnType.VeryLongText,
|
|
565
|
+
nullable: true,
|
|
566
|
+
unique: false,
|
|
567
|
+
})
|
|
568
|
+
public customCertificate?: string = undefined;
|
|
569
|
+
|
|
570
|
+
@ColumnAccessControl({
|
|
571
|
+
create: [
|
|
572
|
+
Permission.ProjectOwner,
|
|
573
|
+
Permission.ProjectAdmin,
|
|
574
|
+
Permission.ProjectMember,
|
|
575
|
+
Permission.CreateStatusPageDomain,
|
|
576
|
+
],
|
|
577
|
+
read: [
|
|
578
|
+
Permission.ProjectOwner,
|
|
579
|
+
Permission.ProjectAdmin,
|
|
580
|
+
Permission.ProjectMember,
|
|
581
|
+
Permission.ReadStatusPageDomain,
|
|
582
|
+
],
|
|
583
|
+
update: [
|
|
584
|
+
Permission.ProjectOwner,
|
|
585
|
+
Permission.ProjectAdmin,
|
|
586
|
+
Permission.ProjectMember,
|
|
587
|
+
Permission.EditStatusPageDomain,
|
|
588
|
+
],
|
|
589
|
+
})
|
|
590
|
+
@TableColumn({ type: TableColumnType.VeryLongText })
|
|
591
|
+
@Column({
|
|
592
|
+
type: ColumnType.VeryLongText,
|
|
593
|
+
nullable: true,
|
|
594
|
+
unique: false,
|
|
595
|
+
})
|
|
596
|
+
public customCertificateKey?: string = undefined;
|
|
597
|
+
|
|
598
|
+
// If this is true, then the certificate is custom and not managed by OneUptime (LetsEncrypt)
|
|
599
|
+
@ColumnAccessControl({
|
|
600
|
+
create: [
|
|
601
|
+
Permission.ProjectOwner,
|
|
602
|
+
Permission.ProjectAdmin,
|
|
603
|
+
Permission.ProjectMember,
|
|
604
|
+
Permission.CreateStatusPageDomain,
|
|
605
|
+
],
|
|
606
|
+
read: [
|
|
607
|
+
Permission.ProjectOwner,
|
|
608
|
+
Permission.ProjectAdmin,
|
|
609
|
+
Permission.ProjectMember,
|
|
610
|
+
Permission.ReadStatusPageDomain,
|
|
611
|
+
],
|
|
612
|
+
update: [
|
|
613
|
+
Permission.ProjectOwner,
|
|
614
|
+
Permission.ProjectAdmin,
|
|
615
|
+
Permission.ProjectMember,
|
|
616
|
+
Permission.EditStatusPageDomain,
|
|
617
|
+
],
|
|
618
|
+
})
|
|
619
|
+
@TableColumn({ type: TableColumnType.Boolean })
|
|
620
|
+
@Column({
|
|
621
|
+
type: ColumnType.Boolean,
|
|
622
|
+
nullable: false,
|
|
623
|
+
unique: false,
|
|
624
|
+
default: false, // default is false
|
|
625
|
+
})
|
|
626
|
+
public isCustomCertificate?: boolean = undefined;
|
|
541
627
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1728472625805 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1728472625805";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "StatusPageDomain" ADD "customCertificate" text`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "StatusPageDomain" ADD "customCertificateKey" text`,
|
|
12
|
+
);
|
|
13
|
+
await queryRunner.query(
|
|
14
|
+
`ALTER TABLE "StatusPageDomain" ADD "isCustomCertificate" boolean NOT NULL DEFAULT false`,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`ALTER TABLE "StatusPageDomain" DROP COLUMN "isCustomCertificate"`,
|
|
21
|
+
);
|
|
22
|
+
await queryRunner.query(
|
|
23
|
+
`ALTER TABLE "StatusPageDomain" DROP COLUMN "customCertificateKey"`,
|
|
24
|
+
);
|
|
25
|
+
await queryRunner.query(
|
|
26
|
+
`ALTER TABLE "StatusPageDomain" DROP COLUMN "customCertificate"`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -73,6 +73,7 @@ import { MigrationName1727194211048 } from "./1727194211048-MigrationName";
|
|
|
73
73
|
import { MigrationName1727194579925 } from "./1727194579925-MigrationName";
|
|
74
74
|
import { MigrationName1727894983857 } from "./1727894983857-MigrationName";
|
|
75
75
|
import { MigrationName1727906598804 } from "./1727906598804-MigrationName";
|
|
76
|
+
import { MigrationName1728472625805 } from "./1728472625805-MigrationName";
|
|
76
77
|
|
|
77
78
|
export default [
|
|
78
79
|
InitialMigration,
|
|
@@ -150,4 +151,5 @@ export default [
|
|
|
150
151
|
MigrationName1727194579925,
|
|
151
152
|
MigrationName1727894983857,
|
|
152
153
|
MigrationName1727906598804,
|
|
154
|
+
MigrationName1728472625805,
|
|
153
155
|
];
|
|
@@ -50,6 +50,17 @@ export class Service extends DatabaseService<StatusPageDomain> {
|
|
|
50
50
|
|
|
51
51
|
createBy.data.cnameVerificationToken = ObjectID.generate().toString();
|
|
52
52
|
|
|
53
|
+
if (createBy.data.isCustomCertificate) {
|
|
54
|
+
if (
|
|
55
|
+
!createBy.data.customCertificate ||
|
|
56
|
+
!createBy.data.customCertificateKey
|
|
57
|
+
) {
|
|
58
|
+
throw new BadDataException(
|
|
59
|
+
"Custom certificate or private key is missing",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
return { createBy, carryForward: null };
|
|
54
65
|
}
|
|
55
66
|
|
|
@@ -168,6 +179,7 @@ export class Service extends DatabaseService<StatusPageDomain> {
|
|
|
168
179
|
const domains: Array<StatusPageDomain> = await this.findBy({
|
|
169
180
|
query: {
|
|
170
181
|
isSslOrdered: true,
|
|
182
|
+
isCustomCertificate: false,
|
|
171
183
|
},
|
|
172
184
|
select: {
|
|
173
185
|
_id: true,
|
|
@@ -419,6 +431,7 @@ export class Service extends DatabaseService<StatusPageDomain> {
|
|
|
419
431
|
const domains: Array<StatusPageDomain> = await this.findBy({
|
|
420
432
|
query: {
|
|
421
433
|
isSslOrdered: false,
|
|
434
|
+
isCustomCertificate: false, // only order for non custom certificates.
|
|
422
435
|
},
|
|
423
436
|
select: {
|
|
424
437
|
_id: true,
|
|
@@ -507,6 +520,7 @@ export class Service extends DatabaseService<StatusPageDomain> {
|
|
|
507
520
|
const domains: Array<StatusPageDomain> = await this.findBy({
|
|
508
521
|
query: {
|
|
509
522
|
isSslOrdered: true,
|
|
523
|
+
isCustomCertificate: false,
|
|
510
524
|
},
|
|
511
525
|
select: {
|
|
512
526
|
_id: true,
|
package/Types/WebsiteRequest.ts
CHANGED
|
@@ -21,6 +21,7 @@ export default class WebsiteRequest {
|
|
|
21
21
|
headers?: Headers | undefined;
|
|
22
22
|
timeout?: number | undefined;
|
|
23
23
|
isHeadRequest?: boolean | undefined;
|
|
24
|
+
doNotFollowRedirects?: boolean | undefined;
|
|
24
25
|
},
|
|
25
26
|
): Promise<WebsiteResponse> {
|
|
26
27
|
const axiosOptions: AxiosRequestConfig = {
|
|
@@ -36,6 +37,10 @@ export default class WebsiteRequest {
|
|
|
36
37
|
axiosOptions.method = HTTPMethod.HEAD;
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
if (options.doNotFollowRedirects) {
|
|
41
|
+
axiosOptions.maxRedirects = 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
// use axios to fetch an HTML page
|
|
40
45
|
let response: AxiosResponse | null = null;
|
|
41
46
|
|
package/Utils/API.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface RequestOptions {
|
|
|
18
18
|
retries?: number | undefined;
|
|
19
19
|
exponentialBackoff?: boolean | undefined;
|
|
20
20
|
timeout?: number | undefined;
|
|
21
|
+
doNotFollowRedirects?: boolean | undefined;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export default class API {
|
|
@@ -395,6 +396,10 @@ export default class API {
|
|
|
395
396
|
axiosOptions.timeout = options.timeout;
|
|
396
397
|
}
|
|
397
398
|
|
|
399
|
+
if (options?.doNotFollowRedirects) {
|
|
400
|
+
axiosOptions.maxRedirects = 0;
|
|
401
|
+
}
|
|
402
|
+
|
|
398
403
|
result = await axios(axiosOptions);
|
|
399
404
|
|
|
400
405
|
break;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Green } from "../../Types/BrandColors";
|
|
2
|
+
import ObjectID from "../../Types/ObjectID";
|
|
3
|
+
import MonitorStatus from "../../Models/DatabaseModels/MonitorStatus";
|
|
4
|
+
import MonitorStatusTimeline from "../../Models/DatabaseModels/MonitorStatusTimeline";
|
|
5
|
+
import StatusPageResource from "../../Models/DatabaseModels/StatusPageResource";
|
|
6
|
+
import Dictionary from "../../Types/Dictionary";
|
|
7
|
+
import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
|
|
8
|
+
import StatusPageGroup from "../../Models/DatabaseModels/StatusPageGroup";
|
|
9
|
+
import UptimeUtil from "../Uptime/UptimeUtil";
|
|
10
|
+
|
|
11
|
+
export default class StatusPageResourceUptimeUtil {
|
|
12
|
+
public static getMonitorStatusTimelineForResource(data: {
|
|
13
|
+
statusPageResource: StatusPageResource;
|
|
14
|
+
monitorStatusTimelines: Array<MonitorStatusTimeline>;
|
|
15
|
+
monitorsInGroup: Dictionary<Array<ObjectID>>;
|
|
16
|
+
}): Array<MonitorStatusTimeline> {
|
|
17
|
+
return [...data.monitorStatusTimelines].filter(
|
|
18
|
+
(timeline: MonitorStatusTimeline) => {
|
|
19
|
+
// check monitor if first.
|
|
20
|
+
|
|
21
|
+
if (data.statusPageResource.monitorId) {
|
|
22
|
+
return (
|
|
23
|
+
timeline.monitorId?.toString() ===
|
|
24
|
+
data.statusPageResource.monitorId?.toString()
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (data.statusPageResource.monitorGroupId) {
|
|
29
|
+
const monitorsInThisGroup: Array<ObjectID> | undefined =
|
|
30
|
+
data.monitorsInGroup[
|
|
31
|
+
data.statusPageResource.monitorGroupId?.toString() || ""
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
if (!monitorsInThisGroup) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return monitorsInThisGroup.find((monitorId: ObjectID) => {
|
|
39
|
+
return monitorId.toString() === timeline.monitorId?.toString();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return false;
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static getCurrentStatusPageGroupStatus(data: {
|
|
49
|
+
statusPageGroup: StatusPageGroup;
|
|
50
|
+
monitorStatusTimelines: Array<MonitorStatusTimeline>;
|
|
51
|
+
statusPageResources: Array<StatusPageResource>;
|
|
52
|
+
monitorStatuses: Array<MonitorStatus>;
|
|
53
|
+
monitorGroupCurrentStatuses: Dictionary<ObjectID>;
|
|
54
|
+
}): MonitorStatus {
|
|
55
|
+
let currentStatus: MonitorStatus = new MonitorStatus();
|
|
56
|
+
currentStatus.name = "Operational";
|
|
57
|
+
currentStatus.color = Green;
|
|
58
|
+
|
|
59
|
+
const resourcesInGroup: Array<StatusPageResource> =
|
|
60
|
+
this.getResourcesInStatusPageGroup({
|
|
61
|
+
statusPageGroup: data.statusPageGroup,
|
|
62
|
+
statusPageResources: data.statusPageResources,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
for (const resource of resourcesInGroup) {
|
|
66
|
+
let currentMonitorStatus: MonitorStatus | undefined = undefined;
|
|
67
|
+
|
|
68
|
+
if (resource.monitor) {
|
|
69
|
+
currentMonitorStatus = data.monitorStatuses.find(
|
|
70
|
+
(status: MonitorStatus) => {
|
|
71
|
+
return (
|
|
72
|
+
status._id?.toString() ===
|
|
73
|
+
resource.monitor?.currentMonitorStatusId?.toString()
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (resource.monitorGroupId) {
|
|
80
|
+
currentMonitorStatus = data.monitorStatuses.find(
|
|
81
|
+
(status: MonitorStatus) => {
|
|
82
|
+
return (
|
|
83
|
+
status._id?.toString() ===
|
|
84
|
+
data.monitorGroupCurrentStatuses[
|
|
85
|
+
resource.monitorGroupId?.toString() || ""
|
|
86
|
+
]?.toString()
|
|
87
|
+
);
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!currentMonitorStatus) {
|
|
93
|
+
currentMonitorStatus = currentStatus;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
(currentStatus &&
|
|
98
|
+
currentStatus.priority &&
|
|
99
|
+
currentMonitorStatus?.priority &&
|
|
100
|
+
currentMonitorStatus?.priority > currentStatus.priority) ||
|
|
101
|
+
!currentStatus ||
|
|
102
|
+
!currentStatus.priority
|
|
103
|
+
) {
|
|
104
|
+
currentStatus = currentMonitorStatus!;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return currentStatus;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public static calculateUptimePercentOfResource(data: {
|
|
112
|
+
statusPageResource: StatusPageResource;
|
|
113
|
+
monitorStatusTimelines: Array<MonitorStatusTimeline>;
|
|
114
|
+
precision: UptimePrecision;
|
|
115
|
+
downtimeMonitorStatuses: Array<MonitorStatus>;
|
|
116
|
+
monitorsInGroup: Dictionary<Array<ObjectID>>;
|
|
117
|
+
}): number | null {
|
|
118
|
+
if (!data.statusPageResource.showUptimePercent) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const monitorStatusTimelines: Array<MonitorStatusTimeline> =
|
|
123
|
+
this.getMonitorStatusTimelineForResource({
|
|
124
|
+
statusPageResource: data.statusPageResource,
|
|
125
|
+
monitorStatusTimelines: data.monitorStatusTimelines,
|
|
126
|
+
monitorsInGroup: data.monitorsInGroup,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const downtimeMonitorStatuses: Array<MonitorStatus> =
|
|
130
|
+
data?.downtimeMonitorStatuses || [];
|
|
131
|
+
|
|
132
|
+
const uptimePercent: number = UptimeUtil.calculateUptimePercentage(
|
|
133
|
+
monitorStatusTimelines,
|
|
134
|
+
data.precision,
|
|
135
|
+
downtimeMonitorStatuses,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return uptimePercent;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public static calculateAvgUptimePercentOfStatusPageGroup(data: {
|
|
142
|
+
statusPageGroup: StatusPageGroup;
|
|
143
|
+
monitorStatusTimelines: Array<MonitorStatusTimeline>;
|
|
144
|
+
precision: UptimePrecision;
|
|
145
|
+
downtimeMonitorStatuses: Array<MonitorStatus>;
|
|
146
|
+
statusPageResources: Array<StatusPageResource>;
|
|
147
|
+
monitorsInGroup: Dictionary<Array<ObjectID>>;
|
|
148
|
+
}): number | null {
|
|
149
|
+
if (!data.statusPageGroup.showUptimePercent) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const resourcesInGroup: Array<StatusPageResource> =
|
|
154
|
+
this.getResourcesInStatusPageGroup({
|
|
155
|
+
statusPageGroup: data.statusPageGroup,
|
|
156
|
+
statusPageResources: data.statusPageResources,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (resourcesInGroup.length === 0) {
|
|
160
|
+
return null; // no resources in group.
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const uptimePercentPerResource: Array<number> = [];
|
|
164
|
+
|
|
165
|
+
for (const resource of resourcesInGroup) {
|
|
166
|
+
const calculateUptimePercentOfResource: number | null =
|
|
167
|
+
this.calculateUptimePercentOfResource({
|
|
168
|
+
statusPageResource: resource,
|
|
169
|
+
monitorStatusTimelines: data.monitorStatusTimelines,
|
|
170
|
+
precision: data.precision,
|
|
171
|
+
downtimeMonitorStatuses: data.downtimeMonitorStatuses,
|
|
172
|
+
monitorsInGroup: data.monitorsInGroup,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (calculateUptimePercentOfResource !== null) {
|
|
176
|
+
uptimePercentPerResource.push(calculateUptimePercentOfResource);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// calculate avg
|
|
181
|
+
|
|
182
|
+
if (uptimePercentPerResource.length === 0) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const averageUptimePercentage: number =
|
|
187
|
+
uptimePercentPerResource.reduce((a: number, b: number) => {
|
|
188
|
+
return a + b;
|
|
189
|
+
}) / uptimePercentPerResource.length;
|
|
190
|
+
|
|
191
|
+
// if the current status is operational then show uptime Percent.
|
|
192
|
+
|
|
193
|
+
let precision: UptimePrecision = UptimePrecision.ONE_DECIMAL;
|
|
194
|
+
|
|
195
|
+
if (data.statusPageGroup.uptimePercentPrecision) {
|
|
196
|
+
precision = data.statusPageGroup.uptimePercentPrecision;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return UptimeUtil.roundToPrecision({
|
|
200
|
+
number: averageUptimePercentage,
|
|
201
|
+
precision: precision,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public static getResourcesWithoutStatusPageGroup(data: {
|
|
206
|
+
statusPageResources: Array<StatusPageResource>;
|
|
207
|
+
}): Array<StatusPageResource> {
|
|
208
|
+
return data.statusPageResources.filter((resource: StatusPageResource) => {
|
|
209
|
+
return !resource.statusPageGroupId;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public static getResourcesInStatusPageGroup(data: {
|
|
214
|
+
statusPageGroup: StatusPageGroup;
|
|
215
|
+
statusPageResources: Array<StatusPageResource>;
|
|
216
|
+
}): Array<StatusPageResource> {
|
|
217
|
+
return data.statusPageResources.filter((resource: StatusPageResource) => {
|
|
218
|
+
return (
|
|
219
|
+
resource.statusPageGroupId?.toString() ===
|
|
220
|
+
data.statusPageGroup._id?.toString()
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
public static calculateAvgUptimePercentageOfAllResources(data: {
|
|
226
|
+
monitorStatusTimelines: Array<MonitorStatusTimeline>;
|
|
227
|
+
precision: UptimePrecision;
|
|
228
|
+
downtimeMonitorStatuses: Array<MonitorStatus>;
|
|
229
|
+
statusPageResources: Array<StatusPageResource>;
|
|
230
|
+
resourceGroups: Array<StatusPageGroup>;
|
|
231
|
+
monitorsInGroup: Dictionary<Array<ObjectID>>;
|
|
232
|
+
}): number | null {
|
|
233
|
+
const showUptimePercentage: boolean = Boolean(
|
|
234
|
+
data.statusPageResources.find((item: StatusPageResource) => {
|
|
235
|
+
return item.showUptimePercent || item.showStatusHistoryChart;
|
|
236
|
+
}),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (!showUptimePercentage) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const allUptimePercent: Array<number> = [];
|
|
244
|
+
|
|
245
|
+
// calculate for groups first.
|
|
246
|
+
|
|
247
|
+
for (const group of data.resourceGroups) {
|
|
248
|
+
const calculateAvgUptimePercentOfStatusPageGroup: number | null =
|
|
249
|
+
this.calculateAvgUptimePercentOfStatusPageGroup({
|
|
250
|
+
statusPageGroup: group,
|
|
251
|
+
monitorStatusTimelines: data.monitorStatusTimelines,
|
|
252
|
+
precision: data.precision,
|
|
253
|
+
downtimeMonitorStatuses: data.downtimeMonitorStatuses,
|
|
254
|
+
statusPageResources: data.statusPageResources,
|
|
255
|
+
monitorsInGroup: data.monitorsInGroup,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (calculateAvgUptimePercentOfStatusPageGroup !== null) {
|
|
259
|
+
allUptimePercent.push(calculateAvgUptimePercentOfStatusPageGroup);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// now fetch resources without group.
|
|
264
|
+
|
|
265
|
+
const resourcesWithoutGroup: Array<StatusPageResource> =
|
|
266
|
+
this.getResourcesWithoutStatusPageGroup({
|
|
267
|
+
statusPageResources: data.statusPageResources,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
for (const resource of resourcesWithoutGroup) {
|
|
271
|
+
const calculateUptimePercentOfResource: number | null =
|
|
272
|
+
this.calculateUptimePercentOfResource({
|
|
273
|
+
statusPageResource: resource,
|
|
274
|
+
monitorStatusTimelines: data.monitorStatusTimelines,
|
|
275
|
+
precision: data.precision,
|
|
276
|
+
downtimeMonitorStatuses: data.downtimeMonitorStatuses,
|
|
277
|
+
monitorsInGroup: data.monitorsInGroup,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (calculateUptimePercentOfResource !== null) {
|
|
281
|
+
allUptimePercent.push(calculateUptimePercentOfResource);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// calculate avg
|
|
286
|
+
|
|
287
|
+
if (allUptimePercent.length === 0) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const averageUptimePercentage: number =
|
|
292
|
+
allUptimePercent.reduce((a: number, b: number) => {
|
|
293
|
+
return a + b;
|
|
294
|
+
}) / allUptimePercent.length;
|
|
295
|
+
|
|
296
|
+
return UptimeUtil.roundToPrecision({
|
|
297
|
+
number: averageUptimePercentage,
|
|
298
|
+
precision: data.precision,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -5,8 +5,6 @@ import OneUptimeDate from "../../Types/Date";
|
|
|
5
5
|
import ObjectID from "../../Types/ObjectID";
|
|
6
6
|
import MonitorStatus from "../../Models/DatabaseModels/MonitorStatus";
|
|
7
7
|
import MonitorStatusTimeline from "../../Models/DatabaseModels/MonitorStatusTimeline";
|
|
8
|
-
import StatusPageResource from "../../Models/DatabaseModels/StatusPageResource";
|
|
9
|
-
import Dictionary from "../../Types/Dictionary";
|
|
10
8
|
import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
|
|
11
9
|
|
|
12
10
|
export default class UptimeUtil {
|
|
@@ -301,111 +299,29 @@ export default class UptimeUtil {
|
|
|
301
299
|
};
|
|
302
300
|
}
|
|
303
301
|
|
|
304
|
-
public static
|
|
305
|
-
|
|
302
|
+
public static roundToPrecision(data: {
|
|
303
|
+
number: number;
|
|
306
304
|
precision: UptimePrecision;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
monitorsInGroup: Dictionary<Array<ObjectID>>;
|
|
310
|
-
}): number | null {
|
|
311
|
-
const showUptimePercentage: boolean = Boolean(
|
|
312
|
-
data.statusPageResources.find((item: StatusPageResource) => {
|
|
313
|
-
return item.showUptimePercent || item.showStatusHistoryChart;
|
|
314
|
-
}),
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
if (!showUptimePercentage) {
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const uptimePercentPerResource: Array<number> = [];
|
|
322
|
-
|
|
323
|
-
for (const resource of data.statusPageResources) {
|
|
324
|
-
if (!resource.showUptimePercent && !resource.showStatusHistoryChart) {
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
let timelinesForThisResource: Array<MonitorStatusTimeline> = [];
|
|
329
|
-
|
|
330
|
-
if (resource.monitorGroupId) {
|
|
331
|
-
timelinesForThisResource = [...data.monitorStatusTimelines].filter(
|
|
332
|
-
(timeline: MonitorStatusTimeline) => {
|
|
333
|
-
const monitorsInThisGroup: Array<ObjectID> | undefined =
|
|
334
|
-
data.monitorsInGroup[resource.monitorGroupId?.toString() || ""];
|
|
335
|
-
|
|
336
|
-
if (!monitorsInThisGroup) {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return monitorsInThisGroup.find((monitorId: ObjectID) => {
|
|
341
|
-
return monitorId.toString() === timeline.monitorId?.toString();
|
|
342
|
-
});
|
|
343
|
-
},
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (resource.monitorId || resource.monitor?.id) {
|
|
348
|
-
const monitorId: ObjectID | null | undefined =
|
|
349
|
-
resource.monitorId || resource.monitor?.id;
|
|
350
|
-
|
|
351
|
-
if (!monitorId) {
|
|
352
|
-
// this should never happen.
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
timelinesForThisResource = [...data.monitorStatusTimelines].filter(
|
|
357
|
-
(timeline: MonitorStatusTimeline) => {
|
|
358
|
-
return (
|
|
359
|
-
timeline.monitorId?.toString() === resource.monitorId?.toString()
|
|
360
|
-
);
|
|
361
|
-
},
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const uptimePercent: number = this.calculateUptimePercentage(
|
|
366
|
-
timelinesForThisResource,
|
|
367
|
-
data.precision,
|
|
368
|
-
data.downtimeMonitorStatuses,
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
uptimePercentPerResource.push(uptimePercent);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// calculate avg
|
|
375
|
-
|
|
376
|
-
if (uptimePercentPerResource.length === 0) {
|
|
377
|
-
return null;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const averageUptimePercentage: number =
|
|
381
|
-
uptimePercentPerResource.reduce((a: number, b: number) => {
|
|
382
|
-
return a + b;
|
|
383
|
-
}) / uptimePercentPerResource.length;
|
|
305
|
+
}): number {
|
|
306
|
+
const { number, precision } = data;
|
|
384
307
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (data.precision === UptimePrecision.NO_DECIMAL) {
|
|
388
|
-
const percent: number = Math.round(averageUptimePercentage);
|
|
389
|
-
|
|
390
|
-
return percent;
|
|
308
|
+
if (precision === UptimePrecision.NO_DECIMAL) {
|
|
309
|
+
return Math.floor(number);
|
|
391
310
|
}
|
|
392
311
|
|
|
393
|
-
if (
|
|
394
|
-
|
|
395
|
-
return percent;
|
|
312
|
+
if (precision === UptimePrecision.ONE_DECIMAL) {
|
|
313
|
+
return Math.floor(number * 10) / 10;
|
|
396
314
|
}
|
|
397
315
|
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
return percent;
|
|
316
|
+
if (precision === UptimePrecision.TWO_DECIMAL) {
|
|
317
|
+
return Math.floor(number * 100) / 100;
|
|
401
318
|
}
|
|
402
319
|
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
return percent;
|
|
320
|
+
if (precision === UptimePrecision.THREE_DECIMAL) {
|
|
321
|
+
return Math.floor(number * 1000) / 1000;
|
|
406
322
|
}
|
|
407
323
|
|
|
408
|
-
return
|
|
324
|
+
return number;
|
|
409
325
|
}
|
|
410
326
|
|
|
411
327
|
public static calculateUptimePercentage(
|
|
@@ -434,42 +350,9 @@ export default class UptimeUtil {
|
|
|
434
350
|
totalSecondsInTimePeriod) *
|
|
435
351
|
100;
|
|
436
352
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
return noDecimalPercent;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (precision === UptimePrecision.ONE_DECIMAL) {
|
|
447
|
-
const noDecimalPercent: number = Math.round(percentage * 10) / 10;
|
|
448
|
-
if (noDecimalPercent === 100 && totalDowntimeInSeconds > 0) {
|
|
449
|
-
return 99.9;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
return noDecimalPercent;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (precision === UptimePrecision.TWO_DECIMAL) {
|
|
456
|
-
const noDecimalPercent: number = Math.round(percentage * 100) / 100;
|
|
457
|
-
if (noDecimalPercent === 100 && totalDowntimeInSeconds > 0) {
|
|
458
|
-
return 99.99;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
return noDecimalPercent;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (precision === UptimePrecision.THREE_DECIMAL) {
|
|
465
|
-
const noDecimalPercent: number = Math.round(percentage * 1000) / 1000;
|
|
466
|
-
if (noDecimalPercent === 100 && totalDowntimeInSeconds > 0) {
|
|
467
|
-
return 99.999;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return noDecimalPercent;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
return percentage;
|
|
353
|
+
return this.roundToPrecision({
|
|
354
|
+
number: percentage,
|
|
355
|
+
precision,
|
|
356
|
+
});
|
|
474
357
|
}
|
|
475
358
|
}
|