@oneuptime/common 12.0.7 → 12.0.8

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 (186) hide show
  1. package/Models/DatabaseModels/TelemetryEntity.ts +45 -0
  2. package/Models/DatabaseModels/TelemetryEntityRelationship.ts +26 -0
  3. package/Models/DatabaseModels/WorkflowLog.ts +38 -0
  4. package/Server/API/UserAPI.ts +16 -1
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.ts +41 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.ts +17 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  8. package/Server/Middleware/MasterAdminAuthorization.ts +11 -6
  9. package/Server/Services/TelemetryEntityRelationshipService.ts +3 -0
  10. package/Server/Services/TelemetryEntityService.ts +114 -1
  11. package/Server/Types/Workflow/Components/API/Utils.ts +44 -1
  12. package/Server/Types/Workflow/TriggerCode.ts +12 -0
  13. package/Server/Types/Workflow/Workflow.ts +5 -0
  14. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +689 -0
  15. package/Server/Utils/VM/VMAPI.ts +56 -8
  16. package/Tests/App/Dashboard/MonitorCriteriaAttributeFilter.test.tsx +574 -0
  17. package/Tests/Server/API/UserProjectsAPI.test.ts +478 -4
  18. package/Tests/Server/Services/TelemetryEntityManualCreate.test.ts +248 -0
  19. package/Tests/Server/Types/Workflow/Components/ApiComponentHeaders.test.ts +192 -0
  20. package/Tests/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.test.ts +152 -0
  21. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +322 -0
  22. package/Tests/Server/Utils/VM/VMAPISubstitution.test.ts +243 -0
  23. package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +197 -0
  24. package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +140 -0
  25. package/Tests/Types/Workflow/StepTrace.test.ts +235 -0
  26. package/Tests/Types/Workflow/TemplateSyntax.test.ts +379 -0
  27. package/Tests/Types/Workflow/Templates.test.ts +403 -0
  28. package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +459 -0
  29. package/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx +477 -0
  30. package/Tests/UI/Components/Forms/AnchoredFieldPopupKeyboard.test.tsx +382 -0
  31. package/Tests/UI/Components/Forms/ColorPickerPicking.test.tsx +569 -0
  32. package/Tests/UI/Components/Forms/ValidationJSON.test.ts +241 -0
  33. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +42 -0
  34. package/Tests/UI/Components/ModalPortalDismissal.test.tsx +90 -0
  35. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +49 -7
  36. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +325 -0
  37. package/Tests/UI/Components/Workflow/GraphLint.test.ts +821 -0
  38. package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +287 -0
  39. package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +185 -0
  40. package/Tests/UI/Components/Workflow/ModelSchema.test.ts +174 -0
  41. package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +152 -0
  42. package/Tests/UI/Components/Workflow/Utils.test.ts +192 -0
  43. package/Tests/UI/Monitor/MonitorStepCriteriaView.test.tsx +432 -0
  44. package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +514 -0
  45. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +240 -0
  46. package/Tests/Utils/Schema/AnalyticsModelSchema.test.ts +469 -0
  47. package/Tests/Utils/Schema/ModelSchema.test.ts +268 -0
  48. package/Tests/Utils/Telemetry/EntityKeyNonTelemetry.test.ts +193 -0
  49. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +31 -0
  50. package/Types/SerializableObjectDictionary.ts +133 -39
  51. package/Types/Telemetry/EntitySource.ts +40 -0
  52. package/Types/Telemetry/EntityType.ts +27 -0
  53. package/Types/Telemetry/EntityTypeGroups.ts +65 -0
  54. package/Types/Workflow/Component.ts +29 -0
  55. package/Types/Workflow/Components/API.ts +35 -0
  56. package/Types/Workflow/Components/BaseModel.ts +7 -3
  57. package/Types/Workflow/StepTrace.ts +181 -0
  58. package/Types/Workflow/TemplateSyntax.ts +499 -0
  59. package/Types/Workflow/Templates.ts +316 -0
  60. package/UI/Components/Date/CustomTimeRangeModal.tsx +278 -0
  61. package/UI/Components/Date/TimeRangePickerDropdown.tsx +250 -0
  62. package/UI/Components/Dictionary/Dictionary.tsx +23 -6
  63. package/UI/Components/Forms/Fields/ColorPicker.tsx +66 -10
  64. package/UI/Components/Forms/Fields/IconPicker.tsx +48 -10
  65. package/UI/Components/Forms/Types/Field.ts +7 -0
  66. package/UI/Components/Forms/Validation.ts +63 -1
  67. package/UI/Components/Input/Input.tsx +31 -3
  68. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +11 -216
  69. package/UI/Components/Modal/Modal.tsx +37 -4
  70. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +11 -210
  71. package/UI/Components/Workflow/ArgumentsForm.tsx +194 -7
  72. package/UI/Components/Workflow/Component.tsx +28 -26
  73. package/UI/Components/Workflow/ComponentSettingsModal.tsx +69 -8
  74. package/UI/Components/Workflow/GraphLint.ts +591 -0
  75. package/UI/Components/Workflow/ModelColumnEditor.tsx +396 -0
  76. package/UI/Components/Workflow/ModelSchema.ts +196 -0
  77. package/UI/Components/Workflow/RunForm.tsx +41 -7
  78. package/UI/Components/Workflow/RunStatusWatcher.ts +122 -0
  79. package/UI/Components/Workflow/StepTraceViewer.tsx +186 -0
  80. package/UI/Components/Workflow/Utils.ts +93 -1
  81. package/UI/Components/Workflow/Workflow.tsx +77 -4
  82. package/UI/Types/LayeredDismissal.ts +31 -0
  83. package/UI/Types/UseAnchoredFieldPopup.ts +99 -0
  84. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +533 -0
  85. package/Utils/Monitor/NetworkTopologyUtil.ts +45 -7
  86. package/Utils/Telemetry/EntityKey.ts +66 -0
  87. package/build/dist/Models/DatabaseModels/TelemetryEntity.js +47 -0
  88. package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +1 -1
  89. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js +27 -0
  90. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +1 -1
  91. package/build/dist/Models/DatabaseModels/WorkflowLog.js +39 -0
  92. package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
  93. package/build/dist/Server/API/UserAPI.js +16 -1
  94. package/build/dist/Server/API/UserAPI.js.map +1 -1
  95. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js +20 -0
  96. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js.map +1 -0
  97. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js +12 -0
  98. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js.map +1 -0
  99. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  100. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  101. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -6
  102. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  103. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +3 -0
  104. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  105. package/build/dist/Server/Services/TelemetryEntityService.js +91 -1
  106. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  107. package/build/dist/Server/Types/Workflow/Components/API/Utils.js +28 -0
  108. package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
  109. package/build/dist/Server/Types/Workflow/TriggerCode.js.map +1 -1
  110. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +507 -0
  111. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -0
  112. package/build/dist/Server/Utils/VM/VMAPI.js +51 -4
  113. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  114. package/build/dist/Types/SerializableObjectDictionary.js +133 -39
  115. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  116. package/build/dist/Types/Telemetry/EntitySource.js +39 -0
  117. package/build/dist/Types/Telemetry/EntitySource.js.map +1 -0
  118. package/build/dist/Types/Telemetry/EntityType.js +27 -0
  119. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  120. package/build/dist/Types/Telemetry/EntityTypeGroups.js +57 -0
  121. package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -0
  122. package/build/dist/Types/Workflow/Component.js +21 -0
  123. package/build/dist/Types/Workflow/Component.js.map +1 -1
  124. package/build/dist/Types/Workflow/Components/API.js +35 -0
  125. package/build/dist/Types/Workflow/Components/API.js.map +1 -1
  126. package/build/dist/Types/Workflow/Components/BaseModel.js +7 -3
  127. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  128. package/build/dist/Types/Workflow/StepTrace.js +104 -0
  129. package/build/dist/Types/Workflow/StepTrace.js.map +1 -0
  130. package/build/dist/Types/Workflow/TemplateSyntax.js +326 -0
  131. package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -0
  132. package/build/dist/Types/Workflow/Templates.js +218 -0
  133. package/build/dist/Types/Workflow/Templates.js.map +1 -0
  134. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +126 -0
  135. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -0
  136. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +123 -0
  137. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -0
  138. package/build/dist/UI/Components/Dictionary/Dictionary.js +22 -6
  139. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  140. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +52 -13
  141. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
  142. package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -12
  143. package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
  144. package/build/dist/UI/Components/Forms/Validation.js +44 -0
  145. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  146. package/build/dist/UI/Components/Input/Input.js +12 -4
  147. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  148. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +5 -108
  149. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  150. package/build/dist/UI/Components/Modal/Modal.js +31 -5
  151. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  152. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +5 -105
  153. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  154. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +140 -11
  155. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  156. package/build/dist/UI/Components/Workflow/Component.js +20 -19
  157. package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
  158. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +31 -3
  159. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
  160. package/build/dist/UI/Components/Workflow/GraphLint.js +396 -0
  161. package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -0
  162. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +225 -0
  163. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -0
  164. package/build/dist/UI/Components/Workflow/ModelSchema.js +118 -0
  165. package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -0
  166. package/build/dist/UI/Components/Workflow/RunForm.js +22 -6
  167. package/build/dist/UI/Components/Workflow/RunForm.js.map +1 -1
  168. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +76 -0
  169. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -0
  170. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +76 -0
  171. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -0
  172. package/build/dist/UI/Components/Workflow/Utils.js +73 -1
  173. package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
  174. package/build/dist/UI/Components/Workflow/Workflow.js +51 -6
  175. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  176. package/build/dist/UI/Types/LayeredDismissal.js +21 -0
  177. package/build/dist/UI/Types/LayeredDismissal.js.map +1 -0
  178. package/build/dist/UI/Types/UseAnchoredFieldPopup.js +74 -1
  179. package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
  180. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +386 -0
  181. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -0
  182. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +35 -6
  183. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  184. package/build/dist/Utils/Telemetry/EntityKey.js +52 -0
  185. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  186. package/package.json +1 -1
@@ -15,6 +15,7 @@ import IconProp from "../../Types/Icon/IconProp";
15
15
  import { JSONObject } from "../../Types/JSON";
16
16
  import ObjectID from "../../Types/ObjectID";
17
17
  import Permission from "../../Types/Permission";
18
+ import EntitySource from "../../Types/Telemetry/EntitySource";
18
19
  import EntityType from "../../Types/Telemetry/EntityType";
19
20
  import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
20
21
  import DatabaseBaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
@@ -187,6 +188,50 @@ export default class TelemetryEntity extends DatabaseBaseModel {
187
188
  })
188
189
  public displayName?: string = undefined;
189
190
 
191
+ /*
192
+ * Immutable after create (`update: []`): the value decides whether
193
+ * PruneStaleEntities may reap the row, so letting it be edited would let a
194
+ * user flip a manual CI into a TTL-pruned one — or quietly make a
195
+ * discovered row immortal. Re-create the row instead.
196
+ */
197
+ @ColumnAccessControl({ create: CREATE_PERMS, read: READ_PERMS, update: [] })
198
+ @TableColumn({
199
+ type: TableColumnType.ShortText,
200
+ required: true,
201
+ title: "Source",
202
+ description:
203
+ "How this row came to exist: discovered from telemetry, mirrored from a OneUptime inventory table, or created manually by a user. Determines whether stale-entity pruning applies.",
204
+ example: "discovered",
205
+ })
206
+ @Index()
207
+ @Column({
208
+ type: ColumnType.ShortText,
209
+ length: ColumnLength.ShortText,
210
+ nullable: false,
211
+ default: EntitySource.Discovered,
212
+ })
213
+ public source?: EntitySource = undefined;
214
+
215
+ @ColumnAccessControl({
216
+ create: CREATE_PERMS,
217
+ read: READ_PERMS,
218
+ update: UPDATE_PERMS,
219
+ })
220
+ @TableColumn({
221
+ type: TableColumnType.LongText,
222
+ required: false,
223
+ title: "Description",
224
+ description:
225
+ "Free-text description. Primarily for manually created entities, where there are no telemetry attributes to explain what the thing is.",
226
+ example: "Stripe payments API - vendor managed, no telemetry.",
227
+ })
228
+ @Column({
229
+ type: ColumnType.LongText,
230
+ length: ColumnLength.LongText,
231
+ nullable: true,
232
+ })
233
+ public description?: string = undefined;
234
+
190
235
  @ColumnAccessControl({
191
236
  create: CREATE_PERMS,
192
237
  read: READ_PERMS,
@@ -15,6 +15,7 @@ import IconProp from "../../Types/Icon/IconProp";
15
15
  import ObjectID from "../../Types/ObjectID";
16
16
  import Permission from "../../Types/Permission";
17
17
  import EntityRelationshipType from "../../Types/Telemetry/EntityRelationshipType";
18
+ import EntitySource from "../../Types/Telemetry/EntitySource";
18
19
  import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
19
20
  import DatabaseBaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
20
21
 
@@ -173,6 +174,31 @@ export default class TelemetryEntityRelationship extends DatabaseBaseModel {
173
174
  })
174
175
  public relationshipType?: EntityRelationshipType = undefined;
175
176
 
177
+ /*
178
+ * Immutable after create, for the same reason as TelemetryEntity.source:
179
+ * it is the predicate the edge prune keys on. An edge drawn by hand — the
180
+ * only way to connect a manual CI to anything — has nothing re-bumping its
181
+ * `lastSeenAt`, so without this it would be reaped by the 30-day sweep
182
+ * while the entities at both ends survived.
183
+ */
184
+ @ColumnAccessControl({ create: CREATE_PERMS, read: READ_PERMS, update: [] })
185
+ @TableColumn({
186
+ type: TableColumnType.ShortText,
187
+ required: true,
188
+ title: "Source",
189
+ description:
190
+ "Whether this edge was derived from telemetry or drawn manually by a user. Determines whether stale-edge pruning applies.",
191
+ example: "discovered",
192
+ })
193
+ @Index()
194
+ @Column({
195
+ type: ColumnType.ShortText,
196
+ length: ColumnLength.ShortText,
197
+ nullable: false,
198
+ default: EntitySource.Discovered,
199
+ })
200
+ public source?: EntitySource = undefined;
201
+
176
202
  @ColumnAccessControl({
177
203
  create: CREATE_PERMS,
178
204
  read: READ_PERMS,
@@ -348,6 +348,44 @@ export default class WorkflowLog extends BaseModel {
348
348
  })
349
349
  public resumeData?: JSONObject = undefined;
350
350
 
351
+ /*
352
+ * What each step of the run did, as data rather than as lines in `logs`:
353
+ * the component, its resolved arguments, what it returned, which out port it
354
+ * took and how long it took. Readable by anyone who can read the log itself,
355
+ * because it is the same information in a shape that can be rendered as a
356
+ * list of steps instead of a wall of text.
357
+ *
358
+ * Sensitive arguments and return values are redacted before they get here,
359
+ * by the same rules that redact them in `logs`.
360
+ */
361
+ @ColumnAccessControl({
362
+ create: [],
363
+ read: [
364
+ Permission.ProjectOwner,
365
+ Permission.ProjectAdmin,
366
+ Permission.ProjectMember,
367
+ Permission.Viewer,
368
+ Permission.WorkflowAdmin,
369
+ Permission.WorkflowMember,
370
+ Permission.WorkflowViewer,
371
+ Permission.ReadWorkflowLog,
372
+ ],
373
+ update: [],
374
+ })
375
+ @TableColumn({
376
+ required: false,
377
+ isDefaultValueColumn: false,
378
+ type: TableColumnType.JSON,
379
+ title: "Step Trace",
380
+ description:
381
+ "Structured per-step record of this run: arguments, return values, the port taken and timing.",
382
+ })
383
+ @Column({
384
+ type: ColumnType.JSON,
385
+ nullable: true,
386
+ })
387
+ public stepTrace?: JSONObject = undefined;
388
+
351
389
  @ColumnAccessControl({
352
390
  create: [],
353
391
  read: [],
@@ -53,10 +53,25 @@ export default class UserAPI extends BaseAPI<User, UserServiceType> {
53
53
  * the gate; the read below runs as root because a master admin is not a
54
54
  * member of the projects being listed and so has no tenant permissions to
55
55
  * read them with.
56
+ *
57
+ * The gate accepts the instance-wide master API key as well as a
58
+ * master-admin session, because the caller asking "which projects is this
59
+ * person in?" is typically a script rather than somebody with a browser
60
+ * open. That is a widening, so it is worth being explicit about why it is
61
+ * the right one HERE and not on the routes below: this endpoint only reads,
62
+ * and it reads nothing the key does not already reach through
63
+ * POST /team-member/get-list, which any master-key caller can call today
64
+ * (untenanted, so cross-project) to assemble the same answer one membership
65
+ * at a time. The key buys convenience and correct paging, not new reach.
66
+ *
67
+ * The writes below stay on the session-only middleware. A leaked static key
68
+ * that can list memberships is a disclosure of what it could already
69
+ * disclose; one that can remove a user from a project, set their password or
70
+ * mail them a reset link is a headless account takeover.
56
71
  */
57
72
  this.router.post(
58
73
  `${new this.entityType().getCrudApiPath()?.toString()}/:userId/projects`,
59
- MasterAdminAuthorization.isAuthorizedMasterAdminMiddleware,
74
+ MasterAdminAuthorization.isAuthorizedMasterAdminOrMasterApiKeyMiddleware,
60
75
  async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
61
76
  try {
62
77
  const userId: ObjectID = UserAPI.getUserIdFromParams(req);
@@ -0,0 +1,41 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class MigrationName1786551733814 implements MigrationInterface {
4
+ public name: string = "MigrationName1786551733814";
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `ALTER TABLE "TelemetryEntity" ADD "source" character varying(100) NOT NULL DEFAULT 'discovered'`,
9
+ );
10
+ await queryRunner.query(
11
+ `ALTER TABLE "TelemetryEntity" ADD "description" character varying(500)`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "TelemetryEntityRelationship" ADD "source" character varying(100) NOT NULL DEFAULT 'discovered'`,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE INDEX "IDX_8106b1d6443d982b12e3318318" ON "TelemetryEntity" ("source") `,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE INDEX "IDX_26ea726e2ee8fa622348581ac1" ON "TelemetryEntityRelationship" ("source") `,
21
+ );
22
+ }
23
+
24
+ public async down(queryRunner: QueryRunner): Promise<void> {
25
+ await queryRunner.query(
26
+ `DROP INDEX "public"."IDX_26ea726e2ee8fa622348581ac1"`,
27
+ );
28
+ await queryRunner.query(
29
+ `DROP INDEX "public"."IDX_8106b1d6443d982b12e3318318"`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "TelemetryEntityRelationship" DROP COLUMN "source"`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "TelemetryEntity" DROP COLUMN "description"`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "TelemetryEntity" DROP COLUMN "source"`,
39
+ );
40
+ }
41
+ }
@@ -0,0 +1,17 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddWorkflowLogStepTrace1786559879134
4
+ implements MigrationInterface
5
+ {
6
+ public name: string = "AddWorkflowLogStepTrace1786559879134";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(`ALTER TABLE "WorkflowLog" ADD "stepTrace" jsonb`);
10
+ }
11
+
12
+ public async down(queryRunner: QueryRunner): Promise<void> {
13
+ await queryRunner.query(
14
+ `ALTER TABLE "WorkflowLog" DROP COLUMN "stepTrace"`,
15
+ );
16
+ }
17
+ }
@@ -515,6 +515,8 @@ import { AddDataSourceTable1786303472848 } from "./1786303472848-AddDataSourceTa
515
515
  import { MigrationName1786446545142 } from "./1786446545142-MigrationName";
516
516
  import { AddMonitorDependency1786449497966 } from "./1786449497966-AddMonitorDependency";
517
517
  import { AddDeletedProjectTable1786461136170 } from "./1786461136170-AddDeletedProjectTable";
518
+ import { MigrationName1786551733814 } from "./1786551733814-MigrationName";
519
+ import { AddWorkflowLogStepTrace1786559879134 } from "./1786559879134-AddWorkflowLogStepTrace";
518
520
 
519
521
  export default [
520
522
  InitialMigration,
@@ -1034,4 +1036,6 @@ export default [
1034
1036
  MigrationName1786446545142,
1035
1037
  AddMonitorDependency1786449497966,
1036
1038
  AddDeletedProjectTable1786461136170,
1039
+ MigrationName1786551733814,
1040
+ AddWorkflowLogStepTrace1786559879134,
1037
1041
  ];
@@ -59,13 +59,18 @@ export default class MasterAdminAuthorization {
59
59
  * Same as isAuthorizedMasterAdminMiddleware, but ALSO accepts the instance-wide
60
60
  * master API key (Admin Dashboard → Settings → API Key) supplied in the
61
61
  * `apikey` header. The master key has root/master-admin access, so this lets
62
- * automated callers reach the master-admin OneUptime Health endpoints with the
63
- * key instead of a logged-in master-admin session.
62
+ * automated callers reach master-admin endpoints with the key instead of a
63
+ * logged-in master-admin session.
64
64
  *
65
- * Deliberately scoped to the read-only health / diagnostics routes only. It is
66
- * NOT used on higher-risk master-admin actions (e.g. the read/write query
67
- * console or broadcast email), which stay on the JWT-only middleware above so
68
- * that a leaked static key cannot trigger them headlessly.
65
+ * Deliberately scoped to READ-ONLY routes: the OneUptime Health / diagnostics
66
+ * endpoints, and the cross-tenant read of one user's projects
67
+ * (POST /user/:userId/projects). It is NOT used on master-admin routes that
68
+ * change anything the read/write query console, broadcast email, removing a
69
+ * user from a project, setting a password — which stay on the JWT-only
70
+ * middleware above so that a leaked static key cannot trigger them headlessly.
71
+ *
72
+ * Read-only is the line to hold when adding a route here. A key that can read
73
+ * discloses; a key that can write takes over.
69
74
  */
70
75
  public static async isAuthorizedMasterAdminOrMasterApiKeyMiddleware(
71
76
  req: ExpressRequest,
@@ -5,6 +5,7 @@ import OneUptimeDate from "../../Types/Date";
5
5
  import logger from "../Utils/Logger";
6
6
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
7
7
  import { EntityRelationshipEdge } from "../../Utils/Telemetry/EntityRelationship";
8
+ import EntitySource from "../../Types/Telemetry/EntitySource";
8
9
  import { reconcileByNaturalKey } from "../Utils/Telemetry/EntityRegistry";
9
10
  import QueryDeepPartialEntity from "../../Types/Database/PartialEntity";
10
11
 
@@ -62,6 +63,8 @@ export class TelemetryEntityRelationshipService extends DatabaseService<Model> {
62
63
  model.fromEntityKey = edge.fromEntityKey;
63
64
  model.toEntityKey = edge.toEntityKey;
64
65
  model.relationshipType = edge.relationshipType;
66
+ // Explicit: this is what makes the edge eligible for stale pruning.
67
+ model.source = EntitySource.Discovered;
65
68
  model.firstSeenAt = now;
66
69
  model.lastSeenAt = now;
67
70
  if (edge.metrics) {
@@ -8,8 +8,19 @@ import ObjectID from "../../Types/ObjectID";
8
8
  import OneUptimeDate from "../../Types/Date";
9
9
  import ColumnLength from "../../Types/Database/ColumnLength";
10
10
  import QueryDeepPartialEntity from "../../Types/Database/PartialEntity";
11
+ import CreateBy from "../Types/Database/CreateBy";
12
+ import { OnCreate } from "../Types/Database/Hooks";
13
+ import BadDataException from "../../Types/Exception/BadDataException";
14
+ import Dictionary from "../../Types/Dictionary";
11
15
  import { JSONObject } from "../../Types/JSON";
16
+ import EntitySource from "../../Types/Telemetry/EntitySource";
12
17
  import EntityType from "../../Types/Telemetry/EntityType";
18
+ import { MANUAL_ENTITY_TYPES } from "../../Types/Telemetry/EntityTypeGroups";
19
+ import {
20
+ canonicalizeEntityValue,
21
+ computeEntityKey,
22
+ MANUAL_ENTITY_IDENTITY_ATTRIBUTE,
23
+ } from "../../Utils/Telemetry/EntityKey";
13
24
  import logger from "../Utils/Logger";
14
25
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
15
26
  import { ExtractedEntity } from "../Utils/Telemetry/TelemetryEntity";
@@ -25,6 +36,91 @@ export class TelemetryEntityService extends DatabaseService<Model> {
25
36
  super(Model);
26
37
  }
27
38
 
39
+ /**
40
+ * Completes a manually created CI.
41
+ *
42
+ * Every machine writer — the ingest reconciler and the inventory mirror —
43
+ * computes `entityKey` itself and passes an explicit `source`, because
44
+ * both derive identity from something the user never sees (a canonicalized
45
+ * semconv attribute set, or the owning row's id). A create arriving
46
+ * WITHOUT a key is therefore a user create by construction, and this hook
47
+ * is what turns the two fields a human can reasonably supply (type and
48
+ * name) into the identity the registry needs.
49
+ *
50
+ * Deriving the key here rather than in the dashboard keeps it enforced for
51
+ * API clients too, and keeps the one identity rule — canonicalize, then
52
+ * hash — in the module that owns it.
53
+ */
54
+ @CaptureSpan()
55
+ protected override async onBeforeCreate(
56
+ createBy: CreateBy<Model>,
57
+ ): Promise<OnCreate<Model>> {
58
+ const data: Model = createBy.data;
59
+
60
+ if (!data.entityKey) {
61
+ data.source = EntitySource.Manual;
62
+ }
63
+
64
+ if (data.source !== EntitySource.Manual) {
65
+ return { createBy, carryForward: null };
66
+ }
67
+
68
+ if (!data.projectId) {
69
+ throw new BadDataException("Project ID is required.");
70
+ }
71
+
72
+ if (!data.entityType) {
73
+ throw new BadDataException("Entity Type is required.");
74
+ }
75
+
76
+ /*
77
+ * Confined to the manual-only vocabulary. A hand-made row of an
78
+ * observable type would key off the manual identity attribute and so
79
+ * could never converge with the row ingest derives for the same thing —
80
+ * it would just sit beside it forever. See MANUAL_ENTITY_TYPES.
81
+ */
82
+ if (!MANUAL_ENTITY_TYPES.has(data.entityType)) {
83
+ throw new BadDataException(
84
+ `Entities of type ${data.entityType} are discovered automatically and cannot be created manually. Manually creatable types are: ${Array.from(
85
+ MANUAL_ENTITY_TYPES,
86
+ ).join(", ")}.`,
87
+ );
88
+ }
89
+
90
+ const displayName: string = (data.displayName || "").trim();
91
+
92
+ if (!displayName) {
93
+ throw new BadDataException(
94
+ "Name is required for a manually created entity.",
95
+ );
96
+ }
97
+
98
+ data.displayName = displayName.substring(0, ColumnLength.ShortText);
99
+
100
+ const identifyingAttributes: Dictionary<string> = {
101
+ [MANUAL_ENTITY_IDENTITY_ATTRIBUTE]: canonicalizeEntityValue(displayName),
102
+ };
103
+ data.identifyingAttributes = identifyingAttributes as JSONObject;
104
+
105
+ data.entityKey = computeEntityKey({
106
+ projectId: data.projectId.toString(),
107
+ entityType: data.entityType,
108
+ identifyingAttributes,
109
+ });
110
+
111
+ /*
112
+ * Stamped so the explorer can sort manual CIs alongside discovered ones
113
+ * on the same columns. `lastSeenAt` never moves again for these rows —
114
+ * it means "created", not "observed" — which is exactly why the prune
115
+ * sweep excludes them.
116
+ */
117
+ const now: Date = OneUptimeDate.getCurrentDate();
118
+ data.firstSeenAt = data.firstSeenAt || now;
119
+ data.lastSeenAt = data.lastSeenAt || now;
120
+
121
+ return { createBy, carryForward: null };
122
+ }
123
+
28
124
  /**
29
125
  * Forward-only registry reconciliation: upsert a catalog row per
30
126
  * discovered entity, bump `lastSeenAt`, merge descriptive attributes
@@ -169,7 +265,18 @@ export class TelemetryEntityService extends DatabaseService<Model> {
169
265
  if (count === undefined) {
170
266
  count = (
171
267
  await this.countBy({
172
- query: { projectId, entityType: entity.entityType },
268
+ query: {
269
+ projectId,
270
+ entityType: entity.entityType,
271
+ /*
272
+ * The budget exists to bound rows minted by ingest, so it
273
+ * counts only those. Manual CIs and inventory mirrors are
274
+ * bounded by their own creators and must not consume it —
275
+ * otherwise registering enough devices could stop new
276
+ * telemetry entities being recorded at all.
277
+ */
278
+ source: EntitySource.Discovered,
279
+ },
173
280
  props: { isRoot: true },
174
281
  })
175
282
  ).toNumber();
@@ -198,6 +305,12 @@ export class TelemetryEntityService extends DatabaseService<Model> {
198
305
  model.projectId = projectId;
199
306
  model.entityType = entity.entityType;
200
307
  model.entityKey = entity.entityKey;
308
+ /*
309
+ * Explicit rather than leaning on the column default: this is the
310
+ * flag that makes the row eligible for stale-entity pruning, and a
311
+ * silently-defaulted value is a poor way to carry that.
312
+ */
313
+ model.source = EntitySource.Discovered;
201
314
  model.identifyingAttributes = entity.identifyingAttributes;
202
315
  if (
203
316
  entity.descriptiveAttributes &&
@@ -4,7 +4,7 @@ import HTTPResponse from "../../../../../Types/API/HTTPResponse";
4
4
  import URL from "../../../../../Types/API/URL";
5
5
  import BadDataException from "../../../../../Types/Exception/BadDataException";
6
6
  import Exception from "../../../../../Types/Exception/Exception";
7
- import { JSONObject } from "../../../../../Types/JSON";
7
+ import { JSONObject, JSONValue } from "../../../../../Types/JSON";
8
8
  import JSONFunctions from "../../../../../Types/JSONFunctions";
9
9
  import ComponentMetadata, {
10
10
  Port,
@@ -82,6 +82,49 @@ export class ApiComponentUtils {
82
82
  );
83
83
  }
84
84
 
85
+ /*
86
+ * Headers reach here from a key/value grid that can emit numbers and
87
+ * booleans, from a JSON document someone typed, or from a {{...}}
88
+ * substitution — and every consumer below blind-casts the result to
89
+ * Dictionary<string> before spreading it into the outgoing request. An
90
+ * array or a bare scalar spreads into per-index junk headers rather than
91
+ * failing, so check the shape and flatten the values to strings here.
92
+ */
93
+ if (args["request-headers"]) {
94
+ const headers: JSONValue = args["request-headers"] as JSONValue;
95
+
96
+ if (
97
+ typeof headers !== "object" ||
98
+ headers === null ||
99
+ Array.isArray(headers)
100
+ ) {
101
+ throw options.onError(
102
+ new BadDataException(
103
+ "Request headers must be a JSON object of header names and values.",
104
+ ),
105
+ );
106
+ }
107
+
108
+ const stringifiedHeaders: JSONObject = {};
109
+
110
+ for (const headerName of Object.keys(headers as JSONObject)) {
111
+ const headerValue: JSONValue = (headers as JSONObject)[
112
+ headerName
113
+ ] as JSONValue;
114
+
115
+ if (headerValue === null || headerValue === undefined) {
116
+ continue;
117
+ }
118
+
119
+ stringifiedHeaders[headerName] =
120
+ typeof headerValue === "object"
121
+ ? JSON.stringify(headerValue)
122
+ : String(headerValue);
123
+ }
124
+
125
+ args["request-headers"] = stringifiedHeaders;
126
+ }
127
+
85
128
  if (!args["url"]) {
86
129
  throw options.onError(new BadDataException("URL not found"));
87
130
  }
@@ -20,6 +20,18 @@ export interface ExecuteWorkflowType {
20
20
  * should leave this undefined.
21
21
  */
22
22
  callChain?: Array<string>;
23
+ /**
24
+ * When set, the run executes only this one component instead of starting at
25
+ * the trigger and following the graph. Backs the builder's "run just this
26
+ * step" button.
27
+ *
28
+ * Deliberately routed through the queue like any other run, so it inherits
29
+ * every gate a run has: the workflow must be enabled, the subscription must
30
+ * be paid, the plan's run limit applies, a WorkflowLog is written for the
31
+ * audit trail, and the component executes on a worker rather than inside the
32
+ * API process.
33
+ */
34
+ runOnlyComponentId?: string | undefined;
23
35
  }
24
36
 
25
37
  export interface InitProps {
@@ -19,4 +19,9 @@ export interface RunProps {
19
19
  * depth across workflow boundaries.
20
20
  */
21
21
  callChain?: Array<string>;
22
+ /**
23
+ * Execute only this component (by its user-facing id) instead of walking the
24
+ * graph from the trigger. See ExecuteWorkflowType.runOnlyComponentId.
25
+ */
26
+ runOnlyComponentId?: string | undefined;
22
27
  }