@oneuptime/common 7.0.4922 → 7.0.4976
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/CallLog.ts +578 -0
- package/Models/DatabaseModels/EmailLog.ts +579 -0
- package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -1
- package/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/OnCallDutyPolicy.ts +1 -1
- package/Models/DatabaseModels/PushNotificationLog.ts +877 -0
- package/Models/DatabaseModels/SmsLog.ts +578 -0
- package/Models/DatabaseModels/WorkspaceNotificationLog.ts +931 -0
- package/Server/API/StatusPageAPI.ts +2 -0
- package/Server/API/UserPushAPI.ts +12 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1754776130988-MigrationName.ts +259 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1754828812691-MigrationName.ts +105 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1754910440587-MigrationName.ts +105 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1755030730926-MigrationName.ts +101 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1755088852971-MigrationName.ts +371 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1755093133870-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1755109893911-MigrationName.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1755110936888-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
- package/Server/Services/CallService.ts +26 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MailService.ts +60 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +6 -0
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +12 -0
- package/Server/Services/ProjectService.ts +3 -7
- package/Server/Services/PushNotificationLogService.ts +14 -0
- package/Server/Services/PushNotificationService.ts +129 -13
- package/Server/Services/ScheduledMaintenanceService.ts +4 -0
- package/Server/Services/SmsService.ts +25 -0
- package/Server/Services/StatusPagePrivateUserService.ts +1 -0
- package/Server/Services/StatusPageService.ts +1 -0
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Services/TeamMemberService.ts +1 -0
- package/Server/Services/UserCallService.ts +1 -0
- package/Server/Services/UserEmailService.ts +1 -0
- package/Server/Services/UserNotificationRuleService.ts +85 -6
- package/Server/Services/UserNotificationSettingService.ts +58 -0
- package/Server/Services/UserOnCallLogService.ts +1 -0
- package/Server/Services/UserSmsService.ts +1 -0
- package/Server/Services/WorkspaceNotificationLogService.ts +276 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +290 -33
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +21 -6
- package/Server/Utils/Workspace/Slack/Actions/Alert.ts +66 -0
- package/Server/Utils/Workspace/Slack/Actions/Incident.ts +66 -1
- package/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.ts +65 -0
- package/Server/Utils/Workspace/Slack/Slack.ts +21 -6
- package/Types/Permission.ts +20 -0
- package/Types/PushNotification/PushNotificationRequest.ts +4 -1
- package/Types/PushNotification/PushStatus.ts +6 -0
- package/Types/Workspace/WorkspaceNotificationActionType.ts +8 -0
- package/Types/Workspace/WorkspaceNotificationStatus.ts +6 -0
- package/Typings/elkjs.d.ts +30 -0
- package/UI/Components/Detail/Detail.tsx +1 -1
- package/UI/Components/Graphs/ServiceDependencyGraph.tsx +281 -0
- package/UI/Components/Tabs/Tab.tsx +8 -9
- package/UI/Components/Tabs/Tabs.tsx +17 -16
- package/Utils/Uptime/UptimeUtil.ts +20 -1
- package/build/dist/Models/DatabaseModels/CallLog.js +580 -0
- package/build/dist/Models/DatabaseModels/CallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EmailLog.js +580 -0
- package/build/dist/Models/DatabaseModels/EmailLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicy.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicy.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PushNotificationLog.js +904 -0
- package/build/dist/Models/DatabaseModels/PushNotificationLog.js.map +1 -0
- package/build/dist/Models/DatabaseModels/SmsLog.js +580 -0
- package/build/dist/Models/DatabaseModels/SmsLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js +961 -0
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationLog.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +2 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/UserPushAPI.js +9 -1
- package/build/dist/Server/API/UserPushAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754776130988-MigrationName.js +104 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754776130988-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754828812691-MigrationName.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754828812691-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754910440587-MigrationName.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1754910440587-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755030730926-MigrationName.js +44 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755030730926-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755088852971-MigrationName.js +134 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755088852971-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755093133870-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755093133870-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755109893911-MigrationName.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755109893911-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755110936888-MigrationName.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1755110936888-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/CallService.js +12 -1
- package/build/dist/Server/Services/CallService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MailService.js +37 -0
- package/build/dist/Server/Services/MailService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +4 -0
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +9 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +3 -5
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/PushNotificationLogService.js +13 -0
- package/build/dist/Server/Services/PushNotificationLogService.js.map +1 -0
- package/build/dist/Server/Services/PushNotificationService.js +98 -13
- package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +4 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/SmsService.js +12 -1
- package/build/dist/Server/Services/SmsService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserService.js +1 -0
- package/build/dist/Server/Services/StatusPagePrivateUserService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +1 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +1 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserCallService.js +1 -0
- package/build/dist/Server/Services/UserCallService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +1 -0
- package/build/dist/Server/Services/UserEmailService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +70 -6
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +43 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +1 -0
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserSmsService.js +1 -0
- package/build/dist/Server/Services/UserSmsService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationLogService.js +181 -0
- package/build/dist/Server/Services/WorkspaceNotificationLogService.js.map +1 -0
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +193 -3
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +19 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +48 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +48 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js +47 -0
- package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +19 -6
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Types/Permission.js +16 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/PushNotification/PushStatus.js +7 -0
- package/build/dist/Types/PushNotification/PushStatus.js.map +1 -0
- package/build/dist/Types/Workspace/WorkspaceNotificationActionType.js +9 -0
- package/build/dist/Types/Workspace/WorkspaceNotificationActionType.js.map +1 -0
- package/build/dist/Types/Workspace/WorkspaceNotificationStatus.js +7 -0
- package/build/dist/Types/Workspace/WorkspaceNotificationStatus.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +1 -1
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/Graphs/ServiceDependencyGraph.js +206 -0
- package/build/dist/UI/Components/Graphs/ServiceDependencyGraph.js.map +1 -0
- package/build/dist/UI/Components/Tabs/Tab.js +6 -5
- package/build/dist/UI/Components/Tabs/Tab.js.map +1 -1
- package/build/dist/UI/Components/Tabs/Tabs.js +5 -6
- package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
- package/build/dist/Utils/Uptime/UptimeUtil.js +10 -1
- package/build/dist/Utils/Uptime/UptimeUtil.js.map +1 -1
- package/package.json +2 -1
|
@@ -34,6 +34,7 @@ import OneUptimeDate from "../../../../../Types/Date";
|
|
|
34
34
|
import AccessTokenService from "../../../../Services/AccessTokenService";
|
|
35
35
|
import CaptureSpan from "../../../Telemetry/CaptureSpan";
|
|
36
36
|
import WorkspaceType from "../../../../../Types/Workspace/WorkspaceType";
|
|
37
|
+
import WorkspaceNotificationLogService from "../../../../Services/WorkspaceNotificationLogService";
|
|
37
38
|
|
|
38
39
|
export default class SlackScheduledMaintenanceActions {
|
|
39
40
|
@CaptureSpan()
|
|
@@ -582,6 +583,38 @@ export default class SlackScheduledMaintenanceActions {
|
|
|
582
583
|
userId,
|
|
583
584
|
);
|
|
584
585
|
|
|
586
|
+
// Log the button interaction
|
|
587
|
+
if (slackRequest.projectId) {
|
|
588
|
+
try {
|
|
589
|
+
const logData: {
|
|
590
|
+
projectId: ObjectID;
|
|
591
|
+
workspaceType: WorkspaceType;
|
|
592
|
+
channelId?: string;
|
|
593
|
+
userId: ObjectID;
|
|
594
|
+
buttonAction: string;
|
|
595
|
+
scheduledMaintenanceId?: ObjectID;
|
|
596
|
+
} = {
|
|
597
|
+
projectId: slackRequest.projectId,
|
|
598
|
+
workspaceType: WorkspaceType.Slack,
|
|
599
|
+
userId: userId,
|
|
600
|
+
buttonAction: "mark_scheduled_maintenance_as_ongoing",
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
if (slackRequest.slackChannelId) {
|
|
604
|
+
logData.channelId = slackRequest.slackChannelId;
|
|
605
|
+
}
|
|
606
|
+
logData.scheduledMaintenanceId = scheduledMaintenanceId;
|
|
607
|
+
|
|
608
|
+
await WorkspaceNotificationLogService.logButtonPressed(logData, {
|
|
609
|
+
isRoot: true,
|
|
610
|
+
});
|
|
611
|
+
} catch (err) {
|
|
612
|
+
logger.error("Error logging button interaction:");
|
|
613
|
+
logger.error(err);
|
|
614
|
+
// Don't throw the error, just log it so the main flow continues
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
585
618
|
// Scheduled Maintenance Feed will send a message to the channel that the scheduledMaintenance has been Ongoing.
|
|
586
619
|
return;
|
|
587
620
|
}
|
|
@@ -679,6 +712,38 @@ export default class SlackScheduledMaintenanceActions {
|
|
|
679
712
|
userId,
|
|
680
713
|
);
|
|
681
714
|
|
|
715
|
+
// Log the button interaction
|
|
716
|
+
if (slackRequest.projectId) {
|
|
717
|
+
try {
|
|
718
|
+
const logData: {
|
|
719
|
+
projectId: ObjectID;
|
|
720
|
+
workspaceType: WorkspaceType;
|
|
721
|
+
channelId?: string;
|
|
722
|
+
userId: ObjectID;
|
|
723
|
+
buttonAction: string;
|
|
724
|
+
scheduledMaintenanceId?: ObjectID;
|
|
725
|
+
} = {
|
|
726
|
+
projectId: slackRequest.projectId,
|
|
727
|
+
workspaceType: WorkspaceType.Slack,
|
|
728
|
+
userId: userId,
|
|
729
|
+
buttonAction: "mark_scheduled_maintenance_as_complete",
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
if (slackRequest.slackChannelId) {
|
|
733
|
+
logData.channelId = slackRequest.slackChannelId;
|
|
734
|
+
}
|
|
735
|
+
logData.scheduledMaintenanceId = scheduledMaintenanceId;
|
|
736
|
+
|
|
737
|
+
await WorkspaceNotificationLogService.logButtonPressed(logData, {
|
|
738
|
+
isRoot: true,
|
|
739
|
+
});
|
|
740
|
+
} catch (err) {
|
|
741
|
+
logger.error("Error logging button interaction:");
|
|
742
|
+
logger.error(err);
|
|
743
|
+
// Don't throw the error, just log it so the main flow continues
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
682
747
|
return;
|
|
683
748
|
}
|
|
684
749
|
|
|
@@ -729,13 +729,28 @@ export default class SlackUtil extends WorkspaceBase {
|
|
|
729
729
|
|
|
730
730
|
// add channel ids.
|
|
731
731
|
for (const channelId of data.workspaceMessagePayload.channelIds) {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
732
|
+
try {
|
|
733
|
+
// Get the channel info including name from channel ID
|
|
734
|
+
const channel: WorkspaceChannel =
|
|
735
|
+
await this.getWorkspaceChannelFromChannelId({
|
|
736
|
+
authToken: data.authToken,
|
|
737
|
+
channelId: channelId,
|
|
738
|
+
});
|
|
737
739
|
|
|
738
|
-
|
|
740
|
+
workspaceChannelsToPostTo.push(channel);
|
|
741
|
+
} catch (err) {
|
|
742
|
+
logger.error(`Error getting channel info for channel ID ${channelId}:`);
|
|
743
|
+
logger.error(err);
|
|
744
|
+
|
|
745
|
+
// Fallback: create channel object with empty name if API call fails
|
|
746
|
+
const channel: WorkspaceChannel = {
|
|
747
|
+
id: channelId,
|
|
748
|
+
name: channelId,
|
|
749
|
+
workspaceType: WorkspaceType.Slack,
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
workspaceChannelsToPostTo.push(channel);
|
|
753
|
+
}
|
|
739
754
|
}
|
|
740
755
|
|
|
741
756
|
logger.debug("Channel IDs to post to:");
|
package/Types/Permission.ts
CHANGED
|
@@ -142,6 +142,8 @@ enum Permission {
|
|
|
142
142
|
ReadSmsLog = "ReadSmsLog",
|
|
143
143
|
ReadEmailLog = "ReadEmailLog",
|
|
144
144
|
ReadCallLog = "ReadCallLog",
|
|
145
|
+
ReadPushLog = "ReadPushLog",
|
|
146
|
+
ReadWorkspaceNotificationLog = "ReadWorkspaceNotificationLog",
|
|
145
147
|
|
|
146
148
|
CreateIncidentOwnerTeam = "CreateIncidentOwnerTeam",
|
|
147
149
|
DeleteIncidentOwnerTeam = "DeleteIncidentOwnerTeam",
|
|
@@ -3003,6 +3005,24 @@ export class PermissionHelper {
|
|
|
3003
3005
|
isAccessControlPermission: false,
|
|
3004
3006
|
},
|
|
3005
3007
|
|
|
3008
|
+
{
|
|
3009
|
+
permission: Permission.ReadPushLog,
|
|
3010
|
+
title: "Read Push Log",
|
|
3011
|
+
description:
|
|
3012
|
+
"This permission can read Push Notification Logs of this project.",
|
|
3013
|
+
isAssignableToTenant: true,
|
|
3014
|
+
isAccessControlPermission: false,
|
|
3015
|
+
},
|
|
3016
|
+
|
|
3017
|
+
{
|
|
3018
|
+
permission: Permission.ReadWorkspaceNotificationLog,
|
|
3019
|
+
title: "Read Workspace Notification Log",
|
|
3020
|
+
description:
|
|
3021
|
+
"This permission can read Workspace Notification Logs (Slack / Microsoft Teams) of this project.",
|
|
3022
|
+
isAssignableToTenant: true,
|
|
3023
|
+
isAccessControlPermission: false,
|
|
3024
|
+
},
|
|
3025
|
+
|
|
3006
3026
|
{
|
|
3007
3027
|
permission: Permission.CreateMonitorProbe,
|
|
3008
3028
|
title: "Create Monitor Probe",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
declare module "elkjs/lib/elk.bundled.js" {
|
|
2
|
+
export interface ElkNode {
|
|
3
|
+
id?: string;
|
|
4
|
+
x?: number;
|
|
5
|
+
y?: number;
|
|
6
|
+
width?: number | undefined;
|
|
7
|
+
height?: number | undefined;
|
|
8
|
+
layoutOptions?: Record<string, string>;
|
|
9
|
+
children?: ElkNode[];
|
|
10
|
+
edges?: Array<ElkPrimitiveEdge | ElkExtendedEdge>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ElkPrimitiveEdge {
|
|
14
|
+
id: string;
|
|
15
|
+
sources: string[];
|
|
16
|
+
targets: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ElkExtendedEdge extends ElkPrimitiveEdge {
|
|
20
|
+
sections?: Array<{
|
|
21
|
+
startPoint?: { x: number; y: number };
|
|
22
|
+
endPoint?: { x: number; y: number };
|
|
23
|
+
bendPoints?: Array<{ x: number; y: number }>;
|
|
24
|
+
}>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default class ELK {
|
|
28
|
+
public layout(graph: ElkNode): Promise<ElkNode>;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -420,7 +420,7 @@ const Detail: DetailFunction = <T extends GenericObject>(
|
|
|
420
420
|
)}
|
|
421
421
|
</div>
|
|
422
422
|
)}
|
|
423
|
-
{
|
|
423
|
+
{(data === null || data === undefined) && field.placeholder && (
|
|
424
424
|
<PlaceholderText text={field.placeholder} />
|
|
425
425
|
)}
|
|
426
426
|
</div>
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
FunctionComponent,
|
|
3
|
+
ReactElement,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
} from "react";
|
|
7
|
+
import ReactFlow, {
|
|
8
|
+
Background,
|
|
9
|
+
Controls,
|
|
10
|
+
Edge,
|
|
11
|
+
MarkerType,
|
|
12
|
+
MiniMap,
|
|
13
|
+
Node,
|
|
14
|
+
Position,
|
|
15
|
+
} from "reactflow";
|
|
16
|
+
import "reactflow/dist/style.css";
|
|
17
|
+
import type { ElkExtendedEdge, ElkNode } from "elkjs";
|
|
18
|
+
import ELK from "elkjs/lib/elk.bundled.js";
|
|
19
|
+
|
|
20
|
+
export interface ServiceNodeData {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
color?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ServiceEdgeData {
|
|
27
|
+
fromServiceId: string;
|
|
28
|
+
toServiceId: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ServiceDependencyGraphProps {
|
|
32
|
+
services: Array<ServiceNodeData>;
|
|
33
|
+
dependencies: Array<ServiceEdgeData>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ServiceDependencyGraph: FunctionComponent<ServiceDependencyGraphProps> = (
|
|
37
|
+
props: ServiceDependencyGraphProps,
|
|
38
|
+
): ReactElement => {
|
|
39
|
+
const computeLuminance: (r: number, g: number, b: number) => number = (
|
|
40
|
+
r: number,
|
|
41
|
+
g: number,
|
|
42
|
+
b: number,
|
|
43
|
+
): number => {
|
|
44
|
+
const transform: (v: number) => number = (v: number): number => {
|
|
45
|
+
const c: number = v / 255;
|
|
46
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
47
|
+
};
|
|
48
|
+
const R: number = transform(r);
|
|
49
|
+
const G: number = transform(g);
|
|
50
|
+
const B: number = transform(b);
|
|
51
|
+
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const getContrastText: (bg?: string) => string = (bg?: string): string => {
|
|
55
|
+
if (!bg) {
|
|
56
|
+
return "#111827"; // gray-900
|
|
57
|
+
}
|
|
58
|
+
// normalize to hex like #rrggbb
|
|
59
|
+
let hex: string = bg.trim();
|
|
60
|
+
if (hex.startsWith("rgb")) {
|
|
61
|
+
// basic rgb(a) parser
|
|
62
|
+
const m: RegExpMatchArray | null = hex
|
|
63
|
+
.replace(/\s+/g, "")
|
|
64
|
+
.match(/rgba?\((\d+),(\d+),(\d+)/i);
|
|
65
|
+
if (m) {
|
|
66
|
+
const r: number = parseInt(m[1] as string, 10);
|
|
67
|
+
const g: number = parseInt(m[2] as string, 10);
|
|
68
|
+
const b: number = parseInt(m[3] as string, 10);
|
|
69
|
+
const luminance: number = computeLuminance(r, g, b);
|
|
70
|
+
return luminance > 0.5 ? "#111827" : "#ffffff";
|
|
71
|
+
}
|
|
72
|
+
return "#111827";
|
|
73
|
+
}
|
|
74
|
+
if (hex[0] === "#") {
|
|
75
|
+
hex = hex.slice(1);
|
|
76
|
+
}
|
|
77
|
+
if (hex.length === 3) {
|
|
78
|
+
hex = hex
|
|
79
|
+
.split("")
|
|
80
|
+
.map((c: string): string => {
|
|
81
|
+
return c + c;
|
|
82
|
+
})
|
|
83
|
+
.join("");
|
|
84
|
+
}
|
|
85
|
+
if (hex.length !== 6) {
|
|
86
|
+
return "#111827";
|
|
87
|
+
}
|
|
88
|
+
const r: number = parseInt(hex.slice(0, 2), 16);
|
|
89
|
+
const g: number = parseInt(hex.slice(2, 4), 16);
|
|
90
|
+
const b: number = parseInt(hex.slice(4, 6), 16);
|
|
91
|
+
const luminance: number = computeLuminance(r, g, b);
|
|
92
|
+
return luminance > 0.5 ? "#111827" : "#ffffff";
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const [rfNodes, setRfNodes] = useState<Node[]>([]);
|
|
96
|
+
const [rfEdges, setRfEdges] = useState<Edge[]>([]);
|
|
97
|
+
|
|
98
|
+
useEffect((): void => {
|
|
99
|
+
const elk: any = new ELK();
|
|
100
|
+
// fixed node dimensions for layout (px)
|
|
101
|
+
const NODE_WIDTH: number = 220;
|
|
102
|
+
const NODE_HEIGHT: number = 56;
|
|
103
|
+
|
|
104
|
+
const sortedServices: Array<ServiceNodeData> = [...props.services].sort(
|
|
105
|
+
(a: ServiceNodeData, b: ServiceNodeData): number => {
|
|
106
|
+
return a.name.localeCompare(b.name) || a.id.localeCompare(b.id);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
const sortedDeps: Array<ServiceEdgeData> = [...props.dependencies].sort(
|
|
110
|
+
(a: ServiceEdgeData, b: ServiceEdgeData): number => {
|
|
111
|
+
if (a.fromServiceId === b.fromServiceId) {
|
|
112
|
+
return a.toServiceId.localeCompare(b.toServiceId);
|
|
113
|
+
}
|
|
114
|
+
return a.fromServiceId.localeCompare(b.fromServiceId);
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const elkGraph: ElkNode = {
|
|
119
|
+
id: "root",
|
|
120
|
+
layoutOptions: {
|
|
121
|
+
algorithm: "layered",
|
|
122
|
+
"elk.direction": "RIGHT",
|
|
123
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": "120",
|
|
124
|
+
"elk.spacing.nodeNode": "60",
|
|
125
|
+
"elk.edgeRouting": "POLYLINE",
|
|
126
|
+
},
|
|
127
|
+
children: sortedServices.map((svc: ServiceNodeData): ElkNode => {
|
|
128
|
+
return {
|
|
129
|
+
id: svc.id,
|
|
130
|
+
width: NODE_WIDTH,
|
|
131
|
+
height: NODE_HEIGHT,
|
|
132
|
+
} as ElkNode;
|
|
133
|
+
}),
|
|
134
|
+
edges: sortedDeps.map((dep: ServiceEdgeData): ElkExtendedEdge => {
|
|
135
|
+
return {
|
|
136
|
+
id: `e-${dep.fromServiceId}-${dep.toServiceId}`,
|
|
137
|
+
sources: [dep.fromServiceId],
|
|
138
|
+
targets: [dep.toServiceId],
|
|
139
|
+
};
|
|
140
|
+
}),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const layout: () => Promise<void> = async (): Promise<void> => {
|
|
144
|
+
try {
|
|
145
|
+
const res: any = await elk.layout(elkGraph as any);
|
|
146
|
+
const placedNodes: Node[] = (res.children || []).map(
|
|
147
|
+
(child: any): Node => {
|
|
148
|
+
const svc: ServiceNodeData | undefined = sortedServices.find(
|
|
149
|
+
(s: ServiceNodeData): boolean => {
|
|
150
|
+
return s.id === child.id;
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
const background: string = svc?.color || "#ffffff";
|
|
154
|
+
const textColor: string = getContrastText(background);
|
|
155
|
+
return {
|
|
156
|
+
id: child.id || "",
|
|
157
|
+
data: { label: svc?.name || "" },
|
|
158
|
+
position: { x: child.x || 0, y: child.y || 0 },
|
|
159
|
+
sourcePosition: Position.Right,
|
|
160
|
+
targetPosition: Position.Left,
|
|
161
|
+
style: {
|
|
162
|
+
borderRadius: 8,
|
|
163
|
+
padding: 8,
|
|
164
|
+
border: "1px solid rgba(0,0,0,0.08)",
|
|
165
|
+
background,
|
|
166
|
+
color: textColor,
|
|
167
|
+
boxShadow: "0 1px 2px rgba(16,24,40,.05)",
|
|
168
|
+
width: NODE_WIDTH,
|
|
169
|
+
height: NODE_HEIGHT,
|
|
170
|
+
},
|
|
171
|
+
} as Node;
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const stroke: string = "#94a3b8"; // slate-400
|
|
176
|
+
const placedEdges: Edge[] = sortedDeps.map(
|
|
177
|
+
(dep: ServiceEdgeData): Edge => {
|
|
178
|
+
return {
|
|
179
|
+
id: `e-${dep.fromServiceId}-${dep.toServiceId}`,
|
|
180
|
+
source: dep.fromServiceId,
|
|
181
|
+
target: dep.toServiceId,
|
|
182
|
+
animated: false,
|
|
183
|
+
style: { stroke, strokeWidth: 2 },
|
|
184
|
+
markerEnd: { type: MarkerType.Arrow, color: stroke },
|
|
185
|
+
type: "smoothstep",
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
setRfNodes(placedNodes);
|
|
191
|
+
setRfEdges(placedEdges);
|
|
192
|
+
} catch {
|
|
193
|
+
// Fallback: deterministic grid by name
|
|
194
|
+
const sorted: Array<ServiceNodeData> = sortedServices;
|
|
195
|
+
const COLS: number = 4;
|
|
196
|
+
const GAP_X: number = 260;
|
|
197
|
+
const GAP_Y: number = 120;
|
|
198
|
+
const nodes: Node[] = sorted.map(
|
|
199
|
+
(svc: ServiceNodeData, i: number): Node => {
|
|
200
|
+
const col: number = i % COLS;
|
|
201
|
+
const row: number = Math.floor(i / COLS);
|
|
202
|
+
const x: number = col * GAP_X;
|
|
203
|
+
const y: number = row * GAP_Y;
|
|
204
|
+
const background: string = svc.color || "#ffffff";
|
|
205
|
+
const textColor: string = getContrastText(background);
|
|
206
|
+
return {
|
|
207
|
+
id: svc.id,
|
|
208
|
+
data: { label: svc.name },
|
|
209
|
+
position: { x, y },
|
|
210
|
+
sourcePosition: Position.Right,
|
|
211
|
+
targetPosition: Position.Left,
|
|
212
|
+
style: {
|
|
213
|
+
borderRadius: 8,
|
|
214
|
+
padding: 8,
|
|
215
|
+
border: "1px solid rgba(0,0,0,0.08)",
|
|
216
|
+
background,
|
|
217
|
+
color: textColor,
|
|
218
|
+
boxShadow: "0 1px 2px rgba(16,24,40,.05)",
|
|
219
|
+
width: NODE_WIDTH,
|
|
220
|
+
height: NODE_HEIGHT,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
const stroke: string = "#94a3b8";
|
|
226
|
+
const edges: Edge[] = sortedDeps.map((dep: ServiceEdgeData): Edge => {
|
|
227
|
+
return {
|
|
228
|
+
id: `e-${dep.fromServiceId}-${dep.toServiceId}`,
|
|
229
|
+
source: dep.fromServiceId,
|
|
230
|
+
target: dep.toServiceId,
|
|
231
|
+
animated: false,
|
|
232
|
+
style: { stroke, strokeWidth: 2 },
|
|
233
|
+
markerEnd: { type: MarkerType.Arrow, color: stroke },
|
|
234
|
+
type: "smoothstep",
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
setRfNodes(nodes);
|
|
238
|
+
setRfEdges(edges);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
layout();
|
|
243
|
+
}, [props.services, props.dependencies]);
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div style={{ width: "100%", height: 600 }}>
|
|
247
|
+
<style>{`
|
|
248
|
+
/* Hide/transparentize connection handles (ports) for read-only view */
|
|
249
|
+
.service-dependency-graph .react-flow__handle {
|
|
250
|
+
background: transparent !important;
|
|
251
|
+
border-color: transparent !important;
|
|
252
|
+
}
|
|
253
|
+
`}</style>
|
|
254
|
+
<ReactFlow
|
|
255
|
+
className="service-dependency-graph"
|
|
256
|
+
nodes={rfNodes}
|
|
257
|
+
edges={rfEdges}
|
|
258
|
+
fitView
|
|
259
|
+
nodesDraggable={false}
|
|
260
|
+
nodesConnectable={false}
|
|
261
|
+
elementsSelectable={false}
|
|
262
|
+
edgesUpdatable={false}
|
|
263
|
+
connectOnClick={false}
|
|
264
|
+
>
|
|
265
|
+
<MiniMap
|
|
266
|
+
nodeColor={(n: Node): string => {
|
|
267
|
+
return (
|
|
268
|
+
(n.style as any)?.background ||
|
|
269
|
+
(n.data as any)?.color ||
|
|
270
|
+
"#ffffff"
|
|
271
|
+
);
|
|
272
|
+
}}
|
|
273
|
+
/>
|
|
274
|
+
<Controls />
|
|
275
|
+
<Background gap={12} size={1} />
|
|
276
|
+
</ReactFlow>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export default ServiceDependencyGraph;
|
|
@@ -25,18 +25,19 @@ const TabElement: FunctionComponent<ComponentProps> = (
|
|
|
25
25
|
): ReactElement => {
|
|
26
26
|
const backgroundColor: string = "bg-gray-100";
|
|
27
27
|
|
|
28
|
+
const baseClasses: string =
|
|
29
|
+
"rounded-md px-3 py-2 text-sm font-medium cursor-pointer inline-flex whitespace-nowrap flex-shrink-0";
|
|
30
|
+
const stateClasses: string = props.isSelected
|
|
31
|
+
? `${backgroundColor} text-gray-700`
|
|
32
|
+
: "text-gray-500 hover:text-gray-700";
|
|
33
|
+
|
|
28
34
|
return (
|
|
29
35
|
<div className="mt-3 mb-3">
|
|
30
36
|
<div
|
|
31
37
|
data-testid={`tab-${props.tab.name}`}
|
|
32
38
|
key={props.tab.name}
|
|
33
39
|
onClick={props.onClick}
|
|
34
|
-
className={`${
|
|
35
|
-
(props.isSelected
|
|
36
|
-
? backgroundColor + " text-gray-700"
|
|
37
|
-
: "text-gray-500 hover:text-gray-700") +
|
|
38
|
-
" rounded-md px-3 py-2 text-sm font-medium cursor-pointer flex"
|
|
39
|
-
}`}
|
|
40
|
+
className={`${stateClasses} ${baseClasses}`}
|
|
40
41
|
aria-current={props.isSelected ? "page" : undefined}
|
|
41
42
|
>
|
|
42
43
|
<div>{props.tab.name}</div>
|
|
@@ -57,9 +58,7 @@ const TabElement: FunctionComponent<ComponentProps> = (
|
|
|
57
58
|
>
|
|
58
59
|
{props.tab.countBadge}
|
|
59
60
|
</span>
|
|
60
|
-
) :
|
|
61
|
-
<></>
|
|
62
|
-
)}
|
|
61
|
+
) : null}
|
|
63
62
|
</div>
|
|
64
63
|
</div>
|
|
65
64
|
);
|
|
@@ -28,22 +28,23 @@ const Tabs: FunctionComponent<ComponentProps> = (
|
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
30
|
<div>
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
<nav
|
|
32
|
+
className="flex space-x-2 overflow-x-auto md:overflow-visible md:space-x-4"
|
|
33
|
+
aria-label="Tabs"
|
|
34
|
+
>
|
|
35
|
+
{props.tabs.map((tab: Tab) => {
|
|
36
|
+
return (
|
|
37
|
+
<TabElement
|
|
38
|
+
key={tab.name}
|
|
39
|
+
tab={tab}
|
|
40
|
+
onClick={() => {
|
|
41
|
+
setCurrentTab(tab);
|
|
42
|
+
}}
|
|
43
|
+
isSelected={tab === currentTab}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
})}
|
|
47
|
+
</nav>
|
|
47
48
|
<div className="mt-3 ml-1">{currentTab && currentTab.children}</div>
|
|
48
49
|
</div>
|
|
49
50
|
);
|
|
@@ -134,7 +134,26 @@ export default class UptimeUtil {
|
|
|
134
134
|
eventList[eventList.length - 1]!.endDate,
|
|
135
135
|
)
|
|
136
136
|
) {
|
|
137
|
-
|
|
137
|
+
let isEndDateOfCurrenteventAfterLastEvent: boolean = false;
|
|
138
|
+
if (
|
|
139
|
+
eventList[eventList.length - 1] &&
|
|
140
|
+
eventList[eventList.length - 1]?.endDate
|
|
141
|
+
) {
|
|
142
|
+
isEndDateOfCurrenteventAfterLastEvent =
|
|
143
|
+
OneUptimeDate.isAfter(
|
|
144
|
+
monitorEvent.endDate,
|
|
145
|
+
eventList[eventList.length - 1]!.endDate,
|
|
146
|
+
) ||
|
|
147
|
+
OneUptimeDate.isEqualBySeconds(
|
|
148
|
+
monitorEvent.endDate,
|
|
149
|
+
eventList[eventList.length - 1]!.endDate,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
monitorEvent.priority > eventList[eventList.length - 1]!.priority ||
|
|
155
|
+
isEndDateOfCurrenteventAfterLastEvent
|
|
156
|
+
) {
|
|
138
157
|
// end the last event at the start of this event.
|
|
139
158
|
|
|
140
159
|
const tempLastEvent: Event = {
|