@oneuptime/common 11.5.13 → 11.6.0

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 (122) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +21 -0
  2. package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +25 -0
  3. package/Models/DatabaseModels/GlobalConfig.ts +76 -0
  4. package/Server/API/EnterpriseLicenseAPI.ts +57 -0
  5. package/Server/API/GlobalConfigAPI.ts +62 -1
  6. package/Server/EnvironmentConfig.ts +22 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.ts +38 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.ts +25 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  10. package/Server/Services/AnalyticsDatabaseService.ts +48 -45
  11. package/Server/Services/DatabaseService.ts +66 -1
  12. package/Server/Services/WorkflowService.ts +100 -39
  13. package/Server/Types/Workflow/Components/Schedule.ts +57 -32
  14. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +59 -0
  15. package/Server/Utils/Express.ts +13 -0
  16. package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +799 -0
  17. package/Server/Utils/Telemetry/TelemetryWriterClient.ts +263 -0
  18. package/Server/Utils/Telemetry/TelemetryWriterServer.ts +240 -0
  19. package/Server/Utils/Telemetry/TelemetryWriterShedMetrics.ts +93 -0
  20. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +232 -0
  21. package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +107 -0
  22. package/Tests/Server/Services/WorkflowService.test.ts +308 -0
  23. package/Tests/Server/Utils/Attribution.test.ts +203 -0
  24. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +183 -0
  25. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +340 -0
  26. package/Tests/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.test.ts +87 -0
  27. package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +165 -0
  28. package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +86 -0
  29. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +1565 -0
  30. package/Tests/Server/Utils/Telemetry/TelemetryWriterClient.test.ts +313 -0
  31. package/Tests/Server/Utils/Telemetry/TelemetryWriterServer.test.ts +270 -0
  32. package/Tests/Server/Utils/Telemetry/TelemetryWriterShedMetrics.test.ts +127 -0
  33. package/Tests/Types/AI/CodeFixTaskType.test.ts +3 -0
  34. package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +228 -0
  35. package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +169 -0
  36. package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +289 -0
  37. package/Tests/Types/Monitor/MonitorStepLogMonitor.test.ts +231 -0
  38. package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +245 -0
  39. package/Tests/Types/Monitor/MonitorStepNetworkDeviceMonitor.test.ts +155 -0
  40. package/Tests/Types/Monitor/MonitorStepTelemetrySerialization.test.ts +141 -0
  41. package/Tests/Types/Monitor/MonitorStepTraceMonitor.test.ts +137 -0
  42. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +311 -0
  43. package/Tests/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.test.ts +461 -0
  44. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +2852 -0
  45. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +758 -0
  46. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +564 -0
  47. package/Tests/Utils/Array.test.ts +156 -0
  48. package/Tests/Utils/CronTab.test.ts +199 -0
  49. package/Tests/Utils/ModelImportExport.test.ts +101 -0
  50. package/Tests/Utils/Number.test.ts +179 -0
  51. package/Tests/Utils/VersionUtil.test.ts +348 -0
  52. package/Tests/jest.setup.ts +1 -0
  53. package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +6 -0
  54. package/Types/Monitor/MonitorStep.ts +24 -4
  55. package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +105 -0
  56. package/UI/Components/EditionLabel/EditionLabel.tsx +445 -12
  57. package/UI/Components/Workflow/ArgumentsForm.tsx +68 -30
  58. package/UI/Components/Workflow/CronScheduleField.tsx +411 -0
  59. package/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.ts +176 -0
  60. package/UI/Utils/NotificationMethodUtil.ts +310 -0
  61. package/Utils/CronTab.ts +823 -0
  62. package/Utils/ModelImportExport.ts +21 -6
  63. package/Utils/VersionUtil.ts +260 -0
  64. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +22 -0
  65. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  66. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +26 -0
  67. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -1
  68. package/build/dist/Models/DatabaseModels/GlobalConfig.js +81 -0
  69. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  70. package/build/dist/Server/API/EnterpriseLicenseAPI.js +47 -0
  71. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  72. package/build/dist/Server/API/GlobalConfigAPI.js +52 -1
  73. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  74. package/build/dist/Server/EnvironmentConfig.js +17 -0
  75. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  76. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js +18 -0
  77. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js.map +1 -0
  78. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js +14 -0
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js.map +1 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  82. package/build/dist/Server/Services/AnalyticsDatabaseService.js +44 -16
  83. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  84. package/build/dist/Server/Services/DatabaseService.js +53 -1
  85. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  86. package/build/dist/Server/Services/WorkflowService.js +80 -30
  87. package/build/dist/Server/Services/WorkflowService.js.map +1 -1
  88. package/build/dist/Server/Types/Workflow/Components/Schedule.js +44 -19
  89. package/build/dist/Server/Types/Workflow/Components/Schedule.js.map +1 -1
  90. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js +24 -0
  91. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -0
  92. package/build/dist/Server/Utils/Express.js +12 -0
  93. package/build/dist/Server/Utils/Express.js.map +1 -1
  94. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +496 -0
  95. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -0
  96. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js +165 -0
  97. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js.map +1 -0
  98. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js +136 -0
  99. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js.map +1 -0
  100. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js +76 -0
  101. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js.map +1 -0
  102. package/build/dist/Types/Monitor/MonitorStep.js +20 -4
  103. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  104. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +73 -0
  105. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -0
  106. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +261 -13
  107. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  108. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +30 -6
  109. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  110. package/build/dist/UI/Components/Workflow/CronScheduleField.js +209 -0
  111. package/build/dist/UI/Components/Workflow/CronScheduleField.js.map +1 -0
  112. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js +129 -0
  113. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js.map +1 -0
  114. package/build/dist/UI/Utils/NotificationMethodUtil.js +189 -0
  115. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -0
  116. package/build/dist/Utils/CronTab.js +609 -0
  117. package/build/dist/Utils/CronTab.js.map +1 -0
  118. package/build/dist/Utils/ModelImportExport.js +20 -6
  119. package/build/dist/Utils/ModelImportExport.js.map +1 -1
  120. package/build/dist/Utils/VersionUtil.js +193 -0
  121. package/build/dist/Utils/VersionUtil.js.map +1 -0
  122. package/package.json +1 -1
@@ -102,6 +102,27 @@ export default class EnterpriseLicense extends BaseModel {
102
102
  })
103
103
  public annualContractValue?: number = undefined;
104
104
 
105
+ @ColumnAccessControl({
106
+ create: [],
107
+ read: [],
108
+ update: [],
109
+ })
110
+ @TableColumn({
111
+ required: true,
112
+ isDefaultValueColumn: true,
113
+ type: TableColumnType.Boolean,
114
+ title: "Evaluation License",
115
+ description:
116
+ "When enabled, this key is for evaluation and testing only — the customer's installation shows an evaluation notice and it is not meant for production use.",
117
+ defaultValue: false,
118
+ })
119
+ @Column({
120
+ nullable: false,
121
+ type: ColumnType.Boolean,
122
+ default: false,
123
+ })
124
+ public isEvaluationLicense?: boolean = undefined;
125
+
105
126
  @ColumnAccessControl({
106
127
  create: [],
107
128
  read: [],
@@ -188,4 +188,29 @@ export default class EnterpriseLicenseInstance extends BaseModel {
188
188
  nullable: true,
189
189
  })
190
190
  public masterAdminEmails?: Array<string> = undefined;
191
+
192
+ /*
193
+ * Named oneuptimeVersion, not version: DatabaseBaseModel already declares a
194
+ * `version` @VersionColumn() (the optimistic-locking row revision) on every
195
+ * model, so a second `version` property would collide in the TypeORM entity
196
+ * metadata.
197
+ */
198
+ @ColumnAccessControl({
199
+ create: [],
200
+ read: [],
201
+ update: [],
202
+ })
203
+ @TableColumn({
204
+ required: false,
205
+ type: TableColumnType.ShortText,
206
+ title: "OneUptime Version",
207
+ description:
208
+ "OneUptime version this instance last reported, for example 11.5.13. Lets support see which instances are running an outdated build.",
209
+ })
210
+ @Column({
211
+ nullable: true,
212
+ type: ColumnType.ShortText,
213
+ length: ColumnLength.ShortText,
214
+ })
215
+ public oneuptimeVersion?: string = undefined;
191
216
  }
@@ -824,6 +824,25 @@ export default class GlobalConfig extends GlobalConfigModel {
824
824
  })
825
825
  public enterpriseLicenseToken?: string = undefined;
826
826
 
827
+ @ColumnAccessControl({
828
+ create: [],
829
+ read: [],
830
+ update: [],
831
+ })
832
+ @TableColumn({
833
+ type: TableColumnType.Boolean,
834
+ title: "Enterprise License Is Evaluation",
835
+ description:
836
+ "True when the validated enterprise license is an evaluation/testing license. Drives the evaluation notice shown in the edition modal.",
837
+ defaultValue: false,
838
+ })
839
+ @Column({
840
+ type: ColumnType.Boolean,
841
+ nullable: true,
842
+ default: false,
843
+ })
844
+ public enterpriseLicenseIsEvaluation?: boolean = undefined;
845
+
827
846
  @ColumnAccessControl({
828
847
  create: [],
829
848
  read: [],
@@ -929,6 +948,63 @@ export default class GlobalConfig extends GlobalConfigModel {
929
948
  })
930
949
  public enterpriseLicenseExpiryReminderDays?: number = undefined;
931
950
 
951
+ /*
952
+ * The four latestRelease* columns cache the daily GitHub release check
953
+ * (Workers/Jobs/InstanceUpdate/CheckForNewVersion). They are a cache, not
954
+ * configuration: an air-gapped installation simply leaves them null and no
955
+ * update prompt is ever shown.
956
+ */
957
+ @ColumnAccessControl({
958
+ create: [],
959
+ read: [],
960
+ update: [],
961
+ })
962
+ @TableColumn({
963
+ type: TableColumnType.ShortText,
964
+ title: "Latest Release Version",
965
+ description:
966
+ "Latest OneUptime version published on GitHub, for example 11.6.0, as of the last update check. Null until the first successful check.",
967
+ })
968
+ @Column({
969
+ type: ColumnType.ShortText,
970
+ length: ColumnLength.ShortText,
971
+ nullable: true,
972
+ })
973
+ public latestReleaseVersion?: string = undefined;
974
+
975
+ @ColumnAccessControl({
976
+ create: [],
977
+ read: [],
978
+ update: [],
979
+ })
980
+ @TableColumn({
981
+ type: TableColumnType.Date,
982
+ title: "Latest Release Published At",
983
+ description: "When the latest OneUptime version was published on GitHub.",
984
+ })
985
+ @Column({
986
+ type: ColumnType.Date,
987
+ nullable: true,
988
+ })
989
+ public latestReleasePublishedAt?: Date = undefined;
990
+
991
+ @ColumnAccessControl({
992
+ create: [],
993
+ read: [],
994
+ update: [],
995
+ })
996
+ @TableColumn({
997
+ type: TableColumnType.Date,
998
+ title: "Latest Release Checked At",
999
+ description:
1000
+ "When this installation last successfully checked GitHub for a newer OneUptime release.",
1001
+ })
1002
+ @Column({
1003
+ type: ColumnType.Date,
1004
+ nullable: true,
1005
+ })
1006
+ public latestReleaseCheckedAt?: Date = undefined;
1007
+
932
1008
  @ColumnAccessControl({
933
1009
  create: [],
934
1010
  read: [],
@@ -5,6 +5,7 @@ import { JSONObject } from "../../Types/JSON";
5
5
  import PartialEntity from "../../Types/Database/PartialEntity";
6
6
  import EnterpriseLicenseInstanceSummary from "../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
7
7
  import EnterpriseLicenseUsageUtil from "../../Utils/EnterpriseLicense/EnterpriseLicenseUsage";
8
+ import VersionUtil from "../../Utils/VersionUtil";
8
9
  import LIMIT_MAX from "../../Types/Database/LimitMax";
9
10
  import ObjectID from "../../Types/ObjectID";
10
11
  import PositiveNumber from "../../Types/PositiveNumber";
@@ -29,6 +30,8 @@ export interface LicenseInstanceUpsert {
29
30
  licenseId: ObjectID;
30
31
  instanceId: string;
31
32
  host: string | undefined;
33
+ // OneUptime version the instance is running. Absent on instances too old to report it.
34
+ oneuptimeVersion?: string | null | undefined;
32
35
  // Usage fields are only set on report-user-count, not on validate.
33
36
  userCount?: number | undefined;
34
37
  userEmailHashes?: Array<string> | undefined;
@@ -84,6 +87,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
84
87
  userLimit: true,
85
88
  currentUserCount: true,
86
89
  userCountUpdatedAt: true,
90
+ isEvaluationLicense: true,
87
91
  },
88
92
  props: {
89
93
  isRoot: true,
@@ -117,12 +121,16 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
117
121
  req.body["instanceId"],
118
122
  );
119
123
  const instanceHost: string = this.parseShortText(req.body["host"]);
124
+ const instanceVersion: string | null | undefined = this.parseVersion(
125
+ req.body["version"],
126
+ );
120
127
 
121
128
  if (instanceId) {
122
129
  await this.upsertLicenseInstance({
123
130
  licenseId: license.id!,
124
131
  instanceId: instanceId,
125
132
  host: instanceHost || undefined,
133
+ oneuptimeVersion: instanceVersion,
126
134
  });
127
135
  }
128
136
 
@@ -154,6 +162,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
154
162
  userCountUpdatedAt: license.userCountUpdatedAt
155
163
  ? license.userCountUpdatedAt.toISOString()
156
164
  : null,
165
+ isEvaluationLicense: Boolean(license.isEvaluationLicense),
157
166
  instances: this.getInstanceSummaries(instances),
158
167
  token,
159
168
  });
@@ -196,6 +205,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
196
205
  select: {
197
206
  _id: true,
198
207
  userLimit: true,
208
+ isEvaluationLicense: true,
199
209
  },
200
210
  props: {
201
211
  isRoot: true,
@@ -212,6 +222,9 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
212
222
  req.body["instanceId"],
213
223
  );
214
224
  const instanceHost: string = this.parseShortText(req.body["host"]);
225
+ const instanceVersion: string | null | undefined = this.parseVersion(
226
+ req.body["version"],
227
+ );
215
228
  const userEmailHashes: Array<string> =
216
229
  EnterpriseLicenseUsageUtil.sanitizeUserEmailHashes(
217
230
  req.body["userEmailHashes"],
@@ -230,6 +243,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
230
243
  licenseId: license.id!,
231
244
  instanceId: instanceId,
232
245
  host: instanceHost || undefined,
246
+ oneuptimeVersion: instanceVersion,
233
247
  userCount: userCount,
234
248
  userEmailHashes: userEmailHashes,
235
249
  masterAdminEmails: masterAdminEmails,
@@ -273,6 +287,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
273
287
  userCountUpdatedAt: reportedAt.toISOString(),
274
288
  userLimit:
275
289
  typeof license.userLimit === "number" ? license.userLimit : null,
290
+ isEvaluationLicense: Boolean(license.isEvaluationLicense),
276
291
  instances: this.getInstanceSummaries(instances),
277
292
  });
278
293
  } catch (err) {
@@ -290,6 +305,31 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
290
305
  return value.trim().substring(0, 100);
291
306
  }
292
307
 
308
+ /*
309
+ * report-user-count is unauthenticated, so the reported version is only
310
+ * stored when it is a version we could actually compare against a release —
311
+ * garbage never reaches the admin dashboard or the customer's modal.
312
+ *
313
+ * Three outcomes, and the callers depend on the distinction:
314
+ * undefined — the instance sent no version at all (it predates this
315
+ * field). Leave whatever is stored alone.
316
+ * null — it sent something, but not a version. Clear the stored
317
+ * value, so an instance rolled back to a build with no
318
+ * APP_VERSION stops advertising the version it used to run.
319
+ * string — a valid, canonical version to store.
320
+ */
321
+ private parseVersion(value: unknown): string | null | undefined {
322
+ if (value === undefined || value === null) {
323
+ return undefined;
324
+ }
325
+
326
+ /*
327
+ * Canonicalized rather than passed through, so the stored string is never
328
+ * something the UIs would render with a doubled "v".
329
+ */
330
+ return VersionUtil.canonicalize(value);
331
+ }
332
+
293
333
  private async findLicenseInstances(
294
334
  licenseId: ObjectID,
295
335
  ): Promise<Array<EnterpriseLicenseInstance>> {
@@ -304,6 +344,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
304
344
  userCount: true,
305
345
  userEmailHashes: true,
306
346
  lastReportedAt: true,
347
+ oneuptimeVersion: true,
307
348
  },
308
349
  sort: {
309
350
  createdAt: SortOrder.Ascending,
@@ -331,6 +372,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
331
372
  lastReportedAt: instance.lastReportedAt
332
373
  ? instance.lastReportedAt.toISOString()
333
374
  : null,
375
+ version: instance.oneuptimeVersion || null,
334
376
  };
335
377
  },
336
378
  );
@@ -370,6 +412,11 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
370
412
  newInstance.host = data.host;
371
413
  }
372
414
 
415
+ // Null means "reported something that is not a version" — nothing to store.
416
+ if (data.oneuptimeVersion) {
417
+ newInstance.oneuptimeVersion = data.oneuptimeVersion;
418
+ }
419
+
373
420
  if (data.userCount !== undefined) {
374
421
  newInstance.userCount = data.userCount;
375
422
  }
@@ -434,6 +481,16 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
434
481
  updateData.host = data.host;
435
482
  }
436
483
 
484
+ /*
485
+ * Null clears the column: an instance rebuilt onto a build with no
486
+ * APP_VERSION must stop advertising the version it used to run, because
487
+ * lastReportedAt is refreshed on the same request and would otherwise
488
+ * read as "confirmed running v11.5.13 as of today".
489
+ */
490
+ if (data.oneuptimeVersion !== undefined) {
491
+ updateData.oneuptimeVersion = data.oneuptimeVersion;
492
+ }
493
+
437
494
  if (data.userCount !== undefined) {
438
495
  updateData.userCount = data.userCount;
439
496
  }
@@ -17,8 +17,14 @@ import API from "../../Utils/API";
17
17
  import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse";
18
18
  import HTTPResponse from "../../Types/API/HTTPResponse";
19
19
  import PartialEntity from "../../Types/Database/PartialEntity";
20
- import { EnterpriseLicenseValidationUrl, Host } from "../EnvironmentConfig";
20
+ import {
21
+ AppVersion,
22
+ DisableUpdateCheck,
23
+ EnterpriseLicenseValidationUrl,
24
+ Host,
25
+ } from "../EnvironmentConfig";
21
26
  import EnterpriseLicenseInstanceSummary from "../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
27
+ import VersionUtil from "../../Utils/VersionUtil";
22
28
  import UserMiddleware from "../Middleware/UserAuthorization";
23
29
 
24
30
  export default class GlobalConfigAPI extends BaseAPI<
@@ -67,11 +73,15 @@ export default class GlobalConfigAPI extends BaseAPI<
67
73
  enterpriseLicenseExpiresAt: true,
68
74
  enterpriseLicenseKey: true,
69
75
  enterpriseLicenseToken: true,
76
+ enterpriseLicenseIsEvaluation: true,
70
77
  enterpriseLicenseUserLimit: true,
71
78
  enterpriseLicenseCurrentUserCount: true,
72
79
  enterpriseLicenseUserCountUpdatedAt: true,
73
80
  enterpriseLicenseInstances: true,
74
81
  instanceId: true,
82
+ latestReleaseVersion: true,
83
+ latestReleasePublishedAt: true,
84
+ latestReleaseCheckedAt: true,
75
85
  },
76
86
  props: {
77
87
  isRoot: true,
@@ -106,6 +116,12 @@ export default class GlobalConfigAPI extends BaseAPI<
106
116
  ? config?.enterpriseLicenseToken || null
107
117
  : null,
108
118
  licenseValid: licenseValid,
119
+ /*
120
+ * Whether this is an evaluation/testing license. Benign like the
121
+ * company name and expiry, so it is not gated behind sign-in — the
122
+ * edition modal shows the evaluation notice to anyone who opens it.
123
+ */
124
+ isEvaluationLicense: Boolean(config?.enterpriseLicenseIsEvaluation),
109
125
  userLimit:
110
126
  typeof config?.enterpriseLicenseUserLimit === "number"
111
127
  ? config.enterpriseLicenseUserLimit
@@ -126,6 +142,38 @@ export default class GlobalConfigAPI extends BaseAPI<
126
142
  isAuthenticatedUser && config?.instanceId
127
143
  ? config.instanceId.toString()
128
144
  : null,
145
+ /*
146
+ * Which build this installation runs, and whether a newer one has
147
+ * been released, are gated the same way as the instance topology:
148
+ * telling an anonymous visitor on the login page that this server
149
+ * is behind on patches advertises an unpatched target.
150
+ */
151
+ currentVersion: isAuthenticatedUser ? AppVersion : null,
152
+ latestVersion: isAuthenticatedUser
153
+ ? config?.latestReleaseVersion || null
154
+ : null,
155
+ latestVersionPublishedAt:
156
+ isAuthenticatedUser && config?.latestReleasePublishedAt
157
+ ? config.latestReleasePublishedAt.toISOString()
158
+ : null,
159
+ latestVersionCheckedAt:
160
+ isAuthenticatedUser && config?.latestReleaseCheckedAt
161
+ ? config.latestReleaseCheckedAt.toISOString()
162
+ : null,
163
+ isUpdateAvailable: isAuthenticatedUser
164
+ ? VersionUtil.isUpdateAvailable({
165
+ currentVersion: AppVersion,
166
+ latestVersion: config?.latestReleaseVersion,
167
+ })
168
+ : false,
169
+ /*
170
+ * Lets the modal say "update checks are off" instead of "has not
171
+ * checked yet", which would be a promise the installation is never
172
+ * going to keep.
173
+ */
174
+ isUpdateCheckDisabled: isAuthenticatedUser
175
+ ? DisableUpdateCheck
176
+ : false,
129
177
  };
130
178
 
131
179
  return Response.sendJsonObjectResponse(req, res, responseBody);
@@ -179,6 +227,12 @@ export default class GlobalConfigAPI extends BaseAPI<
179
227
  licenseKey,
180
228
  instanceId: instanceId.toString(),
181
229
  host: Host,
230
+ /*
231
+ * Sent here as well as from the daily report job so the version
232
+ * lands on the license server the moment the key is validated,
233
+ * rather than up to 24 hours later.
234
+ */
235
+ version: AppVersion,
182
236
  },
183
237
  });
184
238
 
@@ -239,6 +293,9 @@ export default class GlobalConfigAPI extends BaseAPI<
239
293
  }
240
294
  }
241
295
 
296
+ const isEvaluationLicense: boolean =
297
+ payload["isEvaluationLicense"] === true;
298
+
242
299
  const instances: Array<EnterpriseLicenseInstanceSummary> =
243
300
  Array.isArray(payload["instances"])
244
301
  ? (payload[
@@ -251,6 +308,7 @@ export default class GlobalConfigAPI extends BaseAPI<
251
308
  enterpriseLicenseKey: licenseKeyRaw || null,
252
309
  enterpriseLicenseExpiresAt: licenseExpiry || null,
253
310
  enterpriseLicenseToken: licenseToken || null,
311
+ enterpriseLicenseIsEvaluation: isEvaluationLicense,
254
312
  enterpriseLicenseUserLimit: userLimit,
255
313
  enterpriseLicenseCurrentUserCount: currentUserCount,
256
314
  enterpriseLicenseUserCountUpdatedAt: userCountUpdatedAt,
@@ -287,6 +345,8 @@ export default class GlobalConfigAPI extends BaseAPI<
287
345
  newConfig.enterpriseLicenseToken = licenseToken;
288
346
  }
289
347
 
348
+ newConfig.enterpriseLicenseIsEvaluation = isEvaluationLicense;
349
+
290
350
  if (licenseExpiry) {
291
351
  newConfig.enterpriseLicenseExpiresAt = licenseExpiry;
292
352
  }
@@ -321,6 +381,7 @@ export default class GlobalConfigAPI extends BaseAPI<
321
381
  expiresAt: licenseExpiry ? licenseExpiry.toISOString() : null,
322
382
  licenseKey: licenseKeyRaw || null,
323
383
  token: licenseToken || null,
384
+ isEvaluationLicense: isEvaluationLicense,
324
385
  userLimit: userLimit,
325
386
  currentUserCount: currentUserCount,
326
387
  userCountUpdatedAt: userCountUpdatedAt
@@ -665,6 +665,14 @@ export const IpWhitelist: string = process.env["IP_WHITELIST"] || "";
665
665
  export const DisableTelemetry: boolean =
666
666
  process.env["DISABLE_TELEMETRY"] === "true";
667
667
 
668
+ /*
669
+ * Opt out of the daily "is a newer OneUptime released?" check against the
670
+ * GitHub API. Deliberately separate from DISABLE_TELEMETRY, which turns off
671
+ * the OpenTelemetry SDK and says nothing about outbound calls.
672
+ */
673
+ export const DisableUpdateCheck: boolean =
674
+ process.env["DISABLE_UPDATE_CHECK"] === "true";
675
+
668
676
  export const EnableProfiling: boolean =
669
677
  process.env["ENABLE_PROFILING"] === "true";
670
678
 
@@ -782,6 +790,20 @@ export const EnterpriseLicenseUserCountReportUrl: URL = URL.fromString(
782
790
  "https://oneuptime.com/api/enterprise-license/report-user-count",
783
791
  );
784
792
 
793
+ /*
794
+ * GitHub's "latest release" endpoint. It already excludes drafts and
795
+ * prereleases, so whatever it returns is a version an administrator can
796
+ * safely be told to upgrade to.
797
+ *
798
+ * Overridable so an installation with no route to github.com can point the
799
+ * check at an internal mirror instead of turning it off entirely. The mirror
800
+ * must answer with GitHub's release shape (tag_name, html_url, published_at).
801
+ */
802
+ export const LatestReleaseCheckUrl: URL = URL.fromString(
803
+ process.env["LATEST_RELEASE_CHECK_URL"] ||
804
+ "https://api.github.com/repos/OneUptime/oneuptime/releases/latest",
805
+ );
806
+
785
807
  // Inbound Email Configuration for Incoming Email Monitor
786
808
  export enum InboundEmailProviderType {
787
809
  SendGrid = "SendGrid",
@@ -0,0 +1,38 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddInstanceVersionAndLatestReleaseColumns1784659816363
4
+ implements MigrationInterface
5
+ {
6
+ public name: string =
7
+ "AddInstanceVersionAndLatestReleaseColumns1784659816363";
8
+
9
+ public async up(queryRunner: QueryRunner): Promise<void> {
10
+ await queryRunner.query(
11
+ `ALTER TABLE "EnterpriseLicenseInstance" ADD "oneuptimeVersion" character varying(100)`,
12
+ );
13
+ await queryRunner.query(
14
+ `ALTER TABLE "GlobalConfig" ADD "latestReleaseVersion" character varying(100)`,
15
+ );
16
+ await queryRunner.query(
17
+ `ALTER TABLE "GlobalConfig" ADD "latestReleasePublishedAt" TIMESTAMP WITH TIME ZONE`,
18
+ );
19
+ await queryRunner.query(
20
+ `ALTER TABLE "GlobalConfig" ADD "latestReleaseCheckedAt" TIMESTAMP WITH TIME ZONE`,
21
+ );
22
+ }
23
+
24
+ public async down(queryRunner: QueryRunner): Promise<void> {
25
+ await queryRunner.query(
26
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "latestReleaseCheckedAt"`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "latestReleasePublishedAt"`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "latestReleaseVersion"`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "EnterpriseLicenseInstance" DROP COLUMN "oneuptimeVersion"`,
36
+ );
37
+ }
38
+ }
@@ -0,0 +1,25 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddEnterpriseLicenseEvaluationColumns1784705487674
4
+ implements MigrationInterface
5
+ {
6
+ public name: string = "AddEnterpriseLicenseEvaluationColumns1784705487674";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "EnterpriseLicense" ADD "isEvaluationLicense" boolean NOT NULL DEFAULT false`,
11
+ );
12
+ await queryRunner.query(
13
+ `ALTER TABLE "GlobalConfig" ADD "enterpriseLicenseIsEvaluation" boolean DEFAULT false`,
14
+ );
15
+ }
16
+
17
+ public async down(queryRunner: QueryRunner): Promise<void> {
18
+ await queryRunner.query(
19
+ `ALTER TABLE "GlobalConfig" DROP COLUMN "enterpriseLicenseIsEvaluation"`,
20
+ );
21
+ await queryRunner.query(
22
+ `ALTER TABLE "EnterpriseLicense" DROP COLUMN "isEvaluationLicense"`,
23
+ );
24
+ }
25
+ }
@@ -462,6 +462,8 @@ import { AddEnterpriseLicenseNotificationColumns1784218257664 } from "./17842182
462
462
  import { AddAttributionColumnsToUserAndProject1784293516000 } from "./1784293516000-AddAttributionColumnsToUserAndProject";
463
463
  import { AddMarketingConversionTable1784298000000 } from "./1784298000000-AddMarketingConversionTable";
464
464
  import { AddExceptionTriageAndPrivacyColumns1784640000000 } from "./1784640000000-AddExceptionTriageAndPrivacyColumns";
465
+ import { AddInstanceVersionAndLatestReleaseColumns1784659816363 } from "./1784659816363-AddInstanceVersionAndLatestReleaseColumns";
466
+ import { AddEnterpriseLicenseEvaluationColumns1784705487674 } from "./1784705487674-AddEnterpriseLicenseEvaluationColumns";
465
467
 
466
468
  export default [
467
469
  InitialMigration,
@@ -928,4 +930,6 @@ export default [
928
930
  AddAttributionColumnsToUserAndProject1784293516000,
929
931
  AddMarketingConversionTable1784298000000,
930
932
  AddExceptionTriageAndPrivacyColumns1784640000000,
933
+ AddInstanceVersionAndLatestReleaseColumns1784659816363,
934
+ AddEnterpriseLicenseEvaluationColumns1784705487674,
931
935
  ];