@oneuptime/common 9.5.8 → 9.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/Models/DatabaseModels/Alert.ts +8 -9
  2. package/Models/DatabaseModels/Incident.ts +5 -5
  3. package/Models/DatabaseModels/IncidentTemplate.ts +4 -3
  4. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -1
  5. package/Models/DatabaseModels/UserOnCallLog.ts +1 -1
  6. package/Server/API/OpenSourceDeploymentAPI.ts +8 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.ts +156 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.ts +119 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  10. package/Server/Middleware/UserAuthorization.ts +8 -3
  11. package/Server/Services/AlertEpisodeFeedService.ts +50 -0
  12. package/Server/Services/AlertEpisodeInternalNoteService.ts +162 -0
  13. package/Server/Services/AlertEpisodeMemberService.ts +7 -0
  14. package/Server/Services/AlertEpisodeOwnerTeamService.ts +186 -0
  15. package/Server/Services/AlertEpisodeOwnerUserService.ts +180 -0
  16. package/Server/Services/AlertEpisodeService.ts +68 -0
  17. package/Server/Services/AlertEpisodeStateTimelineService.ts +5 -0
  18. package/Server/Services/AlertService.ts +3 -0
  19. package/Server/Services/IncidentEpisodeFeedService.ts +50 -0
  20. package/Server/Services/IncidentEpisodeInternalNoteService.ts +163 -0
  21. package/Server/Services/IncidentEpisodeMemberService.ts +7 -0
  22. package/Server/Services/IncidentEpisodeOwnerTeamService.ts +189 -0
  23. package/Server/Services/IncidentEpisodeOwnerUserService.ts +183 -0
  24. package/Server/Services/IncidentEpisodePublicNoteService.ts +8 -0
  25. package/Server/Services/IncidentEpisodeService.ts +91 -12
  26. package/Server/Services/IncidentEpisodeStateTimelineService.ts +5 -0
  27. package/Server/Services/IncidentService.ts +5 -0
  28. package/Server/Services/MonitorService.ts +8 -0
  29. package/Server/Services/WorkspaceNotificationRuleService.ts +31 -2
  30. package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +183 -0
  31. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +13 -0
  32. package/Server/Utils/Monitor/MonitorTemplateUtil.ts +37 -0
  33. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +1 -1
  34. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +7 -6
  35. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +1 -1
  36. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +7 -6
  37. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +11 -7
  38. package/Server/Utils/Workspace/Slack/Actions/Alert.ts +17 -0
  39. package/Server/Utils/Workspace/Slack/Actions/AlertEpisode.ts +27 -12
  40. package/Server/Utils/Workspace/Slack/Actions/Incident.ts +17 -0
  41. package/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.ts +86 -28
  42. package/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.ts +6 -6
  43. package/Server/Utils/Workspace/Slack/Slack.ts +64 -12
  44. package/Server/Utils/Workspace/WorkspaceMessages/Alert.ts +2 -1
  45. package/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.ts +3 -1
  46. package/Server/Utils/Workspace/WorkspaceMessages/Incident.ts +2 -1
  47. package/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.ts +3 -1
  48. package/Tests/Server/Middleware/UserAuthorization.test.ts +10 -2
  49. package/Types/Monitor/CriteriaFilter.ts +20 -3
  50. package/Types/Monitor/DnsMonitor/DnsMonitorResponse.ts +16 -0
  51. package/Types/Monitor/DnsMonitor/DnsRecordType.ts +14 -0
  52. package/Types/Monitor/MonitorCriteriaInstance.ts +67 -0
  53. package/Types/Monitor/MonitorStep.ts +30 -0
  54. package/Types/Monitor/MonitorStepDnsMonitor.ts +46 -0
  55. package/Types/Monitor/MonitorType.ts +15 -2
  56. package/Types/Permission.ts +641 -0
  57. package/Types/Probe/ProbeMonitorResponse.ts +2 -0
  58. package/UI/Components/Detail/Detail.tsx +13 -4
  59. package/UI/Components/Detail/Field.ts +2 -2
  60. package/UI/Components/Dropdown/Dropdown.tsx +38 -7
  61. package/UI/Components/Forms/BasicForm.tsx +35 -5
  62. package/UI/Components/Forms/Fields/PermissionPicker.tsx +261 -0
  63. package/UI/Components/Forms/Types/Field.ts +5 -3
  64. package/UI/Utils/Permission.ts +29 -6
  65. package/Utils/Monitor/MonitorMetricType.ts +2 -1
  66. package/build/dist/Models/DatabaseModels/Alert.js +8 -8
  67. package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
  68. package/build/dist/Models/DatabaseModels/Incident.js +5 -5
  69. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  70. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +3 -3
  71. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  72. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -1
  73. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  74. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -1
  75. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  76. package/build/dist/Server/API/OpenSourceDeploymentAPI.js +5 -0
  77. package/build/dist/Server/API/OpenSourceDeploymentAPI.js.map +1 -1
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.js +63 -0
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770833704656-MigrationName.js.map +1 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.js +46 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1770834237090-MigrationName.js.map +1 -0
  82. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  83. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  84. package/build/dist/Server/Middleware/UserAuthorization.js +2 -3
  85. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  86. package/build/dist/Server/Services/AlertEpisodeFeedService.js +33 -0
  87. package/build/dist/Server/Services/AlertEpisodeFeedService.js.map +1 -1
  88. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js +132 -0
  89. package/build/dist/Server/Services/AlertEpisodeInternalNoteService.js.map +1 -1
  90. package/build/dist/Server/Services/AlertEpisodeMemberService.js +7 -0
  91. package/build/dist/Server/Services/AlertEpisodeMemberService.js.map +1 -1
  92. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js +163 -0
  93. package/build/dist/Server/Services/AlertEpisodeOwnerTeamService.js.map +1 -1
  94. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js +156 -0
  95. package/build/dist/Server/Services/AlertEpisodeOwnerUserService.js.map +1 -1
  96. package/build/dist/Server/Services/AlertEpisodeService.js +53 -0
  97. package/build/dist/Server/Services/AlertEpisodeService.js.map +1 -1
  98. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js +4 -0
  99. package/build/dist/Server/Services/AlertEpisodeStateTimelineService.js.map +1 -1
  100. package/build/dist/Server/Services/AlertService.js +3 -5
  101. package/build/dist/Server/Services/AlertService.js.map +1 -1
  102. package/build/dist/Server/Services/IncidentEpisodeFeedService.js +33 -0
  103. package/build/dist/Server/Services/IncidentEpisodeFeedService.js.map +1 -1
  104. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js +132 -0
  105. package/build/dist/Server/Services/IncidentEpisodeInternalNoteService.js.map +1 -1
  106. package/build/dist/Server/Services/IncidentEpisodeMemberService.js +7 -0
  107. package/build/dist/Server/Services/IncidentEpisodeMemberService.js.map +1 -1
  108. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js +163 -0
  109. package/build/dist/Server/Services/IncidentEpisodeOwnerTeamService.js.map +1 -1
  110. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js +156 -0
  111. package/build/dist/Server/Services/IncidentEpisodeOwnerUserService.js.map +1 -1
  112. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +8 -0
  113. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  114. package/build/dist/Server/Services/IncidentEpisodeService.js +72 -10
  115. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  116. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +4 -0
  117. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  118. package/build/dist/Server/Services/IncidentService.js +5 -5
  119. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  120. package/build/dist/Server/Services/MonitorService.js +8 -1
  121. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  122. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +26 -1
  123. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  124. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +138 -0
  125. package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -0
  126. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +10 -0
  127. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  128. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +24 -0
  129. package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
  130. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +1 -1
  131. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  132. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +7 -6
  133. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  134. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +1 -1
  135. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  136. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +7 -6
  137. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  138. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +10 -7
  139. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  140. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +16 -0
  141. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js.map +1 -1
  142. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js +25 -9
  143. package/build/dist/Server/Utils/Workspace/Slack/Actions/AlertEpisode.js.map +1 -1
  144. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +16 -0
  145. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js.map +1 -1
  146. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js +71 -25
  147. package/build/dist/Server/Utils/Workspace/Slack/Actions/IncidentEpisode.js.map +1 -1
  148. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js +6 -6
  149. package/build/dist/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.js.map +1 -1
  150. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +55 -11
  151. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  152. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js +1 -1
  153. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Alert.js.map +1 -1
  154. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js +1 -1
  155. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/AlertEpisode.js.map +1 -1
  156. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js +1 -1
  157. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/Incident.js.map +1 -1
  158. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js +1 -1
  159. package/build/dist/Server/Utils/Workspace/WorkspaceMessages/IncidentEpisode.js.map +1 -1
  160. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js +4 -2
  161. package/build/dist/Tests/Server/Middleware/UserAuthorization.test.js.map +1 -1
  162. package/build/dist/Types/Monitor/CriteriaFilter.js +15 -3
  163. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  164. package/build/dist/Types/Monitor/DnsMonitor/DnsMonitorResponse.js +2 -0
  165. package/build/dist/Types/Monitor/DnsMonitor/DnsMonitorResponse.js.map +1 -0
  166. package/build/dist/Types/Monitor/DnsMonitor/DnsRecordType.js +15 -0
  167. package/build/dist/Types/Monitor/DnsMonitor/DnsRecordType.js.map +1 -0
  168. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +62 -0
  169. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  170. package/build/dist/Types/Monitor/MonitorStep.js +22 -0
  171. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  172. package/build/dist/Types/Monitor/MonitorStepDnsMonitor.js +34 -0
  173. package/build/dist/Types/Monitor/MonitorStepDnsMonitor.js.map +1 -0
  174. package/build/dist/Types/Monitor/MonitorType.js +13 -2
  175. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  176. package/build/dist/Types/Permission.js +637 -0
  177. package/build/dist/Types/Permission.js.map +1 -1
  178. package/build/dist/UI/Components/Detail/Detail.js +7 -1
  179. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  180. package/build/dist/UI/Components/Dropdown/Dropdown.js +17 -2
  181. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  182. package/build/dist/UI/Components/Forms/BasicForm.js +17 -3
  183. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  184. package/build/dist/UI/Components/Forms/Fields/PermissionPicker.js +129 -0
  185. package/build/dist/UI/Components/Forms/Fields/PermissionPicker.js.map +1 -0
  186. package/build/dist/UI/Utils/Permission.js +17 -4
  187. package/build/dist/UI/Utils/Permission.js.map +1 -1
  188. package/build/dist/Utils/Monitor/MonitorMetricType.js +2 -1
  189. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  190. package/package.json +1 -1
@@ -7,6 +7,7 @@ import CustomCodeMonitorResponse from "../Monitor/CustomCodeMonitor/CustomCodeMo
7
7
  import SslMonitorResponse from "../Monitor/SSLMonitor/SslMonitorResponse";
8
8
  import SyntheticMonitorResponse from "../Monitor/SyntheticMonitors/SyntheticMonitorResponse";
9
9
  import SnmpMonitorResponse from "../Monitor/SnmpMonitor/SnmpMonitorResponse";
10
+ import DnsMonitorResponse from "../Monitor/DnsMonitor/DnsMonitorResponse";
10
11
  import MonitorEvaluationSummary from "../Monitor/MonitorEvaluationSummary";
11
12
  import ObjectID from "../ObjectID";
12
13
  import Port from "../Port";
@@ -30,6 +31,7 @@ export default interface ProbeMonitorResponse {
30
31
  syntheticMonitorResponse?: Array<SyntheticMonitorResponse> | undefined;
31
32
  customCodeMonitorResponse?: CustomCodeMonitorResponse | undefined;
32
33
  snmpResponse?: SnmpMonitorResponse | undefined;
34
+ dnsResponse?: DnsMonitorResponse | undefined;
33
35
  monitoredAt: Date;
34
36
  isTimeout?: boolean | undefined;
35
37
  ingestedAt?: Date | undefined;
@@ -4,7 +4,7 @@ import CodeBlock from "../CodeBlock/CodeBlock";
4
4
  import ColorViewer from "../ColorViewer/ColorViewer";
5
5
  import CopyableButton from "../CopyableButton/CopyableButton";
6
6
  import DictionaryOfStringsViewer from "../Dictionary/DictionaryOfStingsViewer";
7
- import { DropdownOption } from "../Dropdown/Dropdown";
7
+ import { DropdownOption, DropdownOptionGroup } from "../Dropdown/Dropdown";
8
8
  import HiddenText from "../HiddenText/HiddenText";
9
9
  import MarkdownViewer from "../Markdown.tsx/LazyMarkdownViewer";
10
10
  import ObjectIDView from "../ObjectID/ObjectIDView";
@@ -72,13 +72,13 @@ const Detail: DetailFunction = <T extends GenericObject>(
72
72
 
73
73
  type GetDropdownViewerFunction = (
74
74
  data: string,
75
- options: Array<DropdownOption>,
75
+ options: Array<DropdownOption | DropdownOptionGroup>,
76
76
  placeholder: string,
77
77
  ) => ReactElement;
78
78
 
79
79
  const getDropdownViewer: GetDropdownViewerFunction = (
80
80
  data: string,
81
- options: Array<DropdownOption>,
81
+ options: Array<DropdownOption | DropdownOptionGroup>,
82
82
  placeholder: string,
83
83
  ): ReactElement => {
84
84
  if (!options) {
@@ -87,7 +87,16 @@ const Detail: DetailFunction = <T extends GenericObject>(
87
87
  );
88
88
  }
89
89
 
90
- const selectedOption: DropdownOption | undefined = options.find(
90
+ const flatOptions: Array<DropdownOption> = options.flatMap(
91
+ (item: DropdownOption | DropdownOptionGroup) => {
92
+ if ("options" in item && Array.isArray(item.options)) {
93
+ return item.options;
94
+ }
95
+ return [item as DropdownOption];
96
+ },
97
+ );
98
+
99
+ const selectedOption: DropdownOption | undefined = flatOptions.find(
91
100
  (i: DropdownOption) => {
92
101
  return i.value === data;
93
102
  },
@@ -1,5 +1,5 @@
1
1
  import AlignItem from "../../Types/AlignItem";
2
- import { DropdownOption } from "../Dropdown/Dropdown";
2
+ import { DropdownOption, DropdownOptionGroup } from "../Dropdown/Dropdown";
3
3
  import FieldType from "../Types/FieldType";
4
4
  import { Size } from "./FieldLabel";
5
5
  import Route from "../../../Types/API/Route";
@@ -18,7 +18,7 @@ export interface FieldBase<T> {
18
18
  description?: string | ReactElement;
19
19
  fieldTitleSize?: Size | undefined;
20
20
  fieldType?: FieldType;
21
- dropdownOptions?: Array<DropdownOption> | undefined;
21
+ dropdownOptions?: Array<DropdownOption | DropdownOptionGroup> | undefined;
22
22
  colSpan?: number | undefined;
23
23
  alignItem?: AlignItem | undefined;
24
24
  contentClassName?: string | undefined;
@@ -35,8 +35,13 @@ export interface DropdownOption {
35
35
  color?: Color;
36
36
  }
37
37
 
38
- export interface ComponentProps {
38
+ export interface DropdownOptionGroup {
39
+ label: string;
39
40
  options: Array<DropdownOption>;
41
+ }
42
+
43
+ export interface ComponentProps {
44
+ options: Array<DropdownOption | DropdownOptionGroup>;
40
45
  initialValue?: undefined | DropdownOption | Array<DropdownOption>;
41
46
  onClick?: undefined | (() => void);
42
47
  placeholder?: undefined | string;
@@ -61,6 +66,25 @@ const Dropdown: FunctionComponent<ComponentProps> = (
61
66
  const uniqueId: string = useId();
62
67
  const errorId: string = `dropdown-error-${uniqueId}`;
63
68
 
69
+ const isDropdownOptionGroup: (
70
+ item: DropdownOption | DropdownOptionGroup,
71
+ ) => item is DropdownOptionGroup = (
72
+ item: DropdownOption | DropdownOptionGroup,
73
+ ): item is DropdownOptionGroup => {
74
+ return (
75
+ "options" in item && Array.isArray((item as DropdownOptionGroup).options)
76
+ );
77
+ };
78
+
79
+ const flatOptions: Array<DropdownOption> = props.options.flatMap(
80
+ (item: DropdownOption | DropdownOptionGroup) => {
81
+ if (isDropdownOptionGroup(item)) {
82
+ return item.options;
83
+ }
84
+ return [item];
85
+ },
86
+ );
87
+
64
88
  type GetDropdownOptionFromValueFunctionProps =
65
89
  | undefined
66
90
  | DropdownValue
@@ -103,13 +127,14 @@ const Dropdown: FunctionComponent<ComponentProps> = (
103
127
  !Array.isArray(item) &&
104
128
  (typeof item === "string" || typeof item === "number")
105
129
  ) {
106
- const option: DropdownOption | undefined | Array<DropdownOption> =
107
- props.options.find((option: DropdownOption) => {
130
+ const option: DropdownOption | undefined = flatOptions.find(
131
+ (option: DropdownOption) => {
108
132
  return option.value === item;
109
- }) as DropdownOption | Array<DropdownOption>;
133
+ },
134
+ );
110
135
 
111
136
  if (option) {
112
- options.push(option as DropdownOption);
137
+ options.push(option);
113
138
  }
114
139
  }
115
140
  }
@@ -121,9 +146,9 @@ const Dropdown: FunctionComponent<ComponentProps> = (
121
146
  !Array.isArray(value) &&
122
147
  (typeof value === "string" || typeof value === "number")
123
148
  ) {
124
- return props.options.find((option: DropdownOption) => {
149
+ return flatOptions.find((option: DropdownOption) => {
125
150
  return option.value === value;
126
- }) as DropdownOption | Array<DropdownOption>;
151
+ });
127
152
  }
128
153
 
129
154
  return value as DropdownOption | Array<DropdownOption>;
@@ -569,6 +594,12 @@ const Dropdown: FunctionComponent<ComponentProps> = (
569
594
 
570
595
  return "px-3 py-2 text-sm text-gray-700";
571
596
  },
597
+ group: () => {
598
+ return "py-1";
599
+ },
600
+ groupHeading: () => {
601
+ return "px-3 py-2 text-xs font-semibold uppercase tracking-wider text-gray-500";
602
+ },
572
603
  noOptionsMessage: () => {
573
604
  return "px-3 py-2 text-sm text-gray-500";
574
605
  },
@@ -4,7 +4,11 @@ import Alert, { AlertType } from "../Alerts/Alert";
4
4
  import Button, { ButtonStyleType } from "../Button/Button";
5
5
  import ButtonTypes from "../Button/ButtonTypes";
6
6
 
7
- import { DropdownOption, DropdownValue } from "../Dropdown/Dropdown";
7
+ import {
8
+ DropdownOption,
9
+ DropdownOptionGroup,
10
+ DropdownValue,
11
+ } from "../Dropdown/Dropdown";
8
12
  import ErrorMessage from "../ErrorMessage/ErrorMessage";
9
13
  import FormField from "./Fields/FormField";
10
14
  import FormSummary from "./FormSummary";
@@ -301,7 +305,7 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
301
305
  setIsDropdownOptionsLoading(true);
302
306
  // If a dropdown has fetch optiosn then we need to fetch them
303
307
  try {
304
- const options: Array<DropdownOption> =
308
+ const options: Array<DropdownOption | DropdownOptionGroup> =
305
309
  await item.fetchDropdownOptions(refCurrentValue.current);
306
310
  item.dropdownOptions = options;
307
311
  } catch (err) {
@@ -501,8 +505,21 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
501
505
  field.fieldType === FormFieldSchemaType.Dropdown &&
502
506
  (values as any)[fieldName]
503
507
  ) {
508
+ const flatDropdownOptions: Array<DropdownOption> =
509
+ field.dropdownOptions?.flatMap(
510
+ (item: DropdownOption | DropdownOptionGroup) => {
511
+ if (
512
+ "options" in item &&
513
+ Array.isArray((item as DropdownOptionGroup).options)
514
+ ) {
515
+ return (item as DropdownOptionGroup).options;
516
+ }
517
+ return [item as DropdownOption];
518
+ },
519
+ ) || [];
520
+
504
521
  const dropdownOption: DropdownOption | undefined =
505
- field.dropdownOptions?.find((option: DropdownOption) => {
522
+ flatDropdownOptions.find((option: DropdownOption) => {
506
523
  let valueToCompare: DropdownValue = (values as any)[fieldName];
507
524
 
508
525
  if ((valueToCompare as any) instanceof ObjectID) {
@@ -519,8 +536,21 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
519
536
  field.fieldType === FormFieldSchemaType.MultiSelectDropdown &&
520
537
  (values as any)[fieldName]
521
538
  ) {
539
+ const flatDropdownOptions: Array<DropdownOption> =
540
+ field.dropdownOptions?.flatMap(
541
+ (item: DropdownOption | DropdownOptionGroup) => {
542
+ if (
543
+ "options" in item &&
544
+ Array.isArray((item as DropdownOptionGroup).options)
545
+ ) {
546
+ return (item as DropdownOptionGroup).options;
547
+ }
548
+ return [item as DropdownOption];
549
+ },
550
+ ) || [];
551
+
522
552
  const dropdownOptions: Array<DropdownOption> =
523
- field.dropdownOptions?.filter((option: DropdownOption) => {
553
+ flatDropdownOptions.filter((option: DropdownOption) => {
524
554
  let valueToCompare: Array<DropdownValue> = [
525
555
  ...(values as any)[fieldName],
526
556
  ];
@@ -534,7 +564,7 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
534
564
  });
535
565
 
536
566
  return valueToCompare.includes(option.value);
537
- }) || [];
567
+ });
538
568
 
539
569
  (values as any)[fieldName] = dropdownOptions.map(
540
570
  (option: DropdownOption) => {
@@ -0,0 +1,261 @@
1
+ import Permission, {
2
+ PermissionGroup,
3
+ PermissionHelper,
4
+ PermissionProps,
5
+ } from "../../../../Types/Permission";
6
+ import React, {
7
+ FunctionComponent,
8
+ ReactElement,
9
+ useEffect,
10
+ useMemo,
11
+ useState,
12
+ } from "react";
13
+
14
+ export interface ComponentProps {
15
+ onChange: (value: Permission | null) => void;
16
+ initialValue?: Permission | undefined;
17
+ placeholder?: string | undefined;
18
+ onFocus?: (() => void) | undefined;
19
+ tabIndex?: number | undefined;
20
+ onBlur?: (() => void) | undefined;
21
+ error?: string | undefined;
22
+ }
23
+
24
+ const PermissionPicker: FunctionComponent<ComponentProps> = (
25
+ props: ComponentProps,
26
+ ): ReactElement => {
27
+ const [selectedPermission, setSelectedPermission] =
28
+ useState<Permission | null>(props.initialValue || null);
29
+ const [activeGroup, setActiveGroup] = useState<PermissionGroup | null>(null);
30
+ const [searchQuery, setSearchQuery] = useState<string>("");
31
+
32
+ const allPermissions: Array<PermissionProps> = useMemo(() => {
33
+ return PermissionHelper.getTenantPermissionProps();
34
+ }, []);
35
+
36
+ const groupedPermissions: Map<
37
+ PermissionGroup,
38
+ Array<PermissionProps>
39
+ > = useMemo(() => {
40
+ const map: Map<PermissionGroup, Array<PermissionProps>> = new Map();
41
+ for (const perm of allPermissions) {
42
+ if (!map.has(perm.group)) {
43
+ map.set(perm.group, []);
44
+ }
45
+ map.get(perm.group)!.push(perm);
46
+ }
47
+ return map;
48
+ }, [allPermissions]);
49
+
50
+ const groups: Array<PermissionGroup> = useMemo(() => {
51
+ return Array.from(groupedPermissions.keys());
52
+ }, [groupedPermissions]);
53
+
54
+ // Auto-select the group for the initial value
55
+ useEffect(() => {
56
+ if (props.initialValue && !activeGroup) {
57
+ const match: PermissionProps | undefined = allPermissions.find(
58
+ (p: PermissionProps) => {
59
+ return p.permission === props.initialValue;
60
+ },
61
+ );
62
+ if (match) {
63
+ setActiveGroup(match.group);
64
+ }
65
+ }
66
+ }, [props.initialValue, allPermissions, activeGroup]);
67
+
68
+ // Default to first group if none selected
69
+ useEffect(() => {
70
+ if (!activeGroup && groups.length > 0 && !props.initialValue) {
71
+ setActiveGroup(groups[0]!);
72
+ }
73
+ }, [activeGroup, groups, props.initialValue]);
74
+
75
+ const isSearching: boolean = searchQuery.trim().length > 0;
76
+ const lowerQuery: string = searchQuery.toLowerCase();
77
+
78
+ const filteredPermissions: Array<PermissionProps> = useMemo(() => {
79
+ if (!isSearching) {
80
+ return [];
81
+ }
82
+ return allPermissions.filter((p: PermissionProps) => {
83
+ return (
84
+ p.title.toLowerCase().includes(lowerQuery) ||
85
+ p.description.toLowerCase().includes(lowerQuery)
86
+ );
87
+ });
88
+ }, [allPermissions, isSearching, lowerQuery]);
89
+
90
+ const searchMatchCountByGroup: Map<PermissionGroup, number> = useMemo(() => {
91
+ const counts: Map<PermissionGroup, number> = new Map();
92
+ if (!isSearching) {
93
+ return counts;
94
+ }
95
+ for (const perm of filteredPermissions) {
96
+ counts.set(perm.group, (counts.get(perm.group) || 0) + 1);
97
+ }
98
+ return counts;
99
+ }, [filteredPermissions, isSearching]);
100
+
101
+ const visiblePermissions: Array<PermissionProps> = useMemo(() => {
102
+ if (isSearching) {
103
+ if (activeGroup) {
104
+ return filteredPermissions.filter((p: PermissionProps) => {
105
+ return p.group === activeGroup;
106
+ });
107
+ }
108
+ return filteredPermissions;
109
+ }
110
+ if (!activeGroup) {
111
+ return [];
112
+ }
113
+ return groupedPermissions.get(activeGroup) || [];
114
+ }, [isSearching, activeGroup, filteredPermissions, groupedPermissions]);
115
+
116
+ type HandlePermissionClickFunction = (perm: PermissionProps) => void;
117
+
118
+ const handlePermissionClick: HandlePermissionClickFunction = (
119
+ perm: PermissionProps,
120
+ ): void => {
121
+ setSelectedPermission(perm.permission);
122
+ props.onChange(perm.permission);
123
+ };
124
+
125
+ type GetGroupCountFunction = (group: PermissionGroup) => number;
126
+
127
+ const getGroupCount: GetGroupCountFunction = (
128
+ group: PermissionGroup,
129
+ ): number => {
130
+ if (isSearching) {
131
+ return searchMatchCountByGroup.get(group) || 0;
132
+ }
133
+ return groupedPermissions.get(group)?.length || 0;
134
+ };
135
+
136
+ return (
137
+ <div
138
+ tabIndex={props.tabIndex}
139
+ onFocus={props.onFocus}
140
+ onBlur={props.onBlur}
141
+ >
142
+ <div
143
+ className={`border rounded-md overflow-hidden ${
144
+ props.error ? "border-red-400" : "border-gray-300"
145
+ }`}
146
+ style={{ height: "400px" }}
147
+ >
148
+ {/* Search bar */}
149
+ <div className="border-b border-gray-200 p-2">
150
+ <input
151
+ type="text"
152
+ className="w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 focus:outline-none"
153
+ placeholder={props.placeholder || "Search permissions..."}
154
+ value={searchQuery}
155
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
156
+ setSearchQuery(e.target.value);
157
+ }}
158
+ />
159
+ </div>
160
+
161
+ <div className="flex" style={{ height: "calc(100% - 45px)" }}>
162
+ {/* Left sidebar - groups */}
163
+ <div
164
+ className="border-r border-gray-200 overflow-y-auto bg-gray-50 flex-shrink-0"
165
+ style={{ width: "200px" }}
166
+ >
167
+ {groups.map((group: PermissionGroup) => {
168
+ const count: number = getGroupCount(group);
169
+ const isActive: boolean = activeGroup === group;
170
+ const isDimmed: boolean = isSearching && count === 0;
171
+
172
+ return (
173
+ <button
174
+ key={group}
175
+ type="button"
176
+ className={`w-full text-left px-3 py-2 text-sm flex items-center justify-between border-b border-gray-100 transition-colors ${
177
+ isActive
178
+ ? "bg-indigo-50 text-indigo-700 font-medium"
179
+ : isDimmed
180
+ ? "text-gray-300 cursor-default"
181
+ : "text-gray-700 hover:bg-gray-100"
182
+ }`}
183
+ onClick={() => {
184
+ if (!isDimmed) {
185
+ setActiveGroup(group);
186
+ }
187
+ }}
188
+ >
189
+ <span className="truncate">{group}</span>
190
+ <span
191
+ className={`ml-1 text-xs flex-shrink-0 ${
192
+ isActive ? "text-indigo-500" : "text-gray-400"
193
+ }`}
194
+ >
195
+ {count}
196
+ </span>
197
+ </button>
198
+ );
199
+ })}
200
+ </div>
201
+
202
+ {/* Right panel - permissions */}
203
+ <div className="flex-1 overflow-y-auto">
204
+ {visiblePermissions.length === 0 && (
205
+ <div className="flex items-center justify-center h-full text-gray-400 text-sm">
206
+ {isSearching
207
+ ? "No permissions match your search."
208
+ : "Select a group to view permissions."}
209
+ </div>
210
+ )}
211
+
212
+ {visiblePermissions.map((perm: PermissionProps) => {
213
+ const isSelected: boolean =
214
+ selectedPermission === perm.permission;
215
+ return (
216
+ <button
217
+ key={perm.permission}
218
+ type="button"
219
+ className={`w-full text-left px-4 py-2.5 border-b border-gray-100 transition-colors ${
220
+ isSelected
221
+ ? "bg-indigo-50 border-l-2 border-l-indigo-500"
222
+ : "hover:bg-gray-50"
223
+ }`}
224
+ onClick={() => {
225
+ handlePermissionClick(perm);
226
+ }}
227
+ >
228
+ <div className="flex items-start gap-2">
229
+ <div className="flex-1 min-w-0">
230
+ <div
231
+ className={`text-sm font-medium ${
232
+ isSelected ? "text-indigo-700" : "text-gray-900"
233
+ }`}
234
+ >
235
+ {perm.title}
236
+ </div>
237
+ <div className="text-xs text-gray-500 mt-0.5">
238
+ {perm.description}
239
+ </div>
240
+ </div>
241
+ {isSearching && (
242
+ <span className="inline-flex items-center rounded bg-gray-100 px-1.5 py-0.5 text-xs text-gray-600 flex-shrink-0">
243
+ {perm.group}
244
+ </span>
245
+ )}
246
+ </div>
247
+ </button>
248
+ );
249
+ })}
250
+ </div>
251
+ </div>
252
+ </div>
253
+
254
+ {props.error && (
255
+ <p className="mt-1 text-sm text-red-400">{props.error}</p>
256
+ )}
257
+ </div>
258
+ );
259
+ };
260
+
261
+ export default PermissionPicker;
@@ -4,7 +4,7 @@ import {
4
4
  CheckboxCategory,
5
5
  } from "../../CategoryCheckbox/CategoryCheckboxTypes";
6
6
  import { CardSelectOption } from "../../CardSelect/CardSelect";
7
- import { DropdownOption } from "../../Dropdown/Dropdown";
7
+ import { DropdownOption, DropdownOptionGroup } from "../../Dropdown/Dropdown";
8
8
  import { RadioButton } from "../../RadioButtons/GroupRadioButtons";
9
9
  import FormFieldSchemaType from "./FormFieldSchemaType";
10
10
  import FormValues from "./FormValues";
@@ -50,10 +50,12 @@ export default interface Field<TEntity> {
50
50
  disabled?: boolean;
51
51
  stepId?: string | undefined;
52
52
  required?: boolean | ((item: FormValues<TEntity>) => boolean) | undefined;
53
- dropdownOptions?: Array<DropdownOption> | undefined;
53
+ dropdownOptions?: Array<DropdownOption | DropdownOptionGroup> | undefined;
54
54
  cardSelectOptions?: Array<CardSelectOption> | undefined;
55
55
  fetchDropdownOptions?:
56
- | ((item: FormValues<TEntity>) => Promise<Array<DropdownOption>>)
56
+ | ((
57
+ item: FormValues<TEntity>,
58
+ ) => Promise<Array<DropdownOption | DropdownOptionGroup>>)
57
59
  | undefined;
58
60
  showHorizontalRuleBelow?: boolean | undefined;
59
61
  showHorizontalRuleAbove?: boolean | undefined;
@@ -1,7 +1,11 @@
1
- import { DropdownOption } from "../Components/Dropdown/Dropdown";
1
+ import {
2
+ DropdownOption,
3
+ DropdownOptionGroup,
4
+ } from "../Components/Dropdown/Dropdown";
2
5
  import LocalStorage from "./LocalStorage";
3
6
  import { JSONObject } from "../../Types/JSON";
4
7
  import Permission, {
8
+ PermissionGroup,
5
9
  PermissionHelper,
6
10
  PermissionProps,
7
11
  UserGlobalAccessPermission,
@@ -60,16 +64,35 @@ export default class PermissionUtil {
60
64
  return userTenantAccessPermission;
61
65
  }
62
66
 
63
- public static projectPermissionsAsDropdownOptions(): Array<DropdownOption> {
67
+ public static projectPermissionsAsDropdownOptions(): Array<DropdownOptionGroup> {
64
68
  const permissions: Array<PermissionProps> =
65
69
  PermissionHelper.getTenantPermissionProps();
66
70
 
67
- return permissions.map((permissionProp: PermissionProps) => {
68
- return {
71
+ const groupMap: Map<PermissionGroup, Array<DropdownOption>> = new Map();
72
+
73
+ for (const permissionProp of permissions) {
74
+ const group: PermissionGroup = permissionProp.group;
75
+
76
+ if (!groupMap.has(group)) {
77
+ groupMap.set(group, []);
78
+ }
79
+
80
+ groupMap.get(group)!.push({
69
81
  value: permissionProp.permission,
70
82
  label: permissionProp.title,
71
- };
72
- });
83
+ });
84
+ }
85
+
86
+ const groups: Array<DropdownOptionGroup> = [];
87
+
88
+ for (const [group, options] of groupMap) {
89
+ groups.push({
90
+ label: group,
91
+ options,
92
+ });
93
+ }
94
+
95
+ return groups;
73
96
  }
74
97
 
75
98
  public static setGlobalPermissions(
@@ -85,7 +85,8 @@ class MonitorMetricTypeUtil {
85
85
  monitorType === MonitorType.Ping ||
86
86
  monitorType === MonitorType.IP ||
87
87
  monitorType === MonitorType.Port ||
88
- monitorType === MonitorType.SNMP
88
+ monitorType === MonitorType.SNMP ||
89
+ monitorType === MonitorType.DNS
89
90
  ) {
90
91
  return [MonitorMetricType.IsOnline, MonitorMetricType.ResponseTime];
91
92
  }
@@ -51,7 +51,7 @@ let Alert = class Alert extends BaseModel {
51
51
  // monitor this alert was created for.
52
52
  this.monitor = undefined;
53
53
  this.monitorId = undefined;
54
- this.onCallDutyPolicies = undefined; // monitors affected by this alert.
54
+ this.onCallDutyPolicies = undefined; // on-call duty policies affected by this alert.
55
55
  this.labels = undefined;
56
56
  this.currentAlertState = undefined;
57
57
  this.currentAlertStateId = undefined;
@@ -389,8 +389,8 @@ __decorate([
389
389
  }),
390
390
  TableColumn({
391
391
  type: TableColumnType.ObjectID,
392
- title: "Deleted by User ID",
393
- description: "User ID who deleted this object (if this object was deleted by a User)",
392
+ title: "Monitor ID",
393
+ description: "ID of the monitor this alert belongs to",
394
394
  }),
395
395
  Column({
396
396
  type: ColumnType.ObjectID,
@@ -424,9 +424,9 @@ __decorate([
424
424
  TableColumn({
425
425
  required: false,
426
426
  type: TableColumnType.EntityArray,
427
- modelType: Monitor,
427
+ modelType: OnCallDutyPolicy,
428
428
  title: "On-Call Duty Policies",
429
- description: "List of on-call duty policy affected by this alert.",
429
+ description: "List of on-call duty policies affected by this alert.",
430
430
  }),
431
431
  ManyToMany(() => {
432
432
  return OnCallDutyPolicy;
@@ -434,11 +434,11 @@ __decorate([
434
434
  JoinTable({
435
435
  name: "AlertOnCallDutyPolicy",
436
436
  inverseJoinColumn: {
437
- name: "monitorId",
437
+ name: "onCallDutyPolicyId",
438
438
  referencedColumnName: "_id",
439
439
  },
440
440
  joinColumn: {
441
- name: "onCallDutyPolicyId",
441
+ name: "alertId",
442
442
  referencedColumnName: "_id",
443
443
  },
444
444
  }),
@@ -663,7 +663,7 @@ __decorate([
663
663
  TableColumn({
664
664
  manyToOneRelationColumn: "monitorStatusWhenThisAlertWasCreatedId",
665
665
  type: TableColumnType.Entity,
666
- modelType: AlertState,
666
+ modelType: MonitorStatus,
667
667
  title: "Monitor status when this alert was created",
668
668
  description: "Monitor status when this alert was created",
669
669
  }),