@oneuptime/common 12.0.2 → 12.0.3

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 (228) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
  2. package/Models/DatabaseModels/Index.ts +2 -0
  3. package/Models/DatabaseModels/LogDropFilter.ts +9 -20
  4. package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
  5. package/Models/DatabaseModels/StatusPageResource.ts +73 -0
  6. package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
  7. package/Server/API/BaseAPI.ts +13 -2
  8. package/Server/API/DashboardAPI.ts +39 -0
  9. package/Server/EnvironmentConfig.ts +31 -1
  10. package/Server/Infrastructure/GlobalCache.ts +115 -0
  11. package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  14. package/Server/Services/AlertStateTimelineService.ts +20 -0
  15. package/Server/Services/CephClusterService.ts +26 -86
  16. package/Server/Services/CloudResourceService.ts +23 -83
  17. package/Server/Services/DatabaseService.ts +111 -0
  18. package/Server/Services/DockerHostService.ts +19 -79
  19. package/Server/Services/DockerSwarmClusterService.ts +27 -88
  20. package/Server/Services/HostService.ts +46 -99
  21. package/Server/Services/IncidentStateTimelineService.ts +20 -0
  22. package/Server/Services/Index.ts +2 -0
  23. package/Server/Services/IoTFleetService.ts +19 -79
  24. package/Server/Services/KubernetesClusterService.ts +17 -78
  25. package/Server/Services/LabelService.ts +57 -10
  26. package/Server/Services/MetricTypeService.ts +84 -0
  27. package/Server/Services/MonitorService.ts +89 -14
  28. package/Server/Services/MonitorStatusTimelineService.ts +29 -0
  29. package/Server/Services/MonitorTemplateService.ts +65 -0
  30. package/Server/Services/PodmanHostService.ts +19 -79
  31. package/Server/Services/ProxmoxClusterService.ts +23 -83
  32. package/Server/Services/RumApplicationService.ts +19 -80
  33. package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
  34. package/Server/Services/ServerlessFunctionService.ts +24 -85
  35. package/Server/Services/ServiceService.ts +299 -73
  36. package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
  37. package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
  38. package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
  39. package/Server/Services/TelemetryEntityService.ts +1 -0
  40. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
  41. package/Server/Utils/Monitor/MonitorResource.ts +63 -36
  42. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
  43. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
  44. package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
  45. package/Server/Utils/SingleFlight.ts +83 -0
  46. package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
  47. package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
  48. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
  49. package/Server/Utils/Telemetry/Telemetry.ts +351 -122
  50. package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
  51. package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
  52. package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
  53. package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
  54. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
  55. package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
  56. package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
  57. package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
  58. package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
  59. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
  60. package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
  61. package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
  62. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
  63. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
  64. package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
  65. package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
  66. package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
  67. package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
  68. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
  69. package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
  70. package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
  71. package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
  72. package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
  73. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
  74. package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
  75. package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
  76. package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
  77. package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
  78. package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
  79. package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
  80. package/Tests/Types/Decimal.test.ts +37 -0
  81. package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
  82. package/Tests/Types/Port.test.ts +38 -0
  83. package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
  84. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
  85. package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
  86. package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
  87. package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
  88. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  89. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  90. package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
  91. package/Types/Dashboard/DashboardTemplates.ts +397 -0
  92. package/Types/Database/BigIntColumnTransformer.ts +74 -0
  93. package/Types/Database/DatabaseProperty.ts +26 -0
  94. package/Types/Database/NumericColumnValue.ts +128 -0
  95. package/Types/Decimal.ts +16 -4
  96. package/Types/Permission.ts +48 -0
  97. package/Types/Port.ts +16 -4
  98. package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
  99. package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
  100. package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
  101. package/Utils/Dashboard/Components/Index.ts +7 -0
  102. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
  103. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  105. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  106. package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
  107. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
  109. package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
  110. package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
  111. package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
  113. package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
  114. package/build/dist/Server/API/BaseAPI.js +11 -1
  115. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  116. package/build/dist/Server/API/DashboardAPI.js +39 -0
  117. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  118. package/build/dist/Server/EnvironmentConfig.js +27 -1
  119. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  120. package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
  121. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  122. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
  123. package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  128. package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
  129. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  130. package/build/dist/Server/Services/CephClusterService.js +26 -76
  131. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  132. package/build/dist/Server/Services/CloudResourceService.js +23 -73
  133. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  134. package/build/dist/Server/Services/DatabaseService.js +86 -0
  135. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  136. package/build/dist/Server/Services/DockerHostService.js +19 -69
  137. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  138. package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
  139. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  140. package/build/dist/Server/Services/HostService.js +58 -102
  141. package/build/dist/Server/Services/HostService.js.map +1 -1
  142. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  143. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  144. package/build/dist/Server/Services/Index.js +2 -0
  145. package/build/dist/Server/Services/Index.js.map +1 -1
  146. package/build/dist/Server/Services/IoTFleetService.js +19 -69
  147. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  148. package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
  149. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  150. package/build/dist/Server/Services/LabelService.js +42 -7
  151. package/build/dist/Server/Services/LabelService.js.map +1 -1
  152. package/build/dist/Server/Services/MetricTypeService.js +74 -0
  153. package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
  154. package/build/dist/Server/Services/MonitorService.js +81 -9
  155. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  156. package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
  157. package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
  158. package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
  159. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  160. package/build/dist/Server/Services/PodmanHostService.js +19 -69
  161. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  162. package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
  163. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  164. package/build/dist/Server/Services/RumApplicationService.js +19 -70
  165. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  166. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
  167. package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
  168. package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
  169. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  170. package/build/dist/Server/Services/ServiceService.js +236 -61
  171. package/build/dist/Server/Services/ServiceService.js.map +1 -1
  172. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
  173. package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
  174. package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
  175. package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
  176. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
  177. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  178. package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
  179. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  180. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
  181. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  182. package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
  183. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  184. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
  185. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  186. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
  187. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
  188. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
  189. package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
  190. package/build/dist/Server/Utils/SingleFlight.js +76 -0
  191. package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
  192. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
  193. package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
  194. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
  195. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  196. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
  197. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
  198. package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
  199. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  200. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  201. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  202. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  203. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  204. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
  205. package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
  206. package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
  207. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  208. package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
  209. package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
  210. package/build/dist/Types/Database/DatabaseProperty.js +25 -0
  211. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  212. package/build/dist/Types/Database/NumericColumnValue.js +97 -0
  213. package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
  214. package/build/dist/Types/Decimal.js +14 -3
  215. package/build/dist/Types/Decimal.js.map +1 -1
  216. package/build/dist/Types/Permission.js +42 -0
  217. package/build/dist/Types/Permission.js.map +1 -1
  218. package/build/dist/Types/Port.js +15 -5
  219. package/build/dist/Types/Port.js.map +1 -1
  220. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
  221. package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
  222. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
  223. package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
  224. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
  225. package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
  226. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  227. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  228. package/package.json +1 -1
@@ -1,117 +1,129 @@
1
1
  import AlertSeverityService from "../../Services/AlertSeverityService";
2
+ import IncidentRoleService from "../../Services/IncidentRoleService";
2
3
  import IncidentSeverityService from "../../Services/IncidentSeverityService";
4
+ import LabelService from "../../Services/LabelService";
3
5
  import MonitorStatusService from "../../Services/MonitorStatusService";
4
- import QueryHelper from "../../Types/Database/QueryHelper";
5
- import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
6
- import BadDataException from "../../../Types/Exception/BadDataException";
7
- import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
8
- const UUID_REGEX = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g;
6
+ import NetworkDeviceService from "../../Services/NetworkDeviceService";
7
+ import OnCallDutyPolicyService from "../../Services/OnCallDutyPolicyService";
8
+ import ServiceService from "../../Services/ServiceService";
9
+ import TeamService from "../../Services/TeamService";
10
+ import UserService from "../../Services/UserService";
11
+ import ProjectScopedReferenceValidator from "../Database/ProjectScopedReferenceValidator";
12
+ import MonitorStepsReferenceExtractor, { MonitorStepsReferenceModel, } from "./MonitorStepsReferenceExtractor";
9
13
  /*
10
- * `monitorSteps` is a JSON blob that embeds ids of other project-scoped
11
- * records — the default monitor status, the status each criteria switches to,
12
- * and the severities used to open incidents and alerts. Nothing used to check
13
- * that those ids belonged to the monitor's own project, so a status id from a
14
- * second project could be saved and then stamped onto every
15
- * MonitorStatusTimeline row the monitor wrote. That silently ties two projects
16
- * together and leaves the referenced project undeletable.
14
+ * `monitorSteps` is a JSON blob that embeds ids of other records — the default
15
+ * monitor status, the status each criteria switches to, the severities used to
16
+ * open incidents and alerts, and the on-call policies, labels, teams and users
17
+ * stamped on what those incidents and alerts create.
17
18
  *
18
- * The check below is deliberately narrow: it only rejects an id that really is
19
- * another project's record. monitorSteps is full of unrelated uuids (step ids,
20
- * criteria ids, incident/alert template ids) and those never match a row here,
21
- * so they pass through untouched. Ids that match nothing at all are also left
22
- * alone refusing them would block users from saving their way out of a
23
- * monitor that already references a deleted record.
19
+ * Two things can be wrong with one of those ids, and both used to be accepted:
20
+ *
21
+ * 1. It belongs to ANOTHER project. That silently ties two projects together
22
+ * and leaves the referenced project undeletable, because the row the id ends
23
+ * up on (a MonitorStatusTimeline, an Incident) has an ON DELETE NO ACTION
24
+ * foreign key pointing back at it.
25
+ *
26
+ * 2. It exists nowhere at all (issue #3039). The monitor saved fine and then
27
+ * failed every time it ran, deep inside the probe worker, with
28
+ * insert or update on table "MonitorStatusTimeline" violates foreign key
29
+ * constraint
30
+ * and nothing surfaced to whoever supplied the id — which is easy to do over
31
+ * the REST API or Terraform, where these are just uuids in a JSON body.
32
+ *
33
+ * Telling a real reference apart from the many uuids in monitorSteps that are
34
+ * NOT references (step ids, criteria ids, incident/alert template ids) is what
35
+ * makes check 2 possible, and that is MonitorStepsReferenceExtractor's job: it
36
+ * walks the structure instead of scraping the text, so every id it hands back
37
+ * is known to point at a specific model.
24
38
  */
39
+ const SERVICE_BY_MODEL = {
40
+ [MonitorStepsReferenceModel.MonitorStatus]: MonitorStatusService,
41
+ [MonitorStepsReferenceModel.IncidentSeverity]: IncidentSeverityService,
42
+ [MonitorStepsReferenceModel.AlertSeverity]: AlertSeverityService,
43
+ [MonitorStepsReferenceModel.OnCallDutyPolicy]: OnCallDutyPolicyService,
44
+ [MonitorStepsReferenceModel.Label]: LabelService,
45
+ [MonitorStepsReferenceModel.Team]: TeamService,
46
+ [MonitorStepsReferenceModel.User]: UserService,
47
+ [MonitorStepsReferenceModel.IncidentRole]: IncidentRoleService,
48
+ [MonitorStepsReferenceModel.TelemetryService]: ServiceService,
49
+ [MonitorStepsReferenceModel.NetworkDevice]: NetworkDeviceService,
50
+ };
25
51
  export default class MonitorStepsProjectValidator {
26
52
  static async validateMonitorStepsBelongToProject(data) {
27
- var _a, _b, _c, _d, _e, _f;
28
53
  if (!data.monitorSteps || !data.projectId) {
29
54
  return;
30
55
  }
31
- const referencedIds = this.extractUuids(data.monitorSteps);
32
- if (referencedIds.length === 0) {
56
+ const references = MonitorStepsReferenceExtractor.extract(data.monitorSteps);
57
+ if (references.length === 0) {
33
58
  return;
34
59
  }
35
- const foreignReferences = [];
36
- const monitorStatuses = await MonitorStatusService.findBy({
37
- query: {
38
- _id: QueryHelper.any(referencedIds),
39
- },
40
- select: { _id: true, name: true, projectId: true },
41
- limit: LIMIT_PER_PROJECT,
42
- skip: 0,
43
- props: { isRoot: true },
60
+ /*
61
+ * An id the record ALREADY holds is this write's inheritance, not its
62
+ * doing, and is left alone entirely — neither checked for existence nor for
63
+ * belonging to another project.
64
+ *
65
+ * None of these ids has a foreign key behind it, so one really can end up
66
+ * wrong through no fault of the person now saving: deleting a monitor
67
+ * status, label, team or on-call policy does not rewrite the criteria that
68
+ * name it, and the 1785240000000 repair migration deliberately left behind
69
+ * the cross-project ids it could not map to a local equivalent. The
70
+ * dashboard re-submits the whole blob on every save and renders an
71
+ * unresolvable id as an empty dropdown, so checking them would refuse EVERY
72
+ * edit of such a monitor — including renaming it — over a field the user
73
+ * never touched and cannot see is wrong, with no migration to get them out.
74
+ *
75
+ * Letting a stale id ride costs less than it used to. The status and
76
+ * severity ids — the ones that reach a foreign-key column and made the
77
+ * referenced project undeletable — are now screened where they are written:
78
+ * MonitorStatusTimelineService rejects a status that does not resolve, and
79
+ * the probe worker skips one it cannot use rather than writing it, as
80
+ * MonitorIncident and MonitorAlert already did for severities.
81
+ *
82
+ * That screening does NOT extend to the ids that land on join rows
83
+ * (IncidentLabel, IncidentOnCallDutyPolicy and friends). A stale label or
84
+ * on-call policy carried forward here can still fail when the criteria
85
+ * fires. That is a pre-existing gap in MonitorIncident/MonitorAlert rather
86
+ * than something this exemption opens up — those ids were not validated at
87
+ * all before — and refusing the SAVE would not close it, because the
88
+ * monitor that never gets edited again fails just the same.
89
+ *
90
+ * What this write INTRODUCES is checked in full. That is issue #3039.
91
+ *
92
+ * Compared case-insensitively: ObjectID keeps whatever case it was given,
93
+ * so the same id can be stored as Postgres rendered it (lower) and re-sent
94
+ * by a caller in upper.
95
+ */
96
+ const grandfatheredIds = new Set(MonitorStepsReferenceExtractor.extract(data.alreadyStoredMonitorSteps).map((reference) => {
97
+ return reference.id.toLowerCase();
98
+ }));
99
+ const introduced = references.filter((reference) => {
100
+ return !grandfatheredIds.has(reference.id.toLowerCase());
44
101
  });
45
- for (const monitorStatus of monitorStatuses) {
46
- if (((_a = monitorStatus.projectId) === null || _a === void 0 ? void 0 : _a.toString()) !== data.projectId.toString()) {
47
- foreignReferences.push({
48
- modelName: "Monitor Status",
49
- name: monitorStatus.name || ((_b = monitorStatus.id) === null || _b === void 0 ? void 0 : _b.toString()) || "",
50
- });
51
- }
52
- }
53
- const incidentSeverities = await IncidentSeverityService.findBy({
54
- query: {
55
- _id: QueryHelper.any(referencedIds),
56
- },
57
- select: { _id: true, name: true, projectId: true },
58
- limit: LIMIT_PER_PROJECT,
59
- skip: 0,
60
- props: { isRoot: true },
61
- });
62
- for (const incidentSeverity of incidentSeverities) {
63
- if (((_c = incidentSeverity.projectId) === null || _c === void 0 ? void 0 : _c.toString()) !== data.projectId.toString()) {
64
- foreignReferences.push({
65
- modelName: "Incident Severity",
66
- name: incidentSeverity.name || ((_d = incidentSeverity.id) === null || _d === void 0 ? void 0 : _d.toString()) || "",
67
- });
68
- }
102
+ if (introduced.length === 0) {
103
+ return;
69
104
  }
70
- const alertSeverities = await AlertSeverityService.findBy({
71
- query: {
72
- _id: QueryHelper.any(referencedIds),
73
- },
74
- select: { _id: true, name: true, projectId: true },
75
- limit: LIMIT_PER_PROJECT,
76
- skip: 0,
77
- props: { isRoot: true },
105
+ await ProjectScopedReferenceValidator.validateReferencesBelongToProject({
106
+ projectId: data.projectId,
107
+ subject: "monitor's criteria",
108
+ references: introduced.map((reference) => {
109
+ return {
110
+ /*
111
+ * The path — `criteria "Monitor is offline" incident severity` —
112
+ * rather than the bare model name, so the message points at the
113
+ * field to fix. monitorSteps can hold a dozen of these.
114
+ */
115
+ modelName: `${reference.model} (${reference.path})`,
116
+ id: reference.id,
117
+ service: SERVICE_BY_MODEL[reference.model],
118
+ /*
119
+ * Only ids the monitor actually reads at run time are required to
120
+ * exist — see MonitorStepsReference.isOperative. The rest are still
121
+ * checked for belonging to this project.
122
+ */
123
+ mustExist: reference.isOperative,
124
+ };
125
+ }),
78
126
  });
79
- for (const alertSeverity of alertSeverities) {
80
- if (((_e = alertSeverity.projectId) === null || _e === void 0 ? void 0 : _e.toString()) !== data.projectId.toString()) {
81
- foreignReferences.push({
82
- modelName: "Alert Severity",
83
- name: alertSeverity.name || ((_f = alertSeverity.id) === null || _f === void 0 ? void 0 : _f.toString()) || "",
84
- });
85
- }
86
- }
87
- if (foreignReferences.length > 0) {
88
- const described = foreignReferences
89
- .map((reference) => {
90
- return `${reference.modelName} "${reference.name}"`;
91
- })
92
- .join(", ");
93
- throw new BadDataException(`Monitor criteria reference records that belong to a different project: ${described}. Please pick values from this project and try again.`);
94
- }
95
- }
96
- static extractUuids(monitorSteps) {
97
- let serialized = "";
98
- try {
99
- serialized =
100
- monitorSteps instanceof MonitorSteps
101
- ? JSON.stringify(monitorSteps.toJSON())
102
- : JSON.stringify(monitorSteps);
103
- }
104
- catch (_a) {
105
- /*
106
- * Unserializable monitorSteps is a different problem and is reported by
107
- * MonitorSteps' own validation. Nothing to check here.
108
- */
109
- return [];
110
- }
111
- const matches = serialized.match(UUID_REGEX) || [];
112
- return Array.from(new Set(matches.map((match) => {
113
- return match.toLowerCase();
114
- })));
115
127
  }
116
128
  }
117
129
  //# sourceMappingURL=MonitorStepsProjectValidator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MonitorStepsProjectValidator.js","sourceRoot":"","sources":["../../../../../Server/Utils/Monitor/MonitorStepsProjectValidator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MonitorStepsProjectValidator.js","sourceRoot":"","sources":["../../../../../Server/Utils/Monitor/MonitorStepsProjectValidator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,312 @@
1
+ import MonitorSteps from "../../../Types/Monitor/MonitorSteps";
2
+ import ObjectID from "../../../Types/ObjectID";
3
+ import Typeof from "../../../Types/Typeof";
4
+ /*
5
+ * `monitorSteps` is a free-form JSON column, so none of the ids it embeds have
6
+ * a foreign key behind them. They are still real references: the monitor status
7
+ * a criteria switches to, the severity it opens incidents and alerts with, the
8
+ * on-call policies it pages, the labels/owners it stamps on what it creates.
9
+ *
10
+ * Until issue #3039 the only guard over this blob scraped every uuid out of the
11
+ * serialized JSON with a regex and asked whether it happened to be another
12
+ * project's record. That can never reject an id that exists nowhere, because
13
+ * monitorSteps is FULL of uuids that are not references at all — step ids,
14
+ * criteria ids, incident/alert template ids — and they match nothing either.
15
+ *
16
+ * This walker exists to tell those apart. It reads the structure rather than the
17
+ * text, so every id it yields is known to be a reference to a specific model and
18
+ * can be checked for existence without any risk of a false rejection.
19
+ */
20
+ export var MonitorStepsReferenceModel;
21
+ (function (MonitorStepsReferenceModel) {
22
+ MonitorStepsReferenceModel["MonitorStatus"] = "Monitor Status";
23
+ MonitorStepsReferenceModel["IncidentSeverity"] = "Incident Severity";
24
+ MonitorStepsReferenceModel["AlertSeverity"] = "Alert Severity";
25
+ MonitorStepsReferenceModel["OnCallDutyPolicy"] = "On-Call Duty Policy";
26
+ MonitorStepsReferenceModel["Label"] = "Label";
27
+ MonitorStepsReferenceModel["Team"] = "Team";
28
+ MonitorStepsReferenceModel["User"] = "User";
29
+ MonitorStepsReferenceModel["IncidentRole"] = "Incident Role";
30
+ MonitorStepsReferenceModel["TelemetryService"] = "Telemetry Service";
31
+ MonitorStepsReferenceModel["NetworkDevice"] = "Network Device";
32
+ })(MonitorStepsReferenceModel || (MonitorStepsReferenceModel = {}));
33
+ /*
34
+ * Step-level sub-configs that scope a monitor to specific telemetry services.
35
+ * All five carry the field under the same name.
36
+ */
37
+ const TELEMETRY_SERVICE_STEP_FIELDS = [
38
+ "logMonitor",
39
+ "traceMonitor",
40
+ "exceptionMonitor",
41
+ "profileMonitor",
42
+ "metricMonitor",
43
+ ];
44
+ /*
45
+ * Every level of monitorSteps is persisted inside a `{ _type, value }` envelope
46
+ * (MonitorSteps.toJSON -> MonitorStep.toJSON -> MonitorCriteria.toJSON ->
47
+ * MonitorCriteriaInstance.toJSON). API callers - Terraform among them - post
48
+ * the same envelope. Unwrap it when it is there and accept the bare object when
49
+ * it is not, so a hand-written payload is walked just as thoroughly.
50
+ */
51
+ function unwrap(value) {
52
+ if (!value || typeof value !== Typeof.Object || Array.isArray(value)) {
53
+ return null;
54
+ }
55
+ const object = value;
56
+ if (object["value"] && typeof object["value"] === Typeof.Object) {
57
+ return object["value"];
58
+ }
59
+ return object;
60
+ }
61
+ function asArray(value) {
62
+ return Array.isArray(value) ? value : [];
63
+ }
64
+ /*
65
+ * An id reaches here in every shape the codebase produces: a plain uuid string
66
+ * (MonitorSteps.toJSON stringifies defaultMonitorStatusId and monitorStatusId),
67
+ * a serialized `{ _type: "ObjectID", value }` envelope (what JSONFunctions.serialize
68
+ * does to the ObjectIDs nested inside the incidents/alerts arrays), a live
69
+ * ObjectID instance (validating a MonitorSteps object that was never
70
+ * serialized), or `{ value }` without the marker.
71
+ *
72
+ * Blank is treated as absent on purpose: a stored `{"_type":"ObjectID","value":""}`
73
+ * rehydrates to `new ObjectID("")`, which is truthy. Reporting that as a missing
74
+ * *reference* would be misleading - it is an unset field, and the required-field
75
+ * checks own that case.
76
+ */
77
+ export function toReferenceId(value) {
78
+ if (!value) {
79
+ return null;
80
+ }
81
+ if (value instanceof ObjectID) {
82
+ return value.toString().trim() || null;
83
+ }
84
+ if (typeof value === Typeof.String) {
85
+ return value.trim() || null;
86
+ }
87
+ if (typeof value === Typeof.Object && !Array.isArray(value)) {
88
+ const inner = value["value"];
89
+ if (typeof inner === Typeof.String) {
90
+ return inner.trim() || null;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+ export default class MonitorStepsReferenceExtractor {
96
+ /*
97
+ * Walks monitorSteps and returns every id it holds that points at another
98
+ * record. Ids that are NOT references (step ids, criteria ids, the id on each
99
+ * incident/alert template) are deliberately not returned - they are local
100
+ * identity, not a pointer to a row.
101
+ */
102
+ static extract(monitorSteps) {
103
+ const root = this.toJSONObject(monitorSteps);
104
+ if (!root) {
105
+ return [];
106
+ }
107
+ const references = [];
108
+ this.push(references, {
109
+ model: MonitorStepsReferenceModel.MonitorStatus,
110
+ value: root["defaultMonitorStatusId"],
111
+ path: "default monitor status",
112
+ /*
113
+ * Always operative: MonitorResource stamps it onto the monitor's first
114
+ * MonitorStatusTimeline row, which is the very insert issue #3039 reports
115
+ * failing.
116
+ */
117
+ isOperative: true,
118
+ });
119
+ const steps = asArray(root["monitorStepsInstanceArray"]);
120
+ for (const step of steps) {
121
+ const stepValue = unwrap(step);
122
+ if (!stepValue) {
123
+ continue;
124
+ }
125
+ this.extractFromStepConfig(references, stepValue);
126
+ const criteria = unwrap(stepValue["monitorCriteria"]);
127
+ if (!criteria) {
128
+ continue;
129
+ }
130
+ const instances = asArray(criteria["monitorCriteriaInstanceArray"]);
131
+ for (let index = 0; index < instances.length; index++) {
132
+ this.extractFromCriteriaInstance({
133
+ references: references,
134
+ criteriaInstance: unwrap(instances[index]),
135
+ index: index,
136
+ });
137
+ }
138
+ }
139
+ return references;
140
+ }
141
+ /*
142
+ * The step's own targets: the telemetry services a log/trace/exception/
143
+ * profile/metric monitor is scoped to, and the device a network monitor
144
+ * walks.
145
+ *
146
+ * These are reported as references — so a *cross-project* id in them is
147
+ * refused like any other — but never as `isOperative`, so they are not
148
+ * required to exist. Both already degrade on their own when the target is
149
+ * gone (the query simply matches nothing; NetworkDeviceHydrationUtil logs a
150
+ * warning and carries on), neither can produce the foreign key violation this
151
+ * guard exists for, and deleting a telemetry service is ordinary enough that
152
+ * refusing every later edit of a monitor that once watched it would be a
153
+ * worse failure than the one being fixed.
154
+ */
155
+ static extractFromStepConfig(references, step) {
156
+ for (const field of TELEMETRY_SERVICE_STEP_FIELDS) {
157
+ const config = unwrap(step[field]);
158
+ if (!config) {
159
+ continue;
160
+ }
161
+ for (const id of asArray(config["telemetryServiceIds"])) {
162
+ this.push(references, {
163
+ model: MonitorStepsReferenceModel.TelemetryService,
164
+ value: id,
165
+ path: "monitored telemetry service",
166
+ isOperative: false,
167
+ });
168
+ }
169
+ }
170
+ const networkDeviceMonitor = unwrap(step["networkDeviceMonitor"]);
171
+ if (networkDeviceMonitor) {
172
+ this.push(references, {
173
+ model: MonitorStepsReferenceModel.NetworkDevice,
174
+ value: networkDeviceMonitor["networkDeviceId"],
175
+ path: "monitored network device",
176
+ isOperative: false,
177
+ });
178
+ }
179
+ }
180
+ static extractFromCriteriaInstance(data) {
181
+ const criteriaInstance = data.criteriaInstance;
182
+ if (!criteriaInstance) {
183
+ return;
184
+ }
185
+ const name = criteriaInstance["name"] || `#${data.index + 1}`;
186
+ const label = `criteria "${name}"`;
187
+ this.push(data.references, {
188
+ model: MonitorStepsReferenceModel.MonitorStatus,
189
+ value: criteriaInstance["monitorStatusId"],
190
+ path: `${label} monitor status`,
191
+ isOperative: criteriaInstance["changeMonitorStatus"] === true,
192
+ });
193
+ const createsIncidents = criteriaInstance["createIncidents"] === true;
194
+ for (const incident of asArray(criteriaInstance["incidents"])) {
195
+ const incidentValue = unwrap(incident);
196
+ if (!incidentValue) {
197
+ continue;
198
+ }
199
+ this.push(data.references, {
200
+ model: MonitorStepsReferenceModel.IncidentSeverity,
201
+ value: incidentValue["incidentSeverityId"],
202
+ path: `${label} incident severity`,
203
+ isOperative: createsIncidents,
204
+ });
205
+ this.pushSharedNotificationReferences({
206
+ references: data.references,
207
+ source: incidentValue,
208
+ label: `${label} incident`,
209
+ isOperative: createsIncidents,
210
+ });
211
+ for (const roleAssignment of asArray(incidentValue["incidentMemberRoles"])) {
212
+ const assignment = unwrap(roleAssignment);
213
+ if (!assignment) {
214
+ continue;
215
+ }
216
+ this.push(data.references, {
217
+ model: MonitorStepsReferenceModel.IncidentRole,
218
+ value: assignment["roleId"],
219
+ path: `${label} incident member role`,
220
+ isOperative: createsIncidents,
221
+ });
222
+ this.push(data.references, {
223
+ model: MonitorStepsReferenceModel.User,
224
+ value: assignment["userId"],
225
+ path: `${label} incident member`,
226
+ isOperative: createsIncidents,
227
+ });
228
+ }
229
+ }
230
+ const createsAlerts = criteriaInstance["createAlerts"] === true;
231
+ for (const alert of asArray(criteriaInstance["alerts"])) {
232
+ const alertValue = unwrap(alert);
233
+ if (!alertValue) {
234
+ continue;
235
+ }
236
+ this.push(data.references, {
237
+ model: MonitorStepsReferenceModel.AlertSeverity,
238
+ value: alertValue["alertSeverityId"],
239
+ path: `${label} alert severity`,
240
+ isOperative: createsAlerts,
241
+ });
242
+ this.pushSharedNotificationReferences({
243
+ references: data.references,
244
+ source: alertValue,
245
+ label: `${label} alert`,
246
+ isOperative: createsAlerts,
247
+ });
248
+ }
249
+ }
250
+ // The four id arrays CriteriaIncident and CriteriaAlert have in common.
251
+ static pushSharedNotificationReferences(data) {
252
+ const arrays = [
253
+ {
254
+ field: "onCallPolicyIds",
255
+ model: MonitorStepsReferenceModel.OnCallDutyPolicy,
256
+ path: "on-call policy",
257
+ },
258
+ {
259
+ field: "labelIds",
260
+ model: MonitorStepsReferenceModel.Label,
261
+ path: "label",
262
+ },
263
+ {
264
+ field: "ownerTeamIds",
265
+ model: MonitorStepsReferenceModel.Team,
266
+ path: "owner team",
267
+ },
268
+ {
269
+ field: "ownerUserIds",
270
+ model: MonitorStepsReferenceModel.User,
271
+ path: "owner user",
272
+ },
273
+ ];
274
+ for (const array of arrays) {
275
+ for (const id of asArray(data.source[array.field])) {
276
+ this.push(data.references, {
277
+ model: array.model,
278
+ value: id,
279
+ path: `${data.label} ${array.path}`,
280
+ isOperative: data.isOperative,
281
+ });
282
+ }
283
+ }
284
+ }
285
+ static push(references, data) {
286
+ const id = toReferenceId(data.value);
287
+ if (!id) {
288
+ return;
289
+ }
290
+ references.push({
291
+ model: data.model,
292
+ id: id,
293
+ path: data.path,
294
+ isOperative: data.isOperative,
295
+ });
296
+ }
297
+ static toJSONObject(monitorSteps) {
298
+ if (!monitorSteps) {
299
+ return null;
300
+ }
301
+ if (monitorSteps instanceof MonitorSteps) {
302
+ /*
303
+ * toJSON() on a MonitorSteps whose `data` is undefined returns the
304
+ * "new monitor steps" skeleton, which holds no references. Walking it is
305
+ * harmless.
306
+ */
307
+ return unwrap(monitorSteps.toJSON());
308
+ }
309
+ return unwrap(monitorSteps);
310
+ }
311
+ }
312
+ //# sourceMappingURL=MonitorStepsReferenceExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MonitorStepsReferenceExtractor.js","sourceRoot":"","sources":["../../../../../Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /*
2
+ * In-process de-duplication of concurrent identical work.
3
+ *
4
+ * A Redis fence admits one writer per window across the FLEET, but every
5
+ * caller still pays a Redis round trip to discover it lost. That is the wrong
6
+ * unit of work when the duplicate callers are in the same process: an ingest
7
+ * pod running hundreds of concurrent jobs re-resolves the same handful of
8
+ * services on every batch, so hundreds of coroutines independently ask Redis
9
+ * the same question and then throw the answer away.
10
+ *
11
+ * SingleFlight collapses them first. The first caller for a key runs the work;
12
+ * everyone who arrives while it is still in flight awaits the SAME promise and
13
+ * gets the same result. Per pod, N concurrent duplicates cost one Redis round
14
+ * trip and at most one database write instead of N of each — which is what
15
+ * turns fleet-wide write amplification from (pods x jobs) into (pods).
16
+ *
17
+ * Deliberately unbounded in time but not in size: entries live only for the
18
+ * duration of the work itself and are removed in a `finally`, so the map holds
19
+ * exactly the set of operations currently running. There is no eviction policy
20
+ * to get wrong and nothing to leak if work hangs beyond a caller's patience —
21
+ * the entry is released when the underlying promise settles either way.
22
+ *
23
+ * This is a per-process optimisation and NOT a correctness mechanism: nothing
24
+ * here coordinates across pods. It composes with a distributed fence, it does
25
+ * not replace one.
26
+ */
27
+ class SingleFlight {
28
+ /**
29
+ * Run `work` for `key`, or join the run already in flight for it.
30
+ *
31
+ * Followers observe whatever the leader observes, including a rejection.
32
+ * That is intentional: these callers asked for identical work, so they are
33
+ * entitled to identical outcomes, and a follower silently retrying a failure
34
+ * the leader just hit would re-create the stampede this exists to prevent.
35
+ */
36
+ static async run(key, work) {
37
+ const existing = this.inflight.get(key);
38
+ if (existing) {
39
+ return (await existing);
40
+ }
41
+ /*
42
+ * Invoke through a wrapper so a `work` that throws SYNCHRONOUSLY still
43
+ * produces a promise to register and await. Calling it bare would throw
44
+ * past the bookkeeping below and leave the key registered forever.
45
+ */
46
+ const promise = (async () => {
47
+ return await work();
48
+ })();
49
+ this.inflight.set(key, promise);
50
+ try {
51
+ return await promise;
52
+ }
53
+ finally {
54
+ /*
55
+ * Only clear the entry if it is still ours. A follower cannot install a
56
+ * new one (it awaited instead), but a later leader can, and blindly
57
+ * deleting here would evict that live run and let the next caller start
58
+ * a duplicate of work already underway.
59
+ */
60
+ if (this.inflight.get(key) === promise) {
61
+ this.inflight.delete(key);
62
+ }
63
+ }
64
+ }
65
+ /** Number of operations currently in flight. For tests and diagnostics. */
66
+ static inflightCount() {
67
+ return this.inflight.size;
68
+ }
69
+ /** Drop all bookkeeping. For tests only. */
70
+ static clear() {
71
+ this.inflight.clear();
72
+ }
73
+ }
74
+ SingleFlight.inflight = new Map();
75
+ export default SingleFlight;
76
+ //# sourceMappingURL=SingleFlight.js.map