@oneuptime/common 11.0.4 → 11.0.6

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 (72) hide show
  1. package/Server/Infrastructure/GlobalCache.ts +11 -0
  2. package/Server/Middleware/UserAuthorization.ts +94 -20
  3. package/Server/Services/CephClusterService.ts +22 -10
  4. package/Server/Services/CloudResourceService.ts +22 -10
  5. package/Server/Services/DockerHostService.ts +22 -10
  6. package/Server/Services/DockerSwarmClusterService.ts +22 -10
  7. package/Server/Services/HostService.ts +22 -10
  8. package/Server/Services/KubernetesClusterService.ts +22 -10
  9. package/Server/Services/PodmanHostService.ts +22 -10
  10. package/Server/Services/ProxmoxClusterService.ts +22 -10
  11. package/Server/Services/RumApplicationService.ts +22 -10
  12. package/Server/Services/ServerlessFunctionService.ts +22 -10
  13. package/Server/Utils/Cookie.ts +61 -0
  14. package/Tests/Server/Infrastructure/GlobalCache.test.ts +43 -0
  15. package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +151 -0
  16. package/Types/CookieName.ts +1 -0
  17. package/UI/Components/Charts/Area/AreaChart.tsx +4 -8
  18. package/UI/Components/Charts/Bar/BarChart.tsx +4 -8
  19. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +4 -8
  20. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +1 -1
  21. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +1 -1
  22. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +1 -1
  23. package/UI/Components/Charts/Line/LineChart.tsx +4 -8
  24. package/UI/Components/EditionLabel/EditionLabel.tsx +68 -16
  25. package/UI/Components/Modal/Modal.tsx +4 -1
  26. package/build/dist/Server/Infrastructure/GlobalCache.js +13 -0
  27. package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
  28. package/build/dist/Server/Middleware/UserAuthorization.js +72 -16
  29. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  30. package/build/dist/Server/Services/CephClusterService.js +19 -2
  31. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  32. package/build/dist/Server/Services/CloudResourceService.js +19 -2
  33. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  34. package/build/dist/Server/Services/DockerHostService.js +19 -2
  35. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  36. package/build/dist/Server/Services/DockerSwarmClusterService.js +19 -2
  37. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  38. package/build/dist/Server/Services/HostService.js +19 -2
  39. package/build/dist/Server/Services/HostService.js.map +1 -1
  40. package/build/dist/Server/Services/KubernetesClusterService.js +19 -2
  41. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  42. package/build/dist/Server/Services/PodmanHostService.js +19 -2
  43. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  44. package/build/dist/Server/Services/ProxmoxClusterService.js +19 -2
  45. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  46. package/build/dist/Server/Services/RumApplicationService.js +19 -2
  47. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  48. package/build/dist/Server/Services/ServerlessFunctionService.js +19 -2
  49. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  50. package/build/dist/Server/Utils/Cookie.js +61 -0
  51. package/build/dist/Server/Utils/Cookie.js.map +1 -1
  52. package/build/dist/Types/CookieName.js +1 -0
  53. package/build/dist/Types/CookieName.js.map +1 -1
  54. package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -6
  55. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  56. package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -6
  57. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  58. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +8 -8
  59. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  60. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +1 -1
  61. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  62. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +1 -1
  63. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  64. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +1 -1
  65. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  66. package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -6
  67. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  68. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +31 -5
  69. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  70. package/build/dist/UI/Components/Modal/Modal.js +1 -1
  71. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  72. package/package.json +1 -1
@@ -98,6 +98,57 @@ export default class CookieUtil {
98
98
  });
99
99
  }
100
100
 
101
+ /*
102
+ * Builds a Global SSO token. Unlike the per-project token, this is NOT bound
103
+ * to a single project — a Global SSO/OIDC login proves the user authenticated
104
+ * against the instance-wide IdP, so one token satisfies SSO enforcement for
105
+ * every project the user belongs to (including projects created after login).
106
+ * It carries the `ssoProviderId` / `ssoProviderType` discriminator so a
107
+ * project that pins a specific provider can still be enforced.
108
+ */
109
+ @CaptureSpan()
110
+ public static getGlobalSSOToken(data: {
111
+ user: User;
112
+ ssoProviderId: ObjectID;
113
+ ssoProviderType: SsoProviderType;
114
+ }): string {
115
+ const { user } = data;
116
+
117
+ return JSONWebToken.sign({
118
+ data: {
119
+ userId: user.id!,
120
+ name: user.name!,
121
+ email: user.email,
122
+ isMasterAdmin: false,
123
+ isGeneralLogin: false,
124
+ ssoProviderId: data.ssoProviderId.toString(),
125
+ ssoProviderType: data.ssoProviderType.toString(),
126
+ },
127
+ expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)),
128
+ });
129
+ }
130
+
131
+ @CaptureSpan()
132
+ public static setGlobalSSOCookie(data: {
133
+ user: User;
134
+ expressResponse: ExpressResponse;
135
+ ssoProviderId: ObjectID;
136
+ ssoProviderType: SsoProviderType;
137
+ }): void {
138
+ const { expressResponse: res } = data;
139
+
140
+ const globalSsoToken: string = CookieUtil.getGlobalSSOToken({
141
+ user: data.user,
142
+ ssoProviderId: data.ssoProviderId,
143
+ ssoProviderType: data.ssoProviderType,
144
+ });
145
+
146
+ CookieUtil.setCookie(res, CookieUtil.getGlobalSSOKey(), globalSsoToken, {
147
+ maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
148
+ httpOnly: true,
149
+ });
150
+ }
151
+
101
152
  @CaptureSpan()
102
153
  public static setUserCookie(data: {
103
154
  expressResponse: ExpressResponse;
@@ -444,6 +495,16 @@ export default class CookieUtil {
444
495
  return `sso-`;
445
496
  }
446
497
 
498
+ /*
499
+ * Fixed cookie name for the single Global SSO token. Deliberately does NOT
500
+ * start with the per-project `sso-` prefix so the project-scoped cookie
501
+ * parser in UserMiddleware.getSsoTokens never mis-keys it.
502
+ */
503
+ @CaptureSpan()
504
+ public static getGlobalSSOKey(): string {
505
+ return CookieName.GlobalSSOToken;
506
+ }
507
+
447
508
  // delete all cookies.
448
509
  @CaptureSpan()
449
510
  public static removeAllCookies(
@@ -17,6 +17,7 @@ type MockClient = {
17
17
  set: jest.Mock;
18
18
  expire: jest.Mock;
19
19
  get: jest.Mock;
20
+ del: jest.Mock;
20
21
  };
21
22
 
22
23
  describe("GlobalCache.setString", () => {
@@ -27,6 +28,7 @@ describe("GlobalCache.setString", () => {
27
28
  set: jest.fn().mockResolvedValue("OK"),
28
29
  expire: jest.fn().mockResolvedValue(1),
29
30
  get: jest.fn(),
31
+ del: jest.fn().mockResolvedValue(1),
30
32
  };
31
33
  (Redis.getClient as jest.Mock).mockReturnValue(client);
32
34
  (Redis.isConnected as jest.Mock).mockReturnValue(true);
@@ -98,3 +100,44 @@ describe("GlobalCache.setString", () => {
98
100
  expect(client.expire).not.toHaveBeenCalled();
99
101
  });
100
102
  });
103
+
104
+ describe("GlobalCache.deleteKey", () => {
105
+ let client: MockClient;
106
+
107
+ beforeEach(() => {
108
+ client = {
109
+ set: jest.fn().mockResolvedValue("OK"),
110
+ expire: jest.fn().mockResolvedValue(1),
111
+ get: jest.fn(),
112
+ del: jest.fn().mockResolvedValue(1),
113
+ };
114
+ (Redis.getClient as jest.Mock).mockReturnValue(client);
115
+ (Redis.isConnected as jest.Mock).mockReturnValue(true);
116
+ });
117
+
118
+ afterEach(() => {
119
+ jest.clearAllMocks();
120
+ });
121
+
122
+ /*
123
+ * deleteKey backs clearMaintenanceFence (OtelIngestBaseService): when
124
+ * the fenced maintenance work (updateLastSeen) fails, the fence is
125
+ * released so the next ingest batch retries instead of leaving the
126
+ * resource stranded as "disconnected" for the whole TTL window.
127
+ */
128
+ test("deletes the namespaced key", async () => {
129
+ await GlobalCache.deleteKey("ns", "key");
130
+
131
+ expect(client.del).toHaveBeenCalledTimes(1);
132
+ expect(client.del).toHaveBeenCalledWith("ns-key");
133
+ });
134
+
135
+ test("throws when the cache is not connected", async () => {
136
+ (Redis.isConnected as jest.Mock).mockReturnValue(false);
137
+
138
+ await expect(GlobalCache.deleteKey("ns", "key")).rejects.toThrow(
139
+ DatabaseNotConnectedException,
140
+ );
141
+ expect(client.del).not.toHaveBeenCalled();
142
+ });
143
+ });
@@ -161,3 +161,154 @@ describe("UserMiddleware.doesSsoTokenForProjectExist - requiredSsoProviderId", (
161
161
  ).toBe(false);
162
162
  });
163
163
  });
164
+
165
+ /*
166
+ * A single Global SSO token (minted by a Global SSO/OIDC login) is NOT bound to
167
+ * a project: it satisfies SSO enforcement for ANY project the user belongs to,
168
+ * including projects created after login. It still carries the provider
169
+ * discriminator so a project pinned to a specific provider is only satisfied by
170
+ * a matching token. The token is sourced from the `global-sso-token` cookie
171
+ * (web) or the `x-global-sso-token` header (mobile).
172
+ */
173
+ describe("UserMiddleware.doesSsoTokenForProjectExist - global SSO token", () => {
174
+ const projectId: ObjectID = ObjectID.generate();
175
+ const otherProjectId: ObjectID = ObjectID.generate();
176
+ const userId: ObjectID = ObjectID.generate();
177
+ const globalProviderId: ObjectID = ObjectID.generate();
178
+ const otherProviderId: ObjectID = ObjectID.generate();
179
+
180
+ const buildUser: () => User = (): User => {
181
+ const u: User = new User();
182
+ u.id = userId;
183
+ u.email = new Email("global-sso-user@oneuptime.com");
184
+ return u;
185
+ };
186
+
187
+ const buildRequestWithGlobalToken: (data: {
188
+ providerType: SsoProviderType;
189
+ providerId?: ObjectID;
190
+ viaHeader?: boolean;
191
+ }) => ExpressRequest = (data: {
192
+ providerType: SsoProviderType;
193
+ providerId?: ObjectID;
194
+ viaHeader?: boolean;
195
+ }): ExpressRequest => {
196
+ const token: string = CookieUtil.getGlobalSSOToken({
197
+ user: buildUser(),
198
+ ssoProviderId: data.providerId ?? globalProviderId,
199
+ ssoProviderType: data.providerType,
200
+ });
201
+
202
+ if (data.viaHeader) {
203
+ return {
204
+ cookies: {},
205
+ headers: { "x-global-sso-token": token },
206
+ } as unknown as ExpressRequest;
207
+ }
208
+
209
+ return {
210
+ cookies: { [CookieUtil.getGlobalSSOKey()]: token },
211
+ headers: {},
212
+ } as unknown as ExpressRequest;
213
+ };
214
+
215
+ test("global token (cookie), NO requiredProviderId -> true", () => {
216
+ const req: ExpressRequest = buildRequestWithGlobalToken({
217
+ providerType: SsoProviderType.GlobalSSO,
218
+ });
219
+
220
+ expect(
221
+ UserMiddleware.doesSsoTokenForProjectExist(req, projectId, userId),
222
+ ).toBe(true);
223
+ });
224
+
225
+ test("global token satisfies a project with NO per-project token (mid-session/new project) -> true", () => {
226
+ const req: ExpressRequest = buildRequestWithGlobalToken({
227
+ providerType: SsoProviderType.GlobalSSO,
228
+ });
229
+
230
+ // A different project the token was never minted for is still satisfied.
231
+ expect(
232
+ UserMiddleware.doesSsoTokenForProjectExist(req, otherProjectId, userId),
233
+ ).toBe(true);
234
+ });
235
+
236
+ test("global token, requiredProviderId EQUALS token's provider -> true", () => {
237
+ const req: ExpressRequest = buildRequestWithGlobalToken({
238
+ providerType: SsoProviderType.GlobalOIDC,
239
+ providerId: globalProviderId,
240
+ });
241
+
242
+ expect(
243
+ UserMiddleware.doesSsoTokenForProjectExist(
244
+ req,
245
+ projectId,
246
+ userId,
247
+ globalProviderId,
248
+ ),
249
+ ).toBe(true);
250
+ });
251
+
252
+ test("global token, requiredProviderId DIFFERENT from token's provider -> false", () => {
253
+ const req: ExpressRequest = buildRequestWithGlobalToken({
254
+ providerType: SsoProviderType.GlobalSSO,
255
+ providerId: globalProviderId,
256
+ });
257
+
258
+ expect(
259
+ UserMiddleware.doesSsoTokenForProjectExist(
260
+ req,
261
+ projectId,
262
+ userId,
263
+ otherProviderId,
264
+ ),
265
+ ).toBe(false);
266
+ });
267
+
268
+ test("global token, wrong userId -> false", () => {
269
+ const req: ExpressRequest = buildRequestWithGlobalToken({
270
+ providerType: SsoProviderType.GlobalSSO,
271
+ });
272
+
273
+ const differentUserId: ObjectID = ObjectID.generate();
274
+
275
+ expect(
276
+ UserMiddleware.doesSsoTokenForProjectExist(
277
+ req,
278
+ projectId,
279
+ differentUserId,
280
+ ),
281
+ ).toBe(false);
282
+ });
283
+
284
+ test("global token via x-global-sso-token header (mobile) -> true", () => {
285
+ const req: ExpressRequest = buildRequestWithGlobalToken({
286
+ providerType: SsoProviderType.GlobalSSO,
287
+ viaHeader: true,
288
+ });
289
+
290
+ expect(
291
+ UserMiddleware.doesSsoTokenForProjectExist(req, projectId, userId),
292
+ ).toBe(true);
293
+ });
294
+
295
+ test("project-typed token in the global cookie slot is ignored -> false", () => {
296
+ // Only Global provider types are accepted via the global-token path.
297
+ const token: string = CookieUtil.getSSOToken({
298
+ user: buildUser(),
299
+ projectId: projectId,
300
+ ssoProviderId: globalProviderId,
301
+ ssoProviderType: SsoProviderType.ProjectSSO,
302
+ });
303
+
304
+ const req: ExpressRequest = {
305
+ cookies: { [CookieUtil.getGlobalSSOKey()]: token },
306
+ headers: {},
307
+ } as unknown as ExpressRequest;
308
+
309
+ // No per-project cookie (sso-<projectId>) is present, so this must fail.
310
+ expect(
311
+ UserMiddleware.doesSsoTokenForProjectExist(req, otherProjectId, userId),
312
+ ).toBe(false);
313
+ });
314
+ });
@@ -3,6 +3,7 @@ enum CookieName {
3
3
  Email = "user-email",
4
4
  Token = "user-token",
5
5
  RefreshToken = "user-refresh-token",
6
+ GlobalSSOToken = "global-sso-token",
6
7
  Name = "user-name",
7
8
  Timezone = "user-timezone",
8
9
  IsMasterAdmin = "user-is-master-admin",
@@ -108,11 +108,6 @@ const AreaChartElement: FunctionComponent<AreaInternalProps> = (
108
108
  });
109
109
  }, [props.exemplarPoints, props.xAxis]);
110
110
 
111
- const className: string = props.heightInPx ? `` : "h-80";
112
- const style: React.CSSProperties = props.heightInPx
113
- ? { height: `${props.heightInPx}px` }
114
- : {};
115
-
116
111
  const hasNoData: boolean =
117
112
  !props.data ||
118
113
  props.data.length === 0 ||
@@ -134,10 +129,11 @@ const AreaChartElement: FunctionComponent<AreaInternalProps> = (
134
129
  typeof yAxisMaxOption === "number" ? { maxValue: yAxisMaxOption } : {};
135
130
 
136
131
  return (
137
- <div className="relative">
132
+ <div
133
+ className="relative flex flex-1"
134
+ style={props.heightInPx ? { height: `${props.heightInPx}px` } : undefined}
135
+ >
138
136
  <AreaChart
139
- className={className}
140
- style={style}
141
137
  data={records}
142
138
  tickGap={30}
143
139
  index={"Time"}
@@ -54,11 +54,6 @@ const BarChartElement: FunctionComponent<BarInternalProps> = (
54
54
  setRecords(records);
55
55
  }, [props.data]);
56
56
 
57
- const className: string = props.heightInPx ? `` : "h-80";
58
- const style: React.CSSProperties = props.heightInPx
59
- ? { height: `${props.heightInPx}px` }
60
- : {};
61
-
62
57
  const hasNoData: boolean =
63
58
  !props.data ||
64
59
  props.data.length === 0 ||
@@ -67,10 +62,11 @@ const BarChartElement: FunctionComponent<BarInternalProps> = (
67
62
  });
68
63
 
69
64
  return (
70
- <div className="relative">
65
+ <div
66
+ className="relative flex flex-1"
67
+ style={props.heightInPx ? { height: `${props.heightInPx}px` } : undefined}
68
+ >
71
69
  <BarChart
72
- className={className}
73
- style={style}
74
70
  data={records}
75
71
  tickGap={30}
76
72
  index={"Time"}
@@ -49,7 +49,6 @@ export interface Chart {
49
49
  export interface ComponentProps {
50
50
  charts: Array<Chart>;
51
51
  hideCard?: boolean | undefined;
52
- heightInPx?: number | undefined;
53
52
  chartCssClass?: string | undefined;
54
53
  }
55
54
 
@@ -80,7 +79,6 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
80
79
  key={index}
81
80
  {...(chart.props as LineChartProps)}
82
81
  syncid={syncId}
83
- heightInPx={props.heightInPx}
84
82
  exemplarPoints={chart.exemplarPoints}
85
83
  onExemplarClick={chart.onExemplarClick}
86
84
  showLegend={showLegend}
@@ -92,7 +90,6 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
92
90
  key={index}
93
91
  {...(chart.props as BarChartProps)}
94
92
  syncid={syncId}
95
- heightInPx={props.heightInPx}
96
93
  showLegend={showLegend}
97
94
  />
98
95
  );
@@ -102,7 +99,6 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
102
99
  key={index}
103
100
  {...(chart.props as AreaChartProps)}
104
101
  syncid={syncId}
105
- heightInPx={props.heightInPx}
106
102
  exemplarPoints={chart.exemplarPoints}
107
103
  onExemplarClick={chart.onExemplarClick}
108
104
  showLegend={showLegend}
@@ -244,14 +240,14 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
244
240
  return (
245
241
  <>
246
242
  {renderMetricInfoModal()}
247
- <div className="space-y-3">
243
+ <div className="space-y-3 flex-col flex flex-1 w-full">
248
244
  {props.charts.map((chart: Chart, index: number) => {
249
245
  return (
250
246
  <div
251
247
  key={index}
252
- className={`bg-white ${props.chartCssClass || ""}`}
248
+ className={`bg-white ${props.chartCssClass || ""} flex-1 flex-col flex w-full`}
253
249
  >
254
- <div className="px-5 pt-4 pb-4">
250
+ <div className="px-5 pt-4 pb-4 flex flex-col flex-1">
255
251
  <div className="mb-3 pb-3 border-b border-gray-100">
256
252
  <div className="flex items-center">
257
253
  <h3 className="text-sm font-semibold text-gray-800 tracking-tight">
@@ -265,10 +261,10 @@ const ChartGroup: FunctionComponent<ComponentProps> = (
265
261
  </p>
266
262
  )}
267
263
  </div>
264
+ {getChartContent(chart, index)}
268
265
  {chart.seriesControls ? (
269
266
  <div className="mb-3">{chart.seriesControls}</div>
270
267
  ) : null}
271
- {getChartContent(chart, index)}
272
268
  </div>
273
269
  </div>
274
270
  );
@@ -689,7 +689,7 @@ const AreaChart: React.ForwardRefExoticComponent<
689
689
  }
690
690
 
691
691
  return (
692
- <div ref={ref} className={cx("h-80 w-full", className)} {...other}>
692
+ <div ref={ref} className={cx("flex-1 w-full", className)} {...other}>
693
693
  <ResponsiveContainer>
694
694
  <RechartsAreaChart
695
695
  data={data}
@@ -780,7 +780,7 @@ const BarChart: React.ForwardRefExoticComponent<
780
780
  return (
781
781
  <div
782
782
  ref={forwardedRef}
783
- className={cx("h-80 w-full", className)}
783
+ className={cx("flex-1 w-full", className)}
784
784
  data-tremor-id="tremor-raw"
785
785
  {...other}
786
786
  >
@@ -695,7 +695,7 @@ const LineChart: React.ForwardRefExoticComponent<
695
695
  }
696
696
 
697
697
  return (
698
- <div ref={ref} className={cx("h-80 w-full", className)} {...other}>
698
+ <div ref={ref} className={cx("flex-1 w-full", className)} {...other}>
699
699
  <ResponsiveContainer>
700
700
  <RechartsLineChart
701
701
  data={data}
@@ -86,11 +86,6 @@ const LineChartElement: FunctionComponent<LineInternalProps> = (
86
86
  });
87
87
  }, [props.exemplarPoints, props.xAxis]);
88
88
 
89
- const className: string = props.heightInPx ? `` : "h-80";
90
- const style: React.CSSProperties = props.heightInPx
91
- ? { height: `${props.heightInPx}px` }
92
- : {};
93
-
94
89
  const hasNoData: boolean =
95
90
  !props.data ||
96
91
  props.data.length === 0 ||
@@ -114,10 +109,11 @@ const LineChartElement: FunctionComponent<LineInternalProps> = (
114
109
  typeof yAxisMaxOption === "number" ? { maxValue: yAxisMaxOption } : {};
115
110
 
116
111
  return (
117
- <div className="relative">
112
+ <div
113
+ className="relative flex flex-1"
114
+ style={props.heightInPx ? { height: `${props.heightInPx}px` } : undefined}
115
+ >
118
116
  <LineChart
119
- className={className}
120
- style={style}
121
117
  data={records}
122
118
  tickGap={30}
123
119
  index={"Time"}
@@ -45,6 +45,7 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
45
45
  const [validationError, setValidationError] = useState<string>("");
46
46
  const [successMessage, setSuccessMessage] = useState<string>("");
47
47
  const [isValidating, setIsValidating] = useState<boolean>(false);
48
+ const [isChangingLicense, setIsChangingLicense] = useState<boolean>(false);
48
49
  const licenseInputEditedRef: React.MutableRefObject<boolean> =
49
50
  useRef<boolean>(false);
50
51
 
@@ -318,6 +319,22 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
318
319
  setIsDialogOpen(false);
319
320
  setValidationError("");
320
321
  setSuccessMessage("");
322
+ setIsChangingLicense(false);
323
+ };
324
+
325
+ const handleStartChangingLicense: () => void = () => {
326
+ setIsChangingLicense(true);
327
+ setValidationError("");
328
+ setSuccessMessage("");
329
+ setLicenseKeyInput("");
330
+ licenseInputEditedRef.current = true;
331
+ };
332
+
333
+ const handleCancelChangingLicense: () => void = () => {
334
+ setIsChangingLicense(false);
335
+ setValidationError("");
336
+ licenseInputEditedRef.current = false;
337
+ setLicenseKeyInput(globalConfig?.enterpriseLicenseKey || "");
321
338
  };
322
339
 
323
340
  const handlePrimaryAction: () => void = () => {
@@ -371,6 +388,7 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
371
388
  licenseInputEditedRef.current = false;
372
389
  setLicenseKeyInput((payload["licenseKey"] as string) || trimmedKey);
373
390
  setSuccessMessage("License validated successfully.");
391
+ setIsChangingLicense(false);
374
392
 
375
393
  await fetchGlobalConfig();
376
394
  } catch (err) {
@@ -396,8 +414,10 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
396
414
  }
397
415
  };
398
416
 
399
- const shouldShowEnterpriseValidationButton: boolean =
400
- IS_ENTERPRISE_EDITION && !licenseValid;
417
+ const showLicenseKeyInput: boolean =
418
+ IS_ENTERPRISE_EDITION && (!licenseValid || isChangingLicense);
419
+
420
+ const shouldShowEnterpriseValidationButton: boolean = showLicenseKeyInput;
401
421
 
402
422
  const modalSubmitButtonText: string | undefined = IS_ENTERPRISE_EDITION
403
423
  ? shouldShowEnterpriseValidationButton
@@ -611,6 +631,20 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
611
631
  </div>
612
632
  )}
613
633
 
634
+ {!configError &&
635
+ !isConfigLoading &&
636
+ licenseValid &&
637
+ !isChangingLicense && (
638
+ <div className="-ml-3">
639
+ <Button
640
+ title="Change license key"
641
+ icon={IconProp.Edit}
642
+ buttonStyle={ButtonStyleType.NORMAL}
643
+ onClick={handleStartChangingLicense}
644
+ />
645
+ </div>
646
+ )}
647
+
614
648
  {!configError &&
615
649
  !isConfigLoading &&
616
650
  !licenseValid &&
@@ -632,11 +666,13 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
632
666
  <Alert type={AlertType.SUCCESS} title={successMessage} />
633
667
  )}
634
668
 
635
- {!licenseValid && (
669
+ {showLicenseKeyInput && (
636
670
  <>
637
671
  <div>
638
672
  <label className="text-sm font-medium text-gray-700">
639
- License Key
673
+ {isChangingLicense
674
+ ? "New License Key"
675
+ : "License Key"}
640
676
  </label>
641
677
  <Input
642
678
  value={licenseKeyInput}
@@ -656,18 +692,34 @@ const EditionLabel: FunctionComponent<ComponentProps> = (
656
692
  />
657
693
  )}
658
694
 
659
- <p className="text-xs text-gray-500">
660
- You have installed Enterprise Edition of OneUptime.
661
- You need to validate your license key. Need a license
662
- key? Contact our sales team at{" "}
663
- <a
664
- href="mailto:sales@oneuptime.com"
665
- className="font-medium text-indigo-600 hover:text-indigo-700"
666
- >
667
- sales@oneuptime.com
668
- </a>
669
- .
670
- </p>
695
+ {isChangingLicense ? (
696
+ <div className="flex items-center justify-between gap-3">
697
+ <p className="text-xs text-gray-500">
698
+ Enter the new enterprise license key and validate
699
+ it to replace the current one. Your existing
700
+ license stays active until the new key is
701
+ validated.
702
+ </p>
703
+ <Button
704
+ title="Cancel"
705
+ buttonStyle={ButtonStyleType.NORMAL}
706
+ onClick={handleCancelChangingLicense}
707
+ />
708
+ </div>
709
+ ) : (
710
+ <p className="text-xs text-gray-500">
711
+ You have installed Enterprise Edition of OneUptime.
712
+ You need to validate your license key. Need a
713
+ license key? Contact our sales team at{" "}
714
+ <a
715
+ href="mailto:sales@oneuptime.com"
716
+ className="font-medium text-indigo-600 hover:text-indigo-700"
717
+ >
718
+ sales@oneuptime.com
719
+ </a>
720
+ .
721
+ </p>
722
+ )}
671
723
  </>
672
724
  )}
673
725
  </>
@@ -188,7 +188,10 @@ const Modal: FunctionComponent<ComponentProps> = (
188
188
  )}
189
189
  </div>
190
190
  {props.rightElement && (
191
- <div data-testid="right-element" className="mt-4 md:mt-0">
191
+ <div
192
+ data-testid="right-element"
193
+ className="mt-4 md:mt-0 lg:mr-4"
194
+ >
192
195
  {props.rightElement}
193
196
  </div>
194
197
  )}
@@ -101,6 +101,13 @@ export default class GlobalCache {
101
101
  */
102
102
  await client.set(`${namespace}-${key}`, value, "EX", expiresInSeconds);
103
103
  }
104
+ static async deleteKey(namespace, key) {
105
+ const client = Redis.getClient();
106
+ if (!client || !Redis.isConnected()) {
107
+ throw new DatabaseNotConnectedException("Cache is not connected");
108
+ }
109
+ await client.del(`${namespace}-${key}`);
110
+ }
104
111
  }
105
112
  __decorate([
106
113
  CaptureSpan(),
@@ -144,4 +151,10 @@ __decorate([
144
151
  __metadata("design:paramtypes", [String, String, String, Object]),
145
152
  __metadata("design:returntype", Promise)
146
153
  ], GlobalCache, "setString", null);
154
+ __decorate([
155
+ CaptureSpan(),
156
+ __metadata("design:type", Function),
157
+ __metadata("design:paramtypes", [String, String]),
158
+ __metadata("design:returntype", Promise)
159
+ ], GlobalCache, "deleteKey", null);
147
160
  //# sourceMappingURL=GlobalCache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GlobalCache.js","sourceRoot":"","sources":["../../../../Server/Infrastructure/GlobalCache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"GlobalCache.js","sourceRoot":"","sources":["../../../../Server/Infrastructure/GlobalCache.ts"],"names":[],"mappings":"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"}