@oneuptime/common 14.0.6 → 14.0.7

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 (127) hide show
  1. package/Models/DatabaseModels/InventoryItem.ts +23 -12
  2. package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  4. package/Server/Services/AlertService.ts +32 -36
  5. package/Server/Services/IncidentAlertService.ts +609 -2
  6. package/Server/Services/IncidentService.ts +134 -44
  7. package/Server/Services/InventoryItemService.ts +38 -0
  8. package/Server/Services/IoTDeviceCredentialService.ts +13 -4
  9. package/Server/Services/ScheduledMaintenanceService.ts +50 -46
  10. package/Server/Services/UserNotificationRuleService.ts +55 -53
  11. package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
  12. package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
  13. package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
  14. package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
  15. package/Server/Utils/Response.ts +31 -0
  16. package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
  17. package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
  18. package/Server/Utils/Topology/TopologyRequest.ts +463 -0
  19. package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
  20. package/Server/Utils/Topology/TopologySql.ts +1024 -0
  21. package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
  22. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
  23. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
  24. package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
  25. package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
  26. package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
  27. package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
  28. package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
  29. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
  30. package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
  31. package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
  32. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
  33. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
  34. package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
  35. package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
  36. package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
  37. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
  38. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
  39. package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
  40. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
  41. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
  42. package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
  43. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
  44. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
  45. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
  46. package/Tests/Server/Services/DomainService.test.ts +525 -0
  47. package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
  48. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
  49. package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
  50. package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
  51. package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
  52. package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
  53. package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
  54. package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
  55. package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
  56. package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
  57. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
  58. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
  59. package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
  60. package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
  61. package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
  62. package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
  63. package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
  64. package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
  65. package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
  66. package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
  67. package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
  68. package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
  69. package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
  70. package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
  71. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
  72. package/Types/Incident/IncidentAlertLink.ts +19 -0
  73. package/Types/Telemetry/InventoryLiveness.ts +26 -0
  74. package/Types/Topology/TopologyApi.ts +428 -0
  75. package/Types/Topology/TopologyTypeRules.ts +191 -0
  76. package/Utils/Incident/IncidentFromAlerts.ts +78 -0
  77. package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
  78. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  82. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  83. package/build/dist/Server/Services/AlertService.js +21 -33
  84. package/build/dist/Server/Services/AlertService.js.map +1 -1
  85. package/build/dist/Server/Services/IncidentAlertService.js +457 -3
  86. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
  87. package/build/dist/Server/Services/IncidentService.js +96 -42
  88. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  89. package/build/dist/Server/Services/InventoryItemService.js +31 -0
  90. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  91. package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
  92. package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
  93. package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
  94. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  95. package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
  96. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  97. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
  98. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  99. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
  100. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
  101. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
  102. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
  103. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
  104. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
  105. package/build/dist/Server/Utils/Response.js +30 -0
  106. package/build/dist/Server/Utils/Response.js.map +1 -1
  107. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
  108. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
  109. package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
  110. package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
  111. package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
  112. package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
  113. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
  114. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
  115. package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
  116. package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
  117. package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
  118. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
  119. package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
  120. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
  121. package/build/dist/Types/Topology/TopologyApi.js +55 -0
  122. package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
  123. package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
  124. package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
  125. package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
  126. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
  127. package/package.json +1 -1
@@ -0,0 +1,428 @@
1
+ import { JSONObject } from "../JSON";
2
+
3
+ /*
4
+ * Wire contract of the Topology API (POST /api/telemetry/topology/...).
5
+ *
6
+ * The Topology maps used to download the whole inventory — every item and
7
+ * every relationship — and build the maps in the browser, which stopped at
8
+ * 10,000 rows of each. These endpoints do the graph work in Postgres instead
9
+ * and return only what each view draws, so counts, search and connections
10
+ * cover the whole inventory however large it is.
11
+ *
12
+ * This is an internal contract between the server and the Dashboard bundle
13
+ * that shipped with it, not a public API. `formatVersion` changes whenever a
14
+ * response shape does, so an old bundle talking to a new server can tell the
15
+ * user to reload instead of misreading the payload.
16
+ *
17
+ * Every request carries `rangeStart` (ISO 8601). The server floors it to the
18
+ * minute, uses it for every "reported in range" decision, and echoes the value
19
+ * it used; the client must compute activity against the echoed value so both
20
+ * sides agree about which resources are active. Timestamps in responses are
21
+ * epoch milliseconds.
22
+ */
23
+
24
+ export const TOPOLOGY_API_FORMAT_VERSION: number = 1;
25
+
26
+ export enum TopologyApiPath {
27
+ ServiceMap = "/telemetry/topology/service-map",
28
+ Infrastructure = "/telemetry/topology/infrastructure",
29
+ InfrastructureCollection = "/telemetry/topology/infrastructure/collection",
30
+ InfrastructureCollectionSearch = "/telemetry/topology/infrastructure/collection-search",
31
+ Entity = "/telemetry/topology/entity",
32
+ EntityConnections = "/telemetry/topology/entity/connections",
33
+ /*
34
+ * The same sections over everything the inventory holds rather than over a
35
+ * range (see TopologyEntityAllTimeRequestJSON): the inventory item page's
36
+ * Connections card, which has no time range.
37
+ */
38
+ EntityAllTime = "/telemetry/topology/entity/all-time",
39
+ EntityAllTimeConnections = "/telemetry/topology/entity/all-time/connections",
40
+ }
41
+
42
+ export const TopologyApiLimits: {
43
+ /*
44
+ * Safety valves. Discovery budgets keep real projects far below these; they
45
+ * exist so a future budget override cannot turn one request into gigabytes.
46
+ * Hitting one is reported (never silent) with exact totals.
47
+ */
48
+ MaxServiceMapEntities: number;
49
+ MaxServiceMapDependencies: number;
50
+ MaxInfrastructureNodes: number;
51
+ /*
52
+ * A flat infrastructure type (see isFlatInfrastructureType) with more
53
+ * non-archived items than this is returned as a collection — exact counts,
54
+ * rows paged from the server on demand — instead of row by row.
55
+ */
56
+ InlineFlatItemsPerType: number;
57
+ CollectionPageSizeDefault: number;
58
+ CollectionPageSizeMax: number;
59
+ MaxSearchTerms: number;
60
+ MaxSearchTermLength: number;
61
+ MaxCollectionSearchTypes: number;
62
+ /* Rows returned per drawer section before "Show more". */
63
+ EntityDependencyRows: number;
64
+ EntityOtherRows: number;
65
+ EntityConnectionsPageSizeMax: number;
66
+ /*
67
+ * Most relationships the drawer reads for one entity. A cluster that has
68
+ * seen hundreds of thousands of short-lived pods in the range stops here and
69
+ * reports its totals as lower bounds ("100,000+") instead of timing out.
70
+ */
71
+ EntityConnectionScanLimit: number;
72
+ /*
73
+ * Oldest range start the two maps honour, in days before now; an older one
74
+ * is clamped to it and the clamped value is echoed. Well past the longest
75
+ * relative range the time picker offers (3 months) and past every retention
76
+ * that matters (relationships are pruned after 30 days), and it bounds how
77
+ * many distinct minutes — each a separate whole-inventory build and cache
78
+ * entry — a caller can ask for.
79
+ */
80
+ MaxMapRangeStartAgeDays: number;
81
+ } = {
82
+ MaxServiceMapEntities: 50_000,
83
+ MaxServiceMapDependencies: 200_000,
84
+ MaxInfrastructureNodes: 200_000,
85
+ InlineFlatItemsPerType: 1_000,
86
+ CollectionPageSizeDefault: 50,
87
+ CollectionPageSizeMax: 200,
88
+ MaxSearchTerms: 10,
89
+ MaxSearchTermLength: 100,
90
+ MaxCollectionSearchTypes: 100,
91
+ EntityDependencyRows: 100,
92
+ EntityOtherRows: 25,
93
+ EntityConnectionsPageSizeMax: 200,
94
+ EntityConnectionScanLimit: 100_000,
95
+ MaxMapRangeStartAgeDays: 400,
96
+ };
97
+
98
+ // ---------------------------------------------------------------- requests
99
+
100
+ export interface TopologyRangeRequestJSON {
101
+ /** ISO 8601; floored to the minute by the server. */
102
+ rangeStart: string;
103
+ }
104
+
105
+ /** The Service Map and Infrastructure maps. */
106
+ export interface TopologyMapRequestJSON extends TopologyRangeRequestJSON {
107
+ /*
108
+ * An explicit refresh by the user ("Refresh topology"). The maps are served
109
+ * from a short-lived per-process cache (up to a minute old); a fresh
110
+ * request skips that cached copy and rebuilds — still sharing a build of
111
+ * the same map that is already running — and the new result replaces the
112
+ * cached one. Omit (or false) for every other load.
113
+ */
114
+ fresh?: boolean | undefined;
115
+ }
116
+
117
+ export type TopologyServiceMapRequestJSON = TopologyMapRequestJSON;
118
+
119
+ export type TopologyInfrastructureRequestJSON = TopologyMapRequestJSON;
120
+
121
+ export interface TopologyCollectionCursorJSON {
122
+ /** Display name of the last row of the previous page ("" when it had none). */
123
+ name: string;
124
+ key: string;
125
+ }
126
+
127
+ /** One page of a collection, ordered by display name then entity key. */
128
+ export interface TopologyCollectionRequestJSON
129
+ extends TopologyRangeRequestJSON {
130
+ entityType: string;
131
+ includeInactive: boolean;
132
+ /*
133
+ * Case-insensitive substrings that must ALL appear in the display name.
134
+ * Search terms the client already matched against the type's label are
135
+ * left out, mirroring how the in-browser search matches name OR type label.
136
+ */
137
+ nameTerms?: Array<string> | undefined;
138
+ cursor?: TopologyCollectionCursorJSON | null | undefined;
139
+ limit?: number | undefined;
140
+ }
141
+
142
+ /** How many items of each collection match a search. */
143
+ export interface TopologyCollectionSearchRequestJSON
144
+ extends TopologyRangeRequestJSON {
145
+ includeInactive: boolean;
146
+ types: Array<{ entityType: string; nameTerms: Array<string> }>;
147
+ }
148
+
149
+ /* Which entity a drawer or inventory read is about. */
150
+ export interface TopologyEntityTargetJSON {
151
+ entityKey: string;
152
+ /* Narrows the lookup when the caller knows it; the key alone is enough. */
153
+ entityType?: string | null | undefined;
154
+ }
155
+
156
+ export interface TopologyEntityRequestJSON
157
+ extends TopologyRangeRequestJSON,
158
+ TopologyEntityTargetJSON {}
159
+
160
+ export type TopologyConnectionSection =
161
+ | "calls"
162
+ | "calledBy"
163
+ | "runsOn"
164
+ | "related";
165
+
166
+ export const TOPOLOGY_CONNECTION_SECTIONS: ReadonlyArray<TopologyConnectionSection> =
167
+ ["calls", "calledBy", "runsOn", "related"];
168
+
169
+ /** Where one page of one section starts, and how many rows it holds. */
170
+ export interface TopologyConnectionsPageRequestJSON {
171
+ section: TopologyConnectionSection;
172
+ offset: number;
173
+ limit?: number | undefined;
174
+ }
175
+
176
+ /** "Show more" for one drawer section. */
177
+ export interface TopologyEntityConnectionsRequestJSON
178
+ extends TopologyEntityRequestJSON,
179
+ TopologyConnectionsPageRequestJSON {}
180
+
181
+ /*
182
+ * The all-time variant, for the inventory item page. It has no range start:
183
+ * every relationship the inventory holds counts however long ago it was last
184
+ * seen (edges drawn by hand are never re-bumped and never pruned, so a range
185
+ * would hide them), and archived items count as items — both the entity
186
+ * asked about, which the inventory page still shows, and the other ends.
187
+ */
188
+ export type TopologyEntityAllTimeRequestJSON = TopologyEntityTargetJSON;
189
+
190
+ export interface TopologyEntityAllTimeConnectionsRequestJSON
191
+ extends TopologyEntityTargetJSON,
192
+ TopologyConnectionsPageRequestJSON {}
193
+
194
+ // --------------------------------------------------------------- responses
195
+
196
+ export interface TopologyResponseEnvelopeJSON {
197
+ formatVersion: number;
198
+ /** The range start the server used (ISO 8601, floored to the minute). */
199
+ rangeStart: string;
200
+ /** ISO 8601. */
201
+ generatedAt: string;
202
+ }
203
+
204
+ export interface TopologyTruncationJSON {
205
+ shown: number;
206
+ total: number;
207
+ }
208
+
209
+ /** The lean inventory item every map row is built from. */
210
+ export interface TopologyEntityJSON {
211
+ key: string;
212
+ type: string;
213
+ name: string | null;
214
+ source: string;
215
+ /** Epoch ms. */
216
+ lastSeenAt: number | null;
217
+ }
218
+
219
+ export interface TopologyServiceMapEntityJSON extends TopologyEntityJSON {
220
+ /*
221
+ * Only SERVICE_MAP_DETAIL_ATTRIBUTE_KEYS whose value is a string, per bag.
222
+ * The bags stay separate because the reader prefers descriptive values.
223
+ */
224
+ descriptiveAttributes?: Record<string, string> | undefined;
225
+ identifyingAttributes?: Record<string, string> | undefined;
226
+ }
227
+
228
+ /** An in-range `depends-on` relationship whose caller is a service. */
229
+ export interface TopologyDependencyJSON {
230
+ from: string;
231
+ to: string;
232
+ callCount: number | null;
233
+ errorCount: number | null;
234
+ avgDurationMs: number | null;
235
+ }
236
+
237
+ /** Distinct resources of one type a service runs on (runs-on / hosted-on). */
238
+ export interface TopologyRunsOnCountJSON {
239
+ service: string;
240
+ type: string;
241
+ /** Targets that reported in range. */
242
+ active: number;
243
+ /** All non-archived targets. */
244
+ total: number;
245
+ }
246
+
247
+ export interface TopologyServiceMapResponseJSON
248
+ extends TopologyResponseEnvelopeJSON {
249
+ /*
250
+ * Every non-archived service (active or not, so inactive services can be
251
+ * counted), plus every non-archived item a service calls.
252
+ */
253
+ entities: Array<TopologyServiceMapEntityJSON>;
254
+ /* In the order the old list API returned them: createdAt DESC, _id ASC. */
255
+ dependencies: Array<TopologyDependencyJSON>;
256
+ runsOn: Array<TopologyRunsOnCountJSON>;
257
+ entityTruncation: TopologyTruncationJSON | null;
258
+ dependencyTruncation: TopologyTruncationJSON | null;
259
+ }
260
+
261
+ /*
262
+ * One infrastructure resource and where it nests. The server chooses each
263
+ * resource's container in SQL with the Dashboard's own nesting rules, so the
264
+ * browser never needs the relationships themselves.
265
+ */
266
+ export interface TopologyInfrastructureNodeJSON extends TopologyEntityJSON {
267
+ /*
268
+ * Index into `nodes` of the best container among ALL nodes, and the type
269
+ * of the relationship that won. Absent when nothing contains it.
270
+ */
271
+ parent?: number | undefined;
272
+ parentVia?: string | undefined;
273
+ /*
274
+ * The best container among ACTIVE nodes, sent only when it differs from
275
+ * `parent` (i.e. `parent` did not report in range). -1 means no active node
276
+ * contains it. With inactive resources hidden the browser falls back to it.
277
+ */
278
+ activeParent?: number | undefined;
279
+ activeParentVia?: string | undefined;
280
+ }
281
+
282
+ export interface TopologyInfrastructureServiceJSON {
283
+ key: string;
284
+ name: string | null;
285
+ }
286
+
287
+ /** A flat infrastructure type too large to ship row by row. */
288
+ export interface TopologyInfrastructureCollectionJSON {
289
+ type: string;
290
+ /** Non-archived items of this type. */
291
+ total: number;
292
+ /** Of those, the ones that reported in range. */
293
+ active: number;
294
+ /** Epoch ms: latest lastSeenAt over all items, and over active ones. */
295
+ lastSeenAt: number | null;
296
+ activeLastSeenAt: number | null;
297
+ }
298
+
299
+ /*
300
+ * An in-range `depends-on` relationship between two placed services (see
301
+ * `placements`), by index into `services`. The map draws it as traffic
302
+ * between the resources those services run on.
303
+ */
304
+ export interface TopologyInfrastructureDependencyJSON {
305
+ from: number;
306
+ to: number;
307
+ callCount: number | null;
308
+ errorCount: number | null;
309
+ avgDurationMs: number | null;
310
+ }
311
+
312
+ export interface TopologyInfrastructureResponseJSON
313
+ extends TopologyResponseEnvelopeJSON {
314
+ /* Ordered by (type, key). */
315
+ nodes: Array<TopologyInfrastructureNodeJSON>;
316
+ /* Every non-archived service, so placements and labels resolve. */
317
+ services: Array<TopologyInfrastructureServiceJSON>;
318
+ /* Distinct [index into services, index into nodes] from in-range runs-on / hosted-on. */
319
+ placements: Array<[number, number]>;
320
+ /*
321
+ * In-range calls between services that both run on a shipped node,
322
+ * ordered by (from, to). The browser reads a payload without them as "no
323
+ * traffic known", so the field needs no format version of its own.
324
+ */
325
+ dependencies: Array<TopologyInfrastructureDependencyJSON>;
326
+ /* The dependency cap (MaxServiceMapDependencies), when it was hit. */
327
+ dependencyTruncation: TopologyTruncationJSON | null;
328
+ collections: Array<TopologyInfrastructureCollectionJSON>;
329
+ /* Every non-archived infrastructure item, collections included. */
330
+ totals: { resources: number; activeResources: number };
331
+ truncation: TopologyTruncationJSON | null;
332
+ }
333
+
334
+ export interface TopologyCollectionResponseJSON
335
+ extends TopologyResponseEnvelopeJSON {
336
+ entityType: string;
337
+ /** Items matching the request's filters, across all pages. */
338
+ total: number;
339
+ items: Array<TopologyEntityJSON>;
340
+ nextCursor: TopologyCollectionCursorJSON | null;
341
+ }
342
+
343
+ export interface TopologyCollectionSearchResponseJSON
344
+ extends TopologyResponseEnvelopeJSON {
345
+ /* Only types with at least one match. */
346
+ matches: Array<{ entityType: string; count: number }>;
347
+ }
348
+
349
+ export interface TopologyEntityDetailJSON extends TopologyEntityJSON {
350
+ /** InventoryItem _id. */
351
+ id: string;
352
+ firstSeenAt: number | null;
353
+ resourceType: string | null;
354
+ resourceId: string | null;
355
+ identifyingAttributes: JSONObject | null;
356
+ descriptiveAttributes: JSONObject | null;
357
+ }
358
+
359
+ export interface TopologyConnectionRowJSON {
360
+ relationshipType: string;
361
+ /* "out" when the entity is the relationship's `from` (self-loops too). */
362
+ direction: "out" | "in";
363
+ otherKey: string;
364
+ /*
365
+ * false when the other end is not a non-archived item ("Undiscovered
366
+ * resource"); for the all-time endpoints, when it is not an item at all.
367
+ */
368
+ otherKnown: boolean;
369
+ /*
370
+ * InventoryItem _id of the other end, so the inventory page can link to it.
371
+ * Sent by the all-time endpoints only; null when otherKnown is false.
372
+ */
373
+ otherId?: string | null | undefined;
374
+ otherName: string | null;
375
+ otherType: string | null;
376
+ callCount: number | null;
377
+ errorCount: number | null;
378
+ avgDurationMs: number | null;
379
+ lastSeenAt: number | null;
380
+ }
381
+
382
+ export interface TopologyConnectionSectionJSON {
383
+ /* Exact unless the response says it is scan limited. */
384
+ total: number;
385
+ /* How many of `total` point at something no longer in inventory. */
386
+ unknownTotal: number;
387
+ rows: Array<TopologyConnectionRowJSON>;
388
+ /* Offset for the next page, or null when `rows` reached the end. */
389
+ nextOffset: number | null;
390
+ }
391
+
392
+ /*
393
+ * Sections classify an entity's in-range relationships the way the drawer
394
+ * always has: depends-on out = calls, depends-on in = calledBy, outbound
395
+ * runs-on / hosted-on from a service = runsOn, everything else = related.
396
+ * calls / calledBy are ordered by callCount desc, then label; runsOn /
397
+ * related put known resources first, then order by label.
398
+ */
399
+ export interface TopologyEntityResponseJSON
400
+ extends TopologyResponseEnvelopeJSON {
401
+ /* null when no non-archived item has this key (sections are then empty). */
402
+ entity: TopologyEntityDetailJSON | null;
403
+ sections: Record<TopologyConnectionSection, TopologyConnectionSectionJSON>;
404
+ /* The scan stopped at EntityConnectionScanLimit: totals are lower bounds. */
405
+ isScanLimited: boolean;
406
+ }
407
+
408
+ export interface TopologyEntityConnectionsResponseJSON
409
+ extends TopologyResponseEnvelopeJSON {
410
+ section: TopologyConnectionSection;
411
+ connections: TopologyConnectionSectionJSON;
412
+ isScanLimited: boolean;
413
+ }
414
+
415
+ /*
416
+ * The all-time responses (TopologyEntityAllTimeRequestJSON) are shaped like
417
+ * the drawer's, without a range start to echo: `entity` is found among
418
+ * archived items too, and every row carries `otherId`.
419
+ */
420
+ export type TopologyEntityAllTimeResponseJSON = Omit<
421
+ TopologyEntityResponseJSON,
422
+ "rangeStart"
423
+ >;
424
+
425
+ export type TopologyEntityAllTimeConnectionsResponseJSON = Omit<
426
+ TopologyEntityConnectionsResponseJSON,
427
+ "rangeStart"
428
+ >;
@@ -0,0 +1,191 @@
1
+ import EntityRelationshipType from "../Telemetry/EntityRelationshipType";
2
+ import EntityType from "../Telemetry/EntityType";
3
+
4
+ /*
5
+ * The rules that shape the Topology maps, shared by the server (which reduces
6
+ * the inventory graph in SQL) and the Dashboard (which builds the maps from
7
+ * the reduced data). Keeping them in one place is what lets the server hand
8
+ * the browser a few rows per resource instead of every relationship and still
9
+ * produce exactly the tree the Dashboard would have built from all of them.
10
+ */
11
+
12
+ /*
13
+ * Types the Service Map owns. They describe software and its dependencies,
14
+ * not where anything runs, so they never become Infrastructure rows.
15
+ */
16
+ export const TOPOLOGY_APPLICATION_TYPES: ReadonlySet<string> = new Set<string>([
17
+ EntityType.Service,
18
+ EntityType.ServiceInstance,
19
+ EntityType.TelemetrySdk,
20
+ EntityType.Database,
21
+ EntityType.RemoteService,
22
+ EntityType.Process,
23
+ ]);
24
+
25
+ /** Types that may nest inside a structural container. */
26
+ export const NESTABLE_CHILD_TYPES: Set<EntityType> = new Set<EntityType>([
27
+ EntityType.Container,
28
+ EntityType.Process,
29
+ EntityType.KubernetesPod,
30
+ EntityType.KubernetesNode,
31
+ EntityType.KubernetesNamespace,
32
+ EntityType.KubernetesDeployment,
33
+ EntityType.ProxmoxNode,
34
+ EntityType.ProxmoxGuest,
35
+ EntityType.VMwareCluster,
36
+ EntityType.VMwareHost,
37
+ EntityType.VMwareVirtualMachine,
38
+ EntityType.VMwareDatastore,
39
+ EntityType.DockerSwarmNode,
40
+ EntityType.DockerSwarmService,
41
+ EntityType.DockerSwarmTask,
42
+ ]);
43
+
44
+ /*
45
+ * Valid structural containers, higher = more specific. Doubles as the
46
+ * allow-list of parent types: a candidate whose parent type is absent here
47
+ * (e.g. a Service) is rejected before priority is compared.
48
+ */
49
+ export const CONTAINER_SPECIFICITY: Partial<Record<EntityType, number>> = {
50
+ [EntityType.KubernetesCluster]: 0,
51
+ [EntityType.ProxmoxCluster]: 0,
52
+ /*
53
+ * vSphere nests vCenter ⊃ cluster ⊃ ESXi host ⊃ VM; datastores sit
54
+ * directly under the vCenter. The cluster is optional (standalone hosts
55
+ * hang straight off the vCenter), so the host is more specific than the
56
+ * cluster but both may parent directly to the vCenter.
57
+ */
58
+ [EntityType.VMwareVCenter]: 0,
59
+ [EntityType.VMwareCluster]: 1,
60
+ [EntityType.CephCluster]: 0,
61
+ [EntityType.DockerSwarmCluster]: 0,
62
+ [EntityType.Host]: 1,
63
+ [EntityType.KubernetesNamespace]: 1,
64
+ [EntityType.KubernetesNode]: 2,
65
+ [EntityType.KubernetesDeployment]: 2,
66
+ [EntityType.ProxmoxNode]: 2,
67
+ [EntityType.VMwareHost]: 2,
68
+ [EntityType.DockerSwarmNode]: 2,
69
+ [EntityType.KubernetesPod]: 3,
70
+ [EntityType.ProxmoxGuest]: 3,
71
+ [EntityType.VMwareVirtualMachine]: 3,
72
+ [EntityType.DockerSwarmTask]: 3,
73
+ [EntityType.Container]: 4,
74
+ };
75
+
76
+ /* Which relationship expresses containment best: part-of > runs-on > member-of. */
77
+ export const NESTING_RELATIONSHIP_PRIORITY: Partial<
78
+ Record<EntityRelationshipType, number>
79
+ > = {
80
+ [EntityRelationshipType.PartOf]: 3,
81
+ [EntityRelationshipType.RunsOn]: 2,
82
+ [EntityRelationshipType.MemberOf]: 1,
83
+ };
84
+
85
+ /* Replicas worth collapsing: leaf resources that come in fleets. */
86
+ export const REPLICA_GROUPABLE_TYPES: ReadonlySet<string> = new Set<string>([
87
+ EntityType.Host,
88
+ EntityType.KubernetesPod,
89
+ EntityType.Container,
90
+ EntityType.DockerSwarmTask,
91
+ EntityType.ProxmoxGuest,
92
+ EntityType.VMwareVirtualMachine,
93
+ ]);
94
+
95
+ /* service → where it runs (service→host is hosted-on, service→pod runs-on). */
96
+ export const PLACEMENT_RELATIONSHIP_TYPES: ReadonlySet<string> =
97
+ new Set<string>([
98
+ EntityRelationshipType.RunsOn,
99
+ EntityRelationshipType.HostedOn,
100
+ ]);
101
+
102
+ /*
103
+ * The attributes the Service Map reads to label a node ("Node.js",
104
+ * "PostgreSQL"), in the order it tries them. The server ships only these, so
105
+ * the list lives here rather than beside the reader.
106
+ */
107
+ export const SERVICE_MAP_DETAIL_ATTRIBUTE_KEYS: ReadonlyArray<string> = [
108
+ "telemetry.sdk.language",
109
+ "db.system.name",
110
+ "network.protocol.name",
111
+ "messaging.system",
112
+ ];
113
+
114
+ /*
115
+ * Orders two keys by Unicode code point, which is the order Postgres gives
116
+ * `COLLATE "C"` on a UTF-8 database (UTF-8 bytes sort like code points). The
117
+ * server breaks ranking ties that way, so wherever the Dashboard must pick
118
+ * the same winner it compares with this rather than with `<`.
119
+ *
120
+ * JavaScript's `<` compares UTF-16 code units. The two orders agree
121
+ * everywhere except where a character beyond U+FFFF (stored as a surrogate
122
+ * pair, U+D800-U+DFFF) meets one in U+E000-U+FFFF: as code units the
123
+ * surrogate is smaller, as code points it is larger. Keys made only of
124
+ * characters up to U+FFFF therefore compare exactly as `<` compares them.
125
+ */
126
+ export function compareCodePoints(a: string, b: string): number {
127
+ if (a === b) {
128
+ return 0;
129
+ }
130
+ const length: number = Math.min(a.length, b.length);
131
+ for (let index: number = 0; index < length; index++) {
132
+ const left: number = a.charCodeAt(index);
133
+ const right: number = b.charCodeAt(index);
134
+ if (left === right) {
135
+ continue;
136
+ }
137
+ if (left >= 0xd800 && right >= 0xd800) {
138
+ const leftIsSurrogate: boolean = left <= 0xdfff;
139
+ const rightIsSurrogate: boolean = right <= 0xdfff;
140
+ if (leftIsSurrogate !== rightIsSurrogate) {
141
+ /* The half of a character beyond U+FFFF outranks U+E000-U+FFFF. */
142
+ return leftIsSurrogate ? 1 : -1;
143
+ }
144
+ }
145
+ return left < right ? -1 : 1;
146
+ }
147
+ /* One is a prefix of the other: the shorter sorts first. */
148
+ return a.length < b.length ? -1 : 1;
149
+ }
150
+
151
+ export function isTopologyInfrastructureType(
152
+ entityType: string | undefined | null,
153
+ ): boolean {
154
+ return (
155
+ Boolean(entityType) &&
156
+ entityType !== EntityType.Service &&
157
+ !TOPOLOGY_APPLICATION_TYPES.has(entityType!)
158
+ );
159
+ }
160
+
161
+ /*
162
+ * Structural infrastructure types can take part in the tree: they nest, hold
163
+ * other resources, or are grouped with their replicas. Everything else that is
164
+ * infrastructure (network devices, IoT devices, cloud resources, manually
165
+ * registered appliances, types this version does not know) is flat: it never
166
+ * has a parent, never contains anything, and nothing groups it. Flat types are
167
+ * also the ones whose volume is not bounded by the discovery budgets, so the
168
+ * server summarizes a large flat type instead of shipping every row.
169
+ */
170
+ export function isStructuralInfrastructureType(
171
+ entityType: string | undefined | null,
172
+ ): boolean {
173
+ if (!isTopologyInfrastructureType(entityType)) {
174
+ return false;
175
+ }
176
+ const type: EntityType = entityType as EntityType;
177
+ return (
178
+ NESTABLE_CHILD_TYPES.has(type) ||
179
+ CONTAINER_SPECIFICITY[type] !== undefined ||
180
+ REPLICA_GROUPABLE_TYPES.has(type)
181
+ );
182
+ }
183
+
184
+ export function isFlatInfrastructureType(
185
+ entityType: string | undefined | null,
186
+ ): boolean {
187
+ return (
188
+ isTopologyInfrastructureType(entityType) &&
189
+ !isStructuralInfrastructureType(entityType)
190
+ );
191
+ }