@oneuptime/common 8.0.5438 → 8.0.5462
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.
- package/Models/DatabaseModels/StatusPage.ts +80 -0
- package/Server/API/StatusPageAPI.ts +138 -52
- package/Server/EnvironmentConfig.ts +34 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/OpenTelemetryIngestService.ts +1 -39
- package/Server/Services/StatusPageService.ts +117 -0
- package/Server/Services/TelemetryUsageBillingService.ts +208 -15
- package/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +5 -0
- package/Server/Utils/Telemetry/Telemetry.ts +129 -81
- package/Server/Utils/VM/VMRunner.ts +3 -4
- package/UI/Components/Dictionary/Dictionary.tsx +3 -0
- package/UI/Components/Forms/Fields/FieldLabel.tsx +7 -3
- package/UI/Components/LogsViewer/LogItem.tsx +12 -4
- package/UI/Components/LogsViewer/LogsViewer.tsx +131 -29
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -2
- package/UI/Components/ModelFilter/Filter.ts +1 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +2 -1
- package/UI/Components/Table/TableRow.tsx +89 -77
- package/UI/esbuild-config.js +32 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +157 -74
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +14 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1761232578396-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +0 -30
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +95 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +168 -8
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js +4 -0
- package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +84 -60
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +2 -2
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +3 -3
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FieldLabel.js +2 -1
- package/build/dist/UI/Components/Forms/Fields/FieldLabel.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogItem.js +5 -3
- package/build/dist/UI/Components/LogsViewer/LogItem.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +73 -22
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/Table/TableRow.js +18 -6
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/package.json +4 -4
|
@@ -15,9 +15,11 @@ import React, {
|
|
|
15
15
|
ReactElement,
|
|
16
16
|
Ref,
|
|
17
17
|
useCallback,
|
|
18
|
+
useMemo,
|
|
18
19
|
} from "react";
|
|
19
20
|
import Toggle from "../Toggle/Toggle";
|
|
20
21
|
import Card from "../Card/Card";
|
|
22
|
+
import Icon from "../Icon/Icon";
|
|
21
23
|
import Button, { ButtonSize, ButtonStyleType } from "../Button/Button";
|
|
22
24
|
import IconProp from "../../../Types/Icon/IconProp";
|
|
23
25
|
import ModelAPI from "../../Utils/ModelAPI/ModelAPI";
|
|
@@ -66,8 +68,9 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
66
68
|
typeof window !== "undefined" ? window.innerHeight : 900,
|
|
67
69
|
);
|
|
68
70
|
const [autoScroll, setAutoScroll] = React.useState<boolean>(true);
|
|
69
|
-
const [
|
|
71
|
+
const [showScrollToLatest, setShowScrollToLatest] =
|
|
70
72
|
React.useState<boolean>(false);
|
|
73
|
+
const [isDescending, setIsDescending] = React.useState<boolean>(false);
|
|
71
74
|
// removed wrapLines toggle for a cleaner toolbar
|
|
72
75
|
const logsViewerRef: Ref<HTMLDivElement> = React.useRef<HTMLDivElement>(null);
|
|
73
76
|
const scrollContainerRef: Ref<HTMLDivElement> =
|
|
@@ -89,6 +92,14 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
89
92
|
Dictionary<TelemetryService>
|
|
90
93
|
>({});
|
|
91
94
|
|
|
95
|
+
const displayLogs: Array<Log> = useMemo(() => {
|
|
96
|
+
if (isDescending) {
|
|
97
|
+
return [...props.logs].reverse();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return props.logs;
|
|
101
|
+
}, [props.logs, isDescending]);
|
|
102
|
+
|
|
92
103
|
const loadTelemetryServices: PromiseVoidFunction =
|
|
93
104
|
useCallback(async (): Promise<void> => {
|
|
94
105
|
try {
|
|
@@ -178,33 +189,73 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
178
189
|
};
|
|
179
190
|
}, [loadTelemetryServices]);
|
|
180
191
|
|
|
181
|
-
// Keep scroll
|
|
192
|
+
// Keep scroll aligned with the latest log entry
|
|
182
193
|
|
|
183
|
-
const
|
|
194
|
+
const scrollToLatest: VoidFunction = (): void => {
|
|
184
195
|
const scrollContainer: HTMLDivElement | null = scrollContainerRef.current;
|
|
185
196
|
|
|
186
|
-
if (scrollContainer) {
|
|
187
|
-
|
|
197
|
+
if (!scrollContainer) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (isDescending) {
|
|
202
|
+
scrollContainer.scrollTop = 0;
|
|
203
|
+
return;
|
|
188
204
|
}
|
|
205
|
+
|
|
206
|
+
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const applySortDirection: (nextDescending: boolean) => void = (
|
|
210
|
+
nextDescending: boolean,
|
|
211
|
+
) => {
|
|
212
|
+
setShowScrollToLatest(false);
|
|
213
|
+
setIsDescending((previous: boolean) => {
|
|
214
|
+
if (previous === nextDescending) {
|
|
215
|
+
return previous;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Apply scroll alignment after the DOM reorders log entries.
|
|
219
|
+
setTimeout(() => {
|
|
220
|
+
const scrollContainer: HTMLDivElement | null =
|
|
221
|
+
scrollContainerRef.current;
|
|
222
|
+
|
|
223
|
+
if (!scrollContainer) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (nextDescending) {
|
|
228
|
+
scrollContainer.scrollTop = 0;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
233
|
+
}, 0);
|
|
234
|
+
|
|
235
|
+
return nextDescending;
|
|
236
|
+
});
|
|
189
237
|
};
|
|
190
238
|
|
|
191
|
-
const handleScroll: VoidFunction = (): void => {
|
|
239
|
+
const handleScroll: VoidFunction = React.useCallback((): void => {
|
|
192
240
|
const scrollContainer: HTMLDivElement | null = scrollContainerRef.current;
|
|
193
|
-
if (scrollContainer) {
|
|
194
|
-
|
|
195
|
-
const isNearBottom: boolean =
|
|
196
|
-
scrollHeight - scrollTop - clientHeight < 100;
|
|
197
|
-
setShowScrollToBottom(!isNearBottom && props.logs.length > 0);
|
|
241
|
+
if (!scrollContainer) {
|
|
242
|
+
return;
|
|
198
243
|
}
|
|
199
|
-
|
|
244
|
+
|
|
245
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
|
246
|
+
const isNearLatest: boolean = isDescending
|
|
247
|
+
? scrollTop < 100
|
|
248
|
+
: scrollHeight - scrollTop - clientHeight < 100;
|
|
249
|
+
setShowScrollToLatest(!isNearLatest && displayLogs.length > 0);
|
|
250
|
+
}, [isDescending, displayLogs.length]);
|
|
200
251
|
|
|
201
252
|
React.useEffect(() => {
|
|
202
253
|
if (!autoScroll) {
|
|
203
254
|
return;
|
|
204
255
|
}
|
|
205
256
|
|
|
206
|
-
|
|
207
|
-
}, [props.logs]);
|
|
257
|
+
scrollToLatest();
|
|
258
|
+
}, [props.logs, autoScroll, isDescending]);
|
|
208
259
|
|
|
209
260
|
React.useEffect(() => {
|
|
210
261
|
const scrollContainer: HTMLDivElement | null = scrollContainerRef.current;
|
|
@@ -215,7 +266,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
215
266
|
};
|
|
216
267
|
}
|
|
217
268
|
return () => {}; // Return empty cleanup function if no scrollContainer
|
|
218
|
-
}, []);
|
|
269
|
+
}, [handleScroll]);
|
|
219
270
|
|
|
220
271
|
if (isPageLoading) {
|
|
221
272
|
return <PageLoader isVisible={true} />;
|
|
@@ -305,12 +356,54 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
305
356
|
</div>
|
|
306
357
|
<span className="hidden sm:block h-4 w-px bg-slate-200" />
|
|
307
358
|
<span className="text-xs text-slate-500">
|
|
308
|
-
{
|
|
309
|
-
{
|
|
359
|
+
{displayLogs.length} result
|
|
360
|
+
{displayLogs.length !== 1 ? "s" : ""}
|
|
310
361
|
</span>
|
|
311
362
|
</div>
|
|
312
363
|
|
|
313
364
|
<div className="flex items-center gap-2">
|
|
365
|
+
<div className="inline-flex items-center rounded-full border border-slate-200 bg-white/80 p-1 shadow-sm ring-1 ring-slate-200/60">
|
|
366
|
+
<button
|
|
367
|
+
type="button"
|
|
368
|
+
aria-pressed={isDescending}
|
|
369
|
+
className={`flex items-center gap-2 rounded-full px-3 py-1 text-xs font-semibold tracking-wide transition-all duration-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 ${
|
|
370
|
+
isDescending
|
|
371
|
+
? "bg-indigo-600 text-white shadow-sm ring-1 ring-indigo-500/40"
|
|
372
|
+
: "text-slate-500 hover:text-indigo-600"
|
|
373
|
+
}`}
|
|
374
|
+
onClick={() => {
|
|
375
|
+
applySortDirection(true);
|
|
376
|
+
}}
|
|
377
|
+
>
|
|
378
|
+
<Icon
|
|
379
|
+
icon={IconProp.BarsArrowDown}
|
|
380
|
+
className={`h-4 w-4 ${
|
|
381
|
+
isDescending ? "text-white/90" : "text-slate-400"
|
|
382
|
+
}`}
|
|
383
|
+
/>
|
|
384
|
+
<span>Newest first</span>
|
|
385
|
+
</button>
|
|
386
|
+
<button
|
|
387
|
+
type="button"
|
|
388
|
+
aria-pressed={!isDescending}
|
|
389
|
+
className={`flex items-center gap-2 rounded-full px-3 py-1 text-xs font-semibold tracking-wide transition-all duration-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500 ${
|
|
390
|
+
!isDescending
|
|
391
|
+
? "bg-indigo-600 text-white shadow-sm ring-1 ring-indigo-500/40"
|
|
392
|
+
: "text-slate-500 hover:text-indigo-600"
|
|
393
|
+
}`}
|
|
394
|
+
onClick={() => {
|
|
395
|
+
applySortDirection(false);
|
|
396
|
+
}}
|
|
397
|
+
>
|
|
398
|
+
<Icon
|
|
399
|
+
icon={IconProp.BarsArrowUp}
|
|
400
|
+
className={`h-4 w-4 ${
|
|
401
|
+
!isDescending ? "text-white/90" : "text-slate-400"
|
|
402
|
+
}`}
|
|
403
|
+
/>
|
|
404
|
+
<span>Oldest first</span>
|
|
405
|
+
</button>
|
|
406
|
+
</div>
|
|
314
407
|
<Button
|
|
315
408
|
title="Apply Filters"
|
|
316
409
|
icon={IconProp.Search}
|
|
@@ -342,7 +435,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
342
435
|
onScroll={handleScroll}
|
|
343
436
|
>
|
|
344
437
|
<ul role="list" className="divide-y divide-slate-800">
|
|
345
|
-
{
|
|
438
|
+
{displayLogs.map((log: Log, i: number) => {
|
|
346
439
|
const traceRouteProps: OptionalTraceRouteProps =
|
|
347
440
|
props.getTraceRoute
|
|
348
441
|
? { getTraceRoute: props.getTraceRoute }
|
|
@@ -364,7 +457,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
364
457
|
})}
|
|
365
458
|
</ul>
|
|
366
459
|
|
|
367
|
-
{
|
|
460
|
+
{displayLogs.length === 0 && (
|
|
368
461
|
<div className="flex items-center justify-center h-full px-4">
|
|
369
462
|
<div className="text-center">
|
|
370
463
|
<h3 className="text-sm font-medium text-slate-300 mb-1">
|
|
@@ -380,12 +473,12 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
380
473
|
</div>
|
|
381
474
|
</div>
|
|
382
475
|
|
|
383
|
-
{/* Floating Scroll to
|
|
384
|
-
{
|
|
476
|
+
{/* Floating Scroll to Latest Button */}
|
|
477
|
+
{showScrollToLatest && (
|
|
385
478
|
<button
|
|
386
|
-
onClick={
|
|
479
|
+
onClick={scrollToLatest}
|
|
387
480
|
className="absolute bottom-3 right-3 bg-slate-700 hover:bg-slate-600 text-white p-2 rounded-md shadow transition-all"
|
|
388
|
-
title="Scroll to bottom"
|
|
481
|
+
title={isDescending ? "Scroll to top" : "Scroll to bottom"}
|
|
389
482
|
>
|
|
390
483
|
<svg
|
|
391
484
|
className="w-5 h-5"
|
|
@@ -393,12 +486,21 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
393
486
|
stroke="currentColor"
|
|
394
487
|
viewBox="0 0 24 24"
|
|
395
488
|
>
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
489
|
+
{isDescending ? (
|
|
490
|
+
<path
|
|
491
|
+
strokeLinecap="round"
|
|
492
|
+
strokeLinejoin="round"
|
|
493
|
+
strokeWidth={2}
|
|
494
|
+
d="M5 14l7-7 7 7m-7-7v18"
|
|
495
|
+
/>
|
|
496
|
+
) : (
|
|
497
|
+
<path
|
|
498
|
+
strokeLinecap="round"
|
|
499
|
+
strokeLinejoin="round"
|
|
500
|
+
strokeWidth={2}
|
|
501
|
+
d="M19 10l-7 7-7-7m7 7V3"
|
|
502
|
+
/>
|
|
503
|
+
)}
|
|
402
504
|
</svg>
|
|
403
505
|
</button>
|
|
404
506
|
)}
|
|
@@ -98,7 +98,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
|
|
|
98
98
|
|
|
99
99
|
const baseClass: string = isSyntaxHighlighter
|
|
100
100
|
? "mt-4 mb-4 rounded-lg overflow-hidden"
|
|
101
|
-
: "bg-gray-900 text-gray-100 mt-4 mb-4 p-
|
|
101
|
+
: "bg-gray-900 text-gray-100 mt-4 mb-4 p-2 rounded-lg text-sm overflow-x-auto border border-gray-700";
|
|
102
102
|
|
|
103
103
|
return (
|
|
104
104
|
<pre className={baseClass} {...rest}>
|
|
@@ -201,7 +201,7 @@ const MarkdownViewer: FunctionComponent<ComponentProps> = (
|
|
|
201
201
|
children={content}
|
|
202
202
|
language={match[1]}
|
|
203
203
|
style={a11yDark}
|
|
204
|
-
className="rounded-lg mt-4 mb-4 !bg-gray-900 !p-
|
|
204
|
+
className="rounded-lg mt-4 mb-4 !bg-gray-900 !p-2 text-sm"
|
|
205
205
|
codeTagProps={{ className: "font-mono" }}
|
|
206
206
|
/>
|
|
207
207
|
) : (
|
|
@@ -682,6 +682,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
682
682
|
key: key,
|
|
683
683
|
type: filter.type,
|
|
684
684
|
jsonKeys: filter.jsonKeys,
|
|
685
|
+
isAdvancedFilter: filter.isAdvancedFilter,
|
|
685
686
|
};
|
|
686
687
|
})
|
|
687
688
|
.filter((filter: ClassicFilterType<TBaseModel> | null) => {
|
|
@@ -782,7 +783,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
|
|
|
782
783
|
setTableFilterError(API.getFriendlyMessage(err));
|
|
783
784
|
});
|
|
784
785
|
}
|
|
785
|
-
}, [showFilterModal]);
|
|
786
|
+
}, [showFilterModal, props.filters]);
|
|
786
787
|
|
|
787
788
|
type GetSelectFunction = () => Select<TBaseModel>;
|
|
788
789
|
|
|
@@ -350,6 +350,90 @@ const TableRow: TableRowFunction = <T extends GenericObject>(
|
|
|
350
350
|
className =
|
|
351
351
|
"whitespace-nowrap py-4 pl-4 pr-6 text-sm font-medium text-gray-500 sm:pl-6 align-top";
|
|
352
352
|
}
|
|
353
|
+
|
|
354
|
+
let columnContent: React.ReactNode = null;
|
|
355
|
+
|
|
356
|
+
if (column.key && !column.getElement) {
|
|
357
|
+
columnContent =
|
|
358
|
+
column.type === FieldType.Date ? (
|
|
359
|
+
props.item[column.key] ? (
|
|
360
|
+
OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
361
|
+
props.item[column.key] as string,
|
|
362
|
+
true,
|
|
363
|
+
)
|
|
364
|
+
) : (
|
|
365
|
+
column.noValueMessage || ""
|
|
366
|
+
)
|
|
367
|
+
) : column.type === FieldType.DateTime ? (
|
|
368
|
+
props.item[column.key] ? (
|
|
369
|
+
OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
370
|
+
props.item[column.key] as string,
|
|
371
|
+
false,
|
|
372
|
+
)
|
|
373
|
+
) : (
|
|
374
|
+
column.noValueMessage || ""
|
|
375
|
+
)
|
|
376
|
+
) : column.type === FieldType.USDCents ? (
|
|
377
|
+
props.item[column.key] ? (
|
|
378
|
+
((props.item[column.key] as number) || 0) / 100 + " USD"
|
|
379
|
+
) : (
|
|
380
|
+
column.noValueMessage || "0 USD"
|
|
381
|
+
)
|
|
382
|
+
) : column.type === FieldType.Percent ? (
|
|
383
|
+
props.item[column.key] ? (
|
|
384
|
+
props.item[column.key] + "%"
|
|
385
|
+
) : (
|
|
386
|
+
column.noValueMessage || "0%"
|
|
387
|
+
)
|
|
388
|
+
) : column.type === FieldType.Color ? (
|
|
389
|
+
props.item[column.key] ? (
|
|
390
|
+
<ColorInput value={props.item[column.key] as Color} />
|
|
391
|
+
) : (
|
|
392
|
+
column.noValueMessage || "0%"
|
|
393
|
+
)
|
|
394
|
+
) : column.type === FieldType.LongText ? (
|
|
395
|
+
props.item[column.key] ? (
|
|
396
|
+
<LongTextViewer
|
|
397
|
+
text={props.item[column.key] as string}
|
|
398
|
+
/>
|
|
399
|
+
) : (
|
|
400
|
+
column.noValueMessage || ""
|
|
401
|
+
)
|
|
402
|
+
) : column.type === FieldType.Boolean ? (
|
|
403
|
+
props.item[column.key] ? (
|
|
404
|
+
<Icon
|
|
405
|
+
icon={IconProp.Check}
|
|
406
|
+
className={"h-5 w-5 text-gray-500"}
|
|
407
|
+
thick={ThickProp.Thick}
|
|
408
|
+
/>
|
|
409
|
+
) : (
|
|
410
|
+
<Icon
|
|
411
|
+
icon={IconProp.False}
|
|
412
|
+
className={"h-5 w-5 text-gray-500"}
|
|
413
|
+
thick={ThickProp.Thick}
|
|
414
|
+
/>
|
|
415
|
+
)
|
|
416
|
+
) : (
|
|
417
|
+
getNestedValue(
|
|
418
|
+
props.item,
|
|
419
|
+
String(column.key),
|
|
420
|
+
)?.toString() ||
|
|
421
|
+
column.noValueMessage ||
|
|
422
|
+
""
|
|
423
|
+
);
|
|
424
|
+
} else if (column.key && column.getElement) {
|
|
425
|
+
columnContent = column.getElement(props.item);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const contentWrapperClassName: string = column.contentClassName
|
|
429
|
+
? column.contentClassName
|
|
430
|
+
: "";
|
|
431
|
+
|
|
432
|
+
const actionsContainerClassName: string =
|
|
433
|
+
column.contentClassName
|
|
434
|
+
? `flex justify-end ${column.contentClassName}`
|
|
435
|
+
: "flex justify-end";
|
|
436
|
+
|
|
353
437
|
return (
|
|
354
438
|
<td
|
|
355
439
|
key={i}
|
|
@@ -364,85 +448,13 @@ const TableRow: TableRowFunction = <T extends GenericObject>(
|
|
|
364
448
|
}
|
|
365
449
|
}}
|
|
366
450
|
>
|
|
367
|
-
{
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
props.item[column.key] as string,
|
|
372
|
-
true,
|
|
373
|
-
)
|
|
374
|
-
) : (
|
|
375
|
-
column.noValueMessage || ""
|
|
376
|
-
)
|
|
377
|
-
) : column.type === FieldType.DateTime ? (
|
|
378
|
-
props.item[column.key] ? (
|
|
379
|
-
OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
380
|
-
props.item[column.key] as string,
|
|
381
|
-
false,
|
|
382
|
-
)
|
|
383
|
-
) : (
|
|
384
|
-
column.noValueMessage || ""
|
|
385
|
-
)
|
|
386
|
-
) : column.type === FieldType.USDCents ? (
|
|
387
|
-
props.item[column.key] ? (
|
|
388
|
-
((props.item[column.key] as number) || 0) / 100 +
|
|
389
|
-
" USD"
|
|
390
|
-
) : (
|
|
391
|
-
column.noValueMessage || "0 USD"
|
|
392
|
-
)
|
|
393
|
-
) : column.type === FieldType.Percent ? (
|
|
394
|
-
props.item[column.key] ? (
|
|
395
|
-
props.item[column.key] + "%"
|
|
396
|
-
) : (
|
|
397
|
-
column.noValueMessage || "0%"
|
|
398
|
-
)
|
|
399
|
-
) : column.type === FieldType.Color ? (
|
|
400
|
-
props.item[column.key] ? (
|
|
401
|
-
<ColorInput value={props.item[column.key] as Color} />
|
|
402
|
-
) : (
|
|
403
|
-
column.noValueMessage || "0%"
|
|
404
|
-
)
|
|
405
|
-
) : column.type === FieldType.LongText ? (
|
|
406
|
-
props.item[column.key] ? (
|
|
407
|
-
<LongTextViewer
|
|
408
|
-
text={props.item[column.key] as string}
|
|
409
|
-
/>
|
|
410
|
-
) : (
|
|
411
|
-
column.noValueMessage || ""
|
|
412
|
-
)
|
|
413
|
-
) : column.type === FieldType.Boolean ? (
|
|
414
|
-
props.item[column.key] ? (
|
|
415
|
-
<Icon
|
|
416
|
-
icon={IconProp.Check}
|
|
417
|
-
className={"h-5 w-5 text-gray-500"}
|
|
418
|
-
thick={ThickProp.Thick}
|
|
419
|
-
/>
|
|
420
|
-
) : (
|
|
421
|
-
<Icon
|
|
422
|
-
icon={IconProp.False}
|
|
423
|
-
className={"h-5 w-5 text-gray-500"}
|
|
424
|
-
thick={ThickProp.Thick}
|
|
425
|
-
/>
|
|
426
|
-
)
|
|
427
|
-
) : (
|
|
428
|
-
getNestedValue(
|
|
429
|
-
props.item,
|
|
430
|
-
String(column.key),
|
|
431
|
-
)?.toString() ||
|
|
432
|
-
column.noValueMessage ||
|
|
433
|
-
""
|
|
434
|
-
)
|
|
435
|
-
) : (
|
|
436
|
-
<></>
|
|
437
|
-
)}
|
|
438
|
-
|
|
439
|
-
{column.key && column.getElement ? (
|
|
440
|
-
column.getElement(props.item)
|
|
441
|
-
) : (
|
|
442
|
-
<></>
|
|
451
|
+
{columnContent !== null && columnContent !== undefined && (
|
|
452
|
+
<div className={contentWrapperClassName}>
|
|
453
|
+
{columnContent}
|
|
454
|
+
</div>
|
|
443
455
|
)}
|
|
444
456
|
{column.type === FieldType.Actions && (
|
|
445
|
-
<div className=
|
|
457
|
+
<div className={actionsContainerClassName}>
|
|
446
458
|
{error && (
|
|
447
459
|
<div className="text-align-left">
|
|
448
460
|
<ConfirmModal
|
package/UI/esbuild-config.js
CHANGED
|
@@ -8,6 +8,37 @@ const path = require('path');
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const dotenv = require('dotenv');
|
|
10
10
|
|
|
11
|
+
function createRefractorCompatibilityPlugin() {
|
|
12
|
+
const candidateRoots = [
|
|
13
|
+
path.resolve(__dirname, '../node_modules/refractor'),
|
|
14
|
+
path.resolve(__dirname, '../../node_modules/refractor'),
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const refractorRoot = candidateRoots.find((packagePath) => fs.existsSync(packagePath));
|
|
18
|
+
|
|
19
|
+
if (!refractorRoot) {
|
|
20
|
+
throw new Error('Unable to locate refractor package for esbuild compatibility plugin.');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
name: 'refractor-compatibility',
|
|
25
|
+
setup(build) {
|
|
26
|
+
build.onResolve({ filter: /^refractor\/lib\// }, (args) => {
|
|
27
|
+
const relativePath = args.path.replace(/^refractor\/lib\//, '');
|
|
28
|
+
const candidatePath = path.join(refractorRoot, 'lib', `${relativePath}.js`);
|
|
29
|
+
return { path: candidatePath };
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
build.onResolve({ filter: /^refractor\/lang\// }, (args) => {
|
|
33
|
+
const relativePath = args.path.replace(/^refractor\/lang\//, '');
|
|
34
|
+
const filename = relativePath.endsWith('.js') ? relativePath : `${relativePath}.js`;
|
|
35
|
+
const candidatePath = path.join(refractorRoot, 'lang', filename);
|
|
36
|
+
return { path: candidatePath };
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
11
42
|
// CSS Plugin to handle CSS/SCSS files
|
|
12
43
|
function createCSSPlugin() {
|
|
13
44
|
return {
|
|
@@ -146,7 +177,7 @@ function createConfig(options) {
|
|
|
146
177
|
'react': path.resolve('./node_modules/react'),
|
|
147
178
|
...additionalAlias,
|
|
148
179
|
},
|
|
149
|
-
plugins: [createCSSPlugin(), createFileLoaderPlugin()],
|
|
180
|
+
plugins: [createRefractorCompatibilityPlugin(), createCSSPlugin(), createFileLoaderPlugin()],
|
|
150
181
|
loader: {
|
|
151
182
|
'.tsx': 'tsx',
|
|
152
183
|
'.ts': 'ts',
|
|
@@ -107,6 +107,8 @@ let StatusPage = class StatusPage extends BaseModel {
|
|
|
107
107
|
this.showScheduledMaintenanceEventsOnStatusPage = undefined;
|
|
108
108
|
this.showSubscriberPageOnStatusPage = undefined;
|
|
109
109
|
this.ipWhitelist = undefined;
|
|
110
|
+
this.enableEmbeddedOverallStatus = undefined;
|
|
111
|
+
this.embeddedOverallStatusToken = undefined;
|
|
110
112
|
}
|
|
111
113
|
};
|
|
112
114
|
__decorate([
|
|
@@ -2361,6 +2363,86 @@ __decorate([
|
|
|
2361
2363
|
}),
|
|
2362
2364
|
__metadata("design:type", String)
|
|
2363
2365
|
], StatusPage.prototype, "ipWhitelist", void 0);
|
|
2366
|
+
__decorate([
|
|
2367
|
+
ColumnAccessControl({
|
|
2368
|
+
create: [
|
|
2369
|
+
Permission.ProjectOwner,
|
|
2370
|
+
Permission.ProjectAdmin,
|
|
2371
|
+
Permission.ProjectMember,
|
|
2372
|
+
Permission.CreateProjectStatusPage,
|
|
2373
|
+
],
|
|
2374
|
+
read: [
|
|
2375
|
+
Permission.ProjectOwner,
|
|
2376
|
+
Permission.ProjectAdmin,
|
|
2377
|
+
Permission.ProjectMember,
|
|
2378
|
+
Permission.ReadProjectStatusPage,
|
|
2379
|
+
],
|
|
2380
|
+
update: [
|
|
2381
|
+
Permission.ProjectOwner,
|
|
2382
|
+
Permission.ProjectAdmin,
|
|
2383
|
+
Permission.ProjectMember,
|
|
2384
|
+
Permission.EditProjectStatusPage,
|
|
2385
|
+
],
|
|
2386
|
+
}),
|
|
2387
|
+
TableColumn({
|
|
2388
|
+
isDefaultValueColumn: true,
|
|
2389
|
+
type: TableColumnType.Boolean,
|
|
2390
|
+
title: "Enable Embedded Overall Status Badge",
|
|
2391
|
+
description: "Enable embedded overall status badge that can be displayed on external websites?",
|
|
2392
|
+
defaultValue: false,
|
|
2393
|
+
}),
|
|
2394
|
+
Column({
|
|
2395
|
+
type: ColumnType.Boolean,
|
|
2396
|
+
default: false,
|
|
2397
|
+
nullable: false,
|
|
2398
|
+
}),
|
|
2399
|
+
ColumnBillingAccessControl({
|
|
2400
|
+
read: PlanType.Free,
|
|
2401
|
+
update: PlanType.Growth,
|
|
2402
|
+
create: PlanType.Free,
|
|
2403
|
+
}),
|
|
2404
|
+
__metadata("design:type", Boolean)
|
|
2405
|
+
], StatusPage.prototype, "enableEmbeddedOverallStatus", void 0);
|
|
2406
|
+
__decorate([
|
|
2407
|
+
ColumnAccessControl({
|
|
2408
|
+
create: [
|
|
2409
|
+
Permission.ProjectOwner,
|
|
2410
|
+
Permission.ProjectAdmin,
|
|
2411
|
+
Permission.ProjectMember,
|
|
2412
|
+
Permission.CreateProjectStatusPage,
|
|
2413
|
+
],
|
|
2414
|
+
read: [
|
|
2415
|
+
Permission.ProjectOwner,
|
|
2416
|
+
Permission.ProjectAdmin,
|
|
2417
|
+
Permission.ProjectMember,
|
|
2418
|
+
Permission.ReadProjectStatusPage,
|
|
2419
|
+
],
|
|
2420
|
+
update: [
|
|
2421
|
+
Permission.ProjectOwner,
|
|
2422
|
+
Permission.ProjectAdmin,
|
|
2423
|
+
Permission.ProjectMember,
|
|
2424
|
+
Permission.EditProjectStatusPage,
|
|
2425
|
+
],
|
|
2426
|
+
}),
|
|
2427
|
+
Index(),
|
|
2428
|
+
TableColumn({
|
|
2429
|
+
type: TableColumnType.ShortText,
|
|
2430
|
+
required: false,
|
|
2431
|
+
title: "Embedded Overall Status Token",
|
|
2432
|
+
description: "Security token required to access the embedded overall status badge. This token must be provided in the URL.",
|
|
2433
|
+
}),
|
|
2434
|
+
Column({
|
|
2435
|
+
type: ColumnType.ShortText,
|
|
2436
|
+
length: ColumnLength.ShortText,
|
|
2437
|
+
nullable: true,
|
|
2438
|
+
}),
|
|
2439
|
+
ColumnBillingAccessControl({
|
|
2440
|
+
read: PlanType.Free,
|
|
2441
|
+
update: PlanType.Growth,
|
|
2442
|
+
create: PlanType.Free,
|
|
2443
|
+
}),
|
|
2444
|
+
__metadata("design:type", String)
|
|
2445
|
+
], StatusPage.prototype, "embeddedOverallStatusToken", void 0);
|
|
2364
2446
|
StatusPage = __decorate([
|
|
2365
2447
|
EnableDocumentation(),
|
|
2366
2448
|
AccessControlColumn("labels"),
|