@oneuptime/common 7.0.3377 → 7.0.3392
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/StatusPageSubscriber.ts +57 -0
- package/Server/API/StatusPageAPI.ts +60 -0
- package/Server/EnvironmentConfig.ts +3 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1734435866602-MigrationName.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AnalyticsDatabaseService.ts +4 -0
- package/Server/Services/StatusPageService.ts +8 -14
- package/Server/Services/StatusPageSubscriberService.ts +242 -13
- package/Server/Types/AnalyticsDatabase/AggregateBy.ts +4 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +26 -16
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +53 -7
- package/Server/Utils/Monitor/MonitorAlert.ts +5 -0
- package/Server/Utils/Monitor/MonitorIncident.ts +5 -0
- package/Server/Utils/Monitor/MonitorResource.ts +26 -0
- package/Server/Utils/Stream.ts +4 -0
- package/Types/Date.ts +9 -0
- package/Types/Email/EmailTemplateType.ts +1 -0
- package/Types/Metrics/MetricViewData.ts +6 -0
- package/Types/Monitor/CriteriaFilter.ts +5 -4
- package/Types/Monitor/MonitorCriteriaInstance.ts +4 -0
- package/Types/Monitor/MonitorType.ts +6 -6
- package/Types/Telemetry/TelemetryQuery.ts +3 -2
- package/UI/Components/Charts/Utils/XAxis.ts +3 -2
- package/UI/Components/Pill/Pill.tsx +34 -22
- package/UI/Components/RollingTimePicker/RollingTimePicker.tsx +12 -25
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +59 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +70 -32
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +1 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1734435866602-MigrationName.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1734435866602-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/AnalyticsDatabaseService.js +3 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +6 -11
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +182 -11
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js +3 -0
- package/build/dist/Server/Types/AnalyticsDatabase/AggregateBy.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +25 -14
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +34 -8
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +4 -0
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +4 -0
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +19 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Stream.js +3 -0
- package/build/dist/Server/Utils/Stream.js.map +1 -1
- package/build/dist/Types/Date.js +5 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Metrics/MetricViewData.js +2 -0
- package/build/dist/Types/Metrics/MetricViewData.js.map +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +3 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +5 -6
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +3 -2
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Pill/Pill.js +19 -11
- package/build/dist/UI/Components/Pill/Pill.js.map +1 -1
- package/build/dist/UI/Components/RollingTimePicker/RollingTimePicker.js +9 -18
- package/build/dist/UI/Components/RollingTimePicker/RollingTimePicker.js.map +1 -1
- package/package.json +2 -2
|
@@ -434,6 +434,63 @@ export default class StatusPageSubscriber extends BaseModel {
|
|
|
434
434
|
})
|
|
435
435
|
public deletedByUserId?: ObjectID = undefined;
|
|
436
436
|
|
|
437
|
+
@ColumnAccessControl({
|
|
438
|
+
create: [
|
|
439
|
+
Permission.ProjectOwner,
|
|
440
|
+
Permission.ProjectAdmin,
|
|
441
|
+
Permission.ProjectMember,
|
|
442
|
+
Permission.CreateStatusPageSubscriber,
|
|
443
|
+
Permission.Public,
|
|
444
|
+
],
|
|
445
|
+
read: [
|
|
446
|
+
Permission.ProjectOwner,
|
|
447
|
+
Permission.ProjectAdmin,
|
|
448
|
+
Permission.ProjectMember,
|
|
449
|
+
Permission.ReadStatusPageSubscriber,
|
|
450
|
+
],
|
|
451
|
+
update: [
|
|
452
|
+
Permission.ProjectOwner,
|
|
453
|
+
Permission.ProjectAdmin,
|
|
454
|
+
Permission.ProjectMember,
|
|
455
|
+
Permission.EditStatusPageSubscriber,
|
|
456
|
+
],
|
|
457
|
+
})
|
|
458
|
+
@TableColumn({
|
|
459
|
+
isDefaultValueColumn: true,
|
|
460
|
+
type: TableColumnType.Boolean,
|
|
461
|
+
title: "Is Subscription Confirmed",
|
|
462
|
+
description:
|
|
463
|
+
"Has subscriber confirmed their subscription? (for example, by clicking on a confirmation link in an email)",
|
|
464
|
+
})
|
|
465
|
+
@Column({
|
|
466
|
+
type: ColumnType.Boolean,
|
|
467
|
+
default: false,
|
|
468
|
+
})
|
|
469
|
+
public isSubscriptionConfirmed?: boolean = undefined;
|
|
470
|
+
|
|
471
|
+
@ColumnAccessControl({
|
|
472
|
+
create: [
|
|
473
|
+
Permission.ProjectOwner,
|
|
474
|
+
Permission.ProjectAdmin,
|
|
475
|
+
Permission.ProjectMember,
|
|
476
|
+
Permission.CreateStatusPageSubscriber,
|
|
477
|
+
],
|
|
478
|
+
read: [],
|
|
479
|
+
update: [],
|
|
480
|
+
})
|
|
481
|
+
@TableColumn({
|
|
482
|
+
isDefaultValueColumn: false,
|
|
483
|
+
type: TableColumnType.ShortText,
|
|
484
|
+
title: "Subscription Confirmation Token",
|
|
485
|
+
description:
|
|
486
|
+
"Token used to confirm subscription. This is a random token that is sent to the subscriber's email address to confirm their subscription.",
|
|
487
|
+
})
|
|
488
|
+
@Column({
|
|
489
|
+
type: ColumnType.ShortText,
|
|
490
|
+
nullable: true,
|
|
491
|
+
})
|
|
492
|
+
public subscriptionConfirmationToken?: string = undefined;
|
|
493
|
+
|
|
437
494
|
@ColumnAccessControl({
|
|
438
495
|
create: [
|
|
439
496
|
Permission.ProjectOwner,
|
|
@@ -82,6 +82,66 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
82
82
|
public constructor() {
|
|
83
83
|
super(StatusPage, StatusPageService);
|
|
84
84
|
|
|
85
|
+
// confirm subscription api
|
|
86
|
+
this.router.get(
|
|
87
|
+
`${new this.entityType()
|
|
88
|
+
.getCrudApiPath()
|
|
89
|
+
?.toString()}/confirm-subscription/:statusPageSubscriberId`,
|
|
90
|
+
async (req: ExpressRequest, res: ExpressResponse) => {
|
|
91
|
+
const token: string = req.query["verification-token"] as string;
|
|
92
|
+
|
|
93
|
+
const statusPageSubscriberId: ObjectID = new ObjectID(
|
|
94
|
+
req.params["statusPageSubscriberId"] as string,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const subscriber: StatusPageSubscriber | null =
|
|
98
|
+
await StatusPageSubscriberService.findOneBy({
|
|
99
|
+
query: {
|
|
100
|
+
_id: statusPageSubscriberId,
|
|
101
|
+
subscriptionConfirmationToken: token,
|
|
102
|
+
},
|
|
103
|
+
select: {
|
|
104
|
+
isSubscriptionConfirmed: true,
|
|
105
|
+
},
|
|
106
|
+
props: {
|
|
107
|
+
isRoot: true,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!subscriber) {
|
|
112
|
+
return Response.sendErrorResponse(
|
|
113
|
+
req,
|
|
114
|
+
res,
|
|
115
|
+
new NotFoundException(
|
|
116
|
+
"Subscriber not found or confirmation token is invalid",
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// check if subscription confirmed already.
|
|
122
|
+
|
|
123
|
+
if (subscriber.isSubscriptionConfirmed) {
|
|
124
|
+
return Response.sendEmptySuccessResponse(req, res);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await StatusPageSubscriberService.updateOneById({
|
|
128
|
+
id: statusPageSubscriberId,
|
|
129
|
+
data: {
|
|
130
|
+
isSubscriptionConfirmed: true,
|
|
131
|
+
},
|
|
132
|
+
props: {
|
|
133
|
+
isRoot: true,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await StatusPageSubscriberService.sendYouHaveSubscribedEmail({
|
|
138
|
+
subscriberId: statusPageSubscriberId,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return Response.sendEmptySuccessResponse(req, res);
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
85
145
|
// CNAME verification api
|
|
86
146
|
this.router.get(
|
|
87
147
|
`${new this.entityType()
|
|
@@ -198,6 +198,9 @@ export const AnalyticsHost: string = process.env["ANALYTICS_HOST"] || "";
|
|
|
198
198
|
export const DisableAutomaticIncidentCreation: boolean =
|
|
199
199
|
process.env["DISABLE_AUTOMATIC_INCIDENT_CREATION"] === "true";
|
|
200
200
|
|
|
201
|
+
export const DisableAutomaticAlertCreation: boolean =
|
|
202
|
+
process.env["DISABLE_AUTOMATIC_ALERT_CREATION"] === "true";
|
|
203
|
+
|
|
201
204
|
export const ClickhouseHost: Hostname = Hostname.fromString(
|
|
202
205
|
process.env["CLICKHOUSE_HOST"] || "clickhouse",
|
|
203
206
|
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1734435866602 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1734435866602";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "StatusPageSubscriber" ADD "isSubscriptionConfirmed" boolean NOT NULL DEFAULT false`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(
|
|
11
|
+
`ALTER TABLE "StatusPageSubscriber" ADD "subscriptionConfirmationToken" character varying`,
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
16
|
+
await queryRunner.query(
|
|
17
|
+
`ALTER TABLE "StatusPageSubscriber" DROP COLUMN "subscriptionConfirmationToken"`,
|
|
18
|
+
);
|
|
19
|
+
await queryRunner.query(
|
|
20
|
+
`ALTER TABLE "StatusPageSubscriber" DROP COLUMN "isSubscriptionConfirmed"`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -83,6 +83,7 @@ import { MigrationName1731433309124 } from "./1731433309124-MigrationName";
|
|
|
83
83
|
import { MigrationName1731435267537 } from "./1731435267537-MigrationName";
|
|
84
84
|
import { MigrationName1731435514287 } from "./1731435514287-MigrationName";
|
|
85
85
|
import { MigrationName1732553444010 } from "./1732553444010-MigrationName";
|
|
86
|
+
import { MigrationName1734435866602 } from "./1734435866602-MigrationName";
|
|
86
87
|
|
|
87
88
|
export default [
|
|
88
89
|
InitialMigration,
|
|
@@ -170,4 +171,5 @@ export default [
|
|
|
170
171
|
MigrationName1731435267537,
|
|
171
172
|
MigrationName1731435514287,
|
|
172
173
|
MigrationName1732553444010,
|
|
174
|
+
MigrationName1734435866602,
|
|
173
175
|
];
|
|
@@ -415,6 +415,10 @@ export default class AnalyticsDatabaseService<
|
|
|
415
415
|
strResult: string,
|
|
416
416
|
columns: string[],
|
|
417
417
|
): JSONObject[] {
|
|
418
|
+
if (!strResult || !strResult.trim()) {
|
|
419
|
+
return [];
|
|
420
|
+
}
|
|
421
|
+
|
|
418
422
|
const jsonItems: Array<JSONObject> = [];
|
|
419
423
|
|
|
420
424
|
const rows: Array<string> = strResult.split("\n");
|
|
@@ -454,8 +454,8 @@ export class Service extends DatabaseService<StatusPage> {
|
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
public async getStatusPageURL(statusPageId: ObjectID): Promise<string> {
|
|
457
|
-
const
|
|
458
|
-
await StatusPageDomainService.
|
|
457
|
+
const domain: StatusPageDomain | null =
|
|
458
|
+
await StatusPageDomainService.findOneBy({
|
|
459
459
|
query: {
|
|
460
460
|
statusPageId: statusPageId,
|
|
461
461
|
isSslProvisioned: true,
|
|
@@ -463,21 +463,15 @@ export class Service extends DatabaseService<StatusPage> {
|
|
|
463
463
|
select: {
|
|
464
464
|
fullDomain: true,
|
|
465
465
|
},
|
|
466
|
-
skip: 0,
|
|
467
|
-
limit: LIMIT_PER_PROJECT,
|
|
468
466
|
props: {
|
|
469
467
|
isRoot: true,
|
|
470
468
|
ignoreHooks: true,
|
|
471
469
|
},
|
|
472
470
|
});
|
|
473
471
|
|
|
474
|
-
let statusPageURL: string =
|
|
475
|
-
.map((d: StatusPageDomain) => {
|
|
476
|
-
return d.fullDomain;
|
|
477
|
-
})
|
|
478
|
-
.join(", ");
|
|
472
|
+
let statusPageURL: string = domain?.fullDomain || "";
|
|
479
473
|
|
|
480
|
-
if (
|
|
474
|
+
if (!statusPageURL) {
|
|
481
475
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
482
476
|
|
|
483
477
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
@@ -750,11 +744,11 @@ export class Service extends DatabaseService<StatusPage> {
|
|
|
750
744
|
statusPageId: data.statusPageId,
|
|
751
745
|
});
|
|
752
746
|
|
|
747
|
+
const numberOfDays: number = data.historyDays || 14;
|
|
748
|
+
|
|
753
749
|
const currentDate: Date = OneUptimeDate.getCurrentDate();
|
|
754
|
-
const startDate: Date = OneUptimeDate.getSomeDaysAgo(
|
|
755
|
-
|
|
756
|
-
);
|
|
757
|
-
const startAndEndDate: string = `${OneUptimeDate.getDateAsLocalFormattedString(startDate, true)} - ${OneUptimeDate.getDateAsLocalFormattedString(currentDate, true)}`;
|
|
750
|
+
const startDate: Date = OneUptimeDate.getSomeDaysAgo(numberOfDays);
|
|
751
|
+
const startAndEndDate: string = `${numberOfDays} days (${OneUptimeDate.getDateAsLocalFormattedString(startDate, true)} - ${OneUptimeDate.getDateAsLocalFormattedString(currentDate, true)})`;
|
|
758
752
|
|
|
759
753
|
if (statusPageResources.length === 0) {
|
|
760
754
|
return {
|
|
@@ -29,6 +29,7 @@ import StatusPageResource from "Common/Models/DatabaseModels/StatusPageResource"
|
|
|
29
29
|
import Model from "Common/Models/DatabaseModels/StatusPageSubscriber";
|
|
30
30
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
31
31
|
import StatusPageEventType from "../../Types/StatusPage/StatusPageEventType";
|
|
32
|
+
import NumberUtil from "../../Utils/Number";
|
|
32
33
|
|
|
33
34
|
export class Service extends DatabaseService<Model> {
|
|
34
35
|
public constructor() {
|
|
@@ -160,6 +161,22 @@ export class Service extends DatabaseService<Model> {
|
|
|
160
161
|
|
|
161
162
|
data.data.projectId = statuspage.projectId;
|
|
162
163
|
|
|
164
|
+
const isEmailSubscriber: boolean = Boolean(data.data.subscriberEmail);
|
|
165
|
+
const isSubscriptionConfirmed: boolean = Boolean(
|
|
166
|
+
data.data.isSubscriptionConfirmed,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (isEmailSubscriber && !isSubscriptionConfirmed) {
|
|
170
|
+
data.data.isSubscriptionConfirmed = false;
|
|
171
|
+
} else {
|
|
172
|
+
data.data.isSubscriptionConfirmed = true; // if the subscriber is not email, then set it to true for SMS subscribers.
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
data.data.subscriptionConfirmationToken = NumberUtil.getRandomNumber(
|
|
176
|
+
100000,
|
|
177
|
+
999999,
|
|
178
|
+
).toString();
|
|
179
|
+
|
|
163
180
|
return { createBy: data, carryForward: statuspage };
|
|
164
181
|
}
|
|
165
182
|
|
|
@@ -180,10 +197,6 @@ export class Service extends DatabaseService<Model> {
|
|
|
180
197
|
onCreate.carryForward.name ||
|
|
181
198
|
"Status Page";
|
|
182
199
|
|
|
183
|
-
const host: Hostname = await DatabaseConfig.getHost();
|
|
184
|
-
|
|
185
|
-
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
186
|
-
|
|
187
200
|
const unsubscribeLink: string = this.getUnsubscribeLink(
|
|
188
201
|
URL.fromString(statusPageURL),
|
|
189
202
|
createdItem.id!,
|
|
@@ -237,28 +250,235 @@ export class Service extends DatabaseService<Model> {
|
|
|
237
250
|
if (
|
|
238
251
|
createdItem.statusPageId &&
|
|
239
252
|
createdItem.subscriberEmail &&
|
|
240
|
-
createdItem._id
|
|
241
|
-
createdItem.sendYouHaveSubscribedMessage
|
|
253
|
+
createdItem._id
|
|
242
254
|
) {
|
|
243
255
|
// Call mail service and send an email.
|
|
244
256
|
|
|
245
257
|
// get status page domain for this status page.
|
|
246
258
|
// if the domain is not found, use the internal status page preview link.
|
|
247
259
|
|
|
260
|
+
const isSubcriptionConfirmed: boolean = Boolean(
|
|
261
|
+
createdItem.isSubscriptionConfirmed,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (!isSubcriptionConfirmed) {
|
|
265
|
+
await this.sendConfirmSubscriptionEmail({
|
|
266
|
+
subscriberId: createdItem.id!,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (isSubcriptionConfirmed && createdItem.sendYouHaveSubscribedMessage) {
|
|
271
|
+
await this.sendYouHaveSubscribedEmail({
|
|
272
|
+
subscriberId: createdItem.id!,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return createdItem;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public async sendConfirmSubscriptionEmail(data: {
|
|
281
|
+
subscriberId: ObjectID;
|
|
282
|
+
}): Promise<void> {
|
|
283
|
+
// get subscriber
|
|
284
|
+
const subscriber: Model | null = await this.findOneBy({
|
|
285
|
+
query: {
|
|
286
|
+
_id: data.subscriberId,
|
|
287
|
+
},
|
|
288
|
+
select: {
|
|
289
|
+
statusPageId: true,
|
|
290
|
+
subscriberEmail: true,
|
|
291
|
+
subscriberPhone: true,
|
|
292
|
+
projectId: true,
|
|
293
|
+
subscriptionConfirmationToken: true,
|
|
294
|
+
sendYouHaveSubscribedMessage: true,
|
|
295
|
+
},
|
|
296
|
+
props: {
|
|
297
|
+
isRoot: true,
|
|
298
|
+
ignoreHooks: true,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// get status page
|
|
303
|
+
if (!subscriber || !subscriber.statusPageId) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const statusPage: StatusPage | null = await StatusPageService.findOneBy({
|
|
308
|
+
query: {
|
|
309
|
+
_id: subscriber.statusPageId.toString(),
|
|
310
|
+
},
|
|
311
|
+
select: {
|
|
312
|
+
logoFileId: true,
|
|
313
|
+
isPublicStatusPage: true,
|
|
314
|
+
pageTitle: true,
|
|
315
|
+
name: true,
|
|
316
|
+
smtpConfig: {
|
|
317
|
+
_id: true,
|
|
318
|
+
hostname: true,
|
|
319
|
+
port: true,
|
|
320
|
+
username: true,
|
|
321
|
+
password: true,
|
|
322
|
+
fromEmail: true,
|
|
323
|
+
fromName: true,
|
|
324
|
+
secure: true,
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
props: {
|
|
328
|
+
isRoot: true,
|
|
329
|
+
ignoreHooks: true,
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
if (!statusPage || !statusPage.id) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
|
338
|
+
statusPage.id,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const statusPageName: string =
|
|
342
|
+
statusPage.pageTitle || statusPage.name || "Status Page";
|
|
343
|
+
|
|
344
|
+
const host: Hostname = await DatabaseConfig.getHost();
|
|
345
|
+
|
|
346
|
+
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
347
|
+
|
|
348
|
+
const confirmSubscriptionLink: string = this.getConfirmSubscriptionLink({
|
|
349
|
+
statusPageUrl: statusPageURL,
|
|
350
|
+
confirmationToken: subscriber.subscriptionConfirmationToken || "",
|
|
351
|
+
statusPageSubscriberId: subscriber.id!,
|
|
352
|
+
}).toString();
|
|
353
|
+
|
|
354
|
+
if (
|
|
355
|
+
subscriber.statusPageId &&
|
|
356
|
+
subscriber.subscriberEmail &&
|
|
357
|
+
subscriber._id
|
|
358
|
+
) {
|
|
359
|
+
MailService.sendMail(
|
|
360
|
+
{
|
|
361
|
+
toEmail: subscriber.subscriberEmail,
|
|
362
|
+
templateType: EmailTemplateType.ConfirmStatusPageSubscription,
|
|
363
|
+
vars: {
|
|
364
|
+
statusPageName: statusPageName,
|
|
365
|
+
logoUrl: statusPage.logoFileId
|
|
366
|
+
? new URL(httpProtocol, host)
|
|
367
|
+
.addRoute(FileRoute)
|
|
368
|
+
.addRoute("/image/" + statusPage.logoFileId)
|
|
369
|
+
.toString()
|
|
370
|
+
: "",
|
|
371
|
+
statusPageUrl: statusPageURL,
|
|
372
|
+
isPublicStatusPage: statusPage.isPublicStatusPage
|
|
373
|
+
? "true"
|
|
374
|
+
: "false",
|
|
375
|
+
confirmationUrl: confirmSubscriptionLink,
|
|
376
|
+
},
|
|
377
|
+
subject: "Confirm your subscription to " + statusPageName,
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
projectId: subscriber.projectId,
|
|
381
|
+
mailServer: ProjectSMTPConfigService.toEmailServer(
|
|
382
|
+
statusPage.smtpConfig,
|
|
383
|
+
),
|
|
384
|
+
},
|
|
385
|
+
).catch((err: Error) => {
|
|
386
|
+
logger.error(err);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
public async sendYouHaveSubscribedEmail(data: {
|
|
392
|
+
subscriberId: ObjectID;
|
|
393
|
+
}): Promise<void> {
|
|
394
|
+
// get subscriber
|
|
395
|
+
const subscriber: Model | null = await this.findOneBy({
|
|
396
|
+
query: {
|
|
397
|
+
_id: data.subscriberId,
|
|
398
|
+
},
|
|
399
|
+
select: {
|
|
400
|
+
statusPageId: true,
|
|
401
|
+
subscriberEmail: true,
|
|
402
|
+
subscriberPhone: true,
|
|
403
|
+
projectId: true,
|
|
404
|
+
sendYouHaveSubscribedMessage: true,
|
|
405
|
+
},
|
|
406
|
+
props: {
|
|
407
|
+
isRoot: true,
|
|
408
|
+
ignoreHooks: true,
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// get status page
|
|
413
|
+
if (!subscriber || !subscriber.statusPageId) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const statusPage: StatusPage | null = await StatusPageService.findOneBy({
|
|
418
|
+
query: {
|
|
419
|
+
_id: subscriber.statusPageId.toString(),
|
|
420
|
+
},
|
|
421
|
+
select: {
|
|
422
|
+
logoFileId: true,
|
|
423
|
+
isPublicStatusPage: true,
|
|
424
|
+
pageTitle: true,
|
|
425
|
+
name: true,
|
|
426
|
+
smtpConfig: {
|
|
427
|
+
_id: true,
|
|
428
|
+
hostname: true,
|
|
429
|
+
port: true,
|
|
430
|
+
username: true,
|
|
431
|
+
password: true,
|
|
432
|
+
fromEmail: true,
|
|
433
|
+
fromName: true,
|
|
434
|
+
secure: true,
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
props: {
|
|
438
|
+
isRoot: true,
|
|
439
|
+
ignoreHooks: true,
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
if (!statusPage || !statusPage.id) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
|
448
|
+
statusPage.id,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
const statusPageName: string =
|
|
452
|
+
statusPage.pageTitle || statusPage.name || "Status Page";
|
|
453
|
+
|
|
454
|
+
const host: Hostname = await DatabaseConfig.getHost();
|
|
455
|
+
|
|
456
|
+
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
457
|
+
|
|
458
|
+
const unsubscribeLink: string = this.getUnsubscribeLink(
|
|
459
|
+
URL.fromString(statusPageURL),
|
|
460
|
+
subscriber.id!,
|
|
461
|
+
).toString();
|
|
462
|
+
|
|
463
|
+
if (
|
|
464
|
+
subscriber.statusPageId &&
|
|
465
|
+
subscriber.subscriberEmail &&
|
|
466
|
+
subscriber._id
|
|
467
|
+
) {
|
|
248
468
|
MailService.sendMail(
|
|
249
469
|
{
|
|
250
|
-
toEmail:
|
|
470
|
+
toEmail: subscriber.subscriberEmail,
|
|
251
471
|
templateType: EmailTemplateType.SubscribedToStatusPage,
|
|
252
472
|
vars: {
|
|
253
473
|
statusPageName: statusPageName,
|
|
254
|
-
logoUrl:
|
|
474
|
+
logoUrl: statusPage.logoFileId
|
|
255
475
|
? new URL(httpProtocol, host)
|
|
256
476
|
.addRoute(FileRoute)
|
|
257
|
-
.addRoute("/image/" +
|
|
477
|
+
.addRoute("/image/" + statusPage.logoFileId)
|
|
258
478
|
.toString()
|
|
259
479
|
: "",
|
|
260
480
|
statusPageUrl: statusPageURL,
|
|
261
|
-
isPublicStatusPage:
|
|
481
|
+
isPublicStatusPage: statusPage.isPublicStatusPage
|
|
262
482
|
? "true"
|
|
263
483
|
: "false",
|
|
264
484
|
unsubscribeUrl: unsubscribeLink,
|
|
@@ -266,17 +486,25 @@ export class Service extends DatabaseService<Model> {
|
|
|
266
486
|
subject: "You have been subscribed to " + statusPageName,
|
|
267
487
|
},
|
|
268
488
|
{
|
|
269
|
-
projectId:
|
|
489
|
+
projectId: subscriber.projectId,
|
|
270
490
|
mailServer: ProjectSMTPConfigService.toEmailServer(
|
|
271
|
-
|
|
491
|
+
statusPage.smtpConfig,
|
|
272
492
|
),
|
|
273
493
|
},
|
|
274
494
|
).catch((err: Error) => {
|
|
275
495
|
logger.error(err);
|
|
276
496
|
});
|
|
277
497
|
}
|
|
498
|
+
}
|
|
278
499
|
|
|
279
|
-
|
|
500
|
+
public getConfirmSubscriptionLink(data: {
|
|
501
|
+
statusPageUrl: string;
|
|
502
|
+
confirmationToken: string;
|
|
503
|
+
statusPageSubscriberId: ObjectID;
|
|
504
|
+
}): URL {
|
|
505
|
+
return URL.fromString(data.statusPageUrl).addRoute(
|
|
506
|
+
`/confirm-subscription/${data.statusPageSubscriberId.toString()}?verification-token=${data.confirmationToken}`,
|
|
507
|
+
);
|
|
280
508
|
}
|
|
281
509
|
|
|
282
510
|
public async getSubscribersByStatusPage(
|
|
@@ -287,6 +515,7 @@ export class Service extends DatabaseService<Model> {
|
|
|
287
515
|
query: {
|
|
288
516
|
statusPageId: statusPageId,
|
|
289
517
|
isUnsubscribed: false,
|
|
518
|
+
isSubscriptionConfirmed: true,
|
|
290
519
|
},
|
|
291
520
|
select: {
|
|
292
521
|
_id: true,
|
|
@@ -2,6 +2,7 @@ import AggregationInterval from "Common/Types/BaseDatabase/AggregationInterval";
|
|
|
2
2
|
import CommonAggregateBy from "Common/Types/BaseDatabase/AggregateBy";
|
|
3
3
|
import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel";
|
|
4
4
|
import DatabaseCommonInteractionProps from "Common/Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
5
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
5
6
|
|
|
6
7
|
export default interface AggregateBy<TBaseModel extends AnalyticsBaseModel>
|
|
7
8
|
extends CommonAggregateBy<TBaseModel> {
|
|
@@ -13,6 +14,9 @@ export class AggregateUtil {
|
|
|
13
14
|
startDate: Date;
|
|
14
15
|
endDate: Date;
|
|
15
16
|
}): AggregationInterval {
|
|
17
|
+
data.startDate = OneUptimeDate.fromString(data.startDate);
|
|
18
|
+
data.endDate = OneUptimeDate.fromString(data.endDate);
|
|
19
|
+
|
|
16
20
|
const diff: number = data.endDate.getTime() - data.startDate.getTime();
|
|
17
21
|
|
|
18
22
|
if (diff <= 1000 * 60 * 60 * 3) {
|
|
@@ -313,7 +313,8 @@ export default class CompareCriteria {
|
|
|
313
313
|
CompareCriteria.isTrue({
|
|
314
314
|
value: data.value,
|
|
315
315
|
evaluationType:
|
|
316
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
316
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
|
|
317
|
+
data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
|
|
317
318
|
})
|
|
318
319
|
) {
|
|
319
320
|
return CompareCriteria.getCompareMessage({
|
|
@@ -331,7 +332,8 @@ export default class CompareCriteria {
|
|
|
331
332
|
CompareCriteria.isFalse({
|
|
332
333
|
value: data.value,
|
|
333
334
|
evaluationType:
|
|
334
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
335
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
|
|
336
|
+
data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
|
|
335
337
|
})
|
|
336
338
|
) {
|
|
337
339
|
return CompareCriteria.getCompareMessage({
|
|
@@ -366,7 +368,8 @@ export default class CompareCriteria {
|
|
|
366
368
|
threshold: data.threshold as number,
|
|
367
369
|
value: data.value,
|
|
368
370
|
evaluationType:
|
|
369
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
371
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
|
|
372
|
+
data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
|
|
370
373
|
})
|
|
371
374
|
) {
|
|
372
375
|
return CompareCriteria.getCompareMessage({
|
|
@@ -385,7 +388,8 @@ export default class CompareCriteria {
|
|
|
385
388
|
threshold: data.threshold as number,
|
|
386
389
|
value: data.value,
|
|
387
390
|
evaluationType:
|
|
388
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
391
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
|
|
392
|
+
data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
|
|
389
393
|
})
|
|
390
394
|
) {
|
|
391
395
|
return CompareCriteria.getCompareMessage({
|
|
@@ -404,7 +408,8 @@ export default class CompareCriteria {
|
|
|
404
408
|
threshold: data.threshold as number,
|
|
405
409
|
value: data.value,
|
|
406
410
|
evaluationType:
|
|
407
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
411
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
|
|
412
|
+
data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
|
|
408
413
|
})
|
|
409
414
|
) {
|
|
410
415
|
return CompareCriteria.getCompareMessage({
|
|
@@ -423,7 +428,8 @@ export default class CompareCriteria {
|
|
|
423
428
|
threshold: data.threshold as number,
|
|
424
429
|
value: data.value,
|
|
425
430
|
evaluationType:
|
|
426
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
431
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
|
|
432
|
+
data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
|
|
427
433
|
})
|
|
428
434
|
) {
|
|
429
435
|
return CompareCriteria.getCompareMessage({
|
|
@@ -442,7 +448,8 @@ export default class CompareCriteria {
|
|
|
442
448
|
threshold: data.threshold as number,
|
|
443
449
|
value: data.value,
|
|
444
450
|
evaluationType:
|
|
445
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
451
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
|
|
452
|
+
data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
|
|
446
453
|
})
|
|
447
454
|
) {
|
|
448
455
|
return CompareCriteria.getCompareMessage({
|
|
@@ -461,7 +468,8 @@ export default class CompareCriteria {
|
|
|
461
468
|
threshold: data.threshold as number,
|
|
462
469
|
value: data.value,
|
|
463
470
|
evaluationType:
|
|
464
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
471
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ||
|
|
472
|
+
data.criteriaFilter.metricMonitorOptions?.metricAggregationType,
|
|
465
473
|
})
|
|
466
474
|
) {
|
|
467
475
|
return CompareCriteria.getCompareMessage({
|
|
@@ -485,17 +493,19 @@ export default class CompareCriteria {
|
|
|
485
493
|
// CPU Percent over the last 5 minutes is 10 which is less than the threshold of 20
|
|
486
494
|
let message: string = "";
|
|
487
495
|
|
|
488
|
-
|
|
489
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType
|
|
490
|
-
|
|
491
|
-
) {
|
|
496
|
+
let evaluationType: EvaluateOverTimeType | undefined =
|
|
497
|
+
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType;
|
|
498
|
+
|
|
499
|
+
if (data.criteriaFilter.metricMonitorOptions?.metricAggregationType) {
|
|
500
|
+
evaluationType =
|
|
501
|
+
data.criteriaFilter.metricMonitorOptions.metricAggregationType;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (evaluationType === EvaluateOverTimeType.AnyValue) {
|
|
492
505
|
message += "Any value of";
|
|
493
506
|
}
|
|
494
507
|
|
|
495
|
-
if (
|
|
496
|
-
data.criteriaFilter.evaluateOverTimeOptions?.evaluateOverTimeType ===
|
|
497
|
-
EvaluateOverTimeType.AllValues
|
|
498
|
-
) {
|
|
508
|
+
if (evaluationType === EvaluateOverTimeType.AllValues) {
|
|
499
509
|
message += "All values of";
|
|
500
510
|
}
|
|
501
511
|
|