@oneuptime/common 10.0.84 → 10.0.85

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 (97) hide show
  1. package/Models/DatabaseModels/Index.ts +2 -0
  2. package/Models/DatabaseModels/KubernetesContainer.ts +552 -0
  3. package/Models/DatabaseModels/KubernetesResource.ts +130 -0
  4. package/Models/DatabaseModels/LlmLog.ts +2 -1
  5. package/Models/DatabaseModels/LlmProvider.ts +5 -4
  6. package/Models/DatabaseModels/Project.ts +40 -0
  7. package/Server/API/KubernetesResourceAPI.ts +144 -12
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1777550162848-MigrationName.ts +29 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1777571961028-MigrationName.ts +99 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  11. package/Server/Infrastructure/Queue.ts +60 -0
  12. package/Server/Infrastructure/QueueWorker.ts +39 -1
  13. package/Server/Middleware/HttpMetricsMiddleware.ts +92 -0
  14. package/Server/Services/AuditLogService.ts +19 -1
  15. package/Server/Services/KubernetesContainerService.ts +264 -0
  16. package/Server/Services/KubernetesResourceService.ts +233 -0
  17. package/Server/Services/StatusPageSubscriberService.ts +4 -4
  18. package/Server/Types/Database/Permissions/AccessControlPermission.ts +3 -3
  19. package/Server/Utils/LLM/LLMService.ts +132 -11
  20. package/Server/Utils/Monitor/MonitorAlert.ts +1 -1
  21. package/Server/Utils/Monitor/MonitorIncident.ts +1 -1
  22. package/Server/Utils/StartServer.ts +2 -0
  23. package/Server/Utils/Telemetry/AppMetrics.ts +211 -0
  24. package/Server/Utils/Telemetry/RuntimeMetrics.ts +169 -0
  25. package/Server/Utils/Telemetry.ts +98 -0
  26. package/Server/Utils/Workspace/Slack/Actions/Alert.ts +2 -2
  27. package/Server/Utils/Workspace/Slack/Actions/Incident.ts +2 -2
  28. package/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.ts +2 -2
  29. package/Tests/jest.setup.ts +18 -0
  30. package/Types/Kubernetes/KubernetesInventoryExtractor.ts +171 -5
  31. package/Types/LLM/LlmType.ts +3 -0
  32. package/UI/Components/Forms/ModelForm.tsx +3 -3
  33. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +2 -2
  34. package/Utils/UUID.ts +1 -3
  35. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  36. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  37. package/build/dist/Models/DatabaseModels/KubernetesContainer.js +581 -0
  38. package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -0
  39. package/build/dist/Models/DatabaseModels/KubernetesResource.js +135 -0
  40. package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
  41. package/build/dist/Models/DatabaseModels/LlmLog.js +1 -1
  42. package/build/dist/Models/DatabaseModels/LlmLog.js.map +1 -1
  43. package/build/dist/Models/DatabaseModels/LlmProvider.js +4 -4
  44. package/build/dist/Models/DatabaseModels/LlmProvider.js.map +1 -1
  45. package/build/dist/Models/DatabaseModels/Project.js +41 -0
  46. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  47. package/build/dist/Server/API/KubernetesResourceAPI.js +106 -9
  48. package/build/dist/Server/API/KubernetesResourceAPI.js.map +1 -1
  49. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777550162848-MigrationName.js +16 -0
  50. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777550162848-MigrationName.js.map +1 -0
  51. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777571961028-MigrationName.js +40 -0
  52. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1777571961028-MigrationName.js.map +1 -0
  53. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  54. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  55. package/build/dist/Server/Infrastructure/Queue.js +44 -0
  56. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  57. package/build/dist/Server/Infrastructure/QueueWorker.js +31 -1
  58. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  59. package/build/dist/Server/Middleware/HttpMetricsMiddleware.js +61 -0
  60. package/build/dist/Server/Middleware/HttpMetricsMiddleware.js.map +1 -0
  61. package/build/dist/Server/Services/AuditLogService.js +14 -1
  62. package/build/dist/Server/Services/AuditLogService.js.map +1 -1
  63. package/build/dist/Server/Services/KubernetesContainerService.js +179 -0
  64. package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -0
  65. package/build/dist/Server/Services/KubernetesResourceService.js +175 -0
  66. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
  67. package/build/dist/Server/Services/StatusPageSubscriberService.js +4 -4
  68. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  69. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +3 -3
  70. package/build/dist/Server/Utils/LLM/LLMService.js +111 -13
  71. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  72. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +1 -1
  73. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  74. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +1 -1
  75. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  76. package/build/dist/Server/Utils/StartServer.js +2 -0
  77. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  78. package/build/dist/Server/Utils/Telemetry/AppMetrics.js +167 -0
  79. package/build/dist/Server/Utils/Telemetry/AppMetrics.js.map +1 -0
  80. package/build/dist/Server/Utils/Telemetry/RuntimeMetrics.js +141 -0
  81. package/build/dist/Server/Utils/Telemetry/RuntimeMetrics.js.map +1 -0
  82. package/build/dist/Server/Utils/Telemetry.js +47 -0
  83. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  84. package/build/dist/Server/Utils/Workspace/Slack/Actions/Alert.js +2 -2
  85. package/build/dist/Server/Utils/Workspace/Slack/Actions/Incident.js +2 -2
  86. package/build/dist/Server/Utils/Workspace/Slack/Actions/ScheduledMaintenance.js +2 -2
  87. package/build/dist/Tests/jest.setup.js +17 -0
  88. package/build/dist/Tests/jest.setup.js.map +1 -1
  89. package/build/dist/Types/Kubernetes/KubernetesInventoryExtractor.js +116 -4
  90. package/build/dist/Types/Kubernetes/KubernetesInventoryExtractor.js.map +1 -1
  91. package/build/dist/Types/LLM/LlmType.js +3 -0
  92. package/build/dist/Types/LLM/LlmType.js.map +1 -1
  93. package/build/dist/UI/Components/Forms/ModelForm.js +3 -3
  94. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  95. package/build/dist/Utils/UUID.js +1 -2
  96. package/build/dist/Utils/UUID.js.map +1 -1
  97. package/package.json +6 -8
@@ -4,6 +4,7 @@ import KubernetesCluster from "./KubernetesCluster";
4
4
  import KubernetesClusterOwnerTeam from "./KubernetesClusterOwnerTeam";
5
5
  import KubernetesClusterOwnerUser from "./KubernetesClusterOwnerUser";
6
6
  import KubernetesResource from "./KubernetesResource";
7
+ import KubernetesContainer from "./KubernetesContainer";
7
8
  import DockerHost from "./DockerHost";
8
9
  import DockerHostOwnerTeam from "./DockerHostOwnerTeam";
9
10
  import DockerHostOwnerUser from "./DockerHostOwnerUser";
@@ -535,6 +536,7 @@ const AllModelTypes: Array<{
535
536
  KubernetesClusterOwnerTeam,
536
537
  KubernetesClusterOwnerUser,
537
538
  KubernetesResource,
539
+ KubernetesContainer,
538
540
  DockerHost,
539
541
  DockerHostOwnerTeam,
540
542
  DockerHostOwnerUser,
@@ -0,0 +1,552 @@
1
+ import KubernetesCluster from "./KubernetesCluster";
2
+ import Project from "./Project";
3
+ import User from "./User";
4
+ import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
5
+ import Route from "../../Types/API/Route";
6
+ import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
7
+ import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
8
+ import ColumnLength from "../../Types/Database/ColumnLength";
9
+ import ColumnType from "../../Types/Database/ColumnType";
10
+ import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
11
+ import TableColumn from "../../Types/Database/TableColumn";
12
+ import TableColumnType from "../../Types/Database/TableColumnType";
13
+ import TableMetadata from "../../Types/Database/TableMetadata";
14
+ import TenantColumn from "../../Types/Database/TenantColumn";
15
+ import IconProp from "../../Types/Icon/IconProp";
16
+ import ObjectID from "../../Types/ObjectID";
17
+ import Permission from "../../Types/Permission";
18
+ import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
19
+
20
+ /*
21
+ * ------------------------------------------------------------------
22
+ * KubernetesContainer
23
+ * ------------------------------------------------------------------
24
+ *
25
+ * One row per container in the Pod inventory snapshot. Lets the
26
+ * Containers list page do a single indexed SELECT instead of
27
+ * fetching every Pod and unpacking JSONB at render time, and lets
28
+ * the metric ingest path write latest CPU/memory directly so the
29
+ * page never needs to hit ClickHouse.
30
+ *
31
+ * Populated together with KubernetesResource: when a Pod snapshot
32
+ * arrives, the ingest path emits one parent KubernetesResource row
33
+ * (kind = "Pod") and N child KubernetesContainer rows.
34
+ *
35
+ * Hard-deleted alongside its parent Pod by the cleanup worker once
36
+ * the Pod's lastSeenAt falls behind the staleness threshold.
37
+ *
38
+ * ------------------------------------------------------------------
39
+ */
40
+
41
+ const READ_PERMISSIONS: Array<Permission> = [
42
+ Permission.ProjectOwner,
43
+ Permission.ProjectAdmin,
44
+ Permission.ProjectMember,
45
+ Permission.Viewer,
46
+ Permission.SettingsManager,
47
+ Permission.ReadKubernetesCluster,
48
+ Permission.ReadAllProjectResources,
49
+ ];
50
+
51
+ @TenantColumn("projectId")
52
+ @TableAccessControl({
53
+ create: [],
54
+ read: READ_PERMISSIONS,
55
+ update: [],
56
+ delete: [],
57
+ })
58
+ @CrudApiEndpoint(new Route("/kubernetes-container"))
59
+ @TableMetadata({
60
+ tableName: "KubernetesContainer",
61
+ singularName: "Kubernetes Container",
62
+ pluralName: "Kubernetes Containers",
63
+ icon: IconProp.Cube,
64
+ tableDescription:
65
+ "Snapshot of a single container inside a Pod, populated by the telemetry ingest pipeline. Not user-editable.",
66
+ })
67
+ @Index(
68
+ ["projectId", "kubernetesClusterId", "podNamespaceKey", "podName", "name"],
69
+ { unique: true },
70
+ )
71
+ @Entity({
72
+ name: "KubernetesContainer",
73
+ })
74
+ export default class KubernetesContainer extends BaseModel {
75
+ @ColumnAccessControl({
76
+ create: [],
77
+ read: READ_PERMISSIONS,
78
+ update: [],
79
+ })
80
+ @TableColumn({
81
+ manyToOneRelationColumn: "projectId",
82
+ type: TableColumnType.Entity,
83
+ modelType: Project,
84
+ title: "Project",
85
+ description: "Relation to Project this container belongs to.",
86
+ })
87
+ @ManyToOne(
88
+ () => {
89
+ return Project;
90
+ },
91
+ {
92
+ eager: false,
93
+ nullable: true,
94
+ onDelete: "CASCADE",
95
+ orphanedRowAction: "nullify",
96
+ },
97
+ )
98
+ @JoinColumn({ name: "projectId" })
99
+ public project?: Project = undefined;
100
+
101
+ @ColumnAccessControl({
102
+ create: [],
103
+ read: READ_PERMISSIONS,
104
+ update: [],
105
+ })
106
+ @Index()
107
+ @TableColumn({
108
+ type: TableColumnType.ObjectID,
109
+ required: true,
110
+ canReadOnRelationQuery: true,
111
+ title: "Project ID",
112
+ description: "ID of the Project this container belongs to.",
113
+ })
114
+ @Column({
115
+ type: ColumnType.ObjectID,
116
+ nullable: false,
117
+ transformer: ObjectID.getDatabaseTransformer(),
118
+ })
119
+ public projectId?: ObjectID = undefined;
120
+
121
+ @ColumnAccessControl({
122
+ create: [],
123
+ read: READ_PERMISSIONS,
124
+ update: [],
125
+ })
126
+ @TableColumn({
127
+ manyToOneRelationColumn: "kubernetesClusterId",
128
+ type: TableColumnType.Entity,
129
+ modelType: KubernetesCluster,
130
+ title: "Kubernetes Cluster",
131
+ description: "Cluster this container lives in.",
132
+ })
133
+ @ManyToOne(
134
+ () => {
135
+ return KubernetesCluster;
136
+ },
137
+ {
138
+ eager: false,
139
+ nullable: true,
140
+ onDelete: "CASCADE",
141
+ orphanedRowAction: "nullify",
142
+ },
143
+ )
144
+ @JoinColumn({ name: "kubernetesClusterId" })
145
+ public kubernetesCluster?: KubernetesCluster = undefined;
146
+
147
+ @ColumnAccessControl({
148
+ create: [],
149
+ read: READ_PERMISSIONS,
150
+ update: [],
151
+ })
152
+ @Index()
153
+ @TableColumn({
154
+ type: TableColumnType.ObjectID,
155
+ required: true,
156
+ canReadOnRelationQuery: true,
157
+ title: "Kubernetes Cluster ID",
158
+ description: "ID of the Kubernetes Cluster this container lives in.",
159
+ })
160
+ @Column({
161
+ type: ColumnType.ObjectID,
162
+ nullable: false,
163
+ transformer: ObjectID.getDatabaseTransformer(),
164
+ })
165
+ public kubernetesClusterId?: ObjectID = undefined;
166
+
167
+ @ColumnAccessControl({
168
+ create: [],
169
+ read: READ_PERMISSIONS,
170
+ update: [],
171
+ })
172
+ @TableColumn({
173
+ required: true,
174
+ type: TableColumnType.ShortText,
175
+ canReadOnRelationQuery: true,
176
+ title: "Pod Namespace Key",
177
+ description:
178
+ "Namespace of the parent Pod. Empty string for the rare cluster-scoped pod case; kept non-null so the upsert unique index works.",
179
+ })
180
+ @Column({
181
+ nullable: false,
182
+ type: ColumnType.ShortText,
183
+ length: ColumnLength.ShortText,
184
+ default: "",
185
+ })
186
+ public podNamespaceKey?: string = undefined;
187
+
188
+ @ColumnAccessControl({
189
+ create: [],
190
+ read: READ_PERMISSIONS,
191
+ update: [],
192
+ })
193
+ @TableColumn({
194
+ required: true,
195
+ type: TableColumnType.ShortText,
196
+ canReadOnRelationQuery: true,
197
+ title: "Pod Name",
198
+ description: "metadata.name of the parent Pod.",
199
+ })
200
+ @Column({
201
+ nullable: false,
202
+ type: ColumnType.ShortText,
203
+ length: ColumnLength.ShortText,
204
+ })
205
+ public podName?: string = undefined;
206
+
207
+ @ColumnAccessControl({
208
+ create: [],
209
+ read: READ_PERMISSIONS,
210
+ update: [],
211
+ })
212
+ @TableColumn({
213
+ required: true,
214
+ type: TableColumnType.ShortText,
215
+ canReadOnRelationQuery: true,
216
+ title: "Name",
217
+ description: "Container name (spec.containers[].name).",
218
+ })
219
+ @Column({
220
+ nullable: false,
221
+ type: ColumnType.ShortText,
222
+ length: ColumnLength.ShortText,
223
+ })
224
+ public name?: string = undefined;
225
+
226
+ @ColumnAccessControl({
227
+ create: [],
228
+ read: READ_PERMISSIONS,
229
+ update: [],
230
+ })
231
+ @TableColumn({
232
+ required: false,
233
+ type: TableColumnType.LongText,
234
+ canReadOnRelationQuery: true,
235
+ title: "Image",
236
+ description: "Container image reference (spec.containers[].image).",
237
+ })
238
+ @Column({
239
+ nullable: true,
240
+ type: ColumnType.LongText,
241
+ length: ColumnLength.LongText,
242
+ })
243
+ public image?: string = undefined;
244
+
245
+ @ColumnAccessControl({
246
+ create: [],
247
+ read: READ_PERMISSIONS,
248
+ update: [],
249
+ })
250
+ @TableColumn({
251
+ required: false,
252
+ type: TableColumnType.ShortText,
253
+ canReadOnRelationQuery: true,
254
+ title: "State",
255
+ description:
256
+ "Container state from status.containerStatuses[].state (running / waiting / terminated). Null until status is observed.",
257
+ })
258
+ @Column({
259
+ nullable: true,
260
+ type: ColumnType.ShortText,
261
+ length: ColumnLength.ShortText,
262
+ })
263
+ public state?: string = undefined;
264
+
265
+ @ColumnAccessControl({
266
+ create: [],
267
+ read: READ_PERMISSIONS,
268
+ update: [],
269
+ })
270
+ @TableColumn({
271
+ required: false,
272
+ type: TableColumnType.ShortText,
273
+ canReadOnRelationQuery: true,
274
+ title: "Reason",
275
+ description:
276
+ "Container state reason (e.g. ImagePullBackOff, CrashLoopBackOff). Surfaced on the list page when the container is not Running.",
277
+ })
278
+ @Column({
279
+ nullable: true,
280
+ type: ColumnType.ShortText,
281
+ length: ColumnLength.ShortText,
282
+ })
283
+ public reason?: string = undefined;
284
+
285
+ @ColumnAccessControl({
286
+ create: [],
287
+ read: READ_PERMISSIONS,
288
+ update: [],
289
+ })
290
+ @TableColumn({
291
+ required: false,
292
+ type: TableColumnType.Boolean,
293
+ canReadOnRelationQuery: true,
294
+ title: "Is Ready",
295
+ description:
296
+ "True when status.containerStatuses[].ready is true. Null until status is observed.",
297
+ })
298
+ @Column({
299
+ nullable: true,
300
+ type: ColumnType.Boolean,
301
+ })
302
+ public isReady?: boolean = undefined;
303
+
304
+ @ColumnAccessControl({
305
+ create: [],
306
+ read: READ_PERMISSIONS,
307
+ update: [],
308
+ })
309
+ @TableColumn({
310
+ required: false,
311
+ type: TableColumnType.PositiveNumber,
312
+ canReadOnRelationQuery: true,
313
+ title: "Restart Count",
314
+ description:
315
+ "Restart counter from status.containerStatuses[].restartCount.",
316
+ })
317
+ @Column({
318
+ nullable: true,
319
+ type: ColumnType.PositiveNumber,
320
+ })
321
+ public restartCount?: number = undefined;
322
+
323
+ @ColumnAccessControl({
324
+ create: [],
325
+ read: READ_PERMISSIONS,
326
+ update: [],
327
+ })
328
+ @TableColumn({
329
+ required: false,
330
+ type: TableColumnType.BigPositiveNumber,
331
+ canReadOnRelationQuery: true,
332
+ title: "Memory Limit Bytes",
333
+ description:
334
+ "spec.containers[].resources.limits.memory parsed to bytes, used to render the % memory bar on the list view.",
335
+ })
336
+ @Column({
337
+ nullable: true,
338
+ type: ColumnType.BigPositiveNumber,
339
+ transformer: {
340
+ to: (value: number | null | undefined): string | null => {
341
+ if (value === null || value === undefined) {
342
+ return null;
343
+ }
344
+ return Math.trunc(value).toString();
345
+ },
346
+ from: (value: string | null | undefined): number | null => {
347
+ if (value === null || value === undefined) {
348
+ return null;
349
+ }
350
+ const parsed: number = parseInt(value, 10);
351
+ return isNaN(parsed) ? null : parsed;
352
+ },
353
+ },
354
+ })
355
+ public memoryLimitBytes?: number = undefined;
356
+
357
+ @ColumnAccessControl({
358
+ create: [],
359
+ read: READ_PERMISSIONS,
360
+ update: [],
361
+ })
362
+ @TableColumn({
363
+ required: false,
364
+ type: TableColumnType.Number,
365
+ canReadOnRelationQuery: true,
366
+ title: "Latest CPU Percent",
367
+ description:
368
+ "Most recent CPU utilization percent for this container. Stored as decimal so sub-percent precision survives the round trip. Null until the first metric arrives.",
369
+ })
370
+ @Column({
371
+ nullable: true,
372
+ type: ColumnType.Decimal,
373
+ transformer: {
374
+ to: (value: number | null | undefined): number | null => {
375
+ if (value === null || value === undefined) {
376
+ return null;
377
+ }
378
+ return value;
379
+ },
380
+ from: (value: string | number | null | undefined): number | null => {
381
+ if (value === null || value === undefined) {
382
+ return null;
383
+ }
384
+ if (typeof value === "number") {
385
+ return value;
386
+ }
387
+ const parsed: number = parseFloat(value);
388
+ return isNaN(parsed) ? null : parsed;
389
+ },
390
+ },
391
+ })
392
+ public latestCpuPercent?: number = undefined;
393
+
394
+ @ColumnAccessControl({
395
+ create: [],
396
+ read: READ_PERMISSIONS,
397
+ update: [],
398
+ })
399
+ @TableColumn({
400
+ required: false,
401
+ type: TableColumnType.BigPositiveNumber,
402
+ canReadOnRelationQuery: true,
403
+ title: "Latest Memory Bytes",
404
+ description:
405
+ "Most recent memory usage (bytes) for this container. Same lifecycle as latestCpuPercent.",
406
+ })
407
+ @Column({
408
+ nullable: true,
409
+ type: ColumnType.BigPositiveNumber,
410
+ transformer: {
411
+ to: (value: number | null | undefined): string | null => {
412
+ if (value === null || value === undefined) {
413
+ return null;
414
+ }
415
+ return Math.trunc(value).toString();
416
+ },
417
+ from: (value: string | null | undefined): number | null => {
418
+ if (value === null || value === undefined) {
419
+ return null;
420
+ }
421
+ const parsed: number = parseInt(value, 10);
422
+ return isNaN(parsed) ? null : parsed;
423
+ },
424
+ },
425
+ })
426
+ public latestMemoryBytes?: number = undefined;
427
+
428
+ @ColumnAccessControl({
429
+ create: [],
430
+ read: READ_PERMISSIONS,
431
+ update: [],
432
+ })
433
+ @TableColumn({
434
+ required: false,
435
+ type: TableColumnType.Date,
436
+ canReadOnRelationQuery: true,
437
+ title: "Metrics Updated At",
438
+ description:
439
+ "Observed timestamp of the latest CPU/memory point. Acts as the monotonic guard for metric updates and the cutoff for staleness rendering.",
440
+ })
441
+ @Column({
442
+ nullable: true,
443
+ type: ColumnType.Date,
444
+ })
445
+ public metricsUpdatedAt?: Date = undefined;
446
+
447
+ @ColumnAccessControl({
448
+ create: [],
449
+ read: READ_PERMISSIONS,
450
+ update: [],
451
+ })
452
+ @TableColumn({
453
+ required: true,
454
+ type: TableColumnType.Date,
455
+ canReadOnRelationQuery: true,
456
+ title: "Last Seen At",
457
+ description:
458
+ "Agent-observed timestamp of the most recent Pod snapshot containing this container. Acts as the monotonic guard for upserts and the cutoff for stale-row cleanup.",
459
+ })
460
+ @Column({
461
+ nullable: false,
462
+ type: ColumnType.Date,
463
+ })
464
+ public lastSeenAt?: Date = undefined;
465
+
466
+ @ColumnAccessControl({
467
+ create: [],
468
+ read: READ_PERMISSIONS,
469
+ update: [],
470
+ })
471
+ @TableColumn({
472
+ manyToOneRelationColumn: "createdByUserId",
473
+ type: TableColumnType.Entity,
474
+ modelType: User,
475
+ title: "Created By User",
476
+ description:
477
+ "Not user-facing; ingest writes as isRoot so this stays null in practice.",
478
+ })
479
+ @ManyToOne(
480
+ () => {
481
+ return User;
482
+ },
483
+ {
484
+ eager: false,
485
+ nullable: true,
486
+ onDelete: "SET NULL",
487
+ orphanedRowAction: "nullify",
488
+ },
489
+ )
490
+ @JoinColumn({ name: "createdByUserId" })
491
+ public createdByUser?: User = undefined;
492
+
493
+ @ColumnAccessControl({
494
+ create: [],
495
+ read: READ_PERMISSIONS,
496
+ update: [],
497
+ })
498
+ @TableColumn({
499
+ type: TableColumnType.ObjectID,
500
+ title: "Created By User ID",
501
+ description: "ID of the user who created this row.",
502
+ })
503
+ @Column({
504
+ type: ColumnType.ObjectID,
505
+ nullable: true,
506
+ transformer: ObjectID.getDatabaseTransformer(),
507
+ })
508
+ public createdByUserId?: ObjectID = undefined;
509
+
510
+ @ColumnAccessControl({
511
+ create: [],
512
+ read: READ_PERMISSIONS,
513
+ update: [],
514
+ })
515
+ @TableColumn({
516
+ manyToOneRelationColumn: "deletedByUserId",
517
+ type: TableColumnType.Entity,
518
+ modelType: User,
519
+ title: "Deleted By User",
520
+ description: "Relation to the user who deleted this row.",
521
+ })
522
+ @ManyToOne(
523
+ () => {
524
+ return User;
525
+ },
526
+ {
527
+ eager: false,
528
+ nullable: true,
529
+ onDelete: "SET NULL",
530
+ orphanedRowAction: "nullify",
531
+ },
532
+ )
533
+ @JoinColumn({ name: "deletedByUserId" })
534
+ public deletedByUser?: User = undefined;
535
+
536
+ @ColumnAccessControl({
537
+ create: [],
538
+ read: READ_PERMISSIONS,
539
+ update: [],
540
+ })
541
+ @TableColumn({
542
+ type: TableColumnType.ObjectID,
543
+ title: "Deleted By User ID",
544
+ description: "ID of the user who deleted this row.",
545
+ })
546
+ @Column({
547
+ type: ColumnType.ObjectID,
548
+ nullable: true,
549
+ transformer: ObjectID.getDatabaseTransformer(),
550
+ })
551
+ public deletedByUserId?: ObjectID = undefined;
552
+ }
@@ -441,6 +441,136 @@ export default class KubernetesResource extends BaseModel {
441
441
  })
442
442
  public status?: JSONObject = undefined;
443
443
 
444
+ @ColumnAccessControl({
445
+ create: [],
446
+ read: READ_PERMISSIONS,
447
+ update: [],
448
+ })
449
+ @TableColumn({
450
+ required: false,
451
+ type: TableColumnType.ShortText,
452
+ canReadOnRelationQuery: true,
453
+ title: "Controller Deployment Name",
454
+ description:
455
+ "For Pod kinds: the Deployment that ultimately owns this Pod (one hop above the ReplicaSet). Populated from `resource.k8s.deployment.name` on the metric stream so the Deployments list view can SUM by this column without inventorying ReplicaSets. Null for non-Pod kinds and for Pods not owned by a Deployment.",
456
+ })
457
+ @Column({
458
+ nullable: true,
459
+ type: ColumnType.ShortText,
460
+ length: ColumnLength.ShortText,
461
+ })
462
+ public controllerDeploymentName?: string = undefined;
463
+
464
+ @ColumnAccessControl({
465
+ create: [],
466
+ read: READ_PERMISSIONS,
467
+ update: [],
468
+ })
469
+ @TableColumn({
470
+ required: false,
471
+ type: TableColumnType.ShortText,
472
+ canReadOnRelationQuery: true,
473
+ title: "Controller CronJob Name",
474
+ description:
475
+ "For Pod kinds: the CronJob that ultimately owns this Pod (one hop above the Job). Populated from `resource.k8s.cronjob.name` on the metric stream. Null for non-Pod kinds and Pods not owned by a CronJob.",
476
+ })
477
+ @Column({
478
+ nullable: true,
479
+ type: ColumnType.ShortText,
480
+ length: ColumnLength.ShortText,
481
+ })
482
+ public controllerCronJobName?: string = undefined;
483
+
484
+ @ColumnAccessControl({
485
+ create: [],
486
+ read: READ_PERMISSIONS,
487
+ update: [],
488
+ })
489
+ @TableColumn({
490
+ required: false,
491
+ type: TableColumnType.Number,
492
+ canReadOnRelationQuery: true,
493
+ title: "Latest CPU Percent",
494
+ description:
495
+ "Most recent CPU utilization percent for this resource (Pod or Node). Stored as decimal so sub-percent precision survives the round trip. Can exceed 100 when the resource consumes more than one core. Null until the first metric arrives.",
496
+ })
497
+ @Column({
498
+ nullable: true,
499
+ type: ColumnType.Decimal,
500
+ transformer: {
501
+ to: (value: number | null | undefined): number | null => {
502
+ if (value === null || value === undefined) {
503
+ return null;
504
+ }
505
+ return value;
506
+ },
507
+ from: (value: string | number | null | undefined): number | null => {
508
+ if (value === null || value === undefined) {
509
+ return null;
510
+ }
511
+ if (typeof value === "number") {
512
+ return value;
513
+ }
514
+ const parsed: number = parseFloat(value);
515
+ return isNaN(parsed) ? null : parsed;
516
+ },
517
+ },
518
+ })
519
+ public latestCpuPercent?: number = undefined;
520
+
521
+ @ColumnAccessControl({
522
+ create: [],
523
+ read: READ_PERMISSIONS,
524
+ update: [],
525
+ })
526
+ @TableColumn({
527
+ required: false,
528
+ type: TableColumnType.BigPositiveNumber,
529
+ canReadOnRelationQuery: true,
530
+ title: "Latest Memory Bytes",
531
+ description:
532
+ "Most recent memory usage (bytes) for this resource (Pod or Node). Stored as bigint so values past 2 GiB don't overflow. Same lifecycle as latestCpuPercent.",
533
+ })
534
+ @Column({
535
+ nullable: true,
536
+ type: ColumnType.BigPositiveNumber,
537
+ transformer: {
538
+ to: (value: number | null | undefined): string | null => {
539
+ if (value === null || value === undefined) {
540
+ return null;
541
+ }
542
+ return Math.trunc(value).toString();
543
+ },
544
+ from: (value: string | null | undefined): number | null => {
545
+ if (value === null || value === undefined) {
546
+ return null;
547
+ }
548
+ const parsed: number = parseInt(value, 10);
549
+ return isNaN(parsed) ? null : parsed;
550
+ },
551
+ },
552
+ })
553
+ public latestMemoryBytes?: number = undefined;
554
+
555
+ @ColumnAccessControl({
556
+ create: [],
557
+ read: READ_PERMISSIONS,
558
+ update: [],
559
+ })
560
+ @TableColumn({
561
+ required: false,
562
+ type: TableColumnType.Date,
563
+ canReadOnRelationQuery: true,
564
+ title: "Metrics Updated At",
565
+ description:
566
+ "Observed timestamp of the latest CPU/memory point. Acts as the monotonic guard for metric updates and the cutoff for staleness rendering.",
567
+ })
568
+ @Column({
569
+ nullable: true,
570
+ type: ColumnType.Date,
571
+ })
572
+ public metricsUpdatedAt?: Date = undefined;
573
+
444
574
  @ColumnAccessControl({
445
575
  create: [],
446
576
  read: READ_PERMISSIONS,
@@ -217,7 +217,8 @@ export default class LlmLog extends BaseModel {
217
217
  required: false,
218
218
  type: TableColumnType.ShortText,
219
219
  title: "LLM Type",
220
- description: "Type of LLM (OpenAI, Anthropic, Ollama)",
220
+ description:
221
+ "Type of LLM (OpenAI, Azure OpenAI, Anthropic, Groq, Mistral, Ollama)",
221
222
  canReadOnRelationQuery: false,
222
223
  })
223
224
  @Column({