@oneuptime/common 9.3.16 → 9.3.18

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.
Files changed (61) hide show
  1. package/Models/DatabaseModels/Monitor.ts +109 -0
  2. package/Server/EnvironmentConfig.ts +16 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/1768335589018-AddIncomingEmailMonitor.ts +53 -0
  4. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  5. package/Server/Middleware/MultipartFormData.ts +17 -0
  6. package/Server/Services/InboundEmail/InboundEmailProvider.ts +64 -0
  7. package/Server/Services/InboundEmail/InboundEmailProviderFactory.ts +80 -0
  8. package/Server/Services/InboundEmail/Providers/SendGridInboundProvider.ts +211 -0
  9. package/Server/Services/MonitorService.ts +4 -0
  10. package/Server/Utils/Monitor/Criteria/IncomingEmailCriteria.ts +248 -0
  11. package/Server/Utils/Monitor/DataToProcess.ts +2 -0
  12. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
  13. package/Types/Monitor/CriteriaFilter.ts +7 -0
  14. package/Types/Monitor/IncomingEmailMonitor/IncomingEmailMonitorRequest.ts +25 -0
  15. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  16. package/Types/Monitor/MonitorType.ts +28 -0
  17. package/UI/Components/CardSelect/CardSelect.tsx +105 -0
  18. package/UI/Components/Forms/Fields/FormField.tsx +20 -0
  19. package/UI/Components/Forms/Types/Field.ts +2 -0
  20. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
  21. package/UI/Config.ts +3 -0
  22. package/build/dist/Models/DatabaseModels/Monitor.js +110 -0
  23. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  24. package/build/dist/Server/EnvironmentConfig.js +10 -0
  25. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  26. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1768335589018-AddIncomingEmailMonitor.js +24 -0
  27. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1768335589018-AddIncomingEmailMonitor.js.map +1 -0
  28. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  29. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  30. package/build/dist/Server/Middleware/MultipartFormData.js +13 -0
  31. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -0
  32. package/build/dist/Server/Services/InboundEmail/InboundEmailProvider.js +12 -0
  33. package/build/dist/Server/Services/InboundEmail/InboundEmailProvider.js.map +1 -0
  34. package/build/dist/Server/Services/InboundEmail/InboundEmailProviderFactory.js +59 -0
  35. package/build/dist/Server/Services/InboundEmail/InboundEmailProviderFactory.js.map +1 -0
  36. package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js +148 -0
  37. package/build/dist/Server/Services/InboundEmail/Providers/SendGridInboundProvider.js.map +1 -0
  38. package/build/dist/Server/Services/MonitorService.js +3 -0
  39. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  40. package/build/dist/Server/Utils/Monitor/Criteria/IncomingEmailCriteria.js +164 -0
  41. package/build/dist/Server/Utils/Monitor/Criteria/IncomingEmailCriteria.js.map +1 -0
  42. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
  43. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  44. package/build/dist/Types/Monitor/CriteriaFilter.js +6 -0
  45. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  46. package/build/dist/Types/Monitor/IncomingEmailMonitor/IncomingEmailMonitorRequest.js +2 -0
  47. package/build/dist/Types/Monitor/IncomingEmailMonitor/IncomingEmailMonitorRequest.js.map +1 -0
  48. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  49. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  50. package/build/dist/Types/Monitor/MonitorType.js +26 -0
  51. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  52. package/build/dist/UI/Components/CardSelect/CardSelect.js +30 -0
  53. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -0
  54. package/build/dist/UI/Components/Forms/Fields/FormField.js +8 -0
  55. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  56. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  57. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
  58. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  59. package/build/dist/UI/Config.js +1 -0
  60. package/build/dist/UI/Config.js.map +1 -1
  61. package/package.json +3 -1
@@ -0,0 +1,248 @@
1
+ import logger from "../../../Utils/Logger";
2
+ import DataToProcess from "../DataToProcess";
3
+ import OneUptimeDate from "../../../../Types/Date";
4
+ import {
5
+ CheckOn,
6
+ CriteriaFilter,
7
+ FilterType,
8
+ } from "../../../../Types/Monitor/CriteriaFilter";
9
+ import IncomingEmailMonitorRequest from "../../../../Types/Monitor/IncomingEmailMonitor/IncomingEmailMonitorRequest";
10
+ import Typeof from "../../../../Types/Typeof";
11
+ import CaptureSpan from "../../Telemetry/CaptureSpan";
12
+
13
+ export default class IncomingEmailCriteria {
14
+ @CaptureSpan()
15
+ public static async isMonitorInstanceCriteriaFilterMet(input: {
16
+ dataToProcess: DataToProcess;
17
+ criteriaFilter: CriteriaFilter;
18
+ }): Promise<string | null> {
19
+ // Incoming Email Monitoring Checks
20
+
21
+ logger.debug(
22
+ "Checking IncomingEmailCriteria for Monitor: " +
23
+ input.dataToProcess.monitorId.toString(),
24
+ );
25
+
26
+ logger.debug(
27
+ "Data to process: " + JSON.stringify(input.dataToProcess, null, 2),
28
+ );
29
+
30
+ logger.debug(
31
+ "Criteria Filter: " + JSON.stringify(input.criteriaFilter, null, 2),
32
+ );
33
+
34
+ let value: number | string | undefined = input.criteriaFilter.value;
35
+
36
+ const emailData: IncomingEmailMonitorRequest =
37
+ input.dataToProcess as IncomingEmailMonitorRequest;
38
+
39
+ // Check on Email Received time
40
+ if (input.criteriaFilter.checkOn === CheckOn.EmailReceivedAt) {
41
+ logger.debug(
42
+ "Checking EmailReceivedAt for Monitor: " +
43
+ input.dataToProcess.monitorId.toString(),
44
+ );
45
+
46
+ const lastEmailTime: Date = emailData.emailReceivedAt;
47
+
48
+ logger.debug("Last Email Time: " + lastEmailTime);
49
+
50
+ const differenceInMinutes: number = OneUptimeDate.getDifferenceInMinutes(
51
+ lastEmailTime,
52
+ emailData.checkedAt || OneUptimeDate.getCurrentDate(),
53
+ );
54
+
55
+ logger.debug("Difference in minutes: " + differenceInMinutes);
56
+
57
+ if (!value) {
58
+ return null;
59
+ }
60
+
61
+ if (typeof value === Typeof.String) {
62
+ try {
63
+ value = parseInt(value as string);
64
+ } catch (err) {
65
+ logger.error(err);
66
+ return null;
67
+ }
68
+ }
69
+
70
+ if (typeof value !== Typeof.Number) {
71
+ return null;
72
+ }
73
+
74
+ if (input.criteriaFilter.filterType === FilterType.RecievedInMinutes) {
75
+ logger.debug(
76
+ "Checking RecievedInMinutes for Monitor: " +
77
+ input.dataToProcess.monitorId.toString(),
78
+ );
79
+ if (value && differenceInMinutes <= (value as number)) {
80
+ logger.debug(
81
+ "RecievedInMinutes for Monitor: " +
82
+ input.dataToProcess.monitorId.toString() +
83
+ " is true",
84
+ );
85
+ return `Email received in ${value} minutes. It was received ${differenceInMinutes} minutes ago.`;
86
+ }
87
+ return null;
88
+ }
89
+
90
+ if (input.criteriaFilter.filterType === FilterType.NotRecievedInMinutes) {
91
+ logger.debug(
92
+ "Checking NotRecievedInMinutes for Monitor: " +
93
+ input.dataToProcess.monitorId.toString(),
94
+ );
95
+ if (value && differenceInMinutes > (value as number)) {
96
+ logger.debug(
97
+ "NotRecievedInMinutes for Monitor: " +
98
+ input.dataToProcess.monitorId.toString() +
99
+ " is true",
100
+ );
101
+ return `Email not received in ${value} minutes. It was received ${differenceInMinutes} minutes ago.`;
102
+ }
103
+ return null;
104
+ }
105
+ }
106
+
107
+ // Check on Email Subject
108
+ if (
109
+ input.criteriaFilter.checkOn === CheckOn.EmailSubject &&
110
+ !emailData.onlyCheckForIncomingEmailReceivedAt
111
+ ) {
112
+ const subject: string = emailData.emailSubject || "";
113
+
114
+ return this.evaluateStringCriteria(
115
+ subject,
116
+ input.criteriaFilter,
117
+ "Email subject",
118
+ );
119
+ }
120
+
121
+ // Check on Email From
122
+ if (
123
+ input.criteriaFilter.checkOn === CheckOn.EmailFrom &&
124
+ !emailData.onlyCheckForIncomingEmailReceivedAt
125
+ ) {
126
+ const from: string = emailData.emailFrom || "";
127
+
128
+ return this.evaluateStringCriteria(
129
+ from,
130
+ input.criteriaFilter,
131
+ "Email from",
132
+ );
133
+ }
134
+
135
+ // Check on Email Body
136
+ if (
137
+ input.criteriaFilter.checkOn === CheckOn.EmailBody &&
138
+ !emailData.onlyCheckForIncomingEmailReceivedAt
139
+ ) {
140
+ const body: string = emailData.emailBody || "";
141
+
142
+ return this.evaluateStringCriteria(
143
+ body,
144
+ input.criteriaFilter,
145
+ "Email body",
146
+ );
147
+ }
148
+
149
+ // Check on Email To
150
+ if (
151
+ input.criteriaFilter.checkOn === CheckOn.EmailTo &&
152
+ !emailData.onlyCheckForIncomingEmailReceivedAt
153
+ ) {
154
+ const to: string = emailData.emailTo || "";
155
+
156
+ return this.evaluateStringCriteria(to, input.criteriaFilter, "Email to");
157
+ }
158
+
159
+ return null;
160
+ }
161
+
162
+ /**
163
+ * Evaluate string criteria filters
164
+ */
165
+ private static evaluateStringCriteria(
166
+ fieldValue: string,
167
+ criteriaFilter: CriteriaFilter,
168
+ fieldName: string,
169
+ ): string | null {
170
+ const value: string | number | undefined = criteriaFilter.value;
171
+
172
+ if (criteriaFilter.filterType === FilterType.Contains) {
173
+ if (
174
+ value &&
175
+ fieldValue.toLowerCase().includes((value as string).toLowerCase())
176
+ ) {
177
+ return `${fieldName} contains "${value}".`;
178
+ }
179
+ return null;
180
+ }
181
+
182
+ if (criteriaFilter.filterType === FilterType.NotContains) {
183
+ if (
184
+ value &&
185
+ !fieldValue.toLowerCase().includes((value as string).toLowerCase())
186
+ ) {
187
+ return `${fieldName} does not contain "${value}".`;
188
+ }
189
+ return null;
190
+ }
191
+
192
+ if (criteriaFilter.filterType === FilterType.EqualTo) {
193
+ if (
194
+ value &&
195
+ fieldValue.toLowerCase() === (value as string).toLowerCase()
196
+ ) {
197
+ return `${fieldName} equals "${value}".`;
198
+ }
199
+ return null;
200
+ }
201
+
202
+ if (criteriaFilter.filterType === FilterType.NotEqualTo) {
203
+ if (
204
+ value &&
205
+ fieldValue.toLowerCase() !== (value as string).toLowerCase()
206
+ ) {
207
+ return `${fieldName} does not equal "${value}".`;
208
+ }
209
+ return null;
210
+ }
211
+
212
+ if (criteriaFilter.filterType === FilterType.StartsWith) {
213
+ if (
214
+ value &&
215
+ fieldValue.toLowerCase().startsWith((value as string).toLowerCase())
216
+ ) {
217
+ return `${fieldName} starts with "${value}".`;
218
+ }
219
+ return null;
220
+ }
221
+
222
+ if (criteriaFilter.filterType === FilterType.EndsWith) {
223
+ if (
224
+ value &&
225
+ fieldValue.toLowerCase().endsWith((value as string).toLowerCase())
226
+ ) {
227
+ return `${fieldName} ends with "${value}".`;
228
+ }
229
+ return null;
230
+ }
231
+
232
+ if (criteriaFilter.filterType === FilterType.IsEmpty) {
233
+ if (!fieldValue || fieldValue.trim() === "") {
234
+ return `${fieldName} is empty.`;
235
+ }
236
+ return null;
237
+ }
238
+
239
+ if (criteriaFilter.filterType === FilterType.IsNotEmpty) {
240
+ if (fieldValue && fieldValue.trim() !== "") {
241
+ return `${fieldName} is not empty.`;
242
+ }
243
+ return null;
244
+ }
245
+
246
+ return null;
247
+ }
248
+ }
@@ -1,4 +1,5 @@
1
1
  import IncomingMonitorRequest from "../../../Types/Monitor/IncomingMonitor/IncomingMonitorRequest";
2
+ import IncomingEmailMonitorRequest from "../../../Types/Monitor/IncomingEmailMonitor/IncomingEmailMonitorRequest";
2
3
  import ServerMonitorResponse from "../../../Types/Monitor/ServerMonitor/ServerMonitorResponse";
3
4
  import ProbeMonitorResponse from "../../../Types/Probe/ProbeMonitorResponse";
4
5
  import LogMonitorResponse from "../../../Types/Monitor/LogMonitor/LogMonitorResponse";
@@ -9,6 +10,7 @@ import ExceptionMonitorResponse from "../../../Types/Monitor/ExceptionMonitor/Ex
9
10
  type DataToProcess =
10
11
  | ProbeMonitorResponse
11
12
  | IncomingMonitorRequest
13
+ | IncomingEmailMonitorRequest
12
14
  | ServerMonitorResponse
13
15
  | LogMonitorResponse
14
16
  | TraceMonitorResponse
@@ -2,6 +2,7 @@ import logger from "../Logger";
2
2
  import VMUtil from "../VM/VMAPI";
3
3
  import APIRequestCriteria from "./Criteria/APIRequestCriteria";
4
4
  import CustomCodeMonitoringCriteria from "./Criteria/CustomCodeMonitorCriteria";
5
+ import IncomingEmailCriteria from "./Criteria/IncomingEmailCriteria";
5
6
  import IncomingRequestCriteria from "./Criteria/IncomingRequestCriteria";
6
7
  import SSLMonitorCriteria from "./Criteria/SSLMonitorCriteria";
7
8
  import ServerMonitorCriteria from "./Criteria/ServerMonitorCriteria";
@@ -394,6 +395,18 @@ ${contextBlock}
394
395
  }
395
396
  }
396
397
 
398
+ if (input.monitor.monitorType === MonitorType.IncomingEmail) {
399
+ const incomingEmailResult: string | null =
400
+ await IncomingEmailCriteria.isMonitorInstanceCriteriaFilterMet({
401
+ dataToProcess: input.dataToProcess,
402
+ criteriaFilter: input.criteriaFilter,
403
+ });
404
+
405
+ if (incomingEmailResult) {
406
+ return incomingEmailResult;
407
+ }
408
+ }
409
+
397
410
  if (input.monitor.monitorType === MonitorType.SSLCertificate) {
398
411
  const sslMonitorResult: string | null =
399
412
  await SSLMonitorCriteria.isMonitorInstanceCriteriaFilterMet({
@@ -47,6 +47,13 @@ export enum CheckOn {
47
47
 
48
48
  // Metric Monitors.
49
49
  MetricValue = "Metric Value",
50
+
51
+ // Incoming Email monitors.
52
+ EmailSubject = "Email Subject",
53
+ EmailFrom = "Email From Address",
54
+ EmailBody = "Email Body",
55
+ EmailTo = "Email To Address",
56
+ EmailReceivedAt = "Email Received",
50
57
  }
51
58
 
52
59
  export interface ServerMonitorOptions {
@@ -0,0 +1,25 @@
1
+ import Dictionary from "../../Dictionary";
2
+ import ObjectID from "../../ObjectID";
3
+ import MonitorEvaluationSummary from "../MonitorEvaluationSummary";
4
+
5
+ export interface EmailAttachment {
6
+ filename: string;
7
+ contentType: string;
8
+ size: number;
9
+ }
10
+
11
+ export default interface IncomingEmailMonitorRequest {
12
+ projectId: ObjectID;
13
+ monitorId: ObjectID;
14
+ emailFrom: string;
15
+ emailTo: string;
16
+ emailSubject: string;
17
+ emailBody: string;
18
+ emailBodyHtml?: string | undefined;
19
+ emailHeaders?: Dictionary<string> | undefined;
20
+ emailReceivedAt: Date;
21
+ checkedAt: Date;
22
+ attachments?: Array<EmailAttachment> | undefined;
23
+ onlyCheckForIncomingEmailReceivedAt?: boolean | undefined;
24
+ evaluationSummary?: MonitorEvaluationSummary | undefined;
25
+ }
@@ -94,6 +94,33 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
94
94
  return monitorCriteriaInstance;
95
95
  }
96
96
 
97
+ if (arg.monitorType === MonitorType.IncomingEmail) {
98
+ const monitorCriteriaInstance: MonitorCriteriaInstance =
99
+ new MonitorCriteriaInstance();
100
+
101
+ monitorCriteriaInstance.data = {
102
+ id: ObjectID.generate().toString(),
103
+ monitorStatusId: arg.monitorStatusId,
104
+ filterCondition: FilterCondition.All,
105
+ filters: [
106
+ {
107
+ checkOn: CheckOn.EmailReceivedAt,
108
+ filterType: FilterType.RecievedInMinutes,
109
+ value: 30,
110
+ },
111
+ ],
112
+ incidents: [],
113
+ alerts: [],
114
+ createAlerts: false,
115
+ changeMonitorStatus: true,
116
+ createIncidents: false,
117
+ name: `Check if ${arg.monitorName} is online`,
118
+ description: `This criteria checks if the ${arg.monitorName} is online`,
119
+ };
120
+
121
+ return monitorCriteriaInstance;
122
+ }
123
+
97
124
  if (arg.monitorType === MonitorType.Logs) {
98
125
  const monitorCriteriaInstance: MonitorCriteriaInstance =
99
126
  new MonitorCriteriaInstance();
@@ -658,6 +685,46 @@ export default class MonitorCriteriaInstance extends DatabaseProperty {
658
685
  };
659
686
  }
660
687
 
688
+ if (arg.monitorType === MonitorType.IncomingEmail) {
689
+ monitorCriteriaInstance.data = {
690
+ id: ObjectID.generate().toString(),
691
+ monitorStatusId: arg.monitorStatusId,
692
+ filterCondition: FilterCondition.Any,
693
+ filters: [
694
+ {
695
+ checkOn: CheckOn.EmailReceivedAt,
696
+ filterType: FilterType.NotRecievedInMinutes,
697
+ value: 30, // if email is not received in 30 minutes, then the monitor is offline
698
+ },
699
+ ],
700
+ alerts: [
701
+ {
702
+ title: `${arg.monitorName} is offline`,
703
+ description: `${arg.monitorName} is currently offline. No email received.`,
704
+ alertSeverityId: arg.alertSeverityId,
705
+ autoResolveAlert: true,
706
+ id: ObjectID.generate().toString(),
707
+ onCallPolicyIds: [],
708
+ },
709
+ ],
710
+ createAlerts: false,
711
+ incidents: [
712
+ {
713
+ title: `${arg.monitorName} is offline`,
714
+ description: `${arg.monitorName} is currently offline. No email received.`,
715
+ incidentSeverityId: arg.incidentSeverityId,
716
+ autoResolveIncident: true,
717
+ id: ObjectID.generate().toString(),
718
+ onCallPolicyIds: [],
719
+ },
720
+ ],
721
+ changeMonitorStatus: true,
722
+ createIncidents: true,
723
+ name: `Check if ${arg.monitorName} is offline`,
724
+ description: `This criteria checks if the ${arg.monitorName} is offline`,
725
+ };
726
+ }
727
+
661
728
  if (
662
729
  arg.monitorType === MonitorType.CustomJavaScriptCode ||
663
730
  arg.monitorType === MonitorType.SyntheticMonitor
@@ -1,4 +1,5 @@
1
1
  import BadDataException from "../Exception/BadDataException";
2
+ import IconProp from "../Icon/IconProp";
2
3
 
3
4
  enum MonitorType {
4
5
  Manual = "Manual",
@@ -8,6 +9,7 @@ enum MonitorType {
8
9
  Kubernetes = "Kubernetes",
9
10
  IP = "IP",
10
11
  IncomingRequest = "Incoming Request",
12
+ IncomingEmail = "Incoming Email",
11
13
  Port = "Port",
12
14
  Server = "Server",
13
15
  SSLCertificate = "SSL Certificate",
@@ -29,6 +31,7 @@ export interface MonitorTypeProps {
29
31
  monitorType: MonitorType;
30
32
  description: string;
31
33
  title: string;
34
+ icon: IconProp;
32
35
  }
33
36
 
34
37
  export class MonitorTypeHelper {
@@ -52,24 +55,28 @@ export class MonitorTypeHelper {
52
55
  title: "API",
53
56
  description:
54
57
  "This monitor type lets you monitor any API - GET, POST, PUT, DELETE or more.",
58
+ icon: IconProp.Code,
55
59
  },
56
60
  {
57
61
  monitorType: MonitorType.Manual,
58
62
  title: "Manual",
59
63
  description:
60
64
  "This monitor is a static monitor and will not actually monitor anything. It will however help you to integrate OneUptime with external monitoring tools and utilities.",
65
+ icon: IconProp.EmptyCircle,
61
66
  },
62
67
  {
63
68
  monitorType: MonitorType.Website,
64
69
  title: "Website",
65
70
  description:
66
71
  "This monitor type lets you monitor landing pages like home page of your company / blog or more.",
72
+ icon: IconProp.Globe,
67
73
  },
68
74
  {
69
75
  monitorType: MonitorType.Ping,
70
76
  title: "Ping",
71
77
  description:
72
78
  "This monitor type does the basic ping test of an endpoint.",
79
+ icon: IconProp.Signal,
73
80
  },
74
81
  /*
75
82
  * {
@@ -77,6 +84,7 @@ export class MonitorTypeHelper {
77
84
  * title: 'Kubernetes',
78
85
  * description:
79
86
  * 'This monitor types lets you monitor Kubernetes clusters.',
87
+ * icon: IconProp.Cube,
80
88
  * },
81
89
  */
82
90
  {
@@ -84,64 +92,82 @@ export class MonitorTypeHelper {
84
92
  title: "IP",
85
93
  description:
86
94
  "This monitor type lets you monitor any IPv4 or IPv6 addresses.",
95
+ icon: IconProp.AltGlobe,
87
96
  },
88
97
  {
89
98
  monitorType: MonitorType.IncomingRequest,
90
99
  title: "Incoming Request",
91
100
  description:
92
101
  "This monitor type lets you ping OneUptime from any external device or service with a custom payload.",
102
+ icon: IconProp.Webhook,
103
+ },
104
+ {
105
+ monitorType: MonitorType.IncomingEmail,
106
+ title: "Incoming Email",
107
+ description:
108
+ "This monitor type triggers alerts when emails are received at a unique email address with matching criteria.",
109
+ icon: IconProp.Email,
93
110
  },
94
111
  {
95
112
  monitorType: MonitorType.Port,
96
113
  title: "Port",
97
114
  description: "This monitor type lets you monitor any TCP or UDP port.",
115
+ icon: IconProp.Terminal,
98
116
  },
99
117
  {
100
118
  monitorType: MonitorType.Server,
101
119
  title: "Server / VM",
102
120
  description:
103
121
  "This monitor type lets you monitor any server, VM, or any machine.",
122
+ icon: IconProp.Cube,
104
123
  },
105
124
  {
106
125
  monitorType: MonitorType.SSLCertificate,
107
126
  title: "SSL Certificate",
108
127
  description:
109
128
  "This monitor type lets you monitor SSL certificates of any domain.",
129
+ icon: IconProp.ShieldCheck,
110
130
  },
111
131
  {
112
132
  monitorType: MonitorType.SyntheticMonitor,
113
133
  title: "Synthetic Monitor",
114
134
  description:
115
135
  "This monitor type lets you monitor your web application UI.",
136
+ icon: IconProp.Window,
116
137
  },
117
138
  {
118
139
  monitorType: MonitorType.CustomJavaScriptCode,
119
140
  title: "Custom JavaScript Code",
120
141
  description:
121
142
  "This monitor type lets you run custom JavaScript code on a schedule.",
143
+ icon: IconProp.Code,
122
144
  },
123
145
  {
124
146
  monitorType: MonitorType.Logs,
125
147
  title: "Logs",
126
148
  description: "This monitor type lets you monitor logs from any source.",
149
+ icon: IconProp.Logs,
127
150
  },
128
151
  {
129
152
  monitorType: MonitorType.Exceptions,
130
153
  title: "Exceptions",
131
154
  description:
132
155
  "This monitor type lets you monitor exceptions and error groups from any source.",
156
+ icon: IconProp.Bug,
133
157
  },
134
158
  {
135
159
  monitorType: MonitorType.Traces,
136
160
  title: "Traces",
137
161
  description:
138
162
  "This monitor type lets you monitor traces from any source.",
163
+ icon: IconProp.Waterfall,
139
164
  },
140
165
  {
141
166
  monitorType: MonitorType.Metrics,
142
167
  title: "Metrics",
143
168
  description:
144
169
  "This monitor type lets you monitor metrics from any source.",
170
+ icon: IconProp.Heartbeat,
145
171
  },
146
172
  ];
147
173
 
@@ -202,6 +228,7 @@ export class MonitorTypeHelper {
202
228
  MonitorType.SyntheticMonitor,
203
229
  MonitorType.CustomJavaScriptCode,
204
230
  MonitorType.IncomingRequest,
231
+ MonitorType.IncomingEmail,
205
232
  MonitorType.Server,
206
233
  MonitorType.Logs,
207
234
  MonitorType.Metrics,
@@ -215,6 +242,7 @@ export class MonitorTypeHelper {
215
242
  ): boolean {
216
243
  return (
217
244
  monitorType === MonitorType.IncomingRequest ||
245
+ monitorType === MonitorType.IncomingEmail ||
218
246
  monitorType === MonitorType.Server
219
247
  );
220
248
  }
@@ -0,0 +1,105 @@
1
+ import IconProp from "../../../Types/Icon/IconProp";
2
+ import Icon, { SizeProp } from "../Icon/Icon";
3
+ import React, { FunctionComponent, ReactElement } from "react";
4
+
5
+ export interface CardSelectOption {
6
+ value: string;
7
+ title: string;
8
+ description: string;
9
+ icon: IconProp;
10
+ }
11
+
12
+ export interface ComponentProps {
13
+ options: Array<CardSelectOption>;
14
+ value?: string | undefined;
15
+ onChange: (value: string) => void;
16
+ error?: string | undefined;
17
+ tabIndex?: number | undefined;
18
+ dataTestId?: string | undefined;
19
+ }
20
+
21
+ const CardSelect: FunctionComponent<ComponentProps> = (
22
+ props: ComponentProps,
23
+ ): ReactElement => {
24
+ return (
25
+ <div data-testid={props.dataTestId}>
26
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
27
+ {props.options.map((option: CardSelectOption, index: number) => {
28
+ const isSelected: boolean = props.value === option.value;
29
+
30
+ return (
31
+ <div
32
+ key={index}
33
+ tabIndex={props.tabIndex ? props.tabIndex + index : index}
34
+ onClick={() => {
35
+ props.onChange(option.value);
36
+ }}
37
+ onKeyDown={(e: React.KeyboardEvent) => {
38
+ if (e.key === "Enter" || e.key === " ") {
39
+ e.preventDefault();
40
+ props.onChange(option.value);
41
+ }
42
+ }}
43
+ className={`relative flex cursor-pointer rounded-lg border p-4 shadow-sm focus:outline-none transition-all duration-200 hover:border-indigo-400 hover:shadow-md ${
44
+ isSelected
45
+ ? "border-indigo-500 bg-indigo-50/50"
46
+ : "border-gray-200 bg-white"
47
+ }`}
48
+ role="radio"
49
+ aria-checked={isSelected}
50
+ data-testid={`card-select-option-${option.value}`}
51
+ >
52
+ <div className="flex w-full items-start">
53
+ <div
54
+ className={`flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg ${
55
+ isSelected ? "bg-indigo-100" : "bg-gray-100"
56
+ }`}
57
+ >
58
+ <Icon
59
+ icon={option.icon}
60
+ size={SizeProp.Large}
61
+ className={`h-5 w-5 ${
62
+ isSelected ? "text-indigo-600" : "text-gray-600"
63
+ }`}
64
+ />
65
+ </div>
66
+ <div className="ml-4 flex-1">
67
+ <span
68
+ className={`block text-sm font-semibold ${
69
+ isSelected ? "text-gray-900" : "text-gray-900"
70
+ }`}
71
+ >
72
+ {option.title}
73
+ </span>
74
+ <span
75
+ className={`mt-1 block text-sm ${
76
+ isSelected ? "text-gray-600" : "text-gray-500"
77
+ }`}
78
+ >
79
+ {option.description}
80
+ </span>
81
+ </div>
82
+ {isSelected && (
83
+ <div className="flex-shrink-0 ml-2">
84
+ <Icon
85
+ icon={IconProp.CheckCircle}
86
+ size={SizeProp.Large}
87
+ className="h-5 w-5 text-indigo-500"
88
+ />
89
+ </div>
90
+ )}
91
+ </div>
92
+ </div>
93
+ );
94
+ })}
95
+ </div>
96
+ {props.error && (
97
+ <p className="mt-2 text-sm text-red-600" role="alert">
98
+ {props.error}
99
+ </p>
100
+ )}
101
+ </div>
102
+ );
103
+ };
104
+
105
+ export default CardSelect;