@oneuptime/common 12.0.0 → 12.0.2

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 (237) hide show
  1. package/Models/DatabaseModels/Host.ts +12 -4
  2. package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
  3. package/Models/DatabaseModels/KubernetesResource.ts +8 -7
  4. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +12 -2
  5. package/Models/DatabaseModels/PodmanResource.ts +6 -6
  6. package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
  7. package/Models/DatabaseModels/StatusPage.ts +95 -0
  8. package/Server/API/TeamMemberAPI.ts +85 -0
  9. package/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts +12 -2
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  15. package/Server/Middleware/ProjectAuthorization.ts +66 -4
  16. package/Server/Services/BillingService.ts +600 -51
  17. package/Server/Services/CephClusterService.ts +47 -5
  18. package/Server/Services/CephResourceService.ts +43 -4
  19. package/Server/Services/CloudResourceService.ts +47 -5
  20. package/Server/Services/DatabaseService.ts +87 -6
  21. package/Server/Services/DockerHostService.ts +47 -5
  22. package/Server/Services/DockerResourceService.ts +62 -8
  23. package/Server/Services/DockerSwarmClusterService.ts +48 -5
  24. package/Server/Services/DockerSwarmResourceService.ts +53 -4
  25. package/Server/Services/HostService.ts +48 -5
  26. package/Server/Services/IoTDeviceService.ts +1 -17
  27. package/Server/Services/IoTFleetService.ts +47 -5
  28. package/Server/Services/KubernetesClusterService.ts +48 -5
  29. package/Server/Services/KubernetesContainerService.ts +50 -9
  30. package/Server/Services/KubernetesResourceService.ts +53 -7
  31. package/Server/Services/LabelService.ts +106 -1
  32. package/Server/Services/LlmProviderService.ts +69 -9
  33. package/Server/Services/MonitorService.ts +57 -0
  34. package/Server/Services/PodmanHostService.ts +47 -5
  35. package/Server/Services/PodmanResourceService.ts +62 -8
  36. package/Server/Services/ProjectService.ts +165 -35
  37. package/Server/Services/ProxmoxClusterService.ts +47 -5
  38. package/Server/Services/ProxmoxResourceService.ts +43 -4
  39. package/Server/Services/RumApplicationService.ts +48 -5
  40. package/Server/Services/RunnerService.ts +1 -7
  41. package/Server/Services/ServerlessFunctionService.ts +48 -5
  42. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
  43. package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
  44. package/Server/Services/StatusPageService.ts +95 -36
  45. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
  46. package/Server/Services/StatusPageSubscriberService.ts +3 -0
  47. package/Server/Types/Database/JSONColumnQuery.ts +621 -0
  48. package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
  49. package/Server/Types/Database/QueryHelper.ts +17 -32
  50. package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
  51. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +12 -0
  52. package/Tests/App/Dashboard/RunnerInstallInstructions.test.tsx +119 -0
  53. package/Tests/App/Dashboard/RunnerStatus.test.tsx +274 -0
  54. package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
  55. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
  56. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
  57. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
  58. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
  59. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
  60. package/Tests/Server/Services/BillingService.test.ts +78 -3
  61. package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
  62. package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
  63. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
  64. package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
  65. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +510 -0
  66. package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
  67. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
  68. package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
  69. package/Tests/Server/Services/LlmProviderUsableByProject.test.ts +293 -0
  70. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
  71. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
  72. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +387 -0
  73. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
  74. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
  75. package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
  76. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
  77. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
  78. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
  79. package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
  80. package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
  81. package/Tests/Server/Utils/AI/ToolArgsExtractors.test.ts +141 -0
  82. package/Tests/Server/Utils/AI/Toolbox/WidgetBuilder.test.ts +205 -0
  83. package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
  84. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +897 -0
  85. package/Tests/Types/Events/Recurring.test.ts +157 -0
  86. package/Tests/Types/Runner/RunnerLiveStatus.test.ts +320 -0
  87. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +285 -0
  88. package/Tests/UI/Components/Detail/EntityFields.test.tsx +166 -0
  89. package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
  90. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
  91. package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
  92. package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
  93. package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
  94. package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
  95. package/Types/CustomField/CustomFieldDefinition.ts +24 -0
  96. package/Types/Date.ts +112 -0
  97. package/Types/Events/Recurring.ts +99 -1
  98. package/Types/Runbook/RunbookStep.ts +15 -0
  99. package/Types/Runner/RunnerLiveStatus.ts +114 -0
  100. package/Types/StatusPage/StatusPageReport.ts +11 -0
  101. package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
  102. package/UI/Components/Detail/Detail.tsx +155 -0
  103. package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
  104. package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
  105. package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
  106. package/UI/Utils/TeamMembersByUser.ts +302 -0
  107. package/Utils/StatusPage/ReportPeriod.ts +236 -0
  108. package/Utils/Telemetry/HostIpAddresses.ts +98 -0
  109. package/build/dist/Models/DatabaseModels/Host.js +12 -4
  110. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  111. package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
  112. package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
  113. package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
  114. package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
  115. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +16 -4
  116. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
  117. package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
  118. package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
  119. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
  120. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  121. package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
  122. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  123. package/build/dist/Server/API/TeamMemberAPI.js +57 -2
  124. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  125. package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.js +11 -1
  126. package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.js.map +1 -1
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
  131. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
  132. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
  133. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
  134. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
  135. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  136. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  137. package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
  138. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  139. package/build/dist/Server/Services/BillingService.js +466 -36
  140. package/build/dist/Server/Services/BillingService.js.map +1 -1
  141. package/build/dist/Server/Services/CephClusterService.js +42 -5
  142. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  143. package/build/dist/Server/Services/CephResourceService.js +28 -3
  144. package/build/dist/Server/Services/CephResourceService.js.map +1 -1
  145. package/build/dist/Server/Services/CloudResourceService.js +42 -5
  146. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  147. package/build/dist/Server/Services/DatabaseService.js +69 -2
  148. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  149. package/build/dist/Server/Services/DockerHostService.js +42 -5
  150. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  151. package/build/dist/Server/Services/DockerResourceService.js +34 -4
  152. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  153. package/build/dist/Server/Services/DockerSwarmClusterService.js +43 -5
  154. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  155. package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
  156. package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
  157. package/build/dist/Server/Services/HostService.js +43 -5
  158. package/build/dist/Server/Services/HostService.js.map +1 -1
  159. package/build/dist/Server/Services/IoTDeviceService.js +1 -8
  160. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  161. package/build/dist/Server/Services/IoTFleetService.js +42 -5
  162. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  163. package/build/dist/Server/Services/KubernetesClusterService.js +43 -5
  164. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  165. package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
  166. package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
  167. package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
  168. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
  169. package/build/dist/Server/Services/LabelService.js +92 -0
  170. package/build/dist/Server/Services/LabelService.js.map +1 -1
  171. package/build/dist/Server/Services/LlmProviderService.js +61 -8
  172. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  173. package/build/dist/Server/Services/MonitorService.js +47 -0
  174. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  175. package/build/dist/Server/Services/PodmanHostService.js +42 -5
  176. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  177. package/build/dist/Server/Services/PodmanResourceService.js +34 -4
  178. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  179. package/build/dist/Server/Services/ProjectService.js +135 -21
  180. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  181. package/build/dist/Server/Services/ProxmoxClusterService.js +42 -5
  182. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  183. package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
  184. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  185. package/build/dist/Server/Services/RumApplicationService.js +43 -5
  186. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  187. package/build/dist/Server/Services/RunnerService.js +1 -6
  188. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  189. package/build/dist/Server/Services/ServerlessFunctionService.js +43 -5
  190. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  191. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
  192. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
  193. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
  194. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  195. package/build/dist/Server/Services/StatusPageService.js +69 -46
  196. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  197. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
  198. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  199. package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
  200. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  201. package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
  202. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
  203. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
  204. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
  205. package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
  206. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  207. package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
  208. package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
  209. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +11 -0
  210. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  211. package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
  212. package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
  213. package/build/dist/Types/Date.js +68 -0
  214. package/build/dist/Types/Date.js.map +1 -1
  215. package/build/dist/Types/Events/Recurring.js +63 -0
  216. package/build/dist/Types/Events/Recurring.js.map +1 -1
  217. package/build/dist/Types/Runbook/RunbookStep.js.map +1 -1
  218. package/build/dist/Types/Runner/RunnerLiveStatus.js +69 -0
  219. package/build/dist/Types/Runner/RunnerLiveStatus.js.map +1 -0
  220. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
  221. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
  222. package/build/dist/UI/Components/Detail/Detail.js +97 -0
  223. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  224. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  225. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
  226. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  227. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
  228. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
  229. package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
  230. package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
  231. package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
  232. package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
  233. package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
  234. package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
  235. package/jest.config.json +2 -0
  236. package/package.json +1 -1
  237. package/tsconfig.json +12 -1
@@ -0,0 +1,213 @@
1
+ import ModelAPI from "./ModelAPI";
2
+ import TeamMembersByUser from "../TeamMembersByUser";
3
+ import API from "../API/API";
4
+ import { APP_API_URL } from "../../Config";
5
+ import TeamMember from "../../../Models/DatabaseModels/TeamMember";
6
+ import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
7
+ import URL from "../../../Types/API/URL";
8
+ import Includes from "../../../Types/BaseDatabase/Includes";
9
+ import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
10
+ /*
11
+ * A drop-in ModelAPI for tables that list TeamMember but mean to list *people*
12
+ * - the project's Users table being the one that does.
13
+ *
14
+ * TeamMember rows are (user, team) pairs, so the plain API pages over
15
+ * memberships: a user on three teams occupies three rows and reads as three
16
+ * different people with the same name. This API pages over users instead. It
17
+ * reads the project's memberships, folds them into one row per user (see
18
+ * TeamMembersByUser), and then applies the table's skip/limit to *that* list,
19
+ * so `count` is a number of people and every page boundary falls between two
20
+ * people rather than between two of one person's teams.
21
+ *
22
+ * Paging cannot be pushed to the server here: the list endpoint has no DISTINCT
23
+ * or GROUP BY, so there is no way to ask it for the Nth user. Reading the whole
24
+ * membership list up front is what the project user pickers already do.
25
+ *
26
+ * Deleting is redefined to match what a row now means - see deleteItem.
27
+ */
28
+ export default class ProjectUsersModelAPI extends ModelAPI {
29
+ static async getList(data) {
30
+ if (!(new data.modelType() instanceof TeamMember)) {
31
+ // Not the model this API exists for. Behave like the plain one.
32
+ return ModelAPI.getList(data);
33
+ }
34
+ const query = data.query;
35
+ const sort = data.sort;
36
+ const memberships = await this.readAllMemberships({
37
+ query: query,
38
+ select: this.getMembershipSelect(data.select),
39
+ sort: sort,
40
+ requestOptions: data.requestOptions,
41
+ });
42
+ let rows = TeamMembersByUser.groupByUser(memberships);
43
+ /*
44
+ * An explicit sort was already applied by the server and grouping kept it.
45
+ * Without one the server's order is arbitrary, and an arbitrary order that
46
+ * is then sliced into pages is worse here than elsewhere - so fall back to
47
+ * something a reader of a people directory expects, and that pages the same
48
+ * way every time.
49
+ */
50
+ if (Object.keys(sort || {}).length === 0) {
51
+ rows = TeamMembersByUser.sortRowsByUserName(rows);
52
+ }
53
+ const pageRows = rows.slice(data.skip, data.skip + data.limit);
54
+ await this.fillInTeamsHiddenByFilters({
55
+ query: query,
56
+ pageRows: pageRows,
57
+ requestOptions: data.requestOptions,
58
+ });
59
+ return {
60
+ data: pageRows,
61
+ count: rows.length,
62
+ skip: data.skip,
63
+ limit: data.limit,
64
+ };
65
+ }
66
+ /**
67
+ * Removes the user on this row from the project - every one of their team
68
+ * memberships, not just the one whose id the row happens to carry.
69
+ *
70
+ * A grouped row stands for a person, so deleting one membership would leave
71
+ * the row on screen (still a member, via their other teams) and look like the
72
+ * delete silently failed. Removing a user from a single team is done from
73
+ * that user's own Teams tab, where a row is a membership again.
74
+ *
75
+ * This goes through one `remove-user-from-project` request rather than a
76
+ * DELETE per membership, and that is load-bearing rather than an
77
+ * optimisation. The server refuses to remove the last accepted member of the
78
+ * Owners team, and it refuses per request - so a loop deletes every other
79
+ * membership first and only then reports a failure, leaving the person
80
+ * stripped of the teams the caller was just told they had not lost. The
81
+ * endpoint runs that check across the whole set before deleting anything.
82
+ */
83
+ static async deleteItem(data) {
84
+ if (!(new data.modelType() instanceof TeamMember)) {
85
+ return ModelAPI.deleteItem(data);
86
+ }
87
+ const rowMembership = await ModelAPI.getItem({
88
+ modelType: TeamMember,
89
+ id: data.id,
90
+ select: {
91
+ _id: true,
92
+ userId: true,
93
+ },
94
+ requestOptions: data.requestOptions,
95
+ });
96
+ const userId = rowMembership === null || rowMembership === void 0 ? void 0 : rowMembership.userId;
97
+ if (!userId) {
98
+ /*
99
+ * The row is gone, or came back without the user it stands for. Fall back
100
+ * to the single delete the table asked for rather than guessing.
101
+ */
102
+ return ModelAPI.deleteItem(data);
103
+ }
104
+ const url = URL.fromString(APP_API_URL.toString())
105
+ .addRoute(new TeamMember().getCrudApiPath())
106
+ .addRoute("/remove-user-from-project");
107
+ const result = await API.post({
108
+ url: url,
109
+ data: {
110
+ userId: userId.toString(),
111
+ },
112
+ headers: ModelAPI.getCommonHeaders(data.requestOptions),
113
+ });
114
+ if (result instanceof HTTPErrorResponse) {
115
+ throw result;
116
+ }
117
+ }
118
+ /**
119
+ * Every membership matching the query, across as many requests as it takes.
120
+ *
121
+ * A single read would be capped at LIMIT_PER_PROJECT, and a capped read is
122
+ * indistinguishable here from a complete one: the grouped list would simply
123
+ * be missing whichever people fell outside the window, `count` would report
124
+ * the truncated total as authoritative, and nothing on screen would say so.
125
+ * Paging until a short page comes back keeps the grouping honest.
126
+ */
127
+ static async readAllMemberships(data) {
128
+ const memberships = [];
129
+ /*
130
+ * A backstop, not an expected bound: every page after the first means a
131
+ * project with more than 10,000 memberships. Reaching it would mean
132
+ * something is paging without making progress.
133
+ */
134
+ const maxPages = 10;
135
+ for (let page = 0; page < maxPages; page++) {
136
+ const result = await ModelAPI.getList({
137
+ modelType: TeamMember,
138
+ query: data.query,
139
+ limit: LIMIT_PER_PROJECT,
140
+ skip: page * LIMIT_PER_PROJECT,
141
+ select: data.select,
142
+ sort: data.sort,
143
+ requestOptions: data.requestOptions,
144
+ });
145
+ memberships.push(...result.data);
146
+ if (result.data.length < LIMIT_PER_PROJECT) {
147
+ // A short page is the last page.
148
+ break;
149
+ }
150
+ }
151
+ return memberships;
152
+ }
153
+ /**
154
+ * The table's own select plus the fields grouping needs. Columns ask for what
155
+ * they render; grouping also needs to know which user and which team each
156
+ * membership belongs to, and whether it was accepted.
157
+ */
158
+ static getMembershipSelect(select) {
159
+ const teamSelect = typeof (select === null || select === void 0 ? void 0 : select.team) === "object" && select.team !== null
160
+ ? Object.assign({}, select.team) : {};
161
+ const userSelect = typeof (select === null || select === void 0 ? void 0 : select.user) === "object" && select.user !== null
162
+ ? Object.assign({}, select.user) : {};
163
+ return Object.assign(Object.assign({}, select), { _id: true, userId: true, teamId: true, hasAcceptedInvitation: true, user: Object.assign(Object.assign({}, userSelect), { _id: true }), team: Object.assign(Object.assign({}, teamSelect), { _id: true, name: true }) });
164
+ }
165
+ /**
166
+ * When the table is filtered, the memberships that were read are only the
167
+ * matching ones - filter by a team and every user's Teams column would show
168
+ * that one team and nothing else, which is exactly the "which teams is this
169
+ * person on?" question the grouped row exists to answer.
170
+ *
171
+ * So for the users actually on this page (at most one page's worth), re-read
172
+ * their memberships unfiltered and merge the full team list back in.
173
+ */
174
+ static async fillInTeamsHiddenByFilters(data) {
175
+ const projectId = data.query["projectId"];
176
+ const isFiltered = Object.keys(data.query).some((key) => {
177
+ return key !== "projectId";
178
+ });
179
+ if (!isFiltered || !projectId || data.pageRows.length === 0) {
180
+ return;
181
+ }
182
+ const userIds = data.pageRows
183
+ .map((row) => {
184
+ return TeamMembersByUser.getGroupKey(row) || "";
185
+ })
186
+ .filter((userId) => {
187
+ return Boolean(userId);
188
+ });
189
+ if (userIds.length === 0) {
190
+ return;
191
+ }
192
+ const allMemberships = await this.readAllMemberships({
193
+ query: {
194
+ projectId: projectId,
195
+ userId: new Includes(userIds),
196
+ },
197
+ select: {
198
+ _id: true,
199
+ userId: true,
200
+ teamId: true,
201
+ hasAcceptedInvitation: true,
202
+ team: {
203
+ _id: true,
204
+ name: true,
205
+ },
206
+ },
207
+ sort: {},
208
+ requestOptions: data.requestOptions,
209
+ });
210
+ TeamMembersByUser.mergeAllTeamsIntoRows(data.pageRows, allMemberships);
211
+ }
212
+ }
213
+ //# sourceMappingURL=ProjectUsersModelAPI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectUsersModelAPI.js","sourceRoot":"","sources":["../../../../../UI/Utils/ModelAPI/ProjectUsersModelAPI.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,195 @@
1
+ export default class TeamMembersByUser {
2
+ /**
3
+ * The identity a membership is grouped under. Null when the row carries no
4
+ * user at all - those rows are never merged, because merging them would put
5
+ * unrelated memberships on one line.
6
+ */
7
+ static getGroupKey(teamMember) {
8
+ var _a, _b, _c;
9
+ const userId = ((_a = teamMember.userId) === null || _a === void 0 ? void 0 : _a.toString()) || ((_c = (_b = teamMember.user) === null || _b === void 0 ? void 0 : _b._id) === null || _c === void 0 ? void 0 : _c.toString()) || "";
10
+ return userId || null;
11
+ }
12
+ /**
13
+ * The name this row sorts and reads under. Falls back to the email so a user
14
+ * who was invited but has not set a name yet still lands somewhere sensible.
15
+ */
16
+ static getDisplayName(teamMember) {
17
+ var _a, _b, _c, _d;
18
+ return (((_b = (_a = teamMember.user) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.toString()) ||
19
+ ((_d = (_c = teamMember.user) === null || _c === void 0 ? void 0 : _c.email) === null || _d === void 0 ? void 0 : _d.toString()) ||
20
+ "");
21
+ }
22
+ /**
23
+ * One row per user, in the order each user was first seen. The input order is
24
+ * whatever the server sorted by, so first-seen order keeps that sort intact.
25
+ *
26
+ * The returned rows are the same TeamMember instances that came in (the first
27
+ * membership seen for each user), mutated with the aggregate fields.
28
+ */
29
+ static groupByUser(teamMembers) {
30
+ const rowsByGroupKey = new Map();
31
+ const rows = [];
32
+ /*
33
+ * Which teams are already on a row, keyed the same way for every
34
+ * membership. Tracked here rather than re-derived from `teamsForUser`
35
+ * because a membership identifies its team by `teamId` while the team
36
+ * object it carries identifies itself by `_id`, and either one can be
37
+ * missing from a given select.
38
+ */
39
+ const teamKeysByRow = new Map();
40
+ /*
41
+ * Every membership id folded into each row, so the row can be given a
42
+ * stable identity below.
43
+ */
44
+ const membershipIdsByRow = new Map();
45
+ for (const teamMember of teamMembers) {
46
+ const groupKey = this.getGroupKey(teamMember);
47
+ const existingRow = groupKey
48
+ ? rowsByGroupKey.get(groupKey)
49
+ : undefined;
50
+ if (existingRow) {
51
+ this.addMembershipToRow(existingRow, teamMember, teamKeysByRow.get(existingRow), Boolean(teamMember.hasAcceptedInvitation));
52
+ this.rememberMembershipId(membershipIdsByRow.get(existingRow), teamMember);
53
+ continue;
54
+ }
55
+ const row = teamMember;
56
+ const teamKeys = new Set();
57
+ const membershipIds = [];
58
+ /*
59
+ * The row *is* this membership, so read its acceptance before the reset
60
+ * below overwrites it.
61
+ */
62
+ const hasAcceptedInvitation = Boolean(teamMember.hasAcceptedInvitation);
63
+ row.teamsForUser = [];
64
+ row.teamCountForUser = 0;
65
+ row.pendingTeamCountForUser = 0;
66
+ /*
67
+ * Reset before folding the row's own membership back in: on a grouped row
68
+ * this field means "is a member of this project", which is true as soon as
69
+ * any one of the memberships has been accepted.
70
+ */
71
+ row.hasAcceptedInvitation = false;
72
+ teamKeysByRow.set(row, teamKeys);
73
+ membershipIdsByRow.set(row, membershipIds);
74
+ this.addMembershipToRow(row, teamMember, teamKeys, hasAcceptedInvitation);
75
+ this.rememberMembershipId(membershipIds, teamMember);
76
+ if (groupKey) {
77
+ rowsByGroupKey.set(groupKey, row);
78
+ }
79
+ rows.push(row);
80
+ }
81
+ for (const row of rows) {
82
+ this.finalizeRow(row, membershipIdsByRow.get(row) || []);
83
+ }
84
+ return rows;
85
+ }
86
+ /**
87
+ * Replaces the team aggregate on each row with the one derived from
88
+ * `allTeamMembers`.
89
+ *
90
+ * Needed because the rows on screen may have been built from a *filtered*
91
+ * membership list - filter the table by one team and grouping only ever sees
92
+ * that team, so the Teams column would claim each user belongs to exactly the
93
+ * team that was filtered for. The caller re-reads the unfiltered memberships
94
+ * for the users on the current page and merges them back in here.
95
+ *
96
+ * Rows with no matching membership in `allTeamMembers` are left untouched.
97
+ */
98
+ static mergeAllTeamsIntoRows(rows, allTeamMembers) {
99
+ const groupedRows = this.groupByUser(allTeamMembers);
100
+ const groupedRowsByKey = new Map();
101
+ for (const groupedRow of groupedRows) {
102
+ const groupKey = this.getGroupKey(groupedRow);
103
+ if (groupKey) {
104
+ groupedRowsByKey.set(groupKey, groupedRow);
105
+ }
106
+ }
107
+ for (const row of rows) {
108
+ const groupKey = this.getGroupKey(row);
109
+ const groupedRow = groupKey
110
+ ? groupedRowsByKey.get(groupKey)
111
+ : undefined;
112
+ if (!groupedRow) {
113
+ continue;
114
+ }
115
+ row.teamsForUser = groupedRow.teamsForUser;
116
+ row.teamCountForUser = groupedRow.teamCountForUser;
117
+ row.pendingTeamCountForUser = groupedRow.pendingTeamCountForUser;
118
+ row.hasAcceptedInvitation = Boolean(groupedRow.hasAcceptedInvitation);
119
+ }
120
+ return rows;
121
+ }
122
+ /**
123
+ * Alphabetical by display name, case-insensitively. Rows with no name at all
124
+ * sort last so the list does not open on a run of blanks.
125
+ *
126
+ * Returns a new array; the input is left alone.
127
+ */
128
+ static sortRowsByUserName(rows) {
129
+ return [...rows].sort((a, b) => {
130
+ const nameA = this.getDisplayName(a).toLowerCase();
131
+ const nameB = this.getDisplayName(b).toLowerCase();
132
+ if (!nameA && !nameB) {
133
+ return 0;
134
+ }
135
+ if (!nameA) {
136
+ return 1;
137
+ }
138
+ if (!nameB) {
139
+ return -1;
140
+ }
141
+ return nameA.localeCompare(nameB);
142
+ });
143
+ }
144
+ static addMembershipToRow(row, teamMember, teamKeys, hasAcceptedInvitation) {
145
+ var _a, _b, _c;
146
+ if (hasAcceptedInvitation) {
147
+ row.hasAcceptedInvitation = true;
148
+ }
149
+ else {
150
+ row.pendingTeamCountForUser = row.pendingTeamCountForUser + 1;
151
+ }
152
+ const team = teamMember.team;
153
+ if (!team) {
154
+ return;
155
+ }
156
+ const teamKey = ((_a = teamMember.teamId) === null || _a === void 0 ? void 0 : _a.toString()) ||
157
+ ((_b = team._id) === null || _b === void 0 ? void 0 : _b.toString()) ||
158
+ ((_c = team.name) === null || _c === void 0 ? void 0 : _c.toString()) ||
159
+ "";
160
+ if (!teamKey || teamKeys.has(teamKey)) {
161
+ return;
162
+ }
163
+ teamKeys.add(teamKey);
164
+ row.teamsForUser.push(team);
165
+ }
166
+ static rememberMembershipId(membershipIds, teamMember) {
167
+ var _a;
168
+ const membershipId = ((_a = teamMember._id) === null || _a === void 0 ? void 0 : _a.toString()) || "";
169
+ if (membershipId) {
170
+ membershipIds.push(membershipId);
171
+ }
172
+ }
173
+ static finalizeRow(row, membershipIds) {
174
+ row.teamsForUser.sort((a, b) => {
175
+ var _a, _b;
176
+ return (((_a = a.name) === null || _a === void 0 ? void 0 : _a.toString()) || "")
177
+ .toLowerCase()
178
+ .localeCompare((((_b = b.name) === null || _b === void 0 ? void 0 : _b.toString()) || "").toLowerCase());
179
+ });
180
+ row.teamCountForUser = row.teamsForUser.length;
181
+ /*
182
+ * The row's id has to identify the *person*, not "whichever of their
183
+ * memberships was returned first". The table matches a selected row to an
184
+ * on-screen row by _id, and it fetches the on-screen page and the
185
+ * select-all set with two different sorts - so a first-seen id makes the
186
+ * same person carry different ids in the two results, and every
187
+ * multi-team user's checkbox silently fails to tick. The lowest membership
188
+ * id is stable under any ordering the server returns.
189
+ */
190
+ if (membershipIds.length > 1) {
191
+ row._id = [...membershipIds].sort()[0];
192
+ }
193
+ }
194
+ }
195
+ //# sourceMappingURL=TeamMembersByUser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamMembersByUser.js","sourceRoot":"","sources":["../../../../UI/Utils/TeamMembersByUser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,131 @@
1
+ import OneUptimeDate from "../../Types/Date";
2
+ import EventInterval from "../../Types/Events/EventInterval";
3
+ import Recurring from "../../Types/Events/Recurring";
4
+ import StatusPageReportPeriodType from "../../Types/StatusPage/StatusPageReportPeriodType";
5
+ import Timezone from "../../Types/Timezone";
6
+ class StatusPageReportPeriodUtil {
7
+ static getDefaultPeriodType() {
8
+ return StatusPageReportPeriodType.Rolling;
9
+ }
10
+ /*
11
+ * Resolve the window a report sent at `sentAt` (default: now) covers.
12
+ *
13
+ * Everything is optional because this is called with a half-filled form as
14
+ * the user types, and with columns that are null on status pages created
15
+ * before reports were configurable. Missing values fall back to the same
16
+ * defaults the database columns carry.
17
+ */
18
+ static getReportPeriod(data) {
19
+ const timezone = data.timezone || this.DEFAULT_TIMEZONE;
20
+ const sentAt = data.sentAt || OneUptimeDate.getCurrentDate();
21
+ if (data.periodType === StatusPageReportPeriodType.PreviousCalendarPeriod) {
22
+ return this.getPreviousCalendarPeriod({
23
+ reportRecurringInterval: data.reportRecurringInterval,
24
+ timezone: timezone,
25
+ sentAt: sentAt,
26
+ });
27
+ }
28
+ return this.getRollingPeriod({
29
+ reportDataInDays: data.reportDataInDays,
30
+ timezone: timezone,
31
+ sentAt: sentAt,
32
+ });
33
+ }
34
+ static getRollingPeriod(data) {
35
+ const numberOfDays = this.toWholeDays(data.reportDataInDays, this.DEFAULT_REPORT_DATA_IN_DAYS);
36
+ const endDate = data.sentAt;
37
+ const startDate = OneUptimeDate.getSomeDaysAgoFromDate(endDate, numberOfDays);
38
+ return {
39
+ startDate: startDate,
40
+ endDate: endDate,
41
+ numberOfDays: numberOfDays,
42
+ timezone: data.timezone,
43
+ reportDates: `${numberOfDays} days (${this.formatDay(startDate, data.timezone)} - ${this.formatDay(endDate, data.timezone)})`,
44
+ periodName: numberOfDays === 1 ? "the last day" : `the last ${numberOfDays} days`,
45
+ };
46
+ }
47
+ /*
48
+ * The last WHOLE calendar period before the report went out, sized by the
49
+ * send frequency. A monthly report sent at any point in August covers
50
+ * 1 Aug 00:00:00.000 minus one month through 31 Jul 23:59:59.999 - i.e. July,
51
+ * exactly as it reads on a calendar, in the status page's report timezone.
52
+ */
53
+ static getPreviousCalendarPeriod(data) {
54
+ var _a;
55
+ const recurring = data.reportRecurringInterval
56
+ ? Recurring.fromJSON(data.reportRecurringInterval)
57
+ : Recurring.getDefault();
58
+ const unit = Recurring.toCalendarUnit(recurring.intervalType);
59
+ const intervalCount = this.toWholeDays((_a = recurring.intervalCount) === null || _a === void 0 ? void 0 : _a.toNumber(), 1);
60
+ /*
61
+ * Exclusive upper bound: the start of the period the report is being sent
62
+ * IN, which is the first instant not yet covered. The inclusive endDate
63
+ * below steps back a millisecond from it so the email reads "Jul 31" rather
64
+ * than "Aug 1".
65
+ */
66
+ const endExclusive = OneUptimeDate.getStartOfCalendarUnit(data.sentAt, unit, data.timezone);
67
+ const startDate = OneUptimeDate.addRemoveCalendarUnits(endExclusive, unit, -intervalCount, data.timezone);
68
+ const endDate = new Date(endExclusive.getTime() - 1);
69
+ return {
70
+ startDate: startDate,
71
+ endDate: endDate,
72
+ numberOfDays: Math.max(1, Math.round((endExclusive.getTime() - startDate.getTime()) /
73
+ OneUptimeDate.getMillisecondsInDays(1))),
74
+ timezone: data.timezone,
75
+ reportDates: `${this.formatDay(startDate, data.timezone)} - ${this.formatDay(endDate, data.timezone)}`,
76
+ periodName: this.getCalendarPeriodName({
77
+ intervalType: recurring.intervalType,
78
+ intervalCount: intervalCount,
79
+ startDate: startDate,
80
+ endDate: endDate,
81
+ timezone: data.timezone,
82
+ }),
83
+ };
84
+ }
85
+ static getCalendarPeriodName(data) {
86
+ /*
87
+ * Only a single-unit period has a name a reader already knows ("July 2026").
88
+ * Anything spanning several units, and hourly periods, are clearer as an
89
+ * explicit range.
90
+ */
91
+ if (data.intervalCount === 1) {
92
+ if (data.intervalType === EventInterval.Month) {
93
+ return this.format(data.startDate, "MMMM YYYY", data.timezone);
94
+ }
95
+ if (data.intervalType === EventInterval.Year) {
96
+ return this.format(data.startDate, "YYYY", data.timezone);
97
+ }
98
+ if (data.intervalType === EventInterval.Week) {
99
+ return `the week of ${this.formatDay(data.startDate, data.timezone)}`;
100
+ }
101
+ if (data.intervalType === EventInterval.Day) {
102
+ return this.formatDay(data.startDate, data.timezone);
103
+ }
104
+ }
105
+ return `${this.formatDay(data.startDate, data.timezone)} - ${this.formatDay(data.endDate, data.timezone)}`;
106
+ }
107
+ /*
108
+ * Guard against the nulls, decimals and zeroes a JSON column or a mid-edit
109
+ * form field can hold. A zero-length window would divide by zero downstream.
110
+ */
111
+ static toWholeDays(value, fallback) {
112
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
113
+ return fallback;
114
+ }
115
+ return Math.floor(value);
116
+ }
117
+ static formatDay(date, timezone) {
118
+ return this.format(date, "MMM D, YYYY", timezone);
119
+ }
120
+ static format(date, format, timezone) {
121
+ return OneUptimeDate.getDateAsCustomFormattedStringInTimezone({
122
+ date: date,
123
+ format: format,
124
+ timezone: timezone,
125
+ });
126
+ }
127
+ }
128
+ StatusPageReportPeriodUtil.DEFAULT_REPORT_DATA_IN_DAYS = 30;
129
+ StatusPageReportPeriodUtil.DEFAULT_TIMEZONE = Timezone.UTC;
130
+ export default StatusPageReportPeriodUtil;
131
+ //# sourceMappingURL=ReportPeriod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportPeriod.js","sourceRoot":"","sources":["../../../../Utils/StatusPage/ReportPeriod.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /*
2
+ * Normalization for the OpenTelemetry `host.ip` resource attribute, which
3
+ * is an ARRAY of every address on every interface — not a single IP.
4
+ *
5
+ * On a Docker host with IPv6 enabled that array grows with the container
6
+ * count: the physical NIC, one address per docker bridge, one per veth,
7
+ * plus an IPv6 link-local per interface. 55 addresses / ~1450 characters
8
+ * has been observed in the wild (issue #3006), and there is no upper
9
+ * bound in the OTel spec.
10
+ *
11
+ * `Host.hostIpAddresses` is a `text` column so the realistic case is
12
+ * stored losslessly. These caps exist only so a pathological (or hostile)
13
+ * collector cannot push an unbounded blob into Postgres on every batch.
14
+ * They sit far above any plausible host, so in practice this function
15
+ * dedupes and joins and nothing else.
16
+ */
17
+ /** Maximum number of addresses retained. */
18
+ export const MAX_HOST_IP_ADDRESS_COUNT = 256;
19
+ /** Maximum length of the serialized, comma-separated list. */
20
+ export const MAX_HOST_IP_ADDRESSES_LENGTH = 10000;
21
+ const SEPARATOR = ", ";
22
+ /**
23
+ * Turn the raw `host.ip` values into the comma-separated string stored in
24
+ * `Host.hostIpAddresses`.
25
+ *
26
+ * - blank / whitespace-only entries are dropped,
27
+ * - each entry is trimmed,
28
+ * - duplicates are removed case-insensitively (IPv6 hex casing is not
29
+ * stable across resource detectors, so `FE80::1` and `fe80::1` are the
30
+ * same address),
31
+ * - source order is preserved — collectors report interfaces in a stable,
32
+ * meaningful order and reordering would churn the Hosts table column,
33
+ * - the result is capped by both address count and serialized length.
34
+ *
35
+ * Truncation always drops whole addresses from the tail. A half-written
36
+ * address would be worse than a missing one: it reads as a real address
37
+ * and there is no way for a consumer to tell it was cut.
38
+ *
39
+ * Returns `null` when nothing survives, so callers can leave the column
40
+ * untouched rather than writing an empty string.
41
+ */
42
+ export function normalizeHostIpAddresses(ipAddresses) {
43
+ if (!ipAddresses || ipAddresses.length === 0) {
44
+ return null;
45
+ }
46
+ const seen = new Set();
47
+ const kept = [];
48
+ let serializedLength = 0;
49
+ for (const raw of ipAddresses) {
50
+ if (typeof raw !== "string") {
51
+ continue;
52
+ }
53
+ const trimmed = raw.trim();
54
+ if (!trimmed) {
55
+ continue;
56
+ }
57
+ const dedupeKey = trimmed.toLowerCase();
58
+ if (seen.has(dedupeKey)) {
59
+ continue;
60
+ }
61
+ const lengthWithSeparator = trimmed.length + (kept.length > 0 ? SEPARATOR.length : 0);
62
+ if (serializedLength + lengthWithSeparator > MAX_HOST_IP_ADDRESSES_LENGTH) {
63
+ /*
64
+ * Stop rather than continue: entries are already ordered by the
65
+ * collector, so skipping this one to squeeze in a shorter later one
66
+ * would make the stored list order-dependent on address width.
67
+ */
68
+ break;
69
+ }
70
+ seen.add(dedupeKey);
71
+ kept.push(trimmed);
72
+ serializedLength += lengthWithSeparator;
73
+ if (kept.length >= MAX_HOST_IP_ADDRESS_COUNT) {
74
+ break;
75
+ }
76
+ }
77
+ if (kept.length === 0) {
78
+ return null;
79
+ }
80
+ return kept.join(SEPARATOR);
81
+ }
82
+ //# sourceMappingURL=HostIpAddresses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostIpAddresses.js","sourceRoot":"","sources":["../../../../Utils/Telemetry/HostIpAddresses.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,4CAA4C;AAC5C,MAAM,CAAC,MAAM,yBAAyB,GAAW,GAAG,CAAC;AAErD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,4BAA4B,GAAW,KAAK,CAAC;AAE1D,MAAM,SAAS,GAAW,IAAI,CAAC;AAE/B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,wBAAwB,CACtC,WAA6C;IAE7C,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,IAAI,GAAgB,IAAI,GAAG,EAAU,CAAC;IAC5C,MAAM,IAAI,GAAkB,EAAE,CAAC;IAC/B,IAAI,gBAAgB,GAAW,CAAC,CAAC;IAEjC,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC5B,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAW,GAAG,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAW,OAAO,CAAC,WAAW,EAAE,CAAC;QAChD,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QAED,MAAM,mBAAmB,GACvB,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAE5D,IAAI,gBAAgB,GAAG,mBAAmB,GAAG,4BAA4B,EAAE,CAAC;YAC1E;;;;eAIG;YACH,MAAM;QACR,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,gBAAgB,IAAI,mBAAmB,CAAC;QAExC,IAAI,IAAI,CAAC,MAAM,IAAI,yBAAyB,EAAE,CAAC;YAC7C,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAC9B,CAAC"}