Compare commits

...

95 Commits

Author SHA1 Message Date
Simon Larsen
407fc1240a add pricing controls 2023-06-22 20:53:38 +01:00
Simon Larsen
2bc307c564 add column access pricing controls 2023-06-22 20:41:03 +01:00
Simon Larsen
c42790e6f2 center loader 2023-06-22 20:20:04 +01:00
Simon Larsen
ecdbea2aab remove company name from user settings 2023-06-22 20:12:12 +01:00
Simon Larsen
7cb98456b1 ad owner fields to project model 2023-06-22 20:03:21 +01:00
Simon Larsen
4f80317b14 add disabled settings 2023-06-22 18:55:55 +01:00
Simon Larsen
7a589d65a3 add disable monitor page 2023-06-22 18:07:07 +01:00
Simon Larsen
acd5d04ee9 fix createdAt 2023-06-22 17:56:16 +01:00
Simon Larsen
998c85e393 update project last active 2023-06-22 17:51:28 +01:00
Simon Larsen
70478bd1fa fix createdAt not populating 2023-06-22 17:41:19 +01:00
Simon Larsen
4c06feeb50 fix fmt 2023-06-21 19:40:44 +01:00
Simon Larsen
2acd6d5ce0 add disable monitoring 2023-06-21 19:39:17 +01:00
Simon Larsen
56e7c0c7d0 fix monitor type enum 2023-06-21 19:00:23 +01:00
Simon Larsen
7948070be6 add user limit 2023-06-21 18:47:35 +01:00
Nawaz Dhandala
a9139fcca0 Merge pull request #484 from OneUptime/on-call
On-Call Duty -> On Call Duty Policy
2023-06-21 15:57:16 +01:00
Simon Larsen
99b3dc65a7 rename file with typo 2023-06-21 14:25:32 +01:00
Simon Larsen
599c7a175e fix 2023-06-21 14:25:17 +01:00
Simon Larsen
0a03dc652c fix migrations 2023-06-21 14:20:00 +01:00
Simon Larsen
53f72c2192 fix case 2023-06-21 13:39:24 +01:00
Simon Larsen
53334ad3dc make notiifction rule work properly 2023-06-20 15:41:54 +01:00
Simon Larsen
4d7ddf7be1 add notification rule 2023-06-20 15:34:33 +01:00
Simon Larsen
9dfdd0841f add delete trigger to call email and sms 2023-06-20 12:47:26 +01:00
Simon Larsen
7f662291e4 fix rule 2023-06-20 12:03:22 +01:00
Simon Larsen
9bbd32424e Merge branch 'master' into on-call 2023-06-20 11:53:49 +01:00
Simon Larsen
41b5fe3a19 fix null issue with select 2023-06-20 11:51:54 +01:00
Simon Larsen
d35195a591 fix notification types 2023-06-20 11:43:54 +01:00
Simon Larsen
ee229d3711 fix relation fetch in table 2023-06-20 10:05:41 +01:00
Simon Larsen
9eb12a5348 fix issues with table 2023-06-20 10:02:11 +01:00
Simon Larsen
0bb8343f0b fix typo 2023-06-19 20:27:43 +01:00
Simon Larsen
d5c13f5c26 fix fmt 2023-06-19 19:52:24 +01:00
Simon Larsen
9e57fe1531 add notification rule 2023-06-19 19:49:15 +01:00
Simon Larsen
8b10e0d9f0 fix fmt 2023-06-19 19:40:38 +01:00
Simon Larsen
45d7dc90b3 add donation in readme 2023-06-19 19:06:51 +01:00
Simon Larsen
da2683391d add notification rule 2023-06-19 17:57:48 +01:00
Simon Larsen
ea50830dae add notification rule model. 2023-06-19 17:52:49 +01:00
Simon Larsen
985a7ca973 fix side menu link 2023-06-19 13:11:48 +01:00
Simon Larsen
2c649bed07 fix on call custom fields 2023-06-19 13:06:22 +01:00
Simon Larsen
78fad54d6a Merge branch 'master' into on-call 2023-06-19 12:31:44 +01:00
Simon Larsen
05c583fd81 add plan name in project model 2023-06-19 12:30:52 +01:00
Simon Larsen
b99912abd6 add plan name to project 2023-06-19 12:27:10 +01:00
Simon Larsen
c3a5a8a4e8 add custom fields 2023-06-19 12:07:53 +01:00
Simon Larsen
ad451fd9c9 Merge branch 'master' into on-call 2023-06-19 11:29:39 +01:00
Simon Larsen
ce31e0cfff Merge pull request #489 from OneUptime/created-at-refactor
fix createdat
2023-06-19 11:24:40 +01:00
Simon Larsen
545dcea3e8 fix createdat 2023-06-19 11:24:25 +01:00
Simon Larsen
29accb2e6f Merge branch 'master' into release 2023-06-19 10:35:28 +01:00
Simon Larsen
8cb8a1ed72 Merge pull request #488 from OneUptime/standard-workflows
add select clause to trigger
2023-06-19 10:34:02 +01:00
Simon Larsen
a8beed5c5c add select clause to trigger 2023-06-19 10:32:54 +01:00
Simon Larsen
8d59fdc732 Merge pull request #487 from OneUptime/master
Staus Page redirection fix
2023-06-16 19:45:16 +01:00
Simon Larsen
a7fe18fd65 fix rounded corners 2023-06-16 18:25:18 +01:00
Simon Larsen
e80835c380 Merge branch 'master' into on-call 2023-06-16 18:13:21 +01:00
Simon Larsen
bbd60075fa fix preview selector 2023-06-16 18:11:25 +01:00
Simon Larsen
622bb87b89 add call logs 2023-06-16 14:04:57 +01:00
Simon Larsen
3d3c9876eb add env var 2023-06-16 14:00:40 +01:00
Simon Larsen
ea588be0f7 Merge branch 'master' into on-call 2023-06-16 13:32:34 +01:00
Simon Larsen
0fe1779ce4 fix gtetId() 2023-06-16 13:30:06 +01:00
Simon Larsen
71f2d3b87a add side menu item 2023-06-16 13:29:11 +01:00
Simon Larsen
8d0670d05c add call log 2023-06-16 13:26:32 +01:00
Simon Larsen
1b6eccfb36 fix fmt 2023-06-16 12:33:16 +01:00
Simon Larsen
9a7c2cedf5 add call service 2023-06-16 12:25:37 +01:00
Simon Larsen
55b9a6bf9f Merge branch 'master' into on-call 2023-06-16 12:22:05 +01:00
Simon Larsen
9ddca843c8 refactor status page 2023-06-16 12:13:57 +01:00
Simon Larsen
2a1786147b fix status page set 2023-06-16 11:45:28 +01:00
Simon Larsen
1bc572c4be fix lint 2023-06-16 11:32:23 +01:00
Simon Larsen
f961796c7c fix sttaus page redirection 2023-06-16 11:14:32 +01:00
Simon Larsen
ff1f564527 add call config 2023-06-16 10:40:27 +01:00
Simon Larsen
6ef61221bf ad sms service 2023-06-15 15:07:34 +01:00
Simon Larsen
1b7d17fb9e fix terms in sla and make them clearer 2023-06-15 12:21:56 +01:00
Simon Larsen
04f7d1f8bf fix typo 2023-06-15 12:18:36 +01:00
Simon Larsen
2edbe0df08 add resend verifiction code 2023-06-14 21:16:07 +01:00
Simon Larsen
a96dc90104 fix template 2023-06-14 21:05:43 +01:00
Simon Larsen
3e8b966dc8 fix models 2023-06-14 21:02:10 +01:00
Simon Larsen
1ee94f10c4 fix fmt 2023-06-14 20:13:24 +01:00
Simon Larsen
74866edadb add workflow help banner 2023-06-14 20:07:44 +01:00
Simon Larsen
e402ebc14c Merge branch 'master' into on-call 2023-06-14 20:04:00 +01:00
Simon Larsen
4297cead16 fix fmt 2023-06-14 19:59:26 +01:00
Simon Larsen
da021bbd10 add logs for api monitor 2023-06-14 19:53:50 +01:00
Simon Larsen
ab48de6e9b add user settings 2023-06-14 19:41:03 +01:00
Simon Larsen
314b905e8f add api for user notification methods 2023-06-14 14:56:47 +01:00
Simon Larsen
db60c0fefb add more icons 2023-06-14 14:38:35 +01:00
Simon Larsen
713d9464a2 add default value capability 2023-06-14 14:04:32 +01:00
Simon Larsen
84cc4a35d4 fix user email 2023-06-14 13:55:38 +01:00
Simon Larsen
6a64b8658a add user emails for notifications. 2023-06-14 13:47:01 +01:00
Simon Larsen
b8ee827068 fix lint 2023-06-14 12:28:54 +01:00
Simon Larsen
8fea1b6e3d fix sidemenu 2023-06-14 12:19:03 +01:00
Simon Larsen
96934f5f22 fix app.tsx 2023-06-14 12:17:45 +01:00
Simon Larsen
a2ad4d30e7 fix populate route params 2023-06-14 12:11:53 +01:00
Simon Larsen
0623588019 fix lint. 2023-06-14 11:30:09 +01:00
Simon Larsen
f1d087da44 add services and api 2023-06-13 17:03:38 +01:00
Simon Larsen
5dca4a0fd3 add services 2023-06-13 16:57:02 +01:00
Simon Larsen
7d085274de add models of on-call 2023-06-13 16:46:25 +01:00
Simon Larsen
1da9184a14 Merge branch 'master' into on-call 2023-06-13 10:47:06 +01:00
Simon Larsen
12c429597c fix entity name 2023-06-13 10:44:23 +01:00
Simon Larsen
314a9ef61c fix company in legal 2023-06-13 10:37:24 +01:00
Simon Larsen
5961504577 fix redirect issue 2023-06-13 10:01:08 +01:00
Simon Larsen
a7757e547c On-Call Duty -> On Call Duty Policy 2023-06-12 19:59:38 +01:00
219 changed files with 11012 additions and 1055 deletions

View File

@@ -28,13 +28,17 @@ import Permission, {
UserPermission,
UserTenantAccessPermission,
} from '../Types/Permission';
import { ColumnAccessControl } from '../Types/Database/AccessControl/AccessControl';
import {
ColumnAccessControl,
ColumnBillingAccessControl,
} from '../Types/Database/AccessControl/AccessControl';
import { getColumnAccessControlForAllColumns } from '../Types/Database/AccessControl/ColumnAccessControl';
import BadDataException from '../Types/Exception/BadDataException';
import { PlanSelect } from '../Types/Billing/SubscriptionPlan';
import { EnableWorkflowOn } from '../Types/Model/EnableWorkflow';
import IconProp from '../Types/Icon/IconProp';
import Text from '../Types/Text';
import { getColumnBillingAccessControlForAllColumns } from '../Types/Database/AccessControl/ColumnBillingAccessControl';
export type DbTypes =
| string
@@ -202,6 +206,14 @@ export default class BaseModel extends BaseEntity {
return dictionary[columnName] as TableColumnMetadata;
}
public getColumnBillingAccessControl(
columnName: string
): ColumnBillingAccessControl {
const dictionary: Dictionary<ColumnBillingAccessControl> =
getColumnBillingAccessControlForAllColumns(this);
return dictionary[columnName] as ColumnBillingAccessControl;
}
public getColumnAccessControlFor(
columnName: string
): ColumnAccessControl | null {

View File

@@ -0,0 +1,11 @@
export interface Say {
sayMessage: string;
}
export enum CallAction {
Hangup = 'Hangup',
}
export default interface CallRequest {
data: Array<Say | CallAction>;
}

View File

@@ -0,0 +1,9 @@
enum CallStatus {
Success = 'Success',
Error = 'Error',
LowBalance = 'Low Balance',
MissedCall = 'Missed Call',
Busy = 'Busy',
}
export default CallStatus;

View File

@@ -17,3 +17,9 @@ export interface BillingAccessControl {
update: PlanSelect;
delete: PlanSelect;
}
export interface ColumnBillingAccessControl {
create: PlanSelect;
read: PlanSelect;
update: PlanSelect;
}

View File

@@ -0,0 +1,45 @@
import 'reflect-metadata';
import BaseModel from '../../../Models/BaseModel';
import Dictionary from '../../Dictionary';
import { ReflectionMetadataType } from '../../Reflection';
import { ColumnBillingAccessControl } from './AccessControl';
const accessControlSymbol: Symbol = Symbol('ColumnBillingAccessControl');
export default (
accessControl: ColumnBillingAccessControl
): ReflectionMetadataType => {
return Reflect.metadata(accessControlSymbol, accessControl);
};
export const getColumnBillingAccessControl: Function = (
target: BaseModel,
propertyKey: string
): ColumnBillingAccessControl => {
return Reflect.getMetadata(
accessControlSymbol,
target,
propertyKey
) as ColumnBillingAccessControl;
};
export const getColumnBillingAccessControlForAllColumns: Function = <
T extends BaseModel
>(
target: T
): Dictionary<ColumnBillingAccessControl> => {
const dictonary: Dictionary<ColumnBillingAccessControl> = {};
const keys: Array<string> = Object.keys(target);
for (const key of keys) {
if (Reflect.getMetadata(accessControlSymbol, target, key)) {
dictonary[key] = Reflect.getMetadata(
accessControlSymbol,
target,
key
) as ColumnBillingAccessControl;
}
}
return dictonary;
};

View File

@@ -19,6 +19,7 @@ export interface TableColumnMetadata {
type: TableColumnType;
canReadOnRelationQuery?: boolean;
modelType?: { new (): BaseModel };
forceGetDefaultValueOnCreate?: () => string | number | boolean; // overwrites any value that is being passed and generates a new one. Useful for generating OTPs, etc.
}
export default (props: TableColumnMetadata): ReflectionMetadataType => {

View File

@@ -32,6 +32,7 @@ enum EmailTemplateType {
StatusPageOwnerAdded = 'StatusPageOwnerAdded.hbs',
StatusPageOwnerAnnouncementPosted = 'StatusPageOwnerAnnouncementPosted.hbs',
SimpleMessage = 'SimpleMessage.hbs',
VerificationCode = 'VerificationCode.hbs',
}
export default EmailTemplateType;

View File

@@ -94,6 +94,12 @@ enum IconProp {
TransparentCube = 'TransparentCube',
Logs = 'Logs',
Bolt = 'Bolt',
BarsArrowUp = 'BarsArrowUp',
BarsArrowDown = 'BarsArrowDown',
Bell = 'Bell',
BellRinging = 'BellRinging',
AdjustmentVertical = 'AdjustmentVertical',
AdjustmentHorizontal = 'AdjustmentHorizontal',
}
export default IconProp;

View File

@@ -22,6 +22,7 @@ import { BaseEntity } from 'typeorm';
import EqualToOrNull from './Database/EqualToOrNull';
import NotEqual from './Database/NotEqual';
import { CheckOn, FilterType } from './Monitor/CriteriaFilter';
import CallRequest from './Call/CallRequest';
export enum ObjectType {
ObjectID = 'ObjectID',
@@ -122,6 +123,7 @@ export type JSONValue =
| Array<JSONValue>
| Array<Permission>
| Array<JSONValue>
| CallRequest
| undefined
| null;

View File

@@ -0,0 +1,7 @@
enum NoticationRuleType {
ON_CALL_INCIDENT_CREATED = 'When incident is created during on call',
WHEN_USER_GOES_ON_CALL = 'When user goes on call',
WHEN_USER_GOES_OFF_CALL = 'When user goes off call',
}
export default NoticationRuleType;

View File

@@ -0,0 +1,7 @@
enum OnCallDutyPolicyStatus {
SuccessfullyAcknowledged = 'Successfully Acknowledged',
FailedToAcknowledge = 'Failed to Acknowledge',
Error = 'Error',
}
export default OnCallDutyPolicyStatus;

View File

@@ -0,0 +1,8 @@
enum OnCallDutyPolicyStatus {
SuccessfullyAcknowledged = 'Successfully Acknowledged',
ExecutionInProgress = 'Execution in Progress',
FailedToAcknowledge = 'Failed to Acknowledge',
Error = 'Error',
}
export default OnCallDutyPolicyStatus;

View File

@@ -63,6 +63,11 @@ enum Permission {
CanEditMonitorCustomField = 'CanEditMonitorCustomField',
CanReadMonitorCustomField = 'CanReadMonitorCustomField',
CanCreateOnCallDutyPolicyCustomField = 'CanCreateOnCallDutyPolicyCustomField',
CanDeleteOnCallDutyPolicyCustomField = 'CanDeleteOnCallDutyPolicyCustomField',
CanEditOnCallDutyPolicyCustomField = 'CanEditOnCallDutyPolicyCustomField',
CanReadOnCallDutyPolicyCustomField = 'CanReadOnCallDutyPolicyCustomField',
CanCreateScheduledMaintenanceCustomField = 'CanCreateScheduledMaintenanceCustomField',
CanDeleteScheduledMaintenanceCustomField = 'CanDeleteScheduledMaintenanceCustomField',
CanEditScheduledMaintenanceCustomField = 'CanEditScheduledMaintenanceCustomField',
@@ -73,11 +78,10 @@ enum Permission {
CanEditMonitorProbe = 'CanEditMonitorProbe',
CanReadMonitorProbe = 'CanReadMonitorProbe',
CanCreateSmsLog = 'CanCreateSmsLog',
CanDeleteSmsLog = 'CanDeleteSmsLog',
CanEditSmsLog = 'CanEditSmsLog',
CanReadSmsLog = 'CanReadSmsLog',
CanReadCallLog = 'CanReadCallLog',
CanCreateIncidentOwnerTeam = 'CanCreateIncidentOwnerTeam',
CanDeleteIncidentOwnerTeam = 'CanDeleteIncidentOwnerTeam',
CanEditIncidentOwnerTeam = 'CanEditIncidentOwnerTeam',
@@ -296,10 +300,31 @@ enum Permission {
CanReadProjectStatusPage = 'CanReadProjectStatusPage',
// Resource Permissions (Team Permission)
CanCreateProjectOnCallDuty = 'CanCreateProjectOnCallDuty',
CanEditProjectOnCallDuty = 'CanEditProjectOnCallDuty',
CanDeleteProjectOnCallDuty = 'CanDeleteProjectOnCallDuty',
CanReadProjectOnCallDuty = 'CanReadProjectOnCallDuty',
CanCreateProjectOnCallDutyPolicy = 'CanCreateProjectOnCallDutyPolicy',
CanEditProjectOnCallDutyPolicy = 'CanEditProjectOnCallDutyPolicy',
CanDeleteProjectOnCallDutyPolicy = 'CanDeleteProjectOnCallDutyPolicy',
CanReadProjectOnCallDutyPolicy = 'CanReadProjectOnCallDutyPolicy',
CanReadProjectOnCallDutyPolicyExecutionLogTimeline = 'CanReadProjectOnCallDutyPolicyExecutionLogTimeline',
CanReadProjectOnCallDutyPolicyExecutionLog = 'CanReadProjectOnCallDutyPolicyExecutionLog',
// Resource Permissions (Team Permission)
CanCreateProjectOnCallDutyPolicyEscalationRule = 'CanCreateProjectOnCallDutyPolicyEscalationRule',
CanEditProjectOnCallDutyPolicyEscalationRule = 'CanEditProjectOnCallDutyPolicyEscalationRule',
CanDeleteProjectOnCallDutyPolicyEscalationRule = 'CanDeleteProjectOnCallDutyPolicyEscalationRule',
CanReadProjectOnCallDutyPolicyEscalationRule = 'CanReadProjectOnCallDutyPolicyEscalationRule',
// Resource Permissions (Team Permission)
CanCreateProjectOnCallDutyPolicyEscalationRuleUser = 'CanCreateProjectOnCallDutyPolicyEscalationRuleUser',
CanEditProjectOnCallDutyPolicyEscalationRuleUser = 'CanEditProjectOnCallDutyPolicyEscalationRuleUser',
CanDeleteProjectOnCallDutyPolicyEscalationRuleUser = 'CanDeleteProjectOnCallDutyPolicyEscalationRuleUser',
CanReadProjectOnCallDutyPolicyEscalationRuleUser = 'CanReadProjectOnCallDutyPolicyEscalationRuleUser',
// Resource Permissions (Team Permission)
CanCreateProjectOnCallDutyPolicyEscalationRuleTeam = 'CanCreateProjectOnCallDutyPolicyEscalationRuleTeam',
CanEditProjectOnCallDutyPolicyEscalationRuleTeam = 'CanEditProjectOnCallDutyPolicyEscalationRuleTeam',
CanDeleteProjectOnCallDutyPolicyEscalationRuleTeam = 'CanDeleteProjectOnCallDutyPolicyEscalationRuleTeam',
CanReadProjectOnCallDutyPolicyEscalationRuleTeam = 'CanReadProjectOnCallDutyPolicyEscalationRuleTeam',
// Project SMTP Config (Team Permission)
CanCreateProjectSMTPConfig = 'CanCreateProjectSMTPConfig',
@@ -1446,32 +1471,163 @@ export class PermissionHelper {
},
{
permission: Permission.CanCreateProjectOnCallDuty,
title: 'Can Create On-Call Duty',
permission:
Permission.CanReadProjectOnCallDutyPolicyExecutionLogTimeline,
title: 'Can Read On-Call Duty Policy Execution Log Timeline',
description:
'This permission can read teams in on-call duty execution log timeline.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanReadProjectOnCallDutyPolicyExecutionLog,
title: 'Can Read On-Call Duty Policy Execution Log',
description:
'This permission can read teams in on-call duty execution log.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanCreateProjectOnCallDutyPolicyEscalationRuleTeam,
title: 'Can Create On-Call Duty Policy Escalation Rule',
description:
'This permission can create teams in on-call duty escalation rule this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanDeleteProjectOnCallDutyPolicyEscalationRuleTeam,
title: 'Can Delete On-Call Duty Policy Escalation Rule Team',
description:
'This permission can delete teams in on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanEditProjectOnCallDutyPolicyEscalationRuleTeam,
title: 'Can Edit On-Call Duty Policy Escalation Rule Team',
description:
'This permission can edit teams in on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanReadProjectOnCallDutyPolicyEscalationRuleTeam,
title: 'Can Read On-Call Duty Policy Escalation Rule Team',
description:
'This permission can read teams in on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanCreateProjectOnCallDutyPolicyEscalationRuleUser,
title: 'Can Create On-Call Duty Policy Escalation Rule User',
description:
'This permission can create on-call duty escalation rule this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanDeleteProjectOnCallDutyPolicyEscalationRuleUser,
title: 'Can Delete On-Call Duty Policy Escalation Rule User',
description:
'This permission can delete on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanEditProjectOnCallDutyPolicyEscalationRuleUser,
title: 'Can Edit On-Call Duty Policy Escalation Rule User',
description:
'This permission can edit on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanReadProjectOnCallDutyPolicyEscalationRuleUser,
title: 'Can Read On-Call Duty Policy Escalation Rule User',
description:
'This permission can read on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanCreateProjectOnCallDutyPolicyEscalationRule,
title: 'Can Create On-Call Duty Policy Escalation Rule',
description:
'This permission can create on-call duty escalation rule this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanDeleteProjectOnCallDutyPolicyEscalationRule,
title: 'Can Delete On-Call Duty Policy Escalation Rule',
description:
'This permission can delete on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanEditProjectOnCallDutyPolicyEscalationRule,
title: 'Can Edit On-Call Duty Policy Escalation Rule',
description:
'This permission can edit on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission:
Permission.CanReadProjectOnCallDutyPolicyEscalationRule,
title: 'Can Read On-Call Duty Policy Escalation Rule',
description:
'This permission can read on-call duty escalation rule of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission: Permission.CanCreateProjectOnCallDutyPolicy,
title: 'Can Create On-Call Duty Policy',
description:
'This permission can create on-call duty this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission: Permission.CanDeleteProjectOnCallDuty,
title: 'Can Delete On-Call Duty',
permission: Permission.CanDeleteProjectOnCallDutyPolicy,
title: 'Can Delete On-Call Duty Policy',
description:
'This permission can delete on-call duty of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission: Permission.CanEditProjectOnCallDuty,
title: 'Can Edit On-Call Duty',
permission: Permission.CanEditProjectOnCallDutyPolicy,
title: 'Can Edit On-Call Duty Policy',
description:
'This permission can edit on-call duty of this project.',
isAssignableToTenant: true,
isAccessControlPermission: true,
},
{
permission: Permission.CanReadProjectOnCallDuty,
title: 'Can Read On-Call Duty',
permission: Permission.CanReadProjectOnCallDutyPolicy,
title: 'Can Read On-Call Duty Policy',
description:
'This permission can read on-call duty of this project.',
isAssignableToTenant: true,
@@ -1541,6 +1697,39 @@ export class PermissionHelper {
isAccessControlPermission: false,
},
{
permission: Permission.CanCreateOnCallDutyPolicyCustomField,
title: 'Can Create On Call Policy Custom Field',
description:
'This permission can create On Call Policy Custom Field this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanDeleteOnCallDutyPolicyCustomField,
title: 'Can Delete On Call Policy Custom Field',
description:
'This permission can delete On Call Policy Custom Field of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanEditOnCallDutyPolicyCustomField,
title: 'Can Edit On Call Policy Custom Field',
description:
'This permission can edit On Call Policy Custom Field of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanReadOnCallDutyPolicyCustomField,
title: 'Can Read On Call Policy Custom Field',
description:
'This permission can read On Call Policy Custom Field of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanCreateMonitorCustomField,
title: 'Can Create Monitor Custom Field',
@@ -1673,29 +1862,6 @@ export class PermissionHelper {
isAccessControlPermission: false,
},
{
permission: Permission.CanCreateSmsLog,
title: 'Can Create SMS Log',
description: 'This permission can create SMS Log this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanDeleteSmsLog,
title: 'Can Delete SMS Log',
description:
'This permission can delete SMS Log of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanEditSmsLog,
title: 'Can Edit SMS Log',
description:
'This permission can edit SMS Log of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanReadSmsLog,
title: 'Can Read SMS Log',
@@ -1705,6 +1871,15 @@ export class PermissionHelper {
isAccessControlPermission: false,
},
{
permission: Permission.CanReadCallLog,
title: 'Can Read Call Log',
description:
'This permission can read Call Logs of this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanCreateMonitorProbe,
title: 'Can Create Monitor Probe',

View File

@@ -16,6 +16,22 @@ export default class Text {
return result;
}
public static generateRandomNumber(length?: number): string {
if (!length) {
length = 10;
}
let result: string = '';
const characters: string = '12134567890';
const charactersLength: number = characters.length;
for (let i: number = 0; i < length; i++) {
result += characters.charAt(
Math.floor(Math.random() * charactersLength)
);
}
return result;
}
public static convertNumberToWords(num: number): string {
const words: Array<string> = [
'first',

View File

@@ -18,6 +18,7 @@ export enum ComponentInputType {
Email = 'Email',
CronTab = 'CronTab',
Query = 'Database Query',
Select = 'Database Select',
BaseModel = 'Database Record',
BaseModelArray = 'Database Records',
JSONArray = 'List of JSON',

View File

@@ -33,7 +33,7 @@ export default class BaseModelComponent {
placeholder: 'Example: {"columnName": "value", ...}',
},
{
type: ComponentInputType.Query,
type: ComponentInputType.Select,
name: 'Select Fields',
description: `Select on ${model.singularName}`,
required: true,
@@ -92,7 +92,7 @@ export default class BaseModelComponent {
placeholder: 'Example: {"columnName": "value", ...}',
},
{
type: ComponentInputType.Query,
type: ComponentInputType.Select,
name: 'Select Fields',
description: `Select on ${model.singularName}`,
required: true,
@@ -288,7 +288,16 @@ export default class BaseModelComponent {
iconProp: IconProp.Bolt,
tableName: model.tableName!,
componentType: ComponentType.Trigger,
arguments: [],
arguments: [
{
type: ComponentInputType.Select,
name: 'Select Fields',
description: `Select on ${model.singularName}`,
required: true,
id: 'select',
placeholder: 'Example: {"columnName": true, ...}',
},
],
returnValues: [
{
id: 'data',
@@ -422,7 +431,16 @@ export default class BaseModelComponent {
iconProp: IconProp.Bolt,
tableName: model.tableName!,
componentType: ComponentType.Trigger,
arguments: [],
arguments: [
{
type: ComponentInputType.Select,
name: 'Select Fields',
description: `Select on ${model.singularName}`,
required: true,
id: 'select',
placeholder: 'Example: {"columnName": true, ...}',
},
],
returnValues: [
{
id: 'data',

View File

@@ -3,3 +3,4 @@ export const EVERY_DAY: string = '0 8 * * *';
export const EVERY_HOUR: string = '1 * * * *';
export const EVERY_FIVE_MINUTE: string = '*/5 * * * *';
export const EVERY_FIVE_SECONDS: string = '*/5 * * * * *';
export const EVERY_WEEK: string = '0 0 * * 0';

View File

@@ -609,6 +609,7 @@ export default class StatusPageAPI extends BaseAPI<
projectId: statusPage.projectId!,
},
select: {
createdAt: true,
note: true,
incidentId: true,
},
@@ -792,6 +793,7 @@ export default class StatusPageAPI extends BaseAPI<
projectId: statusPage.projectId!,
},
select: {
createdAt: true,
note: true,
scheduledMaintenanceId: true,
},
@@ -1340,6 +1342,7 @@ export default class StatusPageAPI extends BaseAPI<
projectId: statusPage.projectId!,
},
select: {
createdAt: true,
note: true,
scheduledMaintenanceId: true,
},
@@ -1696,6 +1699,7 @@ export default class StatusPageAPI extends BaseAPI<
projectId: statusPage.projectId!,
},
select: {
createdAt: true,
note: true,
incidentId: true,
},

View File

@@ -0,0 +1,122 @@
import UserCall from 'Model/Models/UserCall';
import UserCallService, {
Service as UserCallServiceType,
} from '../Services/UserCallService';
import BaseAPI from './BaseAPI';
import UserMiddleware from '../Middleware/UserAuthorization';
import {
ExpressRequest,
ExpressResponse,
OneUptimeRequest,
} from '../Utils/Express';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Response from '../Utils/Response';
import UserSMS from 'Model/Models/UserSMS';
export default class UserCallAPI extends BaseAPI<
UserCall,
UserCallServiceType
> {
public constructor() {
super(UserCall, UserCallService);
this.router.post(
`/user-call/verify`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
if (!req.body.itemId) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid item ID')
);
}
if (!req.body.code) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid code')
);
}
// Check if the code matches and verify the phone number.
const item: UserSMS | null = await this.service.findOneById({
id: req.body['itemId'],
props: {
isRoot: true,
},
select: {
userId: true,
verificationCode: true,
},
});
if (!item) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Item not found')
);
}
//cehck user id
if (
item.userId?.toString() !==
(
req as OneUptimeRequest
)?.userAuthorization?.userId?.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid user ID')
);
}
if (item.verificationCode !== req.body['code']) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid code')
);
}
await this.service.updateOneById({
id: item.id!,
props: {
isRoot: true,
},
data: {
isVerified: true,
},
});
return Response.sendEmptyResponse(req, res);
}
);
this.router.post(
`/user-call/resend-verification-code`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
if (!req.body.itemId) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid item ID')
);
}
await this.service.resendVerificationCode(req.body.itemId);
return Response.sendEmptyResponse(req, res);
}
);
}
}

View File

@@ -0,0 +1,123 @@
import UserEmail from 'Model/Models/UserEmail';
import UserEmailService, {
Service as UserEmailServiceType,
} from '../Services/UserEmailService';
import BaseAPI from './BaseAPI';
import {
ExpressRequest,
ExpressResponse,
OneUptimeRequest,
} from '../Utils/Express';
import UserMiddleware from '../Middleware/UserAuthorization';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Response from '../Utils/Response';
export default class UserEmailAPI extends BaseAPI<
UserEmail,
UserEmailServiceType
> {
public constructor() {
super(UserEmail, UserEmailService);
this.router.post(
`${new this.entityType().getCrudApiPath()?.toString()}/verify`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
if (!req.body.itemId) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid item ID')
);
}
if (!req.body.code) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid code')
);
}
// Check if the code matches and verify the email.
const item: UserEmail | null = await this.service.findOneById({
id: req.body['itemId'],
props: {
isRoot: true,
},
select: {
userId: true,
verificationCode: true,
},
});
if (!item) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Item not found')
);
}
//cehck user id
if (
item.userId?.toString() !==
(
req as OneUptimeRequest
)?.userAuthorization?.userId?.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid user ID')
);
}
if (item.verificationCode !== req.body['code']) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid code')
);
}
await this.service.updateOneById({
id: item.id!,
props: {
isRoot: true,
},
data: {
isVerified: true,
},
});
return Response.sendEmptyResponse(req, res);
}
);
this.router.post(
`${new this.entityType()
.getCrudApiPath()
?.toString()}/resend-verification-code`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
if (!req.body.itemId) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid item ID')
);
}
await this.service.resendVerificationCode(req.body.itemId);
return Response.sendEmptyResponse(req, res);
}
);
}
}

View File

@@ -0,0 +1,118 @@
import UserSMS from 'Model/Models/UserSMS';
import UserSMSService, {
Service as UserSMSServiceType,
} from '../Services/UserSmsService';
import BaseAPI from './BaseAPI';
import UserMiddleware from '../Middleware/UserAuthorization';
import {
ExpressRequest,
ExpressResponse,
OneUptimeRequest,
} from '../Utils/Express';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Response from '../Utils/Response';
export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
public constructor() {
super(UserSMS, UserSMSService);
this.router.post(
`/user-sms/verify`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
if (!req.body.itemId) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid item ID')
);
}
if (!req.body.code) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid code')
);
}
// Check if the code matches and verify the phone number.
const item: UserSMS | null = await this.service.findOneById({
id: req.body['itemId'],
props: {
isRoot: true,
},
select: {
userId: true,
verificationCode: true,
},
});
if (!item) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Item not found')
);
}
//cehck user id
if (
item.userId?.toString() !==
(
req as OneUptimeRequest
)?.userAuthorization?.userId?.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid user ID')
);
}
if (item.verificationCode !== req.body['code']) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid code')
);
}
await this.service.updateOneById({
id: item.id!,
props: {
isRoot: true,
},
data: {
isVerified: true,
},
});
return Response.sendEmptyResponse(req, res);
}
);
this.router.post(
`/user-sms/resend-verification-code`,
UserMiddleware.getUserMiddleware,
async (req: ExpressRequest, res: ExpressResponse) => {
req = req as OneUptimeRequest;
if (!req.body.itemId) {
return Response.sendErrorResponse(
req,
res,
new BadDataException('Invalid item ID')
);
}
await this.service.resendVerificationCode(req.body.itemId);
return Response.sendEmptyResponse(req, res);
}
);
}
}

View File

@@ -121,7 +121,8 @@ export default class UserMiddleware {
if (tenantId) {
oneuptimeRequest.tenantId = tenantId;
// check if the force sso for login is present and if it is, check if the sso token is present and if it is then allow, otherwise decline.
// update last active of project
await ProjectService.updateLastActive(tenantId);
}
if (ProjectMiddleware.hasApiKey(req)) {

View File

@@ -0,0 +1,12 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/CallLog';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
this.hardDeleteItemsOlderThanInDays('createdAt', 30);
}
}
export default new Service();

View File

@@ -0,0 +1,44 @@
import EmptyResponseData from 'Common/Types/API/EmptyResponse';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import { JSONObject } from 'Common/Types/JSON';
import API from 'Common/Utils/API';
import { NotificationHostname } from '../Config';
import Protocol from 'Common/Types/API/Protocol';
import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization';
import Phone from 'Common/Types/Phone';
import ObjectID from 'Common/Types/ObjectID';
import CallRequest from 'Common/Types/Call/CallRequest';
export default class CallService {
public static async makeCall(
to: Phone,
callRequest: CallRequest,
options: {
projectId?: ObjectID | undefined; // project id for sms log
from?: Phone; // from phone number
isSensitive?: boolean; // if true, message will not be logged
}
): Promise<HTTPResponse<EmptyResponseData>> {
const body: JSONObject = {
to: to.toString(),
callRequest: callRequest,
from: options.from?.toString(),
projectId: options.projectId?.toString(),
isSensitive: options.isSensitive,
};
return await API.post<EmptyResponseData>(
new URL(
Protocol.HTTP,
NotificationHostname,
new Route('/call/make-call')
),
body,
{
...ClusterKeyAuthorization.getClusterKeyHeaders(),
}
);
}
}

View File

@@ -1,5 +1,5 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/OnCallDuty';
import Model from 'Model/Models/DataMigration';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {

View File

@@ -47,7 +47,6 @@ import API from 'Common/Utils/API';
import Protocol from 'Common/Types/API/Protocol';
import Route from 'Common/Types/API/Route';
import URL from 'Common/Types/API/URL';
import JSONFunctions from 'Common/Types/JSONFunctions';
import ClusterKeyAuthorization from '../Middleware/ClusterKeyAuthorization';
import Text from 'Common/Types/Text';
@@ -152,7 +151,21 @@ class DatabaseService<TBaseModel extends BaseModel> {
return true;
}
protected checkRequiredFields(data: TBaseModel): void {
protected generateDefaultValues(data: TBaseModel): TBaseModel {
const tableColumns: Array<string> = data.getTableColumns().columns;
for (const column of tableColumns) {
const metadata: TableColumnMetadata =
data.getTableColumnMetadata(column);
if (metadata.forceGetDefaultValueOnCreate) {
(data as any)[column] = metadata.forceGetDefaultValueOnCreate();
}
}
return data;
}
protected checkRequiredFields(data: TBaseModel): TBaseModel {
// Check required fields.
const relatationalColumns: Dictionary<string> = {};
@@ -204,6 +217,8 @@ class DatabaseService<TBaseModel extends BaseModel> {
throw new BadDataException(`${requiredField} is required`);
}
}
return data;
}
protected async onBeforeCreate(
@@ -502,7 +517,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
}
public async onTrigger(
model: TBaseModel,
id: ObjectID,
projectId: ObjectID,
triggerType: DatabaseTriggerType
): Promise<void> {
@@ -517,7 +532,9 @@ class DatabaseService<TBaseModel extends BaseModel> {
)
),
{
data: JSONFunctions.toJSON(model, this.entityType),
data: {
_id: id.toString(),
},
},
{
...ClusterKeyAuthorization.getClusterKeyHeaders(),
@@ -545,7 +562,8 @@ class DatabaseService<TBaseModel extends BaseModel> {
data.setColumnValue(tenantColumnName, _createdBy.props.tenantId);
}
this.checkRequiredFields(data);
data = this.generateDefaultValues(data);
data = this.checkRequiredFields(data);
if (!this.isValid(data)) {
throw new BadDataException('Data is not valid');
@@ -601,7 +619,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
)))
) {
await this.onTrigger(
createBy.data,
createBy.data.id!,
createBy.props.tenantId ||
createBy.data.getValue<ObjectID>(
this.getModel().getTenantColumn()!
@@ -937,7 +955,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
deleteBy.props.tenantId
) {
await this.onTrigger(
item,
item.id!,
deleteBy.props.tenantId ||
item.getValue<ObjectID>(
this.getModel().getTenantColumn()!
@@ -975,15 +993,27 @@ class DatabaseService<TBaseModel extends BaseModel> {
withDeleted?: boolean | undefined
): Promise<Array<TBaseModel>> {
try {
let automaticallyAddedCreatedAtInSelect: boolean = false;
if (!findBy.sort || Object.keys(findBy.sort).length === 0) {
findBy.sort = {
createdAt: SortOrder.Descending,
};
if (!findBy.select) {
findBy.select = {} as any;
}
if (!(findBy.select as any)['createdAt']) {
(findBy.select as any)['createdAt'] = true;
automaticallyAddedCreatedAtInSelect = true;
}
}
const onFind: OnFind<TBaseModel> = findBy.props.ignoreHooks
? { findBy, carryForward: [] }
: await this.onBeforeFind(findBy);
const onBeforeFind: FindBy<TBaseModel> = onFind.findBy;
const onBeforeFind: FindBy<TBaseModel> = { ...onFind.findBy };
const carryForward: any = onFind.carryForward;
if (
@@ -997,10 +1027,6 @@ class DatabaseService<TBaseModel extends BaseModel> {
(onBeforeFind.select as any)['_id'] = true;
}
if (!(onBeforeFind.select as any)['createdAt']) {
(onBeforeFind.select as any)['createdAt'] = true;
}
const result: {
query: Query<TBaseModel>;
select: Select<TBaseModel> | null;
@@ -1043,6 +1069,13 @@ class DatabaseService<TBaseModel extends BaseModel> {
decryptedItems,
onBeforeFind
);
for (const item of decryptedItems) {
if (automaticallyAddedCreatedAtInSelect) {
delete (item as any).createdAt;
}
}
if (!findBy.props.ignoreHooks) {
decryptedItems = await (
await this.onFindSuccess(
@@ -1213,7 +1246,7 @@ class DatabaseService<TBaseModel extends BaseModel> {
))
) {
await this.onTrigger(
item,
item.id!,
updateBy.props.tenantId ||
item.getValue<ObjectID>(
this.getModel().getTenantColumn()!

View File

@@ -27,7 +27,7 @@ import StatusPageHeaderLinkService from './StatusPageHeaderLinkService';
import StatusPagePrivateUserService from './StatusPagePrivateUserService';
// On Call Duty
import OnCallDutyService from './OnCallDutyService';
import OnCallDutyService from './OnCallDutyPolicyService';
// Monitors
import MonitorService from './MonitorService';

View File

@@ -47,6 +47,22 @@ export class Service extends DatabaseService<Model> {
protected override async onBeforeCreate(
createBy: CreateBy<Model>
): Promise<OnCreate<Model>> {
if (!createBy.data.monitorType) {
throw new BadDataException(
'Monitor type required to create monitor.'
);
}
if (!Object.values(MonitorType).includes(createBy.data.monitorType)) {
throw new BadDataException(
`Invalid monitor type "${
createBy.data.monitorType
}". Valid monitor types are ${Object.values(MonitorType).join(
', '
)}.`
);
}
if (!createBy.props.tenantId) {
throw new BadDataException('ProjectId required to create monitor.');
}

View File

@@ -84,7 +84,7 @@ export default class NotificationService {
failedCallAndSMSBalanceChargeNotificationSentToOwners:
false, // reset this flag
lowCallAndSMSBalanceNotificationSentToOwners: false, // reset this flag
notEnabledSmsNotificationSentToOwners: false,
notEnabledSmsOrCallNotificationSentToOwners: false,
},
id: project.id!,
props: {

View File

@@ -0,0 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/OnCallDutyPolicyCustomField';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -0,0 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/OnCallDutyPolicyEscalationRule';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -0,0 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/OnCallDutyPolicyEscalationRuleTeam';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -0,0 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/OnCallDutyPolicyEscalationRuleUser';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -0,0 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/OnCallDutyPolicyExecutionLog';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -0,0 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/OnCallDutyPolicyExecutionLogTimeline';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -0,0 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/OnCallDutyPolicy';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -47,6 +47,8 @@ import MailService from './MailService';
import logger from '../Utils/Logger';
import Email from 'Common/Types/Email';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import UserService from './UserService';
import UserNotificationRuleService from './UserNotificationRuleService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -75,6 +77,10 @@ export class Service extends DatabaseService<Model> {
) {
throw new BadDataException('Plan is invalid.');
}
data.data.planName = SubscriptionPlan.getPlanSelect(
data.data.paymentProviderPlanId
);
}
// check if the user has the project with the same name. If yes, reject.
@@ -117,6 +123,28 @@ export class Service extends DatabaseService<Model> {
);
}
const user: User | null = await UserService.findOneById({
id: data.props.userId,
select: {
name: true,
email: true,
companyPhoneNumber: true,
companyName: true,
},
props: {
isRoot: true,
},
});
if (!user) {
throw new BadDataException('User not found.');
}
data.data.createdOwnerName = user.name!;
data.data.createdOwnerEmail = user.email!;
data.data.createdOwnerPhone = user.companyPhoneNumber!;
data.data.createdOwnerCompanyName = user.companyName!;
return Promise.resolve({ createBy: data, carryForward: null });
}
@@ -198,6 +226,9 @@ export class Service extends DatabaseService<Model> {
data: {
paymentProviderSubscriptionId: subscription.id,
trialEndsAt: subscription.trialEndsAt || new Date(),
planName: SubscriptionPlan.getPlanSelect(
updateBy.data.paymentProviderPlanId! as string
),
},
props: {
isRoot: true,
@@ -318,13 +349,13 @@ export class Service extends DatabaseService<Model> {
});
}
createdItem = await this.addDefaultIncidentSeverity(createdItem);
createdItem = await this.addDefaultProjectTeams(createdItem);
createdItem = await this.addDefaultMonitorStatus(createdItem);
createdItem = await this.addDefaultIncidentState(createdItem);
createdItem = await this.addDefaultScheduledMaintenanceState(
createdItem
);
createdItem = await this.addDefaultIncidentSeverity(createdItem);
return createdItem;
}
@@ -597,9 +628,40 @@ export class Service extends DatabaseService<Model> {
createdItem.createdByUserId!
);
const user: User | null = await UserService.findOneById({
id: createdItem.createdByUserId!,
props: {
isRoot: true,
},
select: {
isEmailVerified: true,
email: true,
},
});
if (user && user.isEmailVerified) {
await UserNotificationRuleService.addDefaultNotifictionRuleForUser(
createdItem.id!,
user.id!,
user.email!
);
}
return createdItem;
}
public async updateLastActive(projectId: ObjectID): Promise<void> {
await this.updateOneById({
id: projectId,
data: {
lastActive: OneUptimeDate.getCurrentDate(),
},
props: {
isRoot: true,
},
});
}
public async getOwners(projectId: ObjectID): Promise<Array<User>> {
if (!projectId) {
throw new BadDataException('Project ID is required');

View File

@@ -17,6 +17,7 @@ export default class SmsService {
options: {
projectId?: ObjectID | undefined; // project id for sms log
from?: Phone; // from phone number
isSensitive?: boolean; // if true, message will not be logged
}
): Promise<HTTPResponse<EmptyResponseData>> {
const body: JSONObject = {
@@ -24,6 +25,7 @@ export default class SmsService {
message,
from: options.from?.toString(),
projectId: options.projectId?.toString(),
isSensitive: options.isSensitive,
};
return await API.post<EmptyResponseData>(

View File

@@ -31,6 +31,7 @@ import logger from '../Utils/Logger';
import BadDataException from 'Common/Types/Exception/BadDataException';
import PositiveNumber from 'Common/Types/PositiveNumber';
import TeamMember from 'Model/Models/TeamMember';
import UserNotificationRuleService from './UserNotificationRuleService';
export class TeamMemberService extends DatabaseService<TeamMember> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -40,6 +41,32 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
protected override async onBeforeCreate(
createBy: CreateBy<TeamMember>
): Promise<OnCreate<TeamMember>> {
// check if this project can have more members.
if (IsBillingEnabled && createBy.data.projectId) {
const project: Project | null = await ProjectService.findOneById({
id: createBy.data.projectId!,
select: {
seatLimit: true,
paymentProviderSubscriptionSeats: true,
},
props: {
isRoot: true,
},
});
if (
project &&
project.seatLimit &&
project.paymentProviderSubscriptionSeats &&
project.paymentProviderSubscriptionSeats >= project.seatLimit
) {
throw new BadDataException(
'You have reached the user limit. You cannot invite any more users to this project. Please contact billing@oneuptime.com to increase your user limit.'
);
}
}
createBy.data.hasAcceptedInvitation = false;
if (createBy.miscDataProps && createBy.miscDataProps['email']) {
@@ -153,6 +180,10 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
},
select: {
userId: true,
user: {
email: true,
isEmailVerified: true,
},
projectId: true,
},
limit: LIMIT_MAX,
@@ -165,6 +196,17 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
for (const item of items) {
await this.refreshTokens(item.userId!, item.projectId!);
if (
updateBy.data.hasAcceptedInvitation &&
item.user?.isEmailVerified
) {
await UserNotificationRuleService.addDefaultNotifictionRuleForUser(
item.projectId!,
item.userId!,
item.user?.email!
);
}
}
return { updateBy, carryForward: onUpdate.carryForward };

View File

@@ -0,0 +1,175 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/UserCall';
import DatabaseService, { OnCreate, OnDelete } from './DatabaseService';
import CreateBy from '../Types/Database/CreateBy';
import ProjectService from './ProjectService';
import Project from 'Model/Models/Project';
import BadDataException from 'Common/Types/Exception/BadDataException';
import CallService from './CallService';
import logger from '../Utils/Logger';
import ObjectID from 'Common/Types/ObjectID';
import Text from 'Common/Types/Text';
import CallRequest, { CallAction } from 'Common/Types/Call/CallRequest';
import DeleteBy from '../Types/Database/DeleteBy';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import UserNotificationRuleService from './UserNotificationRuleService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
const itemsToDelete: Array<Model> = await this.findBy({
query: deleteBy.query,
select: {
_id: true,
projectId: true,
},
skip: 0,
limit: LIMIT_MAX,
props: {
isRoot: true,
},
});
for (const item of itemsToDelete) {
await UserNotificationRuleService.deleteBy({
query: {
userCallId: item.id!,
projectId: item.projectId!,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
}
return {
deleteBy,
carryForward: null,
};
}
protected override async onBeforeCreate(
createBy: CreateBy<Model>
): Promise<OnCreate<Model>> {
if (!createBy.props.isRoot && createBy.data.isVerified) {
throw new BadDataException('isVerified cannot be set to true');
}
// check if this project has SMS and Call mEnabled.
const project: Project | null = await ProjectService.findOneById({
id: createBy.data.projectId!,
props: {
isRoot: true,
},
select: {
enableCallNotifications: true,
smsOrCallCurrentBalanceInUSDCents: true,
},
});
if (!project) {
throw new BadDataException('Project not found');
}
if (!project.enableCallNotifications) {
throw new BadDataException(
'Call notifications are disabled for this project. Please enable them in Project Settings > Notification Settings.'
);
}
if (project?.smsOrCallCurrentBalanceInUSDCents! <= 100) {
throw new BadDataException(
'Your SMS balance is low. Please recharge your SMS balance in Project Settings > Notification Settings.'
);
}
return { carryForward: null, createBy };
}
protected override async onCreateSuccess(
_onCreate: OnCreate<Model>,
createdItem: Model
): Promise<Model> {
if (!createdItem.isVerified) {
this.sendVerificationCode(createdItem);
}
return createdItem;
}
public async resendVerificationCode(itemId: ObjectID): Promise<void> {
const item: Model | null = await this.findOneById({
id: itemId,
props: {
isRoot: true,
},
select: {
phone: true,
verificationCode: true,
isVerified: true,
},
});
if (!item) {
throw new BadDataException(
'Item with ID ' + itemId.toString() + ' not found'
);
}
if (item.isVerified) {
throw new BadDataException('Phone Number already verified');
}
// generate new verification code
item.verificationCode = Text.generateRandomNumber(6);
await this.updateOneById({
id: item.id!,
props: {
isRoot: true,
},
data: {
verificationCode: item.verificationCode,
},
});
this.sendVerificationCode(item);
}
public sendVerificationCode(item: Model): void {
const callRequest: CallRequest = {
data: [
{
sayMessage:
'Your verification code is ' +
item.verificationCode?.split('').join(' '), // add space to make it more clear
},
{
sayMessage:
'Your verification code is ' +
item.verificationCode?.split('').join(' '), // add space to make it more clear
},
{
sayMessage: 'Thank you for using OneUptime. Goodbye.',
},
CallAction.Hangup,
],
};
// send verifiction sms.
CallService.makeCall(item.phone!, callRequest, {
projectId: item.projectId,
isSensitive: true,
}).catch((err: Error) => {
logger.error(err);
});
}
}
export default new Service();

View File

@@ -0,0 +1,134 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/UserEmail';
import DatabaseService, { OnCreate, OnDelete } from './DatabaseService';
import MailService from './MailService';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import logger from '../Utils/Logger';
import ObjectID from 'Common/Types/ObjectID';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Text from 'Common/Types/Text';
import DeleteBy from '../Types/Database/DeleteBy';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import UserNotificationRuleService from './UserNotificationRuleService';
import CreateBy from '../Types/Database/CreateBy';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
const itemsToDelete: Array<Model> = await this.findBy({
query: deleteBy.query,
select: {
_id: true,
projectId: true,
},
skip: 0,
limit: LIMIT_MAX,
props: {
isRoot: true,
},
});
for (const item of itemsToDelete) {
await UserNotificationRuleService.deleteBy({
query: {
userEmailId: item.id!,
projectId: item.projectId!,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
}
return {
deleteBy,
carryForward: null,
};
}
protected override async onBeforeCreate(
createBy: CreateBy<Model>
): Promise<OnCreate<Model>> {
if (!createBy.props.isRoot && createBy.data.isVerified) {
throw new BadDataException('isVerified cannot be set to true');
}
return {
createBy,
carryForward: null,
};
}
protected override async onCreateSuccess(
_onCreate: OnCreate<Model>,
createdItem: Model
): Promise<Model> {
if (!createdItem.isVerified) {
// send verification code
this.sendVerificationCode(createdItem);
}
return createdItem;
}
public async resendVerificationCode(itemId: ObjectID): Promise<void> {
const item: Model | null = await this.findOneById({
id: itemId,
props: {
isRoot: true,
},
select: {
email: true,
verificationCode: true,
isVerified: true,
},
});
if (!item) {
throw new BadDataException(
'Item with ID ' + itemId.toString() + ' not found'
);
}
if (item.isVerified) {
throw new BadDataException('Email already verified');
}
// generate new verification code
item.verificationCode = Text.generateRandomNumber(6);
await this.updateOneById({
id: item.id!,
props: {
isRoot: true,
},
data: {
verificationCode: item.verificationCode,
},
});
this.sendVerificationCode(item);
}
public sendVerificationCode(item: Model): void {
MailService.sendMail({
toEmail: item.email!,
templateType: EmailTemplateType.VerificationCode,
vars: {
code: item.verificationCode!,
subject: 'Verify this email address',
},
subject: 'Verify this email address',
}).catch((err: Error) => {
logger.error(err);
});
}
}
export default new Service();

View File

@@ -0,0 +1,189 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/UserNotificationRule';
import DatabaseService, { OnCreate } from './DatabaseService';
import CreateBy from '../Types/Database/CreateBy';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ObjectID from 'Common/Types/ObjectID';
import Email from 'Common/Types/Email';
import IncidentSeverityService from './IncidentSeverityService';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import IncidentSeverity from 'Model/Models/IncidentSeverity';
import UserEmailService from './UserEmailService';
import UserEmail from 'Model/Models/UserEmail';
import NotificationRuleType from 'Common/Types/NotificationRule/NotificationRuleType';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
protected override async onBeforeCreate(
createBy: CreateBy<Model>
): Promise<OnCreate<Model>> {
if (
!createBy.data.userCallId &&
!createBy.data.userCall &&
!createBy.data.userEmail &&
!createBy.data.userSms &&
!createBy.data.userSmsId &&
!createBy.data.userEmailId
) {
throw new BadDataException('Call, SMS, or Email is required');
}
return {
createBy,
carryForward: null,
};
}
public async addDefaultNotifictionRuleForUser(
projectId: ObjectID,
userId: ObjectID,
email: Email
): Promise<void> {
const incidentSeverities: Array<IncidentSeverity> =
await IncidentSeverityService.findBy({
query: {
projectId,
},
props: {
isRoot: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
select: {
_id: true,
},
});
//check userEmail
let userEmail: UserEmail | null = await UserEmailService.findOneBy({
query: {
projectId,
userId,
email,
},
props: {
isRoot: true,
},
});
if (!userEmail) {
userEmail = new UserEmail();
userEmail.projectId = projectId;
userEmail.userId = userId;
userEmail.email = email;
userEmail.isVerified = true;
userEmail = await UserEmailService.create({
data: userEmail,
props: {
isRoot: true,
},
});
}
// create for incident severities.
for (const incidentSeverity of incidentSeverities) {
//check if this rule already exists.
const existingRule: Model | null = await this.findOneBy({
query: {
projectId,
userId,
userEmailId: userEmail.id!,
incidentSeverityId: incidentSeverity.id!,
ruleType: NotificationRuleType.ON_CALL_INCIDENT_CREATED,
},
props: {
isRoot: true,
},
});
if (existingRule) {
continue; // skip this rule.
}
const notificationRule: Model = new Model();
notificationRule.projectId = projectId;
notificationRule.userId = userId;
notificationRule.userEmailId = userEmail.id!;
notificationRule.incidentSeverityId = incidentSeverity.id!;
notificationRule.notifyAfterMinutes = 0;
notificationRule.ruleType =
NotificationRuleType.ON_CALL_INCIDENT_CREATED;
await this.create({
data: notificationRule,
props: {
isRoot: true,
},
});
}
//check if this rule already exists.
const existingRuleOnCall: Model | null = await this.findOneBy({
query: {
projectId,
userId,
userEmailId: userEmail.id!,
ruleType: NotificationRuleType.WHEN_USER_GOES_ON_CALL,
},
props: {
isRoot: true,
},
});
if (!existingRuleOnCall) {
// on and off call.
const onCallRule: Model = new Model();
onCallRule.projectId = projectId;
onCallRule.userId = userId;
onCallRule.userEmailId = userEmail.id!;
onCallRule.notifyAfterMinutes = 0;
onCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_ON_CALL;
await this.create({
data: onCallRule,
props: {
isRoot: true,
},
});
}
//check if this rule already exists.
const existingRuleOffCall: Model | null = await this.findOneBy({
query: {
projectId,
userId,
userEmailId: userEmail.id!,
ruleType: NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
},
props: {
isRoot: true,
},
});
if (!existingRuleOffCall) {
// on and off call.
const offCallRule: Model = new Model();
offCallRule.projectId = projectId;
offCallRule.userId = userId;
offCallRule.userEmailId = userEmail.id!;
offCallRule.notifyAfterMinutes = 0;
offCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_OFF_CALL;
await this.create({
data: offCallRule,
props: {
isRoot: true,
},
});
}
}
}
export default new Service();

View File

@@ -15,6 +15,9 @@ import EmailVerificationToken from 'Model/Models/EmailVerificationToken';
import OneUptimeDate from 'Common/Types/Date';
import EmailVerificationTokenService from './EmailVerificationTokenService';
import Route from 'Common/Types/API/Route';
import TeamMember from 'Model/Models/TeamMember';
import TeamMemberService from './TeamMemberService';
import UserNotificationRuleService from './UserNotificationRuleService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -78,6 +81,51 @@ export class Service extends DatabaseService<Model> {
}
}
if (onUpdate && onUpdate.updateBy.data.isEmailVerified) {
// if the email is verified then create default policies for this user.
const newUsers: Array<Model> = await this.findBy({
query: onUpdate.updateBy.query,
select: {
_id: true,
email: true,
},
props: {
isRoot: true,
},
limit: LIMIT_MAX,
skip: 0,
});
for (const user of newUsers) {
// emai is verified. create default policies for this user.
const teamMembers: Array<TeamMember> =
await TeamMemberService.findBy({
query: {
userId: user.id!,
hasAcceptedInvitation: true,
},
select: {
projectId: true,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
for (const member of teamMembers) {
// create default policies for this user.
await UserNotificationRuleService.addDefaultNotifictionRuleForUser(
member.projectId!,
user.id!,
user.email!
);
}
}
}
if (onUpdate && onUpdate.updateBy.data.email) {
const newUsers: Array<Model> = await this.findBy({
query: onUpdate.updateBy.query,

View File

@@ -0,0 +1,160 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/UserSMS';
import DatabaseService, { OnCreate, OnDelete } from './DatabaseService';
import CreateBy from '../Types/Database/CreateBy';
import ProjectService from './ProjectService';
import Project from 'Model/Models/Project';
import BadDataException from 'Common/Types/Exception/BadDataException';
import SmsService from './SmsService';
import logger from '../Utils/Logger';
import ObjectID from 'Common/Types/ObjectID';
import Text from 'Common/Types/Text';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import UserNotificationRuleService from './UserNotificationRuleService';
import DeleteBy from '../Types/Database/DeleteBy';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
const itemsToDelete: Array<Model> = await this.findBy({
query: deleteBy.query,
select: {
_id: true,
projectId: true,
},
skip: 0,
limit: LIMIT_MAX,
props: {
isRoot: true,
},
});
for (const item of itemsToDelete) {
await UserNotificationRuleService.deleteBy({
query: {
userSmsId: item.id!,
projectId: item.projectId!,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
});
}
return {
deleteBy,
carryForward: null,
};
}
protected override async onBeforeCreate(
createBy: CreateBy<Model>
): Promise<OnCreate<Model>> {
// check if this project has SMS and Call mEnabled.
if (!createBy.props.isRoot && createBy.data.isVerified) {
throw new BadDataException('isVerified cannot be set to true');
}
const project: Project | null = await ProjectService.findOneById({
id: createBy.data.projectId!,
props: {
isRoot: true,
},
select: {
enableSmsNotifications: true,
smsOrCallCurrentBalanceInUSDCents: true,
},
});
if (!project) {
throw new BadDataException('Project not found');
}
if (!project.enableSmsNotifications) {
throw new BadDataException(
'SMS notifications are disabled for this project. Please enable them in Project Settings > Notification Settings.'
);
}
if (project?.smsOrCallCurrentBalanceInUSDCents! <= 100) {
throw new BadDataException(
'Your SMS balance is low. Please recharge your SMS balance in Project Settings > Notification Settings.'
);
}
return { carryForward: null, createBy };
}
protected override async onCreateSuccess(
_onCreate: OnCreate<Model>,
createdItem: Model
): Promise<Model> {
if (!createdItem.isVerified) {
this.sendVerificationCode(createdItem);
}
return createdItem;
}
public async resendVerificationCode(itemId: ObjectID): Promise<void> {
const item: Model | null = await this.findOneById({
id: itemId,
props: {
isRoot: true,
},
select: {
phone: true,
verificationCode: true,
isVerified: true,
},
});
if (!item) {
throw new BadDataException(
'Item with ID ' + itemId.toString() + ' not found'
);
}
if (item.isVerified) {
throw new BadDataException('Phone Number already verified');
}
// generate new verification code
item.verificationCode = Text.generateRandomNumber(6);
await this.updateOneById({
id: item.id!,
props: {
isRoot: true,
},
data: {
verificationCode: item.verificationCode,
},
});
this.sendVerificationCode(item);
}
public sendVerificationCode(item: Model): void {
// send verifiction sms.
SmsService.sendSms(
item.phone!,
'Your verification code is ' + item.verificationCode,
{
projectId: item.projectId,
isSensitive: true,
}
).catch((err: Error) => {
logger.error(err);
});
}
}
export default new Service();

View File

@@ -223,6 +223,7 @@ describe('probeService', () => {
select: {
_id: true,
name: true,
createdAt: true,
},
props: { isRoot: true },
});

View File

@@ -1,7 +1,7 @@
import BaseModel from 'Common/Models/BaseModel';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ObjectID from 'Common/Types/ObjectID';
import ComponentMetadata from 'Common/Types/Workflow/Component';
import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component';
import DatabaseService from '../../../../Services/DatabaseService';
import { ExpressRequest, ExpressResponse } from '../../../../Utils/Express';
import Response from '../../../../Utils/Response';
@@ -12,6 +12,10 @@ import WorkflowService from '../../../../Services/WorkflowService';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import Workflow from 'Model/Models/Workflow';
import ClusterKeyAuthorization from '../../../../Middleware/ClusterKeyAuthorization';
import { JSONObject } from 'Common/Types/JSON';
import { RunOptions, RunReturnType } from '../../ComponentCode';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Select from '../../../Database/Select';
export default class OnTriggerBaseModel<
TBaseModel extends BaseModel
@@ -19,12 +23,14 @@ export default class OnTriggerBaseModel<
public modelId: string = '';
public type: string = '';
public service: DatabaseService<TBaseModel> | null = null;
public constructor(
modelService: DatabaseService<TBaseModel>,
type: string
) {
super();
this.service = modelService;
this.modelId = `${Text.pascalCaseToDashes(
modelService.getModel().tableName!
)}`;
@@ -66,6 +72,80 @@ export default class OnTriggerBaseModel<
);
}
public override async run(
args: JSONObject,
options: RunOptions
): Promise<RunReturnType> {
const data: JSONObject = args['data'] as JSONObject;
const successPort: Port | undefined = this.getMetadata().outPorts.find(
(p: Port) => {
return p.id === 'success';
}
);
if (!successPort) {
throw options.onError(
new BadDataException('Success port not found')
);
}
if (
!data['_id'] ||
!args['select'] ||
Object.keys(args['select']).length === 0
) {
return {
returnValues: {
model: data
? JSONFunctions.toJSON(
data as any,
this.service!.entityType
)
: null,
},
executePort: successPort,
};
}
let select: Select<TBaseModel> = args['select'] as Select<TBaseModel>;
if (select) {
select = JSONFunctions.deserialize(
args['select'] as JSONObject
) as Select<TBaseModel>;
}
const model: TBaseModel | null = await this.service!.findOneById({
id: new ObjectID(args['_id'] as string),
props: {
isRoot: true,
},
select: {
_id: true,
...select,
},
});
if (!model) {
throw new BadDataException(
('Model not found with id ' + args['_id']) as string
);
}
return {
returnValues: {
model: data
? JSONFunctions.toJSON(
model as any,
this.service!.entityType
)
: null,
},
executePort: successPort,
};
}
public async initTrigger(
req: ExpressRequest,
res: ExpressResponse,
@@ -95,6 +175,8 @@ export default class OnTriggerBaseModel<
for (const workflow of workflows) {
/// Run Graph.
/// Find the object and send data.
const executeWorkflow: ExecuteWorkflowType = {
workflowId: workflow.id!,
returnValues: {

View File

@@ -4,7 +4,9 @@
import { JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import { ExpressRouter } from '../../Utils/Express';
import ComponentCode from './ComponentCode';
import ComponentCode, { RunOptions, RunReturnType } from './ComponentCode';
import { Port } from 'Common/Types/Workflow/Component';
import BadDataException from 'Common/Types/Exception/BadDataException';
export interface ExecuteWorkflowType {
workflowId: ObjectID;
@@ -44,6 +46,30 @@ export default class TrigegrCode extends ComponentCode {
super();
}
public override async run(
args: JSONObject,
options: RunOptions
): Promise<RunReturnType> {
const successPort: Port | undefined = this.getMetadata().outPorts.find(
(p: Port) => {
return p.id === 'success';
}
);
if (!successPort) {
throw options.onError(
new BadDataException('Success port not found')
);
}
return {
returnValues: {
...args,
},
executePort: successPort,
};
}
public async setupComponent(props: InitProps): Promise<void> {
this.executeWorkflow = props.executeWorkflow;
this.scheduleWorkflow = props.scheduleWorkflow;

View File

@@ -13,7 +13,10 @@ import BadDataException from 'Common/Types/Exception/BadDataException';
import QueryHelper from '../Types/Database/QueryHelper';
import Columns from 'Common/Types/Database/Columns';
import Dictionary from 'Common/Types/Dictionary';
import { ColumnAccessControl } from 'Common/Types/Database/AccessControl/AccessControl';
import {
ColumnAccessControl,
ColumnBillingAccessControl,
} from 'Common/Types/Database/AccessControl/AccessControl';
import RelationSelect from '../Types/Database/RelationSelect';
import Typeof from 'Common/Types/Typeof';
import { TableColumnMetadata } from 'Common/Types/Database/TableColumn';
@@ -174,10 +177,83 @@ export default class ModelPermission {
!permissionColumns.columns.includes(key) &&
tableColumns.includes(key)
) {
if (
requestType === DatabaseRequestType.Create &&
tableColumnMetadata.forceGetDefaultValueOnCreate
) {
continue; // this is a special case where we want to force the default value on create.
}
throw new BadDataException(
`User is not allowed to ${requestType} on ${key} column of ${model.singularName}`
);
}
if (
IsBillingEnabled &&
props.currentPlan &&
model.getColumnBillingAccessControl(key)
) {
const billingAccessControl: ColumnBillingAccessControl =
model.getColumnBillingAccessControl(key);
if (
requestType === DatabaseRequestType.Create &&
billingAccessControl.create
) {
if (
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
billingAccessControl.create,
props.currentPlan,
getAllEnvVars()
)
) {
throw new PaymentRequiredException(
'Please upgrade your plan to ' +
billingAccessControl.create +
' to access this feature'
);
}
}
if (
requestType === DatabaseRequestType.Read &&
billingAccessControl.read
) {
if (
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
billingAccessControl.read,
props.currentPlan,
getAllEnvVars()
)
) {
throw new PaymentRequiredException(
'Please upgrade your plan to ' +
billingAccessControl.read +
' to access this feature'
);
}
}
if (
requestType === DatabaseRequestType.Update &&
billingAccessControl.update
) {
if (
!SubscriptionPlan.isFeatureAccessibleOnCurrentPlan(
billingAccessControl.update,
props.currentPlan,
getAllEnvVars()
)
) {
throw new PaymentRequiredException(
'Please upgrade your plan to ' +
billingAccessControl.update +
' to access this feature'
);
}
}
}
}
}

View File

@@ -82,7 +82,10 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
modelIdToEdit?: ObjectID | undefined;
onError?: ((error: string) => void) | undefined;
onBeforeCreate?:
| ((item: TBaseModel | BaseModel) => Promise<TBaseModel | BaseModel>)
| ((
item: TBaseModel | BaseModel,
miscDataProps: JSONObject
) => Promise<TBaseModel | BaseModel>)
| undefined;
saveRequestOptions?: RequestOptions | undefined;
doNotFetchExistingModel?: boolean | undefined;
@@ -453,7 +456,10 @@ const ModelForm: Function = <TBaseModel extends BaseModel>(
) as BaseModel;
if (props.onBeforeCreate && props.formType === FormType.Create) {
tBaseModel = await props.onBeforeCreate(tBaseModel);
tBaseModel = await props.onBeforeCreate(
tBaseModel,
miscDataProps
);
}
result = await ModelAPI.createOrUpdate<TBaseModel>(

View File

@@ -421,6 +421,54 @@ const Icon: FunctionComponent<ComponentProps> = ({
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
/>
);
} else if (icon === IconProp.BarsArrowDown) {
return getSvgWrapper(
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25"
/>
);
} else if (icon === IconProp.Bell) {
return getSvgWrapper(
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"
/>
);
} else if (icon === IconProp.AdjustmentHorizontal) {
return getSvgWrapper(
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75"
/>
);
} else if (icon === IconProp.AdjustmentVertical) {
return getSvgWrapper(
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 13.5V3.75m0 9.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 3.75V16.5m12-3V3.75m0 9.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 3.75V16.5m-6-9V3.75m0 3.75a1.5 1.5 0 010 3m0-3a1.5 1.5 0 000 3m0 9.75V10.5"
/>
);
} else if (icon === IconProp.BellRinging) {
return getSvgWrapper(
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5"
/>
);
} else if (icon === IconProp.BarsArrowUp) {
return getSvgWrapper(
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25"
/>
);
} else if (icon === IconProp.CheckCircle) {
return getSvgWrapper(
<path

View File

@@ -24,6 +24,7 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
cardProps: CardProps;
modelDetailProps: ModeDetailProps<TBaseModel>;
isEditable?: undefined | boolean;
onSaveSuccess?: undefined | ((item: TBaseModel) => void);
editButtonText?: undefined | string;
formSteps?: undefined | Array<FormStep<TBaseModel>>;
formFields?: undefined | Fields<TBaseModel>;
@@ -100,9 +101,12 @@ const CardModelDetail: Function = <TBaseModel extends BaseModel>(
setShowModal(false);
}}
submitButtonText={`Save Changes`}
onSuccess={(_item: TBaseModel) => {
onSuccess={(item: TBaseModel) => {
setShowModal(false);
setRefresher(!refresher);
if (props.onSaveSuccess) {
props.onSaveSuccess(item);
}
}}
name={props.name}
modelType={props.modelDetailProps.modelType}

View File

@@ -6,7 +6,7 @@ import ModelForm, {
} from '../Forms/ModelForm';
import BaseModel from 'Common/Models/BaseModel';
import ButtonType from '../Button/ButtonTypes';
import { JSONObjectOrArray } from 'Common/Types/JSON';
import { JSONObject, JSONObjectOrArray } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import Alert, { AlertType } from '../Alerts/Alert';
import FormValues from '../Forms/Types/FormValues';
@@ -26,7 +26,9 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
submitButtonStyleType?: undefined | ButtonStyleType;
formProps: ModelFormComponentProps<TBaseModel>;
modelIdToEdit?: ObjectID | undefined;
onBeforeCreate?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined;
onBeforeCreate?:
| ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>)
| undefined;
footer?: ReactElement | undefined;
}

View File

@@ -98,7 +98,9 @@ export interface ComponentProps<TBaseModel extends BaseModel> {
query?: Query<TBaseModel>;
onBeforeFetch?: (() => Promise<JSONObject>) | undefined;
createInitialValues?: FormValues<TBaseModel> | undefined;
onBeforeCreate?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined;
onBeforeCreate?:
| ((item: TBaseModel, miscDataProps: JSONObject) => Promise<TBaseModel>)
| undefined;
onCreateSuccess?: ((item: TBaseModel) => Promise<TBaseModel>) | undefined;
createVerb?: string;
showTableAs?: ShowTableAs | undefined;
@@ -568,7 +570,14 @@ const ModelTable: Function = <TBaseModel extends BaseModel>(
}
for (const moreField of selectMoreFields) {
if (model.getTableColumnMetadata(moreField)) {
if (
model.getTableColumnMetadata(moreField) &&
model.isEntityColumn(moreField)
) {
(selectFields as Dictionary<boolean>)[moreField] = (
props.selectMoreFields as any
)[moreField];
} else if (model.getTableColumnMetadata(moreField)) {
(selectFields as Dictionary<boolean>)[moreField] = true;
} else {
throw new BadDataException(
@@ -1261,7 +1270,7 @@ const ModelTable: Function = <TBaseModel extends BaseModel>(
return (
<>
{getCardComponent()}
<div className="mt-5 mb-5">{getCardComponent()}</div>
{showModel ? (
<ModelFormModal<TBaseModel>
@@ -1305,7 +1314,10 @@ const ModelTable: Function = <TBaseModel extends BaseModel>(
await props.onCreateSuccess(item);
}
}}
onBeforeCreate={async (item: TBaseModel) => {
onBeforeCreate={async (
item: TBaseModel,
miscDataProps: JSONObject
) => {
if (
showTableAs === ShowTableAs.OrderedStatesList &&
props.orderedStatesListProps?.orderField &&
@@ -1318,7 +1330,10 @@ const ModelTable: Function = <TBaseModel extends BaseModel>(
}
if (props.onBeforeCreate) {
item = await props.onBeforeCreate(item);
item = await props.onBeforeCreate(
item,
miscDataProps
);
}
return item;

View File

@@ -73,7 +73,11 @@ const Page: FunctionComponent<ComponentProps> = (
{props.children}
</div>
)}
{props.isLoading && <PageLoader isVisible={true} />}
{props.isLoading && (
<div className="col-span-10">
<PageLoader isVisible={true} />
</div>
)}
</div>
</main>
)}

View File

@@ -74,6 +74,8 @@ const RunModal: FunctionComponent<ComponentProps> = (
ComponentInputType.BaseModelArray ||
args.type ===
ComponentInputType.Query ||
args.type ===
ComponentInputType.Select ||
args.type ===
ComponentInputType.StringDictionary) &&
component.returnValues[args.id] &&

View File

@@ -80,6 +80,12 @@ export const componentInputTypeToFormFieldType: Function = (
};
}
if (componentInputType === ComponentInputType.Select) {
return {
fieldType: FormFieldSchemaType.JSON,
};
}
if (componentInputType === ComponentInputType.StringDictionary) {
return {
fieldType: FormFieldSchemaType.JSON,

View File

@@ -95,11 +95,13 @@ import SettingsIncidentSeverity from './Pages/Settings/IncidentSeverity';
import SettingsBilling from './Pages/Settings/Billing';
import SettingsSSO from './Pages/Settings/SSO';
import SettingsSmsLog from './Pages/Settings/SmsLog';
import SettingsCallLog from './Pages/Settings/CallLog';
import SettingsCallSms from './Pages/Settings/CallSms';
import SettingsInvoices from './Pages/Settings/Invoices';
import MonitorCustomFields from './Pages/Settings/MonitorCustomFields';
import StatusPageCustomFields from './Pages/Settings/StatusPageCustomFields';
import IncidentCustomFields from './Pages/Settings/IncidentCustomFields';
import OnCallDutyPolicyCustomFields from './Pages/Settings/OnCallDutyPolicyCustomFields';
import ScheduledMaintenanceCustomFields from './Pages/Settings/ScheduledMaintenanceCusomFields';
import ActiveIncidents from './Pages/Global/ActiveIncidents';
@@ -109,8 +111,15 @@ import ProjectInvitations from './Pages/Global/ProjectInvitations';
import UserProfileOverview from './Pages/Global/UserProfile/Index';
import UserProfilePicture from './Pages/Global/UserProfile/Picture';
import UserProfilePassword from './Pages/Global/UserProfile/Password';
// On Call Duty
import OnCallDutyPage from './Pages/OnCallDuty/OnCallDuties';
import OnCallDutyPoliciesPage from './Pages/OnCallDuty/OnCallDutyPolicies';
import OnCallDutyPolicyView from './Pages/OnCallDuty/OnCallDutyPolicy/Index';
import OnCallDutyPolicyViewDelete from './Pages/OnCallDuty/OnCallDutyPolicy/Delete';
import OnCallDutyPolicyViewLogs from './Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogs';
import OnCallDutyPolicyViewLogsView from './Pages/OnCallDuty/OnCallDutyPolicy/ExecutionLogView';
import OnCallDutyPolicyViewEscalation from './Pages/OnCallDuty/OnCallDutyPolicy/Escalation';
import OnCallDutyPolicyViewCustomFields from './Pages/OnCallDuty/OnCallDutyPolicy/CustomFields';
// Monitors
import MonitorPage from './Pages/Monitor/Monitors';
@@ -120,10 +129,12 @@ import MonitorViewCriteria from './Pages/Monitor/View/Criteria';
import MonitorViewStatusTimeline from './Pages/Monitor/View/StatusTimeline';
import MonitorIncidents from './Pages/Monitor/View/Incidents';
import MonitorInoperational from './Pages/Monitor/NotOperationalMonitors';
import MonitorDisabled from './Pages/Monitor/DisabledMonitors';
import MonitorViewCustomFields from './Pages/Monitor/View/CustomFields';
import MonitorViewInterval from './Pages/Monitor/View/Interval';
import MonitorViewProbes from './Pages/Monitor/View/Probes';
import MonitorViewOwner from './Pages/Monitor/View/Owners';
import MonitorViewSettings from './Pages/Monitor/View/Settings';
import User from 'CommonUI/src/Utils/User';
import Logout from './Pages/Logout/Logout';
@@ -139,6 +150,9 @@ import API from 'CommonUI/src/Utils/API/API';
import BillingPaymentMethod from 'Model/Models/BillingPaymentMethod';
import PageComponentProps from './Pages/PageComponentProps';
import UserSettingsNotificationMethods from './Pages/UserSettings/NotificationMethods';
import UserSettingsNotificationRules from './Pages/UserSettings/OnCallRules';
const App: FunctionComponent = () => {
Navigation.setNavigateHook(useNavigate());
Navigation.setLocation(useLocation());
@@ -411,6 +425,33 @@ const App: FunctionComponent = () => {
}
/>
<PageRoute
path={
RouteMap[PageMap.MONITOR_VIEW_SETTINGS]?.toString() ||
''
}
element={
<MonitorViewSettings
{...commonPageProps}
pageRoute={
RouteMap[PageMap.MONITOR_VIEW_SETTINGS] as Route
}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.MONITORS_DISABLED]?.toString() || ''}
element={
<MonitorDisabled
{...commonPageProps}
pageRoute={
RouteMap[PageMap.MONITORS_DISABLED] as Route
}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.MONITOR_VIEW]?.toString() || ''}
element={
@@ -1173,6 +1214,25 @@ const App: FunctionComponent = () => {
}
/>
<PageRoute
path={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS
]?.toString() || ''
}
element={
<OnCallDutyPolicyViewCustomFields
{...commonPageProps}
pageRoute={
RouteMap[
PageMap
.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS
] as Route
}
/>
}
/>
<PageRoute
path={
RouteMap[PageMap.INCIDENT_PUBLIC_NOTE]?.toString() || ''
@@ -1403,6 +1463,20 @@ const App: FunctionComponent = () => {
}
/>
<PageRoute
path={
RouteMap[PageMap.SETTINGS_CALL_LOGS]?.toString() || ''
}
element={
<SettingsCallLog
{...commonPageProps}
pageRoute={
RouteMap[PageMap.SETTINGS_CALL_LOGS] as Route
}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_CALL_SMS]?.toString() || ''}
element={
@@ -1642,6 +1716,25 @@ const App: FunctionComponent = () => {
}
/>
<PageRoute
path={
RouteMap[
PageMap.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS
]?.toString() || ''
}
element={
<OnCallDutyPolicyCustomFields
{...commonPageProps}
pageRoute={
RouteMap[
PageMap
.SETTINGS_ON_CALL_DUTY_POLICY_CUSTOM_FIELDS
] as Route
}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.SETTINGS_BILLING]?.toString() || ''}
element={
@@ -1727,7 +1820,7 @@ const App: FunctionComponent = () => {
<PageRoute
path={RouteMap[PageMap.ON_CALL_DUTY]?.toString() || ''}
element={
<OnCallDutyPage
<OnCallDutyPoliciesPage
{...commonPageProps}
pageRoute={
RouteMap[PageMap.SETTINGS_TEAM_VIEW] as Route
@@ -1736,6 +1829,113 @@ const App: FunctionComponent = () => {
}
/>
<PageRoute
path={
RouteMap[PageMap.ON_CALL_DUTY_POLICIES]?.toString() ||
''
}
element={
<OnCallDutyPoliciesPage
{...commonPageProps}
pageRoute={
RouteMap[PageMap.ON_CALL_DUTY_POLICIES] as Route
}
/>
}
/>
<PageRoute
path={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW
]?.toString() || ''
}
element={
<OnCallDutyPolicyView
{...commonPageProps}
pageRoute={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW
] as Route
}
/>
}
/>
<PageRoute
path={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE
]?.toString() || ''
}
element={
<OnCallDutyPolicyViewDelete
{...commonPageProps}
pageRoute={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE
] as Route
}
/>
}
/>
<PageRoute
path={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION
]?.toString() || ''
}
element={
<OnCallDutyPolicyViewEscalation
{...commonPageProps}
pageRoute={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION
] as Route
}
/>
}
/>
<PageRoute
path={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS
]?.toString() || ''
}
element={
<OnCallDutyPolicyViewLogs
{...commonPageProps}
pageRoute={
RouteMap[
PageMap
.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS
] as Route
}
/>
}
/>
<PageRoute
path={
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW
]?.toString() || ''
}
element={
<OnCallDutyPolicyViewLogsView
{...commonPageProps}
pageRoute={
RouteMap[
PageMap
.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOG_VIEW
] as Route
}
/>
}
/>
{/* Misc Routes */}
<PageRoute
path={RouteMap[PageMap.LOGOUT]?.toString() || ''}
@@ -1818,6 +2018,54 @@ const App: FunctionComponent = () => {
}
/>
{/* User Settings */}
<PageRoute
path={RouteMap[PageMap.USER_SETTINGS]?.toString() || ''}
element={
<UserSettingsNotificationMethods
{...commonPageProps}
pageRoute={RouteMap[PageMap.USER_SETTINGS] as Route}
/>
}
/>
<PageRoute
path={
RouteMap[
PageMap.USER_SETTINGS_NOTIFICATION_METHODS
]?.toString() || ''
}
element={
<UserSettingsNotificationMethods
{...commonPageProps}
pageRoute={
RouteMap[
PageMap.USER_SETTINGS_NOTIFICATION_METHODS
] as Route
}
/>
}
/>
<PageRoute
path={
RouteMap[
PageMap.USER_SETTINGS_ON_CALL_RULES
]?.toString() || ''
}
element={
<UserSettingsNotificationRules
{...commonPageProps}
pageRoute={
RouteMap[
PageMap.USER_SETTINGS_ON_CALL_RULES
] as Route
}
/>
}
/>
{/* 👇️ only match this when no other routes match */}
<PageRoute
path="*"

View File

@@ -6,7 +6,7 @@ const DashboardFooter: FunctionComponent = () => {
return (
<Footer
className="bg-white h-16 inset-x-0 bottom-0 px-8"
copyright="OneUptime Limited."
copyright="HackerBay, Inc."
links={[
{
title: 'Help and Support',

View File

@@ -0,0 +1,52 @@
import React, { FunctionComponent, ReactElement, useState } from 'react';
import ObjectID from 'Common/Types/ObjectID';
import Alert, { AlertType } from 'CommonUI/src/Components/Alerts/Alert';
import { useAsyncEffect } from 'use-async-effect';
import Monitor from 'Model/Models/Monitor';
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
export interface ComponentProps {
monitorId: ObjectID | undefined;
refreshToggle?: boolean | undefined;
}
const DisabledWarning: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
const [isDisabled, setIsDisabled] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(true);
useAsyncEffect(async () => {
setIsLoading(true);
const monitorCount: number = await ModelAPI.count(Monitor, {
_id: props.monitorId,
disableActiveMonitoring: true,
});
if (monitorCount > 0) {
setIsDisabled(true);
} else {
setIsDisabled(false);
}
setIsLoading(false);
}, [props.refreshToggle]);
if (isLoading) {
return <></>;
}
if (isDisabled) {
return (
<Alert
type={AlertType.DANGER}
strongTitle="This monitor is disabled"
title="We are not monitoring this monitor since it is disabled. To enable active monitoring, please go to Settings."
/>
);
}
return <></>;
};
export default DisabledWarning;

View File

@@ -129,6 +129,18 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
),
}}
>
{/* <NavBarMenuItem
title="On-Call Duty"
description="Manage your on-call schedules, escalations and more."
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route
)}
icon={IconProp.Call}
onClick={() => {
forceHideMoreMenu();
}}
/> */}
<NavBarMenuItem
title="Workflows"
description="Integrate OneUptime with the rest of your ecosystem."
@@ -142,7 +154,7 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
/>
<NavBarMenuItem
title="Project Settings"
description="Review or manage project settings here."
description="Review or manage settings related to this project here."
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.SETTINGS] as Route
)}
@@ -151,6 +163,17 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
forceHideMoreMenu();
}}
/>
{/* <NavBarMenuItem
title="User Settings"
description="Review or manage user settings related to this project here."
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.USER_SETTINGS] as Route
)}
icon={IconProp.User}
onClick={() => {
forceHideMoreMenu();
}}
/> */}
{/* <NavBarMenuItem
title="Logs Management"
@@ -168,15 +191,6 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
)}
icon={IconProp.Error}
/>
<NavBarMenuItem
title="On-Call Duty"
description='Manage you on-call schedules, escalations and more.'
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route
)}
icon={IconProp.Call}
></NavBarMenuItem>
<NavBarMenuItem
title="Reports"

View File

@@ -0,0 +1,295 @@
import UserCall from 'Model/Models/UserCall';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import IconProp from 'Common/Types/Icon/IconProp';
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from 'react';
import DashboardNavigation from '../../Utils/Navigation';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import User from 'CommonUI/src/Utils/User';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
import { JSONObject } from 'Common/Types/JSON';
import URL from 'Common/Types/API/URL';
import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
import { DASHBOARD_API_URL } from 'CommonUI/src/Config';
import API from 'CommonUI/src/Utils/API/API';
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
const Call: FunctionComponent = (): ReactElement => {
const [showVerificationCodeModal, setShowVerificationCodeModal] =
useState<boolean>(false);
const [showResendCodeModal, setShowResendCodeModal] =
useState<boolean>(false);
const [error, setError] = useState<string>('');
const [currentItem, setCurrentItem] = useState<JSONObject | null>(null);
const [refreshToggle, setRefreshToggle] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [
showVerificationCodeResentModal,
setShowVerificationCodeResentModal,
] = useState<boolean>(false);
useEffect(() => {
setError('');
}, [showVerificationCodeModal]);
return (
<>
<ModelTable<UserCall>
modelType={UserCall}
query={{
projectId: DashboardNavigation.getProjectId()?.toString(),
userId: User.getUserId().toString(),
}}
refreshToggle={refreshToggle}
onBeforeCreate={(model: UserCall): UserCall => {
model.projectId = DashboardNavigation.getProjectId()!;
model.userId = User.getUserId();
return model;
}}
createVerb={'Add'}
actionButtons={[
{
title: 'Verify',
buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE,
icon: IconProp.Check,
isVisible: (item: JSONObject): boolean => {
if (item['isVerified']) {
return false;
}
return true;
},
onClick: async (
item: JSONObject,
onCompleteAction: Function,
onError: (err: Error) => void
) => {
try {
setCurrentItem(item);
setShowVerificationCodeModal(true);
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
{
title: 'Resend Code',
buttonStyleType: ButtonStyleType.NORMAL,
icon: IconProp.Call,
isVisible: (item: JSONObject): boolean => {
if (item['isVerified']) {
return false;
}
return true;
},
onClick: async (
item: JSONObject,
onCompleteAction: Function,
onError: (err: Error) => void
) => {
try {
setCurrentItem(item);
setShowResendCodeModal(true);
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
]}
id="user-call"
name="User Settings > Notification Methods > Call"
isDeleteable={true}
isEditable={false}
isCreateable={true}
cardProps={{
icon: IconProp.Call,
title: 'Phone Numbers for Call Notifications',
description:
'Manage Phone Numbers that will receive call notifications for this project.',
}}
noItemsMessage={
'No phone numbers found. Please add one to receive notifications.'
}
formFields={[
{
field: {
phone: true,
},
title: 'Phone Number',
fieldType: FormFieldSchemaType.Phone,
required: true,
placeholder: '+11234567890',
validation: {
minLength: 2,
},
},
]}
showRefreshButton={true}
showFilterButton={false}
columns={[
{
field: {
phone: true,
},
title: 'Phone Number',
type: FieldType.Phone,
isFilterable: false,
},
{
field: {
isVerified: true,
},
title: 'Verified',
type: FieldType.Boolean,
},
]}
/>
{showVerificationCodeModal && currentItem ? (
<BasicFormModal
title={'Verify Phone Number'}
onClose={() => {
setShowVerificationCodeModal(false);
}}
isLoading={isLoading}
name="Verify Phone Number"
submitButtonText={'Verify'}
onSubmit={async (item: JSONObject) => {
setIsLoading(true);
try {
const response:
| HTTPResponse<JSONObject>
| HTTPErrorResponse = await API.post(
URL.fromString(
DASHBOARD_API_URL.toString()
).addRoute('/user-call/verify'),
{
code: item['code'],
projectId:
DashboardNavigation.getProjectId()?.toString(),
itemId: currentItem['_id'],
}
);
if (response.isFailure()) {
setError(API.getFriendlyMessage(response));
setIsLoading(false);
} else {
setIsLoading(false);
setShowVerificationCodeModal(false);
setRefreshToggle(!refreshToggle);
}
} catch (e) {
setError(API.getFriendlyMessage(e));
setIsLoading(false);
}
}}
formProps={{
error: error || '',
fields: [
{
title: 'Verification Code',
description: `We're calling you with your verifiction code. Please make sure this device can receive calls.`,
field: {
code: true,
},
placeholder: '123456',
required: true,
validation: {
minLength: 6,
maxLength: 6,
},
fieldType: FormFieldSchemaType.Number,
},
],
}}
/>
) : (
<></>
)}
{showResendCodeModal && currentItem ? (
<ConfirmModal
title={`Resend Code`}
error={error}
description={
'Are you sure you want to resend verification code? We will make a call to this number.'
}
submitButtonText={'Resend Code'}
onClose={() => {
setShowResendCodeModal(false);
setError('');
}}
isLoading={isLoading}
onSubmit={async () => {
try {
const response:
| HTTPResponse<JSONObject>
| HTTPErrorResponse = await API.post(
URL.fromString(
DASHBOARD_API_URL.toString()
).addRoute(
'/user-call/resend-verification-code'
),
{
projectId:
DashboardNavigation.getProjectId()?.toString(),
itemId: currentItem['_id'],
}
);
if (response.isFailure()) {
setError(API.getFriendlyMessage(response));
setIsLoading(false);
} else {
setIsLoading(false);
setShowResendCodeModal(false);
setShowVerificationCodeResentModal(true);
}
} catch (err) {
setError(API.getFriendlyMessage(err));
setIsLoading(false);
}
}}
/>
) : (
<></>
)}
{showVerificationCodeResentModal ? (
<ConfirmModal
title={`Calling you with your verification code`}
error={error}
description={
'We are calling you with your verification code. Please make sure this device can receive calls.'
}
submitButtonText={'Close'}
onSubmit={async () => {
setShowVerificationCodeResentModal(false);
setError('');
}}
/>
) : (
<></>
)}
</>
);
};
export default Call;

View File

@@ -0,0 +1,295 @@
import UserEmail from 'Model/Models/UserEmail';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import IconProp from 'Common/Types/Icon/IconProp';
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from 'react';
import DashboardNavigation from '../../Utils/Navigation';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import User from 'CommonUI/src/Utils/User';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
import { JSONObject } from 'Common/Types/JSON';
import URL from 'Common/Types/API/URL';
import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
import { DASHBOARD_API_URL } from 'CommonUI/src/Config';
import API from 'CommonUI/src/Utils/API/API';
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
const Email: FunctionComponent = (): ReactElement => {
const [showVerificationCodeModal, setShowVerificationCodeModal] =
useState<boolean>(false);
const [showResendCodeModal, setShowResendCodeModal] =
useState<boolean>(false);
const [error, setError] = useState<string>('');
const [currentItem, setCurrentItem] = useState<JSONObject | null>(null);
const [refreshToggle, setRefreshToggle] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [
showVerificationCodeResentModal,
setShowVerificationCodeResentModal,
] = useState<boolean>(false);
useEffect(() => {
setError('');
}, [showVerificationCodeModal]);
return (
<>
<ModelTable<UserEmail>
modelType={UserEmail}
query={{
projectId: DashboardNavigation.getProjectId()?.toString(),
userId: User.getUserId().toString(),
}}
refreshToggle={refreshToggle}
onBeforeCreate={(model: UserEmail): UserEmail => {
model.projectId = DashboardNavigation.getProjectId()!;
model.userId = User.getUserId();
return model;
}}
createVerb={'Add'}
actionButtons={[
{
title: 'Verify',
buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE,
icon: IconProp.Check,
isVisible: (item: JSONObject): boolean => {
if (item['isVerified']) {
return false;
}
return true;
},
onClick: async (
item: JSONObject,
onCompleteAction: Function,
onError: (err: Error) => void
) => {
try {
setCurrentItem(item);
setShowVerificationCodeModal(true);
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
{
title: 'Resend Code',
buttonStyleType: ButtonStyleType.NORMAL,
icon: IconProp.Email,
isVisible: (item: JSONObject): boolean => {
if (item['isVerified']) {
return false;
}
return true;
},
onClick: async (
item: JSONObject,
onCompleteAction: Function,
onError: (err: Error) => void
) => {
try {
setCurrentItem(item);
setShowResendCodeModal(true);
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
]}
id="user-emails"
name="User Settings > Notification Methods > Emails"
isDeleteable={true}
isEditable={false}
isCreateable={true}
cardProps={{
icon: IconProp.Email,
title: 'Emails for Notifications',
description:
'Manage emails that will receive notifications for this project.',
}}
noItemsMessage={
'No emails found. Please add one to receive notifications.'
}
formFields={[
{
field: {
email: true,
},
title: 'Email',
fieldType: FormFieldSchemaType.Email,
required: true,
placeholder: 'you@company.com',
validation: {
minLength: 2,
},
},
]}
showRefreshButton={true}
showFilterButton={false}
columns={[
{
field: {
email: true,
},
title: 'Email',
type: FieldType.Email,
isFilterable: false,
},
{
field: {
isVerified: true,
},
title: 'Verified',
type: FieldType.Boolean,
},
]}
/>
{showVerificationCodeModal && currentItem ? (
<BasicFormModal
title={'Verify Email'}
onClose={() => {
setShowVerificationCodeModal(false);
}}
isLoading={isLoading}
name="Verify Email"
submitButtonText={'Verify'}
onSubmit={async (item: JSONObject) => {
setIsLoading(true);
try {
const response:
| HTTPResponse<JSONObject>
| HTTPErrorResponse = await API.post(
URL.fromString(
DASHBOARD_API_URL.toString()
).addRoute('/user-email/verify'),
{
code: item['code'],
projectId:
DashboardNavigation.getProjectId()?.toString(),
itemId: currentItem['_id'],
}
);
if (response.isFailure()) {
setError(API.getFriendlyMessage(response));
setIsLoading(false);
} else {
setIsLoading(false);
setShowVerificationCodeModal(false);
setRefreshToggle(!refreshToggle);
}
} catch (e) {
setError(API.getFriendlyMessage(e));
setIsLoading(false);
}
}}
formProps={{
error: error || '',
fields: [
{
title: 'Verification Code',
description: `We have sent verifiction code to your email. Please dont forget to check your spam.`,
field: {
code: true,
},
placeholder: '123456',
required: true,
validation: {
minLength: 6,
maxLength: 6,
},
fieldType: FormFieldSchemaType.Number,
},
],
}}
/>
) : (
<></>
)}
{showResendCodeModal && currentItem ? (
<ConfirmModal
title={`Resend Code`}
error={error}
description={
'Are you sure you want to resend verification code?'
}
submitButtonText={'Resend Code'}
onClose={() => {
setShowResendCodeModal(false);
setError('');
}}
isLoading={isLoading}
onSubmit={async () => {
try {
const response:
| HTTPResponse<JSONObject>
| HTTPErrorResponse = await API.post(
URL.fromString(
DASHBOARD_API_URL.toString()
).addRoute(
'/user-email/resend-verification-code'
),
{
projectId:
DashboardNavigation.getProjectId()?.toString(),
itemId: currentItem['_id'],
}
);
if (response.isFailure()) {
setError(API.getFriendlyMessage(response));
setIsLoading(false);
} else {
setIsLoading(false);
setShowResendCodeModal(false);
setShowVerificationCodeResentModal(true);
}
} catch (err) {
setError(API.getFriendlyMessage(err));
setIsLoading(false);
}
}}
/>
) : (
<></>
)}
{showVerificationCodeResentModal ? (
<ConfirmModal
title={`Code sent successfully`}
error={error}
description={
'We have sent verification code to your email. Please dont forget to check your spam.'
}
submitButtonText={'Close'}
onSubmit={async () => {
setShowVerificationCodeResentModal(false);
setError('');
}}
/>
) : (
<></>
)}
</>
);
};
export default Email;

View File

@@ -0,0 +1,295 @@
import UserSMS from 'Model/Models/UserSMS';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import IconProp from 'Common/Types/Icon/IconProp';
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from 'react';
import DashboardNavigation from '../../Utils/Navigation';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import User from 'CommonUI/src/Utils/User';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { ButtonStyleType } from 'CommonUI/src/Components/Button/Button';
import { JSONObject } from 'Common/Types/JSON';
import URL from 'Common/Types/API/URL';
import BasicFormModal from 'CommonUI/src/Components/FormModal/BasicFormModal';
import HTTPResponse from 'Common/Types/API/HTTPResponse';
import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse';
import { DASHBOARD_API_URL } from 'CommonUI/src/Config';
import API from 'CommonUI/src/Utils/API/API';
import ConfirmModal from 'CommonUI/src/Components/Modal/ConfirmModal';
const SMS: FunctionComponent = (): ReactElement => {
const [showVerificationCodeModal, setShowVerificationCodeModal] =
useState<boolean>(false);
const [showResendCodeModal, setShowResendCodeModal] =
useState<boolean>(false);
const [error, setError] = useState<string>('');
const [currentItem, setCurrentItem] = useState<JSONObject | null>(null);
const [refreshToggle, setRefreshToggle] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [
showVerificationCodeResentModal,
setShowVerificationCodeResentModal,
] = useState<boolean>(false);
useEffect(() => {
setError('');
}, [showVerificationCodeModal]);
return (
<>
<ModelTable<UserSMS>
modelType={UserSMS}
query={{
projectId: DashboardNavigation.getProjectId()?.toString(),
userId: User.getUserId().toString(),
}}
refreshToggle={refreshToggle}
onBeforeCreate={(model: UserSMS): UserSMS => {
model.projectId = DashboardNavigation.getProjectId()!;
model.userId = User.getUserId();
return model;
}}
createVerb={'Add'}
actionButtons={[
{
title: 'Verify',
buttonStyleType: ButtonStyleType.SUCCESS_OUTLINE,
icon: IconProp.Check,
isVisible: (item: JSONObject): boolean => {
if (item['isVerified']) {
return false;
}
return true;
},
onClick: async (
item: JSONObject,
onCompleteAction: Function,
onError: (err: Error) => void
) => {
try {
setCurrentItem(item);
setShowVerificationCodeModal(true);
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
{
title: 'Resend Code',
buttonStyleType: ButtonStyleType.NORMAL,
icon: IconProp.SMS,
isVisible: (item: JSONObject): boolean => {
if (item['isVerified']) {
return false;
}
return true;
},
onClick: async (
item: JSONObject,
onCompleteAction: Function,
onError: (err: Error) => void
) => {
try {
setCurrentItem(item);
setShowResendCodeModal(true);
onCompleteAction();
} catch (err) {
onCompleteAction();
onError(err as Error);
}
},
},
]}
id="user-sms"
name="User Settings > Notification Methods > SMS"
isDeleteable={true}
isEditable={false}
isCreateable={true}
cardProps={{
icon: IconProp.SMS,
title: 'Phone Numbers for SMS Notifications',
description:
'Manage Phone Numbers that will receive SMS notifications for this project.',
}}
noItemsMessage={
'No phone numbers found. Please add one to receive notifications.'
}
formFields={[
{
field: {
phone: true,
},
title: 'Phone Number',
fieldType: FormFieldSchemaType.Phone,
required: true,
placeholder: '+11234567890',
validation: {
minLength: 2,
},
},
]}
showRefreshButton={true}
showFilterButton={false}
columns={[
{
field: {
phone: true,
},
title: 'Phone Number',
type: FieldType.Phone,
isFilterable: false,
},
{
field: {
isVerified: true,
},
title: 'Verified',
type: FieldType.Boolean,
},
]}
/>
{showVerificationCodeModal && currentItem ? (
<BasicFormModal
title={'Verify Phone Number'}
onClose={() => {
setShowVerificationCodeModal(false);
}}
isLoading={isLoading}
name="Verify Phone Number"
submitButtonText={'Verify'}
onSubmit={async (item: JSONObject) => {
setIsLoading(true);
try {
const response:
| HTTPResponse<JSONObject>
| HTTPErrorResponse = await API.post(
URL.fromString(
DASHBOARD_API_URL.toString()
).addRoute('/user-sms/verify'),
{
code: item['code'],
projectId:
DashboardNavigation.getProjectId()?.toString(),
itemId: currentItem['_id'],
}
);
if (response.isFailure()) {
setError(API.getFriendlyMessage(response));
setIsLoading(false);
} else {
setIsLoading(false);
setShowVerificationCodeModal(false);
setRefreshToggle(!refreshToggle);
}
} catch (e) {
setError(API.getFriendlyMessage(e));
setIsLoading(false);
}
}}
formProps={{
error: error || '',
fields: [
{
title: 'Verification Code',
description: `We have sent an SMS with your verifiction code. Please dont forget to check your spam.`,
field: {
code: true,
},
placeholder: '123456',
required: true,
validation: {
minLength: 6,
maxLength: 6,
},
fieldType: FormFieldSchemaType.Number,
},
],
}}
/>
) : (
<></>
)}
{showResendCodeModal && currentItem ? (
<ConfirmModal
title={`Resend Code`}
error={error}
description={
'Are you sure you want to resend verification code?'
}
submitButtonText={'Resend Code'}
onClose={() => {
setShowResendCodeModal(false);
setError('');
}}
isLoading={isLoading}
onSubmit={async () => {
try {
const response:
| HTTPResponse<JSONObject>
| HTTPErrorResponse = await API.post(
URL.fromString(
DASHBOARD_API_URL.toString()
).addRoute(
'/user-sms/resend-verification-code'
),
{
projectId:
DashboardNavigation.getProjectId()?.toString(),
itemId: currentItem['_id'],
}
);
if (response.isFailure()) {
setError(API.getFriendlyMessage(response));
setIsLoading(false);
} else {
setIsLoading(false);
setShowResendCodeModal(false);
setShowVerificationCodeResentModal(true);
}
} catch (err) {
setError(API.getFriendlyMessage(err));
setIsLoading(false);
}
}}
/>
) : (
<></>
)}
{showVerificationCodeResentModal ? (
<ConfirmModal
title={`Code sent successfully`}
error={error}
description={
'We have sent verification code to your sms. Please dont forget to check your spam.'
}
submitButtonText={'Close'}
onSubmit={async () => {
setShowVerificationCodeResentModal(false);
setError('');
}}
/>
) : (
<></>
)}
</>
);
};
export default SMS;

View File

@@ -0,0 +1,30 @@
import { DropdownOption } from 'CommonUI/src/Components/Dropdown/Dropdown';
const NotifyAfterMinutesDropdownOptions: Array<DropdownOption> = [
{
value: 0,
label: 'Immediately',
},
{
value: 5,
label: '5 minutes',
},
{
value: 10,
label: '10 minutes',
},
{
value: 15,
label: '15 minutes',
},
{
value: 30,
label: '30 minutes',
},
{
value: 60,
label: '1 hour',
},
];
export default NotifyAfterMinutesDropdownOptions;

View File

@@ -62,24 +62,6 @@ const Home: FunctionComponent<PageComponentProps> = (
required: true,
title: 'Full Name',
},
{
field: {
companyName: true,
},
fieldType: FormFieldSchemaType.Text,
placeholder: 'Acme, Inc.',
required: true,
title: 'Company Name',
},
{
field: {
companyPhoneNumber: true,
},
fieldType: FormFieldSchemaType.Phone,
required: true,
placeholder: '+1-123-456-7890',
title: 'Phone Number',
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 2,
@@ -98,18 +80,6 @@ const Home: FunctionComponent<PageComponentProps> = (
},
title: 'Email',
},
{
field: {
companyName: true,
},
title: 'Company Name',
},
{
field: {
companyPhoneNumber: true,
},
title: 'Company Phone Number',
},
],
modelId: UserUtil.getUserId(),
}}

View File

@@ -28,28 +28,28 @@ const IncidentCustomFields: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Custom Fields',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_CUSTOM_FIELDS] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -26,28 +26,28 @@ const IncidentDelete: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Delete Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_DELETE] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -47,21 +47,21 @@ const IncidentView: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -40,28 +40,28 @@ const IncidentDelete: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Private Notes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -39,28 +39,28 @@ const IncidentOwners: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Owners',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_OWNERS] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -39,28 +39,28 @@ const PublicNote: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Public Notes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_PUBLIC_NOTE] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -23,7 +23,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Overview',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Info}
@@ -35,7 +35,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
RouteMap[
PageMap.INCIDENT_VIEW_STATE_TIMELINE
] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.List}
@@ -46,7 +46,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Owners',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_OWNERS] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Team}
@@ -59,7 +59,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Private Notes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Lock}
@@ -69,7 +69,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Public Notes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_PUBLIC_NOTE] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Public}
@@ -84,7 +84,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
RouteMap[
PageMap.INCIDENT_VIEW_CUSTOM_FIELDS
] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.TableCells}
@@ -95,7 +95,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Delete Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_DELETE] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Trash}

View File

@@ -35,28 +35,28 @@ const IncidentDelete: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Status Timeline',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_STATE_TIMELINE] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -0,0 +1,59 @@
import Route from 'Common/Types/API/Route';
import Page from 'CommonUI/src/Components/Page/Page';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import PageComponentProps from '../PageComponentProps';
import MonitorTable from '../../Components/Monitor/MonitorTable';
import DashboardSideMenu from './SideMenu';
import DashboardNavigation from '../../Utils/Navigation';
const DisabledMonitors: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
return (
<Page
title={'Home'}
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route
),
},
{
title: 'Disabled Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS_DISABLED] as Route
),
},
]}
sideMenu={
<DashboardSideMenu
project={props.currentProject || undefined}
/>
}
>
<MonitorTable
viewPageRoute={RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route
)}
query={{
projectId: DashboardNavigation.getProjectId()?.toString(),
disableActiveMonitoring: true,
}}
noItemsMessage="No disabled monitors. All monitors in active state."
title="Disabled Monitors"
description="Here is a list of all the monitors which are in disbaled state."
/>
</Page>
);
};
export default DisabledMonitors;

View File

@@ -48,6 +48,21 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
},
}}
/>
<CountModelSideMenuItem<Monitor>
link={{
title: 'Disabled Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS_DISABLED] as Route
),
}}
icon={IconProp.Error}
badgeType={BadgeType.DANGER}
modelType={Monitor}
countQuery={{
projectId: props.project?._id,
disableActiveMonitoring: true,
}}
/>
</SideMenuSection>
</SideMenu>
);

View File

@@ -32,6 +32,7 @@ import API from 'CommonUI/src/Utils/API/API';
import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorCriteria: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@@ -189,33 +190,34 @@ const MonitorCriteria: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Criteria',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_CRITERIA] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
{getPageContent()}
</ModelPage>
);

View File

@@ -11,6 +11,7 @@ import CustomFieldsDetail from 'CommonUI/src/Components/CustomFields/CustomField
import Monitor from 'Model/Models/Monitor';
import MonitorCustomField from 'Model/Models/MonitorCustomField';
import ProjectUtil from 'CommonUI/src/Utils/Project';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorCustomFields: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@@ -28,33 +29,34 @@ const MonitorCustomFields: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Custom Fields',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_DELETE] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
<CustomFieldsDetail
title="Monitor Custom Fields"
description="Custom fields help you add new fields to your resources in OneUptime."

View File

@@ -9,6 +9,7 @@ import Navigation from 'CommonUI/src/Utils/Navigation';
import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete';
import ObjectID from 'Common/Types/ObjectID';
import Monitor from 'Model/Models/Monitor';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorDelete: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@@ -26,33 +27,34 @@ const MonitorDelete: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Delete Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_DELETE] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
<ModelDelete
modelType={Monitor}
modelId={modelId}

View File

@@ -21,6 +21,7 @@ import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import JSONFunctions from 'Common/Types/JSONFunctions';
import Navigation from 'CommonUI/src/Utils/Navigation';
import IncidentSeverity from 'Model/Models/IncidentSeverity';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorIncidents: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
@@ -37,33 +38,34 @@ const MonitorIncidents: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_INCIDENTS] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
<ModelTable<Incident>
modelType={Incident}
id="incidents-table"

View File

@@ -29,6 +29,7 @@ import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import MonitorStatusTimeline from 'Model/Models/MonitorStatusTimeline';
import JSONFunctions from 'Common/Types/JSONFunctions';
import API from 'CommonUI/src/Utils/API/API';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorView: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@@ -92,26 +93,27 @@ const MonitorView: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
{/* Monitor View */}
<CardModelDetail
name="Monitor Details"

View File

@@ -25,6 +25,7 @@ import API from 'CommonUI/src/Utils/API/API';
import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorCriteria: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@@ -160,33 +161,34 @@ const MonitorCriteria: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Criteria',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_CRITERIA] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
{getPageContent()}
</ModelPage>
);

View File

@@ -22,6 +22,7 @@ import MonitorOwnerUser from 'Model/Models/MonitorOwnerUser';
import User from 'Model/Models/User';
import UserElement from '../../../Components/User/User';
import ProjectUser from '../../../Utils/ProjectUser';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorOwners: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@@ -39,33 +40,34 @@ const MonitorOwners: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Owners',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_OWNERS] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
<ModelTable<MonitorOwnerTeam>
modelType={MonitorOwnerTeam}
id="table-monitor-owner-team"

View File

@@ -31,6 +31,7 @@ import ProbeElement from '../../../Components/Probe/Probe';
import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax';
import URL from 'Common/Types/API/URL';
import { DASHBOARD_API_URL } from 'CommonUI/src/Config';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorProbes: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
@@ -249,33 +250,34 @@ const MonitorProbes: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Probes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_PROBES] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
{getPageContent()}
</ModelPage>
);

View File

@@ -0,0 +1,193 @@
import Route from 'Common/Types/API/Route';
import ModelPage from 'CommonUI/src/Components/Page/ModelPage';
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import Monitor from 'Model/Models/Monitor';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
import IconProp from 'Common/Types/Icon/IconProp';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import MonitorType from 'Common/Types/Monitor/MonitorType';
import ModelAPI from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import API from 'CommonUI/src/Utils/API/API';
import ComponentLoader from 'CommonUI/src/Components/ComponentLoader/ComponentLoader';
import ErrorMessage from 'CommonUI/src/Components/ErrorMessage/ErrorMessage';
import EmptyState from 'CommonUI/src/Components/EmptyState/EmptyState';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorCriteria: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const [alertRefreshToggle, setAlertRefreshToggle] =
useState<boolean>(false);
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string>('');
const fetchItem: Function = async (): Promise<void> => {
// get item.
setIsLoading(true);
setError('');
try {
const item: Monitor | null = await ModelAPI.getItem(
Monitor,
modelId,
{
monitorType: true,
} as any,
{}
);
if (!item) {
setError(`Monitor not found`);
return;
}
setMonitorType(item.monitorType);
} catch (err) {
setError(API.getFriendlyMessage(err));
}
setIsLoading(false);
};
const [monitorType, setMonitorType] = useState<MonitorType | undefined>(
undefined
);
useEffect(() => {
// fetch the model
fetchItem();
}, []);
const getPageContent: Function = (): ReactElement => {
if (!monitorType || isLoading) {
return <ComponentLoader />;
}
if (error) {
return <ErrorMessage error={error} />;
}
if (monitorType === MonitorType.Manual) {
return (
<EmptyState
icon={IconProp.Settings}
title={'No Settings for Manual Monitors'}
description={
<>
This is a manual monitor and it cannot have any
settings. You can have monitor settings on other
monitor types.{' '}
</>
}
/>
);
}
return (
<CardModelDetail
name="Monitor Settings"
editButtonText="Edit Settings"
cardProps={{
title: 'Monitor Settings',
description:
'Here are some advanced settings for this monitor.',
icon: IconProp.Settings,
}}
onSaveSuccess={() => {
setAlertRefreshToggle(!alertRefreshToggle);
}}
isEditable={true}
formFields={[
{
field: {
disableActiveMonitoring: true,
},
title: 'Disable Active Monitoring',
fieldType: FormFieldSchemaType.Toggle,
required: false,
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: Monitor,
id: 'model-detail-monitors',
fields: [
{
field: {
disableActiveMonitoring: true,
},
title: 'Disable Active Monitoring',
fieldType: FieldType.Boolean,
},
],
modelId: modelId,
}}
/>
);
};
return (
<ModelPage
title="Monitor"
modelType={Monitor}
modelId={modelId}
modelNameField="name"
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
{ modelId }
),
},
{
title: 'Criteria',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_CRITERIA] as Route,
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning
monitorId={modelId}
refreshToggle={alertRefreshToggle}
/>
{getPageContent()}
</ModelPage>
);
};
export default MonitorCriteria;

View File

@@ -23,7 +23,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Overview',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Info}
@@ -33,7 +33,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Owners',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_OWNERS] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Team}
@@ -43,7 +43,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Criteria',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_CRITERIA] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Criteria}
@@ -53,7 +53,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Interval',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_INTERVAL] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Clock}
@@ -68,7 +68,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
RouteMap[
PageMap.MONITOR_VIEW_STATUS_TIMELINE
] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.List}
@@ -78,7 +78,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_INCIDENTS] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Alert}
@@ -91,7 +91,7 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
title: 'Probes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_PROBES] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Signal}
@@ -103,17 +103,27 @@ const DashboardSideMenu: FunctionComponent<ComponentProps> = (
RouteMap[
PageMap.MONITOR_VIEW_CUSTOM_FIELDS
] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.TableCells}
/>
<SideMenuItem
link={{
title: 'Settings',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_SETTINGS] as Route,
{ modelId: props.modelId }
),
}}
icon={IconProp.Settings}
/>
<SideMenuItem
link={{
title: 'Delete Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_DELETE] as Route,
props.modelId
{ modelId: props.modelId }
),
}}
icon={IconProp.Trash}

View File

@@ -19,6 +19,7 @@ import Statusbubble from 'CommonUI/src/Components/StatusBubble/StatusBubble';
import Color from 'Common/Types/Color';
import Navigation from 'CommonUI/src/Utils/Navigation';
import Monitor from 'Model/Models/Monitor';
import DisabledWarning from '../../../Components/Monitor/DisabledWarning';
const MonitorDelete: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
@@ -35,33 +36,34 @@ const MonitorDelete: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Monitors',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITORS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Monitor',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW] as Route,
modelId
{ modelId }
),
},
{
title: 'Status Timeline',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.MONITOR_VIEW_STATUS_TIMELINE] as Route,
modelId
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<DisabledWarning monitorId={modelId} />
<ModelTable<MonitorStatusTimeline>
modelType={MonitorStatusTimeline}
id="table-monitor-status-timeline"

View File

@@ -5,7 +5,7 @@ import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import PageComponentProps from '../PageComponentProps';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import OnCallDuty from 'Model/Models/OnCallDuty';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import IconProp from 'Common/Types/Icon/IconProp';
@@ -15,12 +15,13 @@ import LabelsElement from '../../Components/Label/Labels';
import JSONFunctions from 'Common/Types/JSONFunctions';
import DashboardNavigation from '../../Utils/Navigation';
import Navigation from 'CommonUI/src/Utils/Navigation';
const OnCallDutyPage: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
return (
<Page
title={'On-Call-Duty'}
title={'On-Call Duty'}
breadcrumbLinks={[
{
title: 'Project',
@@ -34,23 +35,30 @@ const OnCallDutyPage: FunctionComponent<PageComponentProps> = (
RouteMap[PageMap.ON_CALL_DUTY] as Route
),
},
{
title: 'Policies',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY_POLICIES] as Route
),
},
]}
>
<ModelTable<OnCallDuty>
modelType={OnCallDuty}
<ModelTable<OnCallDutyPolicy>
modelType={OnCallDutyPolicy}
id="on-call-duty-table"
isDeleteable={false}
name="Scheduled Maintenance Events > On Call Duties"
isEditable={true}
name="On Call > Policies"
showViewIdButton={true}
isEditable={false}
isCreateable={true}
isViewable={true}
cardProps={{
icon: IconProp.Call,
title: 'On Call Duties',
title: 'On Call Duty Policies',
description:
'Here is a list of on-call-duty schedules for this project.',
'Here is a list of on-call-duty policies for this project.',
}}
noItemsMessage={'No on-call-duty found.'}
noItemsMessage={'No on-call policy found.'}
formFields={[
{
field: {

View File

@@ -0,0 +1,73 @@
import Route from 'Common/Types/API/Route';
import ModelPage from 'CommonUI/src/Components/Page/ModelPage';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import CustomFieldsDetail from 'CommonUI/src/Components/CustomFields/CustomFieldsDetail';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
import OnCallDutyPolicyCustomField from 'Model/Models/OnCallDutyPolicyCustomField';
import ProjectUtil from 'CommonUI/src/Utils/Project';
const OnCallDutyPolicyCustomFields: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return (
<ModelPage
title="On Call Policy"
modelType={OnCallDutyPolicy}
modelId={modelId}
modelNameField="name"
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
{ modelId }
),
},
{
title: 'On Call Duty',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route,
{ modelId }
),
},
{
title: 'View On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route,
{ modelId }
),
},
{
title: 'Custom Fields',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS
] as Route,
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<CustomFieldsDetail
title="Custom Fields"
description="Custom fields help you add new fields to your resources in OneUptime."
modelType={OnCallDutyPolicy}
customFieldType={OnCallDutyPolicyCustomField}
name="Custom Fields"
projectId={ProjectUtil.getCurrentProject()!.id!}
modelId={modelId}
/>
</ModelPage>
);
};
export default OnCallDutyPolicyCustomFields;

View File

@@ -0,0 +1,71 @@
import Route from 'Common/Types/API/Route';
import ModelPage from 'CommonUI/src/Components/Page/ModelPage';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete';
import ObjectID from 'Common/Types/ObjectID';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
const OnCallPolicyDelete: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return (
<ModelPage
title="On Call Policy"
modelType={OnCallDutyPolicy}
modelId={modelId}
modelNameField="name"
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
{ modelId }
),
},
{
title: 'On Call Duty',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route,
{ modelId }
),
},
{
title: 'View On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route,
{ modelId }
),
},
{
title: 'Delete On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE
] as Route,
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<ModelDelete
modelType={OnCallDutyPolicy}
modelId={modelId}
onDeleteSuccess={() => {
Navigation.navigate(
RouteMap[PageMap.ON_CALL_DUTY] as Route
);
}}
/>
</ModelPage>
);
};
export default OnCallPolicyDelete;

View File

@@ -0,0 +1,71 @@
import Route from 'Common/Types/API/Route';
import ModelPage from 'CommonUI/src/Components/Page/ModelPage';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete';
import ObjectID from 'Common/Types/ObjectID';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
const OnCallPolicyDelete: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return (
<ModelPage
title="On Call Policy"
modelType={OnCallDutyPolicy}
modelId={modelId}
modelNameField="name"
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
{ modelId }
),
},
{
title: 'On Call Duty',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route,
{ modelId }
),
},
{
title: 'View On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route,
{ modelId }
),
},
{
title: 'Delete On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE
] as Route,
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<ModelDelete
modelType={OnCallDutyPolicy}
modelId={modelId}
onDeleteSuccess={() => {
Navigation.navigate(
RouteMap[PageMap.ON_CALL_DUTY] as Route
);
}}
/>
</ModelPage>
);
};
export default OnCallPolicyDelete;

View File

@@ -0,0 +1,71 @@
import Route from 'Common/Types/API/Route';
import ModelPage from 'CommonUI/src/Components/Page/ModelPage';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete';
import ObjectID from 'Common/Types/ObjectID';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
const OnCallPolicyDelete: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return (
<ModelPage
title="On Call Policy"
modelType={OnCallDutyPolicy}
modelId={modelId}
modelNameField="name"
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
{ modelId }
),
},
{
title: 'On Call Duty',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route,
{ modelId }
),
},
{
title: 'View On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route,
{ modelId }
),
},
{
title: 'Delete On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE
] as Route,
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<ModelDelete
modelType={OnCallDutyPolicy}
modelId={modelId}
onDeleteSuccess={() => {
Navigation.navigate(
RouteMap[PageMap.ON_CALL_DUTY] as Route
);
}}
/>
</ModelPage>
);
};
export default OnCallPolicyDelete;

View File

@@ -0,0 +1,71 @@
import Route from 'Common/Types/API/Route';
import ModelPage from 'CommonUI/src/Components/Page/ModelPage';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ModelDelete from 'CommonUI/src/Components/ModelDelete/ModelDelete';
import ObjectID from 'Common/Types/ObjectID';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
const OnCallPolicyDelete: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
return (
<ModelPage
title="On Call Policy"
modelType={OnCallDutyPolicy}
modelId={modelId}
modelNameField="name"
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
{ modelId }
),
},
{
title: 'On Call Duty',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route,
{ modelId }
),
},
{
title: 'View On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route,
{ modelId }
),
},
{
title: 'Delete On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE
] as Route,
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<ModelDelete
modelType={OnCallDutyPolicy}
modelId={modelId}
onDeleteSuccess={() => {
Navigation.navigate(
RouteMap[PageMap.ON_CALL_DUTY] as Route
);
}}
/>
</ModelPage>
);
};
export default OnCallPolicyDelete;

View File

@@ -0,0 +1,171 @@
import Route from 'Common/Types/API/Route';
import ModelPage from 'CommonUI/src/Components/Page/ModelPage';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageComponentProps from '../../PageComponentProps';
import SideMenu from './SideMenu';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import IconProp from 'Common/Types/Icon/IconProp';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
import Navigation from 'CommonUI/src/Utils/Navigation';
import Label from 'Model/Models/Label';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import LabelsElement from '../../../Components/Label/Labels';
import OnCallDutyPolicy from 'Model/Models/OnCallDutyPolicy';
import JSONFunctions from 'Common/Types/JSONFunctions';
const OnCallDutyPolicyView: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID();
return (
<ModelPage
title="On Call Policy"
modelType={OnCallDutyPolicy}
modelId={modelId}
modelNameField="name"
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
{ modelId }
),
},
{
title: 'On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.STATUS_PAGES] as Route,
{ modelId }
),
},
{
title: 'View On Call Policy',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.STATUS_PAGE_VIEW] as Route,
{ modelId }
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
{/* OnCallDutyPolicy View */}
<CardModelDetail
name="On Call Policy > On Call Policy Details"
cardProps={{
title: 'On Call Policy Details',
description: "Here's more details for this on call policy.",
icon: IconProp.Call,
}}
formSteps={[
{
title: 'On Call Policy Info',
id: 'on-call-policy-info',
},
{
title: 'Labels',
id: 'labels',
},
]}
isEditable={true}
formFields={[
{
field: {
name: true,
},
title: 'Name',
stepId: 'on-call-policy-info',
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: 'On Call Policy Name',
validation: {
minLength: 2,
},
},
{
field: {
description: true,
},
stepId: 'on-call-policy-info',
title: 'Description',
fieldType: FormFieldSchemaType.LongText,
required: true,
placeholder: 'Description',
},
{
field: {
labels: true,
},
title: 'Labels ',
stepId: 'labels',
description:
'Team members with access to these labels will only be able to access this resource. This is optional and an advanced feature.',
fieldType: FormFieldSchemaType.MultiSelectDropdown,
dropdownModal: {
type: Label,
labelField: 'name',
valueField: '_id',
},
required: false,
placeholder: 'Labels',
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 2,
modelType: OnCallDutyPolicy,
id: 'model-detail-monitors',
fields: [
{
field: {
_id: true,
},
title: 'On Call Policy ID',
},
{
field: {
name: true,
},
title: 'Name',
},
{
field: {
labels: {
name: true,
color: true,
},
},
title: 'Labels',
type: FieldType.Text,
getElement: (item: JSONObject): ReactElement => {
return (
<LabelsElement
labels={
JSONFunctions.fromJSON(
(item['labels'] as JSONArray) ||
[],
Label
) as Array<Label>
}
/>
);
},
},
{
field: {
description: true,
},
title: 'Description',
},
],
modelId: modelId,
}}
/>
</ModelPage>
);
};
export default OnCallDutyPolicyView;

View File

@@ -0,0 +1,89 @@
import React, { FunctionComponent, ReactElement } from 'react';
import Route from 'Common/Types/API/Route';
import IconProp from 'Common/Types/Icon/IconProp';
import SideMenu from 'CommonUI/src/Components/SideMenu/SideMenu';
import SideMenuItem from 'CommonUI/src/Components/SideMenu/SideMenuItem';
import SideMenuSection from 'CommonUI/src/Components/SideMenu/SideMenuSection';
import RouteMap, { RouteUtil } from '../../../Utils/RouteMap';
import PageMap from '../../../Utils/PageMap';
import ObjectID from 'Common/Types/ObjectID';
export interface ComponentProps {
modelId: ObjectID;
}
const DashboardSideMenu: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
return (
<SideMenu>
<SideMenuSection title="Basic">
<SideMenuItem
link={{
title: 'Overview',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY_POLICY_VIEW] as Route,
{ modelId: props.modelId }
),
}}
icon={IconProp.Info}
/>
<SideMenuItem
link={{
title: 'Escalation Rules',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_ESCALATION
] as Route,
{ modelId: props.modelId }
),
}}
icon={IconProp.BarsArrowDown}
/>
</SideMenuSection>
<SideMenuSection title="Advanced">
<SideMenuItem
link={{
title: 'Execution Logs',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_EXECUTION_LOGS
] as Route,
{ modelId: props.modelId }
),
}}
icon={IconProp.Logs}
/>
<SideMenuItem
link={{
title: 'Custom Fields',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_CUSTOM_FIELDS
] as Route,
{ modelId: props.modelId }
),
}}
icon={IconProp.TableCells}
/>
<SideMenuItem
link={{
title: 'Delete Policy',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.ON_CALL_DUTY_POLICY_VIEW_DELETE
] as Route,
{ modelId: props.modelId }
),
}}
icon={IconProp.Trash}
className="danger-on-hover"
/>
</SideMenuSection>
</SideMenu>
);
};
export default DashboardSideMenu;

View File

@@ -28,21 +28,21 @@ const ScheduledMaintenanceCustomFields: FunctionComponent<
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Scheduled Maintenances',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Scheduled Maintenance',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route,
modelId
{ modelId }
),
},
{
@@ -51,7 +51,7 @@ const ScheduledMaintenanceCustomFields: FunctionComponent<
RouteMap[
PageMap.SCHEDULED_MAINTENANCE_VIEW_CUSTOM_FIELDS
] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -26,21 +26,21 @@ const IncidentDelete: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Scheduled Maintenance Events',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Scheduled Maintenance Event',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route,
modelId
{ modelId }
),
},
{
@@ -49,7 +49,7 @@ const IncidentDelete: FunctionComponent<PageComponentProps> = (
RouteMap[
PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE
] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -47,21 +47,21 @@ const ScheduledMaintenanceView: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Scheduled Maintenance Events',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Scheduled Maintenance Event',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -39,21 +39,21 @@ const ScheduledMaintenanceDelete: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Scheduled Maintenance Events',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Scheduled Maintenance Event',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route,
modelId
{ modelId }
),
},
{
@@ -62,7 +62,7 @@ const ScheduledMaintenanceDelete: FunctionComponent<PageComponentProps> = (
RouteMap[
PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE
] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -39,21 +39,21 @@ const ScheduledMaintenanceOwners: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Scheduled MaintenanceOwnerss',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Scheduled Maintenance',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route,
modelId
{ modelId }
),
},
{
@@ -62,7 +62,7 @@ const ScheduledMaintenanceOwners: FunctionComponent<PageComponentProps> = (
RouteMap[
PageMap.SCHEDULED_MAINTENANCE_VIEW_OWNERS
] as Route,
modelId
{ modelId }
),
},
]}

View File

@@ -39,21 +39,21 @@ const PublicNote: FunctionComponent<PageComponentProps> = (
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
{ modelId }
),
},
{
title: 'Scheduled Maintenance Events',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route,
modelId
{ modelId }
),
},
{
title: 'View Scheduled Maintenance Event',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route,
modelId
{ modelId }
),
},
{
@@ -62,7 +62,7 @@ const PublicNote: FunctionComponent<PageComponentProps> = (
RouteMap[
PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE
] as Route,
modelId
{ modelId }
),
},
]}

Some files were not shown because too many files have changed in this diff Show More