add scheduled maintenance

This commit is contained in:
Simon Larsen
2022-10-18 13:17:17 +01:00
parent c6c09b3400
commit 8839ccb247
24 changed files with 2177 additions and 322 deletions

View File

@@ -17,12 +17,14 @@ import QueryHelper from '../Types/Database/QueryHelper';
import ObjectID from 'Common/Types/ObjectID';
import OneUptimeDate from 'Common/Types/Date';
import MonitorStatus from 'Model/Models/MonitorStatus';
import { Yellow, Green, Red, Moroon } from 'Common/Types/BrandColors';
import { Yellow, Green, Red, Moroon, Black } from 'Common/Types/BrandColors';
import MonitorStatusService from './MonitorStatusService';
import IncidentState from 'Model/Models/IncidentState';
import IncidentStateService from './IncidentStateService';
import IncidentSeverity from 'Model/Models/IncidentSeverity';
import IncidentSeverityService from './IncidentSeverityService';
import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState';
import ScheduledMaintenanceStateService from './ScheduledMaintenanceStateService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
@@ -79,6 +81,61 @@ export class Service extends DatabaseService<Model> {
return Promise.resolve({ createBy: data, carryForward: null });
}
private async addDefaultScheduledMaintenanceState(createdItem: Model): Promise<Model> {
let createdScheduledMaintenanceState: ScheduledMaintenanceState = new ScheduledMaintenanceState();
createdScheduledMaintenanceState.name = 'Scheduled';
createdScheduledMaintenanceState.description =
'When an event is scheduled, it belongs to this state';
createdScheduledMaintenanceState.color = Black;
createdScheduledMaintenanceState.isScheduledState = true;
createdScheduledMaintenanceState.projectId = createdItem.id!;
createdScheduledMaintenanceState.order = 1;
createdScheduledMaintenanceState = await ScheduledMaintenanceStateService.create({
data: createdScheduledMaintenanceState,
props: {
isRoot: true,
},
});
let ongoingScheduledMaintenanceState: ScheduledMaintenanceState = new ScheduledMaintenanceState();
ongoingScheduledMaintenanceState.name = 'Ongoing';
ongoingScheduledMaintenanceState.description =
'When an event is ongoing, it belongs to this state.';
ongoingScheduledMaintenanceState.color = Yellow;
ongoingScheduledMaintenanceState.isOngoingState = true;
ongoingScheduledMaintenanceState.projectId = createdItem.id!;
ongoingScheduledMaintenanceState.order = 2;
ongoingScheduledMaintenanceState = await ScheduledMaintenanceStateService.create({
data: ongoingScheduledMaintenanceState,
props: {
isRoot: true,
},
});
let completedScheduledMaintenanceState: ScheduledMaintenanceState = new ScheduledMaintenanceState();
completedScheduledMaintenanceState.name = 'Completed';
completedScheduledMaintenanceState.description =
'When an event is completed, it belongs to this state.';
completedScheduledMaintenanceState.color = Green;
completedScheduledMaintenanceState.isResolvedState = true;
completedScheduledMaintenanceState.projectId = createdItem.id!;
completedScheduledMaintenanceState.order = 3;
completedScheduledMaintenanceState = await ScheduledMaintenanceStateService.create({
data: completedScheduledMaintenanceState,
props: {
isRoot: true,
},
});
return createdItem;
}
protected override async onCreateSuccess(
_onCreate: OnCreate<Model>,
createdItem: Model
@@ -86,6 +143,7 @@ export class Service extends DatabaseService<Model> {
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;

View File

@@ -0,0 +1,11 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/ScheduledMaintenanceInternalNote';
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,11 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/ScheduledMaintenancePublicNote';
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,109 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/ScheduledMaintenance';
import DatabaseService, { OnCreate } from './DatabaseService';
import ObjectID from 'Common/Types/ObjectID';
import Monitor from 'Model/Models/Monitor';
import MonitorService from './MonitorService';
import DatabaseCommonInteractionProps from 'Common/Types/Database/DatabaseCommonInteractionProps';
import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline';
import ScheduledMaintenanceStateTimelineService from './ScheduledMaintenanceStateTimelineService';
import CreateBy from '../Types/Database/CreateBy';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState';
import ScheduledMaintenanceStateService from './ScheduledMaintenanceStateService';
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.props.tenantId) {
throw new BadDataException('ProjectId required to create monitor.');
}
const scheduledMaintenanceState: ScheduledMaintenanceState | null =
await ScheduledMaintenanceStateService.findOneBy({
query: {
projectId: createBy.props.tenantId,
isScheduledState: true,
},
select: {
_id: true,
},
props: {
isRoot: true,
},
});
if (!scheduledMaintenanceState || !scheduledMaintenanceState.id) {
throw new BadDataException(
'Created state not found for this project. Please add an operational status'
);
}
createBy.data.currentScheduledMaintenanceStateId = scheduledMaintenanceState.id;
return { createBy, carryForward: null };
}
protected override async onCreateSuccess(
onCreate: OnCreate<Model>,
createdItem: Model
): Promise<Model> {
if (!createdItem.projectId) {
throw new BadDataException('projectId is required');
}
if (!createdItem.id) {
throw new BadDataException('id is required');
}
if (!createdItem.currentScheduledMaintenanceStateId) {
throw new BadDataException('currentScheduledMaintenanceStateId is required');
}
if (createdItem.changeMonitorStatusToId && createdItem.projectId) {
// change status of all the monitors.
await MonitorService.changeMonitorStatus(
createdItem.projectId,
createdItem.monitors?.map((monitor: Monitor) => {
return new ObjectID(monitor._id || '');
}) || [],
createdItem.changeMonitorStatusToId,
onCreate.createBy.props
);
}
await this.changeScheduledMaintenanceState(
createdItem.projectId,
createdItem.id,
createdItem.currentScheduledMaintenanceStateId,
onCreate.createBy.props
);
return createdItem;
}
public async changeScheduledMaintenanceState(
projectId: ObjectID,
scheduledMaintenanceId: ObjectID,
scheduledMaintenanceStateId: ObjectID,
props: DatabaseCommonInteractionProps
): Promise<void> {
const statusTimeline: ScheduledMaintenanceStateTimeline =
new ScheduledMaintenanceStateTimeline();
statusTimeline.scheduledMaintenanceId = scheduledMaintenanceId;
statusTimeline.scheduledMaintenanceStateId = scheduledMaintenanceStateId;
statusTimeline.projectId = projectId;
await ScheduledMaintenanceStateTimelineService.create({
data: statusTimeline,
props: props,
});
}
}
export default new Service();

View File

@@ -0,0 +1,162 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/ScheduledMaintenanceState';
import DatabaseService, {
OnCreate,
OnDelete,
OnUpdate,
} from './DatabaseService';
import CreateBy from '../Types/Database/CreateBy';
import LIMIT_MAX from 'Common/Types/Database/LimitMax';
import ObjectID from 'Common/Types/ObjectID';
import BadDataException from 'Common/Types/Exception/BadDataException';
import QueryHelper from '../Types/Database/QueryHelper';
import SortOrder from 'Common/Types/Database/SortOrder';
import UpdateBy from '../Types/Database/UpdateBy';
import DeleteBy from '../Types/Database/DeleteBy';
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.order) {
throw new BadDataException('Incient State order is required');
}
if (!createBy.data.projectId) {
throw new BadDataException('Incient State projectId is required');
}
await this.rearrangeOrder(
createBy.data.order,
createBy.data.projectId,
true
);
return {
createBy: createBy,
carryForward: null,
};
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<Model>
): Promise<OnDelete<Model>> {
if (!deleteBy.query._id && !deleteBy.props.isRoot) {
throw new BadDataException(
'_id should be present when deleting scheduled maintenance states. Please try the delete with objectId'
);
}
let scheduledMaintenanceState: Model | null = null;
if (!deleteBy.props.isRoot) {
scheduledMaintenanceState = await this.findOneBy({
query: deleteBy.query,
props: {
isRoot: true,
},
select: {
order: true,
projectId: true,
},
});
}
return {
deleteBy,
carryForward: scheduledMaintenanceState,
};
}
protected override async onDeleteSuccess(
onDelete: OnDelete<Model>,
_itemIdsBeforeDelete: ObjectID[]
): Promise<OnDelete<Model>> {
const deleteBy: DeleteBy<Model> = onDelete.deleteBy;
const scheduledMaintenanceState: Model | null = onDelete.carryForward;
if (!deleteBy.props.isRoot && scheduledMaintenanceState) {
if (
scheduledMaintenanceState &&
scheduledMaintenanceState.order &&
scheduledMaintenanceState.projectId
) {
await this.rearrangeOrder(
scheduledMaintenanceState.order,
scheduledMaintenanceState.projectId,
false
);
}
}
return {
deleteBy: deleteBy,
carryForward: null,
};
}
protected override async onBeforeUpdate(
updateBy: UpdateBy<Model>
): Promise<OnUpdate<Model>> {
if (updateBy.data.order && !updateBy.props.isRoot) {
throw new BadDataException(
'Scheduled Maintenance State order should not be updated. Delete this scheduled maintenance state and create a new state with the right order.'
);
}
return { updateBy, carryForward: null };
}
private async rearrangeOrder(
currentOrder: number,
projectId: ObjectID,
increaseOrder: boolean = true
): Promise<void> {
// get scheduledMaintenance with this order.
const scheduledMaintenanceStates: Array<Model> = await this.findBy({
query: {
order: QueryHelper.greaterThanEqualTo(currentOrder),
projectId: projectId,
},
limit: LIMIT_MAX,
skip: 0,
props: {
isRoot: true,
},
select: {
_id: true,
order: true,
},
sort: {
order: SortOrder.Ascending,
},
});
let newOrder: number = currentOrder;
for (const scheduledMaintenanceState of scheduledMaintenanceStates) {
if (increaseOrder) {
newOrder = scheduledMaintenanceState.order! + 1;
} else {
newOrder = scheduledMaintenanceState.order! - 1;
}
await this.updateBy({
query: {
_id: scheduledMaintenanceState._id!,
},
data: {
order: newOrder,
},
props: {
isRoot: true,
},
});
}
}
}
export default new Service();

View File

@@ -0,0 +1,132 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline';
import DatabaseService, { OnCreate, OnDelete } from './DatabaseService';
import BadDataException from 'Common/Types/Exception/BadDataException';
import ScheduledMaintenanceService from './ScheduledMaintenanceService';
import DeleteBy from '../Types/Database/DeleteBy';
import ObjectID from 'Common/Types/ObjectID';
import PositiveNumber from 'Common/Types/PositiveNumber';
import SortOrder from 'Common/Types/Database/SortOrder';
export class Service extends DatabaseService<ScheduledMaintenanceStateTimeline> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(ScheduledMaintenanceStateTimeline, postgresDatabase);
}
protected override async onCreateSuccess(
onCreate: OnCreate<ScheduledMaintenanceStateTimeline>,
createdItem: ScheduledMaintenanceStateTimeline
): Promise<ScheduledMaintenanceStateTimeline> {
if (!createdItem.scheduledMaintenanceId) {
throw new BadDataException('scheduledMaintenanceId is null');
}
if (!createdItem.scheduledMaintenanceStateId) {
throw new BadDataException('scheduledMaintenanceStateId is null');
}
await ScheduledMaintenanceService.updateBy({
query: {
_id: createdItem.scheduledMaintenanceId?.toString(),
},
data: {
currentScheduledMaintenanceStateId: createdItem.scheduledMaintenanceStateId,
},
props: onCreate.createBy.props,
});
return createdItem;
}
protected override async onBeforeDelete(
deleteBy: DeleteBy<ScheduledMaintenanceStateTimeline>
): Promise<OnDelete<ScheduledMaintenanceStateTimeline>> {
if (deleteBy.query._id) {
const scheduledMaintenanceStateTimeline: ScheduledMaintenanceStateTimeline | null =
await this.findOneById({
id: new ObjectID(deleteBy.query._id as string),
select: {
scheduledMaintenanceId: true,
},
props: {
isRoot: true,
},
});
const scheduledMaintenanceId: ObjectID | undefined =
scheduledMaintenanceStateTimeline?.scheduledMaintenanceId;
if (scheduledMaintenanceId) {
const scheduledMaintenanceStateTimeline: PositiveNumber =
await this.countBy({
query: {
scheduledMaintenanceId: scheduledMaintenanceId,
},
props: {
isRoot: true,
},
});
if (scheduledMaintenanceStateTimeline.isOne()) {
throw new BadDataException(
'Cannot delete the only state timeline. Scheduled Maintenance should have atleast one state in its timeline.'
);
}
}
return { deleteBy, carryForward: scheduledMaintenanceId };
}
return { deleteBy, carryForward: null };
}
protected override async onDeleteSuccess(
onDelete: OnDelete<ScheduledMaintenanceStateTimeline>,
_itemIdsBeforeDelete: ObjectID[]
): Promise<OnDelete<ScheduledMaintenanceStateTimeline>> {
if (onDelete.carryForward) {
// this is scheduledMaintenanceId.
const scheduledMaintenanceId: ObjectID = onDelete.carryForward as ObjectID;
// get last status of this monitor.
const scheduledMaintenanceStateTimeline: ScheduledMaintenanceStateTimeline | null =
await this.findOneBy({
query: {
scheduledMaintenanceId: scheduledMaintenanceId,
},
sort: {
createdAt: SortOrder.Descending,
},
props: {
isRoot: true,
},
select: {
_id: true,
scheduledMaintenanceStateId: true,
},
});
if (
scheduledMaintenanceStateTimeline &&
scheduledMaintenanceStateTimeline.scheduledMaintenanceStateId
) {
await ScheduledMaintenanceService.updateBy({
query: {
_id: scheduledMaintenanceId.toString(),
},
data: {
currentScheduledMaintenanceStateId:
scheduledMaintenanceStateTimeline.scheduledMaintenanceStateId,
},
props: {
isRoot: true,
},
});
}
}
return onDelete;
}
}
export default new Service();

View File

@@ -9,7 +9,6 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@ailibs/feather-react-ts": "^3.0.3",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
@@ -39,6 +38,7 @@
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.1.0",
"react-dropzone": "^14.2.2",
"react-feather": "^2.0.10",
"react-icons": "^4.4.0",
"react-markdown": "^8.0.3",
"react-modern-drawer": "^1.1.1",
@@ -8450,16 +8450,6 @@
"integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==",
"dev": true
},
"node_modules/@ailibs/feather-react-ts": {
"version": "3.0.3",
"license": "MIT",
"dependencies": {
"html-react-parser": "^1.1.1"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
"license": "Apache-2.0",
@@ -11605,35 +11595,6 @@
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "1.4.1",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
"entities": "^2.0.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/dom-serializer/node_modules/entities": {
"version": "2.2.0",
"license": "BSD-2-Clause",
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domexception": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
@@ -11646,31 +11607,6 @@
"node": ">=12"
}
},
"node_modules/domhandler": {
"version": "4.3.1",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.2.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "2.8.0",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.2.0"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.170",
"license": "ISC"
@@ -11691,16 +11627,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/entities": {
"version": "3.0.1",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"license": "MIT",
@@ -12270,14 +12196,6 @@
"react-is": "^16.7.0"
}
},
"node_modules/html-dom-parser": {
"version": "1.2.0",
"license": "MIT",
"dependencies": {
"domhandler": "4.3.1",
"htmlparser2": "7.2.0"
}
},
"node_modules/html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
@@ -12295,19 +12213,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/html-react-parser": {
"version": "1.4.14",
"license": "MIT",
"dependencies": {
"domhandler": "4.3.1",
"html-dom-parser": "1.2.0",
"react-property": "2.0.0",
"style-to-js": "1.1.1"
},
"peerDependencies": {
"react": "0.14 || 15 || 16 || 17 || 18"
}
},
"node_modules/html-void-elements": {
"version": "2.0.1",
"license": "MIT",
@@ -12316,23 +12221,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/htmlparser2": {
"version": "7.2.0",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.2",
"domutils": "^2.8.0",
"entities": "^3.0.1"
}
},
"node_modules/http-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
@@ -17944,6 +17832,17 @@
"version": "2.0.4",
"license": "MIT"
},
"node_modules/react-feather": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-feather/-/react-feather-2.0.10.tgz",
"integrity": "sha512-BLhukwJ+Z92Nmdcs+EMw6dy1Z/VLiJTzEQACDUEnWMClhYnFykJCGWQx+NmwP/qQHGX/5CzQ+TGi8ofg2+HzVQ==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": ">=16.8.6"
}
},
"node_modules/react-icons": {
"version": "4.4.0",
"license": "MIT",
@@ -18019,10 +17918,6 @@
"version": "3.2.0",
"license": "MIT"
},
"node_modules/react-property": {
"version": "2.0.0",
"license": "MIT"
},
"node_modules/react-redux": {
"version": "7.2.8",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
@@ -18720,13 +18615,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/style-to-js": {
"version": "1.1.1",
"license": "MIT",
"dependencies": {
"style-to-object": "0.3.0"
}
},
"node_modules/style-to-object": {
"version": "0.3.0",
"license": "MIT",
@@ -19561,12 +19449,6 @@
"integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==",
"dev": true
},
"@ailibs/feather-react-ts": {
"version": "3.0.3",
"requires": {
"html-react-parser": "^1.1.1"
}
},
"@ampproject/remapping": {
"version": "2.2.0",
"requires": {
@@ -24343,22 +24225,6 @@
"csstype": "^3.0.2"
}
},
"dom-serializer": {
"version": "1.4.1",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
"entities": "^2.0.0"
},
"dependencies": {
"entities": {
"version": "2.2.0"
}
}
},
"domelementtype": {
"version": "2.3.0"
},
"domexception": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
@@ -24368,20 +24234,6 @@
"webidl-conversions": "^7.0.0"
}
},
"domhandler": {
"version": "4.3.1",
"requires": {
"domelementtype": "^2.2.0"
}
},
"domutils": {
"version": "2.8.0",
"requires": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.2.0"
}
},
"electron-to-chromium": {
"version": "1.4.170"
},
@@ -24393,9 +24245,6 @@
"version": "8.0.0",
"dev": true
},
"entities": {
"version": "3.0.1"
},
"error-ex": {
"version": "1.3.2",
"requires": {
@@ -24768,13 +24617,6 @@
"react-is": "^16.7.0"
}
},
"html-dom-parser": {
"version": "1.2.0",
"requires": {
"domhandler": "4.3.1",
"htmlparser2": "7.2.0"
}
},
"html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
@@ -24788,27 +24630,9 @@
"version": "2.0.2",
"dev": true
},
"html-react-parser": {
"version": "1.4.14",
"requires": {
"domhandler": "4.3.1",
"html-dom-parser": "1.2.0",
"react-property": "2.0.0",
"style-to-js": "1.1.1"
}
},
"html-void-elements": {
"version": "2.0.1"
},
"htmlparser2": {
"version": "7.2.0",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.2",
"domutils": "^2.8.0",
"entities": "^3.0.1"
}
},
"http-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
@@ -33626,6 +33450,14 @@
"react-fast-compare": {
"version": "2.0.4"
},
"react-feather": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-feather/-/react-feather-2.0.10.tgz",
"integrity": "sha512-BLhukwJ+Z92Nmdcs+EMw6dy1Z/VLiJTzEQACDUEnWMClhYnFykJCGWQx+NmwP/qQHGX/5CzQ+TGi8ofg2+HzVQ==",
"requires": {
"prop-types": "^15.7.2"
}
},
"react-icons": {
"version": "4.4.0",
"requires": {}
@@ -33677,9 +33509,6 @@
}
}
},
"react-property": {
"version": "2.0.0"
},
"react-redux": {
"version": "7.2.8",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.8.tgz",
@@ -34136,12 +33965,6 @@
"version": "3.1.1",
"dev": true
},
"style-to-js": {
"version": "1.1.1",
"requires": {
"style-to-object": "0.3.0"
}
},
"style-to-object": {
"version": "0.3.0",
"requires": {

View File

@@ -11,7 +11,6 @@
"license": "MIT",
"type": "module",
"dependencies": {
"@ailibs/feather-react-ts": "^3.0.3",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
@@ -41,6 +40,7 @@
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.1.0",
"react-dropzone": "^14.2.2",
"react-feather": "^2.0.10",
"react-icons": "^4.4.0",
"react-markdown": "^8.0.3",
"react-modern-drawer": "^1.1.1",

View File

@@ -1,61 +1,61 @@
import Color from 'Common/Types/Color';
import React, { CSSProperties, FunctionComponent, ReactElement } from 'react';
import {
FiHome,
FiGrid,
FiActivity,
FiAlertOctagon,
FiPhoneCall,
FiSettings,
FiBell,
FiCheckCircle,
FiSearch,
FiHelpCircle,
FiDisc,
FiPower,
FiImage,
FiGlobe,
FiMoreVertical,
FiCreditCard,
FiUser,
FiChevronDown,
FiChevronRight,
FiChevronLeft,
FiChevronUp,
FiCircle,
FiSend,
FiMail,
FiBarChart2,
FiSlack,
FiClock,
FiTerminal,
FiAlertTriangle,
FiCode,
FiPieChart,
FiUsers,
FiRss,
FiLock,
FiKey,
FiType,
FiFolder,
FiShare2,
FiMessageSquare,
FiInfo,
FiCheck,
FiTrash,
FiX,
FiPlus,
FiTag,
FiRefreshCcw,
FiFilter,
FiEdit2,
FiEyeOff,
FiFileText,
FiList,
FiLink2,
FiExternalLink,
FiLayers
} from 'react-icons/fi';
Home,
Grid,
Activity,
AlertOctagon,
PhoneCall,
Settings,
Bell,
CheckCircle,
Search,
HelpCircle,
Disc,
Power,
Image,
Globe,
MoreVertical,
CreditCard,
User,
ChevronDown,
ChevronRight,
ChevronLeft,
ChevronUp,
Circle,
Send,
Mail,
BarChart2,
Slack,
Clock,
Terminal,
AlertTriangle,
Code,
PieChart,
Users,
Rss,
Lock,
Key,
Type,
Folder,
Share2,
MessageSquare,
Info,
Check,
Trash,
X,
Plus,
Tag,
RefreshCcw,
Filter,
Edit2,
EyeOff,
FileText,
List,
Link2,
ExternalLink,
Layers,
} from 'react-feather';
export enum SizeProp {
ExtraSmall = '8px',
@@ -97,7 +97,7 @@ export enum IconProp {
List,
CheckCircle,
Search,
TextFile,
Textle,
Globe,
Logout,
Billing,
@@ -135,7 +135,8 @@ export enum IconProp {
SendMessage,
ExternalLink,
Link,
Layers
Layers,
Clock
}
export interface ComponentProps {
@@ -168,175 +169,175 @@ const Icon: FunctionComponent<ComponentProps> = ({
}}
>
{icon === IconProp.Home && (
<FiHome
<Home
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.More && (
<FiGrid
<Grid
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Activity && (
<FiActivity
<Activity
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Alert && (
<FiAlertOctagon
<AlertOctagon
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Call && (
<FiPhoneCall
<PhoneCall
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Settings && (
<FiSettings
<Settings
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Notification && (
<FiBell
<Bell
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.CheckCircle && (
<FiCheckCircle
<CheckCircle
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Search && (
<FiSearch
<Search
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Help && (
<FiHelpCircle
<HelpCircle
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Logout && (
<FiPower
<Power
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Billing && (
<FiCreditCard
<CreditCard
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.User && (
<FiUser
<User
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.ChevronDown && (
<FiChevronDown
<ChevronDown
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.ChevronLeft && (
<FiChevronLeft
<ChevronLeft
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.ChevronRight && (
<FiChevronRight
<ChevronRight
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.ChevronUp && (
<FiChevronUp
<ChevronUp
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Email && (
<FiMail
<Mail
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Slack && (
<FiSlack
<Slack
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Time && (
<FiClock
<Clock
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Terminal && (
<FiTerminal
<Terminal
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Error && (
<FiAlertTriangle
<AlertTriangle
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Code && (
<FiCode
<Code
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Report && (
<FiPieChart
<PieChart
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Team && (
<FiUsers
<Users
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -344,7 +345,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Lock && (
<FiLock
<Lock
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -352,7 +353,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Key && (
<FiKey
<Key
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -360,7 +361,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Folder && (
<FiFolder
<Folder
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -368,7 +369,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Integrations && (
<FiShare2
<Share2
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -376,105 +377,105 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.SMS && (
<FiMessageSquare
<MessageSquare
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Info && (
<FiInfo
<Info
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Success && (
<FiCheck
<Check
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Trash && (
<FiTrash
<Trash
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Close && (
<FiX
<X
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Add && (
<FiPlus
<Plus
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Label && (
<FiTag
<Tag
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Refresh && (
<FiRefreshCcw
<RefreshCcw
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Filter && (
<FiFilter
<Filter
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Edit && (
<FiEdit2
<Edit2
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Hide && (
<FiEyeOff
<EyeOff
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Check && (
<FiCheck
<Check
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.True && (
<FiCheck
<Check
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.False && (
<FiX
<X
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.List && (
<FiList
<List
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -482,7 +483,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Public && (
<FiUser
<User
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -490,7 +491,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Circle && (
<FiCircle
<Circle
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -498,7 +499,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Graph && (
<FiBarChart2
<BarChart2
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -506,7 +507,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Globe && (
<FiGlobe
<Globe
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -514,7 +515,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Image && (
<FiImage
<Image
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -522,7 +523,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Text && (
<FiType
<Type
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -530,7 +531,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Drag && (
<FiMoreVertical
<MoreVertical
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -538,7 +539,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Webhook && (
<FiLink2
<Link2
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -546,7 +547,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Link && (
<FiLink2
<Link2
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -554,7 +555,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.SendMessage && (
<FiSend
<Send
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -562,15 +563,15 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.Disc && (
<FiDisc
<Disc
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.TextFile && (
<FiFileText
{icon === IconProp.Textle && (
<FileText
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -578,7 +579,7 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.ExternalLink && (
<FiExternalLink
<ExternalLink
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
@@ -586,14 +587,21 @@ const Icon: FunctionComponent<ComponentProps> = ({
)}
{icon === IconProp.RSS && (
<FiRss
<Rss
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Layers && (
<FiLayers
<Layers
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}
/>
)}
{icon === IconProp.Clock && (
<Clock
size={size}
strokeWidth={thick ? thick : ''}
color={color ? color.toString() : ''}

View File

@@ -43,6 +43,15 @@ import IncidentInternalNote from './Pages/Incidents/View/InternalNote';
import IncidentPublicNote from './Pages/Incidents/View/PublicNote';
import UnresolvedIncidents from './Pages/Incidents/Unresolved';
import ScheduledMaintenanceEvents from './Pages/ScheduledMaintenanceEvents/ScheduledMaintenanceEvents';
import ScheduledMaintenanceEventView from './Pages/ScheduledMaintenanceEvents/View/Index';
import ScheduledMaintenanceEventViewDelete from './Pages/ScheduledMaintenanceEvents/View/Delete';
import ScheduledMaintenanceEventViewStateTimeline from './Pages/ScheduledMaintenanceEvents/View/StateTimeline';
import ScheduledMaintenanceEventInternalNote from './Pages/ScheduledMaintenanceEvents/View/InternalNote';
import ScheduledMaintenanceEventPublicNote from './Pages/ScheduledMaintenanceEvents/View/PublicNote';
import OngoingScheduledMaintenanceEvents from './Pages/ScheduledMaintenanceEvents/Ongoing';
import Logs from './Pages/Logs/Logs';
import Navigation from 'CommonUI/src/Utils/Navigation';
import RouteMap from './Utils/RouteMap';
@@ -59,6 +68,7 @@ import SettingsTeams from './Pages/Settings/Teams';
import SettingsTeamView from './Pages/Settings/TeamView';
import SettingsMonitors from './Pages/Settings/MonitorStatus';
import SettingsIncidents from './Pages/Settings/IncidentState';
import SettingsScheduledMaintenanceState from './Pages/Settings/ScheduledMaintenanceState';
import SettingsDomains from './Pages/Settings/Domains';
import SettingsIncidentSeverity from './Pages/Settings/IncidentSeverity';
@@ -674,6 +684,105 @@ const App: FunctionComponent = () => {
}
/>
{/* Scheduled Events */}
<PageRoute
path={RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS]?.toString()}
element={
<ScheduledMaintenanceEvents
pageRoute={RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route}
currentProject={selectedProject}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS]?.toString()}
element={
<OngoingScheduledMaintenanceEvents
pageRoute={
RouteMap[PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS] as Route
}
currentProject={selectedProject}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW]?.toString()}
element={
<ScheduledMaintenanceEventView
pageRoute={RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW] as Route}
currentProject={selectedProject}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE]?.toString()}
element={
<ScheduledMaintenanceEventViewDelete
pageRoute={
RouteMap[PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE] as Route
}
currentProject={selectedProject}
/>
}
/>
<PageRoute
path={RouteMap[
PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE
]?.toString()}
element={
<ScheduledMaintenanceEventViewStateTimeline
pageRoute={
RouteMap[
PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE
] as Route
}
currentProject={selectedProject}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE]?.toString()}
element={
<ScheduledMaintenanceEventInternalNote
pageRoute={
RouteMap[
PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE
] as Route
}
currentProject={selectedProject}
/>
}
/>
<PageRoute
path={RouteMap[PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE]?.toString()}
element={
<ScheduledMaintenanceEventPublicNote
pageRoute={
RouteMap[PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE] as Route
}
currentProject={selectedProject}
/>
}
/>
{/* Logs */}
<PageRoute
@@ -748,6 +857,22 @@ const App: FunctionComponent = () => {
}
/>
<PageRoute
path={RouteMap[
PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE
]?.toString()}
element={
<SettingsScheduledMaintenanceState
pageRoute={
RouteMap[
PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE
] as Route
}
currentProject={selectedProject}
/>
}
/>
<PageRoute
path={RouteMap[
PageMap.SETTINGS_INCIDENTS_SEVERITY

View File

@@ -57,13 +57,15 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
></NavBarItem>
<NavBarItem
title="On-Call Duty"
title="Scheduled Maintenance"
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route
RouteMap[PageMap.SCHEDULED_MAINTENANCE_EVENTS] as Route
)}
icon={IconProp.Call}
icon={IconProp.Clock}
></NavBarItem>
<NavBarItem
title="Status Pages"
icon={IconProp.CheckCircle}
@@ -89,6 +91,13 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
)}
icon={IconProp.Error}
/>
<NavBarMenuItem
title="On-Call Duty"
route={RouteUtil.populateRouteParams(
RouteMap[PageMap.ON_CALL_DUTY] as Route
)}
icon={IconProp.Call}
></NavBarMenuItem>
</NavBarMenuColumn>
<NavBarMenuColumn title="Advanced">
<NavBarMenuItem

View File

@@ -0,0 +1,49 @@
import Page from 'CommonUI/src/Components/Page/Page';
import React, { FunctionComponent, ReactElement } from 'react';
import PageComponentProps from '../PageComponentProps';
import RouteMap from '../../Utils/RouteMap';
import PageMap from '../../Utils/PageMap';
import Route from 'Common/Types/API/Route';
import IncidentsTable from '../../Components/Incident/IncidentsTable';
import SideMenu from './SideMenu';
const IncidentsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
return (
<Page
title={'Incidents'}
sideMenu={<SideMenu project={props.currentProject || undefined} />}
breadcrumbLinks={[
{
title: 'Project',
to: RouteMap[PageMap.HOME] as Route,
},
{
title: 'Incidents',
to: RouteMap[PageMap.INCIDENTS] as Route,
},
{
title: 'Unresolved Incidents',
to: RouteMap[PageMap.UNRESOLVED_INCIDENTS] as Route,
},
]}
>
<IncidentsTable
currentProject={props.currentProject || undefined}
viewPageRoute={RouteMap[PageMap.INCIDENTS] as Route}
query={{
projectId: props.currentProject?._id,
currentIncidentState: {
isResolvedState: false,
},
}}
noItemsMessage="Nice work! No unresolved incidents so far."
title="Unresolved Incidents"
description="Here is a list of all the unresolved incidents for this project."
/>
</Page>
);
};
export default IncidentsPage;

View File

@@ -0,0 +1,39 @@
import Page from 'CommonUI/src/Components/Page/Page';
import React, { FunctionComponent, ReactElement } from 'react';
import PageComponentProps from '../PageComponentProps';
import RouteMap from '../../Utils/RouteMap';
import PageMap from '../../Utils/PageMap';
import Route from 'Common/Types/API/Route';
import IncidentsTable from '../../Components/Incident/IncidentsTable';
import SideMenu from './SideMenu';
const IncidentsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
return (
<Page
title={'Incidents'}
sideMenu={<SideMenu project={props.currentProject || undefined} />}
breadcrumbLinks={[
{
title: 'Project',
to: RouteMap[PageMap.HOME] as Route,
},
{
title: 'Incidents',
to: RouteMap[PageMap.INCIDENTS] as Route,
},
]}
>
<IncidentsTable
currentProject={props.currentProject || undefined}
viewPageRoute={props.pageRoute}
query={{
projectId: props.currentProject?._id,
}}
/>
</Page>
);
};
export default IncidentsPage;

View File

@@ -0,0 +1,56 @@
import React, { FunctionComponent, ReactElement } from 'react';
import Route from 'Common/Types/API/Route';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
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 { BadgeType } from 'CommonUI/src/Components/Badge/Badge';
import Incident from 'Model/Models/Incident';
import Project from 'Model/Models/Project';
import CountModelSideMenuItem from 'CommonUI/src/Components/SideMenu/CountModelSideMenuItem';
export interface ComponentProps {
project?: Project | undefined;
}
const DashboardSideMenu: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
return (
<SideMenu>
<SideMenuSection title="Overview">
<SideMenuItem
link={{
title: 'All Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route
),
}}
icon={IconProp.List}
/>
<CountModelSideMenuItem<Incident>
link={{
title: 'Unresolved Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.UNRESOLVED_INCIDENTS] as Route
),
}}
icon={IconProp.Alert}
badgeType={BadgeType.DANGER}
modelType={Incident}
countQuery={{
projectId: props.project?._id,
currentIncidentState: {
isResolvedState: false,
},
}}
/>
</SideMenuSection>
</SideMenu>
);
};
export default DashboardSideMenu;

View File

@@ -0,0 +1,66 @@
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 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 Incident from 'Model/Models/Incident';
const IncidentDelete: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = new ObjectID(
Navigation.getLastParam(1)?.toString().substring(1) || ''
);
return (
<Page
title={'Incidents'}
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
),
},
{
title: 'Delete Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_DELETE] as Route,
modelId
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<ModelDelete
modelType={Incident}
modelId={modelId}
onDeleteSuccess={() => {
Navigation.navigate(RouteMap[PageMap.INCIDENTS] as Route);
}}
/>
</Page>
);
};
export default IncidentDelete;

View File

@@ -0,0 +1,369 @@
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 SideMenu from './SideMenu';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import CardModelDetail from 'CommonUI/src/Components/ModelDetail/CardModelDetail';
import Navigation from 'CommonUI/src/Utils/Navigation';
import { JSONArray, JSONObject } from 'Common/Types/JSON';
import ObjectID from 'Common/Types/ObjectID';
import BadDataException from 'Common/Types/Exception/BadDataException';
import Incident from 'Model/Models/Incident';
import Color from 'Common/Types/Color';
import Pill from 'CommonUI/src/Components/Pill/Pill';
import MonitorsElement from '../../../Components/Monitor/Monitors';
import Monitor from 'Model/Models/Monitor';
import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline';
import ModelAPI, { ListResult } from 'CommonUI/src/Utils/ModelAPI/ModelAPI';
import ChangeIncidentState, {
IncidentType,
} from '../../../Components/Incident/ChangeState';
import BaseModel from 'Common/Models/BaseModel';
import IncidentSeverity from 'Model/Models/IncidentSeverity';
import Label from 'Model/Models/Label';
import LabelsElement from '../../../Components/Label/Labels';
const IncidentView: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = new ObjectID(
Navigation.getLastParam()?.toString().substring(1) || ''
);
return (
<Page
title={'Incidents'}
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
{/* Incident View */}
<CardModelDetail
cardProps={{
title: 'Incident Details',
description: "Here's more details for this monitor.",
icon: IconProp.Activity,
}}
isEditable={true}
formFields={[
{
field: {
title: true,
},
title: 'Incident Title',
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: 'Incident Title',
validation: {
minLength: 2,
},
},
{
field: {
description: true,
},
title: 'Description',
fieldType: FormFieldSchemaType.LongText,
required: true,
placeholder: 'Description',
},
{
field: {
incidentSeverity: true,
},
title: 'Incident Severity',
description: 'What type of incident is this?',
fieldType: FormFieldSchemaType.Dropdown,
dropdownModal: {
type: IncidentSeverity,
labelField: 'name',
valueField: '_id',
},
required: true,
placeholder: 'Incident Severity',
},
{
field: {
labels: true,
},
title: 'Labels (Optional)',
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={{
onBeforeFetch: async (): Promise<JSONObject> => {
// get ack incident.
const incidentTimelines: ListResult<IncidentStateTimeline> =
await ModelAPI.getList(
IncidentStateTimeline,
{
incidentId: modelId,
},
99,
0,
{
_id: true,
createdAt: true,
},
{},
{
createdByUser: {
name: true,
email: true,
},
incidentState: {
name: true,
isResolvedState: true,
isAcknowledgedState: true,
},
}
);
return incidentTimelines;
},
showDetailsInNumberOfColumns: 2,
modelType: Incident,
id: 'model-detail-incidents',
fields: [
{
field: {
_id: true,
},
title: 'Incident ID',
fieldType: FieldType.ObjectID,
},
{
field: {
title: true,
},
title: 'Incident Title',
fieldType: FieldType.Text,
},
{
field: {
description: true,
},
title: 'Description',
fieldType: FieldType.LongText,
},
{
field: {
currentIncidentState: {
color: true,
name: true,
},
},
title: 'Current State',
fieldType: FieldType.Entity,
getElement: (item: JSONObject): ReactElement => {
if (!item['currentIncidentState']) {
throw new BadDataException(
'Incident Status not found'
);
}
return (
<Pill
color={
(
item[
'currentIncidentState'
] as JSONObject
)['color'] as Color
}
text={
(
item[
'currentIncidentState'
] as JSONObject
)['name'] as string
}
/>
);
},
},
{
field: {
incidentSeverity: {
color: true,
name: true,
},
},
title: 'Incident Severity',
fieldType: FieldType.Entity,
getElement: (item: JSONObject): ReactElement => {
if (!item['incidentSeverity']) {
throw new BadDataException(
'Incident Severity not found'
);
}
return (
<Pill
color={
(
item[
'incidentSeverity'
] as JSONObject
)['color'] as Color
}
text={
(
item[
'incidentSeverity'
] as JSONObject
)['name'] as string
}
/>
);
},
},
{
field: {
monitors: {
name: true,
_id: true,
},
},
title: 'Monitors Affected',
fieldType: FieldType.Text,
getElement: (item: JSONObject): ReactElement => {
return (
<MonitorsElement
monitors={
Monitor.fromJSON(
(item[
'monitors'
] as JSONArray) || [],
Monitor
) as Array<Monitor>
}
/>
);
},
},
{
field: {
createdAt: true,
},
title: 'Created At',
fieldType: FieldType.DateTime,
},
{
field: {
labels: {
name: true,
color: true,
},
},
title: 'Labels',
type: FieldType.Text,
getElement: (item: JSONObject): ReactElement => {
return (
<LabelsElement
labels={
Label.fromJSON(
(item['labels'] as JSONArray) ||
[],
Label
) as Array<Label>
}
/>
);
},
},
{
title: 'Acknowledge Incident',
fieldType: FieldType.Text,
getElement: (
_item: JSONObject,
onBeforeFetchData: JSONObject,
fetchItems: Function
): ReactElement => {
return (
<ChangeIncidentState
incidentId={modelId}
incidentTimeline={
onBeforeFetchData[
'data'
] as Array<BaseModel>
}
incidentType={IncidentType.Ack}
onActionComplete={() => {
fetchItems();
}}
/>
);
},
},
{
title: 'Resolve Incident',
fieldType: FieldType.Text,
getElement: (
_item: JSONObject,
onBeforeFetchData: JSONObject,
fetchItems: Function
): ReactElement => {
return (
<ChangeIncidentState
incidentId={modelId}
incidentTimeline={
onBeforeFetchData[
'data'
] as Array<BaseModel>
}
incidentType={IncidentType.Resolve}
onActionComplete={() => {
fetchItems();
}}
/>
);
},
},
],
modelId: modelId,
}}
/>
</Page>
);
};
export default IncidentView;

View File

@@ -0,0 +1,155 @@
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 SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import IncidentInternalNote from 'Model/Models/IncidentInternalNote';
import ModelTable, {
ShowTableAs,
} from 'CommonUI/src/Components/ModelTable/ModelTable';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import UserElement from '../../../Components/User/User';
import User from 'Model/Models/User';
const IncidentDelete: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = new ObjectID(
Navigation.getLastParam(1)?.toString().substring(1) || ''
);
return (
<Page
title={'Incidents'}
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
),
},
{
title: 'Private Notes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route,
modelId
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<ModelTable<IncidentInternalNote>
modelType={IncidentInternalNote}
id="table-incident-internal-note"
isDeleteable={true}
isCreateable={true}
isEditable={true}
isViewable={false}
query={{
incidentId: modelId,
projectId: props.currentProject?._id,
}}
onBeforeCreate={(
item: IncidentInternalNote
): Promise<IncidentInternalNote> => {
if (!props.currentProject || !props.currentProject.id) {
throw new BadDataException('Project ID cannot be null');
}
item.incidentId = modelId;
item.projectId = props.currentProject.id;
return Promise.resolve(item);
}}
cardProps={{
icon: IconProp.Lock,
title: 'Private Notes',
description: 'Here are private notes for this incident.',
}}
noItemsMessage={
'No private notes created for this incident so far.'
}
formFields={[
{
field: {
note: true,
},
title: 'Private Incident Note',
description:
'This is in markdown. This note is private to your team members and is not visible to public.',
fieldType: FormFieldSchemaType.Markdown,
required: true,
placeholder:
'Add a private note to this incident here.',
},
]}
showRefreshButton={true}
viewPageRoute={props.pageRoute}
showTableAs={ShowTableAs.List}
columns={[
{
field: {
note: true,
},
title: 'Note',
type: FieldType.Markdown,
},
{
field: {
createdByUser: {
name: true,
email: true,
},
},
title: 'Posted By',
type: FieldType.Entity,
getElement: (item: JSONObject): ReactElement => {
if (item['createdByUser']) {
return (
<UserElement
user={new User().fromJSON(
item['createdByUser'] as JSONObject,
User
)}
/>
);
}
return <></>;
},
},
{
field: {
createdAt: true,
},
title: 'Posted At',
type: FieldType.DateTime,
},
]}
/>
</Page>
);
};
export default IncidentDelete;

View File

@@ -0,0 +1,156 @@
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 SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import IncidentPublicNote from 'Model/Models/IncidentPublicNote';
import ModelTable, {
ShowTableAs,
} from 'CommonUI/src/Components/ModelTable/ModelTable';
import BadDataException from 'Common/Types/Exception/BadDataException';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import UserElement from '../../../Components/User/User';
import User from 'Model/Models/User';
const PublicNote: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = new ObjectID(
Navigation.getLastParam(1)?.toString().substring(1) || ''
);
return (
<Page
title={'Incidents'}
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
),
},
{
title: 'Internal Notes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route,
modelId
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<ModelTable<IncidentPublicNote>
modelType={IncidentPublicNote}
id="table-incident-internal-note"
isDeleteable={true}
isCreateable={true}
isEditable={true}
isViewable={false}
query={{
incidentId: modelId,
projectId: props.currentProject?._id,
}}
onBeforeCreate={(
item: IncidentPublicNote
): Promise<IncidentPublicNote> => {
if (!props.currentProject || !props.currentProject.id) {
throw new BadDataException('Project ID cannot be null');
}
item.incidentId = modelId;
item.projectId = props.currentProject.id;
return Promise.resolve(item);
}}
cardProps={{
icon: IconProp.User,
title: 'Public Notes',
description:
'Here are public notes for this incident. This will show up on the status page.',
}}
noItemsMessage={
'No public notes created for this incident so far.'
}
formFields={[
{
field: {
note: true,
},
title: 'Public Incident Note',
description:
'This is in markdown. This note is private to your team members and is not visible to public.',
fieldType: FormFieldSchemaType.Markdown,
required: true,
placeholder:
'Add a private note to this incident here.',
},
]}
showRefreshButton={true}
viewPageRoute={props.pageRoute}
showTableAs={ShowTableAs.List}
columns={[
{
field: {
note: true,
},
title: 'Note',
type: FieldType.Markdown,
},
{
field: {
createdByUser: {
name: true,
email: true,
},
},
title: 'Posted By',
type: FieldType.Entity,
getElement: (item: JSONObject): ReactElement => {
if (item['createdByUser']) {
return (
<UserElement
user={new User().fromJSON(
item['createdByUser'] as JSONObject,
User
)}
/>
);
}
return <></>;
},
},
{
field: {
createdAt: true,
},
title: 'Posted At',
type: FieldType.DateTime,
},
]}
/>
</Page>
);
};
export default PublicNote;

View File

@@ -0,0 +1,85 @@
import React, { FunctionComponent, ReactElement } from 'react';
import Route from 'Common/Types/API/Route';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
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.INCIDENT_VIEW] as Route,
props.modelId
),
}}
icon={IconProp.Info}
/>
<SideMenuItem
link={{
title: 'State Timeline',
to: RouteUtil.populateRouteParams(
RouteMap[
PageMap.INCIDENT_VIEW_STATE_TIMELINE
] as Route,
props.modelId
),
}}
icon={IconProp.List}
/>
</SideMenuSection>
<SideMenuSection title="Incident Notes">
<SideMenuItem
link={{
title: 'Private Notes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_INTERNAL_NOTE] as Route,
props.modelId
),
}}
icon={IconProp.Lock}
/>
<SideMenuItem
link={{
title: 'Public Notes',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_PUBLIC_NOTE] as Route,
props.modelId
),
}}
icon={IconProp.Public}
/>
</SideMenuSection>
<SideMenuSection title="Advanced">
<SideMenuItem
link={{
title: 'Delete Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_DELETE] as Route,
props.modelId
),
}}
icon={IconProp.Trash}
className="danger-on-hover"
/>
</SideMenuSection>
</SideMenu>
);
};
export default DashboardSideMenu;

View File

@@ -0,0 +1,158 @@
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 SideMenu from './SideMenu';
import Navigation from 'CommonUI/src/Utils/Navigation';
import ObjectID from 'Common/Types/ObjectID';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import IncidentStateTimeline from 'Model/Models/IncidentStateTimeline';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import BadDataException from 'Common/Types/Exception/BadDataException';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import IncidentState from 'Model/Models/IncidentState';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import Color from 'Common/Types/Color';
import Pill from 'CommonUI/src/Components/Pill/Pill';
const IncidentDelete: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
const modelId: ObjectID = new ObjectID(
Navigation.getLastParam(1)?.toString().substring(1) || ''
);
return (
<Page
title={'Incidents'}
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route,
modelId
),
},
{
title: 'Incidents',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENTS] as Route,
modelId
),
},
{
title: 'View Incident',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW] as Route,
modelId
),
},
{
title: 'Status Timeline',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.INCIDENT_VIEW_STATE_TIMELINE] as Route,
modelId
),
},
]}
sideMenu={<SideMenu modelId={modelId} />}
>
<ModelTable<IncidentStateTimeline>
modelType={IncidentStateTimeline}
id="table-incident-status-timeline"
isDeleteable={true}
isCreateable={true}
isViewable={false}
query={{
incidentId: modelId,
projectId: props.currentProject?._id,
}}
onBeforeCreate={(
item: IncidentStateTimeline
): Promise<IncidentStateTimeline> => {
if (!props.currentProject || !props.currentProject.id) {
throw new BadDataException('Project ID cannot be null');
}
item.incidentId = modelId;
item.projectId = props.currentProject.id;
return Promise.resolve(item);
}}
cardProps={{
icon: IconProp.List,
title: 'Status Timeline',
description:
'Here is the status timeline for this incident',
}}
noItemsMessage={
'No status timeline created for this incident so far.'
}
formFields={[
{
field: {
incidentState: true,
},
title: 'Incident Status',
fieldType: FormFieldSchemaType.Dropdown,
required: true,
placeholder: 'Incident Status',
dropdownModal: {
type: IncidentState,
labelField: 'name',
valueField: '_id',
},
},
]}
showRefreshButton={true}
showFilterButton={true}
viewPageRoute={props.pageRoute}
columns={[
{
field: {
incidentState: {
name: true,
color: true,
},
},
title: 'Incident Status',
type: FieldType.Text,
isFilterable: true,
getElement: (item: JSONObject): ReactElement => {
if (!item['incidentState']) {
throw new BadDataException(
'Incident Status not found'
);
}
return (
<Pill
color={
(item['incidentState'] as JSONObject)[
'color'
] as Color
}
text={
(item['incidentState'] as JSONObject)[
'name'
] as string
}
/>
);
},
},
{
field: {
createdAt: true,
},
title: 'Reported At',
type: FieldType.DateTime,
},
]}
/>
</Page>
);
};
export default IncidentDelete;

View File

@@ -0,0 +1,158 @@
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 from '../../Utils/RouteMap';
import PageComponentProps from '../PageComponentProps';
import DashboardSideMenu from './SideMenu';
import ModelTable, {
ShowTableAs,
} from 'CommonUI/src/Components/ModelTable/ModelTable';
import IncidentState from 'Model/Models/IncidentState';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import { JSONObject } from 'Common/Types/JSON';
import Pill from 'CommonUI/src/Components/Pill/Pill';
import Color from 'Common/Types/Color';
import SortOrder from 'Common/Types/Database/SortOrder';
import BadDataException from 'Common/Types/Exception/BadDataException';
const IncidentsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps
): ReactElement => {
return (
<Page
title={'Project Settings'}
breadcrumbLinks={[
{
title: 'Project',
to: RouteMap[PageMap.HOME] as Route,
},
{
title: 'Settings',
to: RouteMap[PageMap.SETTINGS] as Route,
},
{
title: 'Incidents',
to: RouteMap[PageMap.SETTINGS_INCIDENTS_STATE] as Route,
},
]}
sideMenu={<DashboardSideMenu />}
>
<ModelTable<IncidentState>
modelType={IncidentState}
id="incident-state-table"
isDeleteable={true}
isEditable={true}
isCreateable={true}
cardProps={{
icon: IconProp.Disc,
title: 'Incident State',
description:
'Incidents have multiple states like - created, acknowledged and resolved. You can more states help you manage incidents here.',
}}
sortBy="order"
sortOrder={SortOrder.Ascending}
onBeforeDelete={(item: IncidentState) => {
if (item.isCreatedState) {
throw new BadDataException(
'This incident cannot be deleted because its the created incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.'
);
}
if (item.isAcknowledgedState) {
throw new BadDataException(
'This incident cannot be deleted because its the acknowledged incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.'
);
}
if (item.isResolvedState) {
throw new BadDataException(
'This incident cannot be deleted because its the resolved incident state of for this project. Created, Acknowledged, Resolved incident states cannot be deleted.'
);
}
return item;
}}
selectMoreFields={{
color: true,
isCreatedState: true,
isAcknowledgedState: true,
isResolvedState: true,
order: true,
}}
columns={[
{
field: {
name: true,
},
title: 'Name',
type: FieldType.Text,
getElement: (item: JSONObject): ReactElement => {
return (
<Pill
color={item['color'] as Color}
text={item['name'] as string}
/>
);
},
},
{
field: {
description: true,
},
title: 'Description',
type: FieldType.Text,
},
]}
noItemsMessage={'No incident state found.'}
viewPageRoute={props.pageRoute}
formFields={[
{
field: {
name: true,
},
title: 'Name',
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: 'Investigating',
validation: {
minLength: 2,
},
},
{
field: {
description: true,
},
title: 'Description',
fieldType: FormFieldSchemaType.LongText,
required: true,
placeholder:
'This incident state happens when the incident is investigated',
},
{
field: {
color: true,
},
title: 'Color',
fieldType: FormFieldSchemaType.Color,
required: true,
placeholder:
'Please select color for this incident state.',
},
]}
showRefreshButton={true}
showTableAs={ShowTableAs.OrderedStatesList}
orderedStatesListProps={{
titleField: 'name',
descriptionField: 'description',
orderField: 'order',
shouldAddItemInTheEnd: true,
}}
/>
</Page>
);
};
export default IncidentsPage;

View File

@@ -16,6 +16,14 @@ enum PageMap {
INCIDENT_INTERNAL_NOTE = 'INCIDENT_INTERNAL_NOTE',
INCIDENT_PUBLIC_NOTE = 'INCIDENT_PUBLIC_NOTE',
SCHEDULED_MAINTENANCE_EVENTS = 'SCHEDULED_MAINTENANCE_EVENTS',
ONGOING_SCHEDULED_MAINTENANCE_EVENTS = 'ONGOING_SCHEDULED_MAINTENANCE_EVENTS',
SCHEDULED_MAINTENANCE_VIEW = 'SCHEDULED_MAINTENANCE_VIEW',
SCHEDULED_MAINTENANCE_VIEW_DELETE = 'SCHEDULED_MAINTENANCE_VIEW_DELETE',
SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE = 'SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE',
SCHEDULED_MAINTENANCE_INTERNAL_NOTE = 'SCHEDULED_MAINTENANCE_INTERNAL_NOTE',
SCHEDULED_MAINTENANCE_PUBLIC_NOTE = 'SCHEDULED_MAINTENANCE_PUBLIC_NOTE',
MONITORS = 'MONITORS',
MONITORS_INOPERATIONAL = 'MONITORS_INOPERATIONAL',
MONITOR_VIEW = 'MONITOR_VIEW',
@@ -64,8 +72,14 @@ enum PageMap {
// Resource settings.
SETTINGS_INCIDENTS_STATE = 'SETTINGS_INCIDENTS_STATE',
SETTINGS_INCIDENTS_SEVERITY = 'SETTINGS_INCIDENTS_SEVERITY',
// monitors
SETTINGS_MONITORS_STATUS = 'SETTINGS_MONITORS_STATUS',
// Scheduled Events
SETTINGS_SCHEDULED_MAINTENANCE_STATE = 'SETTINGS_SCHEDULED_MAINTENANCE_STATE',
// Labels.
SETTINGS_LABELS = 'SETTINGS_LABELS',

View File

@@ -18,6 +18,10 @@ const RouteMap: Dictionary<Route> = {
`/dashboard/${RouteParams.ProjectID}/home/monitors-inoperational`
),
[PageMap.MONITOR_VIEW_INCIDENTS]: new Route(
`/dashboard/${RouteParams.ProjectID}/monitor/${RouteParams.ModelID}/incidents`
),
[PageMap.INCIDENTS]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents`
),
@@ -34,9 +38,7 @@ const RouteMap: Dictionary<Route> = {
`/dashboard/${RouteParams.ProjectID}/incidents/${RouteParams.ModelID}/state-timeline`
),
[PageMap.MONITOR_VIEW_INCIDENTS]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents/${RouteParams.ModelID}/incidents`
),
[PageMap.INCIDENT_VIEW_DELETE]: new Route(
`/dashboard/${RouteParams.ProjectID}/incidents/${RouteParams.ModelID}/delete`
@@ -50,6 +52,39 @@ const RouteMap: Dictionary<Route> = {
`/dashboard/${RouteParams.ProjectID}/incidents/${RouteParams.ModelID}/public-notes`
),
[PageMap.SCHEDULED_MAINTENANCE_EVENTS]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events`
),
[PageMap.ONGOING_SCHEDULED_MAINTENANCE_EVENTS]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/ongoing`
),
[PageMap.SCHEDULED_MAINTENANCE_VIEW]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${RouteParams.ModelID}`
),
[PageMap.SCHEDULED_MAINTENANCE_VIEW_STATE_TIMELINE]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${RouteParams.ModelID}/state-timeline`
),
[PageMap.SCHEDULED_MAINTENANCE_VIEW_DELETE]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${RouteParams.ModelID}/delete`
),
[PageMap.SCHEDULED_MAINTENANCE_INTERNAL_NOTE]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${RouteParams.ModelID}/internal-notes`
),
[PageMap.SCHEDULED_MAINTENANCE_PUBLIC_NOTE]: new Route(
`/dashboard/${RouteParams.ProjectID}/scheduled-maintenance-events/${RouteParams.ModelID}/public-notes`
),
[PageMap.STATUS_PAGES]: new Route(
`/dashboard/${RouteParams.ProjectID}/status-pages`
),
@@ -186,6 +221,10 @@ const RouteMap: Dictionary<Route> = {
`/dashboard/${RouteParams.ProjectID}/settings/incidents-state`
),
[PageMap.SETTINGS_SCHEDULED_MAINTENANCE_STATE]: new Route(
`/dashboard/${RouteParams.ProjectID}/settings/scheduled-maintenance-state`
),
[PageMap.SETTINGS_INCIDENTS_SEVERITY]: new Route(
`/dashboard/${RouteParams.ProjectID}/settings/incidents-severity`
),

View File

@@ -111,6 +111,32 @@ import MonitorTimelineStatusService, {
Service as MonitorTimelineStatusServiceType,
} from 'CommonServer/Services/MonitorStatusTimelineService';
import ScheduledMaintenanceState from 'Model/Models/ScheduledMaintenanceState';
import ScheduledMaintenanceStateService, {
Service as ScheduledMaintenanceStateServiceType,
} from 'CommonServer/Services/ScheduledMaintenanceStateService';
import ScheduledMaintenance from 'Model/Models/ScheduledMaintenance';
import ScheduledMaintenanceService, {
Service as ScheduledMaintenanceServiceType,
} from 'CommonServer/Services/ScheduledMaintenanceService';
import ScheduledMaintenanceStateTimeline from 'Model/Models/ScheduledMaintenanceStateTimeline';
import ScheduledMaintenanceStateTimelineService, {
Service as ScheduledMaintenanceStateTimelineServiceType,
} from 'CommonServer/Services/ScheduledMaintenanceStateTimelineService';
import ScheduledMaintenanceInternalNote from 'Model/Models/ScheduledMaintenanceInternalNote';
import ScheduledMaintenanceInternalNoteService, {
Service as ScheduledMaintenanceInternalNoteServiceType,
} from 'CommonServer/Services/ScheduledMaintenanceInternalNoteService';
import ScheduledMaintenancePublicNote from 'Model/Models/ScheduledMaintenancePublicNote';
import ScheduledMaintenancePublicNoteService, {
Service as ScheduledMaintenancePublicNoteServiceType,
} from 'CommonServer/Services/ScheduledMaintenancePublicNoteService';
import IncidentState from 'Model/Models/IncidentState';
import IncidentStateService, {
Service as IncidentStateServiceType,
@@ -136,6 +162,7 @@ import IncidentPublicNoteService, {
Service as IncidentPublicNoteServiceType,
} from 'CommonServer/Services/IncidentPublicNoteService';
import Domain from 'Model/Models/Domain';
import DomainService, {
Service as DomainServiceType,
@@ -212,6 +239,13 @@ app.use(
).getRouter()
);
app.use(
new BaseAPI<ScheduledMaintenanceState, ScheduledMaintenanceStateServiceType>(
ScheduledMaintenanceState,
ScheduledMaintenanceStateService
).getRouter()
);
app.use(
new BaseAPI<StatusPageResource, StatusPageResourceServiceType>(
StatusPageResource,
@@ -244,6 +278,13 @@ app.use(
).getRouter()
);
app.use(
new BaseAPI<ScheduledMaintenanceStateTimeline, ScheduledMaintenanceStateTimelineServiceType>(
ScheduledMaintenanceStateTimeline,
ScheduledMaintenanceStateTimelineService
).getRouter()
);
app.use(
new BaseAPI<StatusPageSubscriber, StatusPageSubscriberServiceType>(
StatusPageSubscriber,
@@ -258,6 +299,13 @@ app.use(
).getRouter()
);
app.use(
new BaseAPI<ScheduledMaintenance, ScheduledMaintenanceServiceType>(
ScheduledMaintenance,
ScheduledMaintenanceService
).getRouter()
);
app.use(
new BaseAPI<ApiKey, ApiKeyServiceType>(ApiKey, ApiKeyService).getRouter()
);
@@ -335,6 +383,21 @@ app.use(
).getRouter()
);
app.use(
new BaseAPI<ScheduledMaintenancePublicNote, ScheduledMaintenancePublicNoteServiceType>(
ScheduledMaintenancePublicNote,
ScheduledMaintenancePublicNoteService
).getRouter()
);
app.use(
new BaseAPI<ScheduledMaintenanceInternalNote, ScheduledMaintenanceInternalNoteServiceType>(
ScheduledMaintenanceInternalNote,
ScheduledMaintenanceInternalNoteService
).getRouter()
);
app.use(
new BaseAPI<IncidentPublicNote, IncidentPublicNoteServiceType>(
IncidentPublicNote,