@oneuptime/common 10.0.35 → 10.0.37
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/Index.ts +2 -0
- package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +819 -0
- package/Server/API/StatusPageAPI.ts +7 -0
- package/Server/API/WorkspaceNotificationSummaryAPI.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/{1773761409952-MigrationName.ts → 1774000000001-MigrationName.ts} +2 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -2
- package/Server/Middleware/MasterAdminAuthorization.ts +55 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/WorkspaceNotificationSummaryService.ts +1450 -0
- package/Server/Types/Markdown.ts +11 -3
- package/Server/Utils/Greenlock/Greenlock.ts +1 -0
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +4 -1
- package/Types/Code/CodeType.ts +1 -1
- package/Types/Metrics/MetricQueryConfigData.ts +1 -0
- package/Types/Monitor/CriteriaFilter.ts +19 -0
- package/Types/Monitor/KubernetesAlertTemplates.ts +703 -0
- package/Types/Monitor/KubernetesMetricCatalog.ts +347 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +86 -0
- package/Types/Monitor/MonitorStep.ts +36 -1
- package/Types/Monitor/MonitorStepKubernetesMonitor.ts +50 -0
- package/Types/Monitor/MonitorType.ts +14 -10
- package/Types/Permission.ts +42 -0
- package/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.ts +13 -0
- package/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.ts +8 -0
- package/UI/Components/AlertBanner/AlertBanner.tsx +69 -0
- package/UI/Components/ConditionsTable/ConditionsTable.tsx +149 -0
- package/UI/Components/Dictionary/DictionaryOfStingsViewer.tsx +35 -15
- package/UI/Components/ExpandableText/ExpandableText.tsx +42 -0
- package/UI/Components/FilterButtons/FilterButtons.tsx +60 -0
- package/UI/Components/GanttChart/Bar/Index.tsx +23 -5
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +4 -1
- package/UI/Components/ResourceUsageBar/ResourceUsageBar.tsx +58 -0
- package/UI/Components/StackedProgressBar/StackedProgressBar.tsx +81 -0
- package/UI/Components/StatusBadge/StatusBadge.tsx +44 -0
- package/UI/Components/Tabs/Tabs.tsx +36 -8
- package/UI/Utils/Dropdown.ts +2 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +857 -0
- package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +2 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/WorkspaceNotificationSummaryAPI.js +40 -0
- package/build/dist/Server/API/WorkspaceNotificationSummaryAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/{1773761409952-MigrationName.js → 1774000000001-MigrationName.js} +3 -3
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/{1773761409952-MigrationName.js.map → 1774000000001-MigrationName.js.map} +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.js +24 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774355321449-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774357353502-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -2
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +25 -0
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -0
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +1122 -0
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -0
- package/build/dist/Server/Types/Markdown.js +10 -2
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +1 -0
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +2 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Types/Code/CodeType.js +1 -1
- package/build/dist/Types/Code/CodeType.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +18 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +594 -0
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +311 -0
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +78 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +24 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepKubernetesMonitor.js +30 -0
- package/build/dist/Types/Monitor/MonitorStepKubernetesMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +13 -10
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Permission.js +36 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.js +14 -0
- package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryItem.js.map +1 -0
- package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.js +9 -0
- package/build/dist/Types/Workspace/NotificationSummary/WorkspaceNotificationSummaryType.js.map +1 -0
- package/build/dist/UI/Components/AlertBanner/AlertBanner.js +42 -0
- package/build/dist/UI/Components/AlertBanner/AlertBanner.js.map +1 -0
- package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js +83 -0
- package/build/dist/UI/Components/ConditionsTable/ConditionsTable.js.map +1 -0
- package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js +14 -8
- package/build/dist/UI/Components/Dictionary/DictionaryOfStingsViewer.js.map +1 -1
- package/build/dist/UI/Components/ExpandableText/ExpandableText.js +19 -0
- package/build/dist/UI/Components/ExpandableText/ExpandableText.js.map +1 -0
- package/build/dist/UI/Components/FilterButtons/FilterButtons.js +17 -0
- package/build/dist/UI/Components/FilterButtons/FilterButtons.js.map +1 -0
- package/build/dist/UI/Components/GanttChart/Bar/Index.js +15 -3
- package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +3 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js.map +1 -1
- package/build/dist/UI/Components/ResourceUsageBar/ResourceUsageBar.js +23 -0
- package/build/dist/UI/Components/ResourceUsageBar/ResourceUsageBar.js.map +1 -0
- package/build/dist/UI/Components/StackedProgressBar/StackedProgressBar.js +34 -0
- package/build/dist/UI/Components/StackedProgressBar/StackedProgressBar.js.map +1 -0
- package/build/dist/UI/Components/StatusBadge/StatusBadge.js +22 -0
- package/build/dist/UI/Components/StatusBadge/StatusBadge.js.map +1 -0
- package/build/dist/UI/Components/Tabs/Tabs.js +32 -9
- package/build/dist/UI/Components/Tabs/Tabs.js.map +1 -1
- package/build/dist/UI/Utils/Dropdown.js +2 -1
- package/build/dist/UI/Utils/Dropdown.js.map +1 -1
- package/package.json +1 -1
package/Types/Permission.ts
CHANGED
|
@@ -771,6 +771,11 @@ enum Permission {
|
|
|
771
771
|
EditWorkspaceNotificationRule = "EditWorkspaceNotificationRule",
|
|
772
772
|
ReadWorkspaceNotificationRule = "ReadWorkspaceNotificationRule",
|
|
773
773
|
|
|
774
|
+
CreateWorkspaceNotificationSummary = "CreateWorkspaceNotificationSummary",
|
|
775
|
+
DeleteWorkspaceNotificationSummary = "DeleteWorkspaceNotificationSummary",
|
|
776
|
+
EditWorkspaceNotificationSummary = "EditWorkspaceNotificationSummary",
|
|
777
|
+
ReadWorkspaceNotificationSummary = "ReadWorkspaceNotificationSummary",
|
|
778
|
+
|
|
774
779
|
// Alert Episode Permissions
|
|
775
780
|
CreateAlertEpisode = "CreateAlertEpisode",
|
|
776
781
|
DeleteAlertEpisode = "DeleteAlertEpisode",
|
|
@@ -1389,6 +1394,43 @@ export class PermissionHelper {
|
|
|
1389
1394
|
group: PermissionGroup.Settings,
|
|
1390
1395
|
},
|
|
1391
1396
|
|
|
1397
|
+
{
|
|
1398
|
+
permission: Permission.CreateWorkspaceNotificationSummary,
|
|
1399
|
+
title: "Create Workspace Notification Summary",
|
|
1400
|
+
description:
|
|
1401
|
+
"This permission can create workspace notification summaries for this project.",
|
|
1402
|
+
isAssignableToTenant: true,
|
|
1403
|
+
isAccessControlPermission: false,
|
|
1404
|
+
group: PermissionGroup.Settings,
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
permission: Permission.DeleteWorkspaceNotificationSummary,
|
|
1408
|
+
title: "Delete Workspace Notification Summary",
|
|
1409
|
+
description:
|
|
1410
|
+
"This permission can delete workspace notification summaries of this project.",
|
|
1411
|
+
isAssignableToTenant: true,
|
|
1412
|
+
isAccessControlPermission: false,
|
|
1413
|
+
group: PermissionGroup.Settings,
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
permission: Permission.EditWorkspaceNotificationSummary,
|
|
1417
|
+
title: "Edit Workspace Notification Summary",
|
|
1418
|
+
description:
|
|
1419
|
+
"This permission can edit workspace notification summaries of this project.",
|
|
1420
|
+
isAssignableToTenant: true,
|
|
1421
|
+
isAccessControlPermission: false,
|
|
1422
|
+
group: PermissionGroup.Settings,
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
permission: Permission.ReadWorkspaceNotificationSummary,
|
|
1426
|
+
title: "Read Workspace Notification Summary",
|
|
1427
|
+
description:
|
|
1428
|
+
"This permission can read workspace notification summaries of this project.",
|
|
1429
|
+
isAssignableToTenant: true,
|
|
1430
|
+
isAccessControlPermission: false,
|
|
1431
|
+
group: PermissionGroup.Settings,
|
|
1432
|
+
},
|
|
1433
|
+
|
|
1392
1434
|
{
|
|
1393
1435
|
permission: Permission.CreateIncidentStateTimeline,
|
|
1394
1436
|
title: "Create Incident State Timeline",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
enum WorkspaceNotificationSummaryItem {
|
|
2
|
+
TotalCount = "Total Count",
|
|
3
|
+
ListWithLinks = "List with Links",
|
|
4
|
+
WhoAcknowledged = "Who Acknowledged",
|
|
5
|
+
WhoResolved = "Who Resolved",
|
|
6
|
+
TimeToAcknowledge = "Time to Acknowledge",
|
|
7
|
+
TimeToResolve = "Time to Resolve",
|
|
8
|
+
ResourcesAffected = "Resources Affected",
|
|
9
|
+
SeverityBreakdown = "Severity Breakdown",
|
|
10
|
+
StateBreakdown = "State Breakdown",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default WorkspaceNotificationSummaryItem;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
export enum AlertBannerType {
|
|
4
|
+
Success = "success",
|
|
5
|
+
Warning = "warning",
|
|
6
|
+
Danger = "danger",
|
|
7
|
+
Info = "info",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ComponentProps {
|
|
11
|
+
title: string;
|
|
12
|
+
type: AlertBannerType;
|
|
13
|
+
children?: ReactElement | undefined;
|
|
14
|
+
rightElement?: ReactElement | undefined;
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const bannerStyles: Record<
|
|
19
|
+
AlertBannerType,
|
|
20
|
+
{ container: string; dot: string; title: string }
|
|
21
|
+
> = {
|
|
22
|
+
[AlertBannerType.Success]: {
|
|
23
|
+
container: "bg-emerald-50 border-emerald-200",
|
|
24
|
+
dot: "bg-emerald-500",
|
|
25
|
+
title: "text-emerald-800",
|
|
26
|
+
},
|
|
27
|
+
[AlertBannerType.Warning]: {
|
|
28
|
+
container: "bg-amber-50 border-amber-200",
|
|
29
|
+
dot: "bg-amber-500",
|
|
30
|
+
title: "text-amber-800",
|
|
31
|
+
},
|
|
32
|
+
[AlertBannerType.Danger]: {
|
|
33
|
+
container: "bg-red-50 border-red-200",
|
|
34
|
+
dot: "bg-red-500",
|
|
35
|
+
title: "text-red-800",
|
|
36
|
+
},
|
|
37
|
+
[AlertBannerType.Info]: {
|
|
38
|
+
container: "bg-blue-50 border-blue-200",
|
|
39
|
+
dot: "bg-blue-500",
|
|
40
|
+
title: "text-blue-800",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const AlertBanner: FunctionComponent<ComponentProps> = (
|
|
45
|
+
props: ComponentProps,
|
|
46
|
+
): ReactElement => {
|
|
47
|
+
const styles: { container: string; dot: string; title: string } =
|
|
48
|
+
bannerStyles[props.type];
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className={`rounded-lg border p-4 ${styles.container} ${props.className || ""}`}
|
|
53
|
+
role="alert"
|
|
54
|
+
>
|
|
55
|
+
<div className="flex items-center justify-between">
|
|
56
|
+
<div className="flex items-center gap-3">
|
|
57
|
+
<span className={`inline-flex h-3 w-3 rounded-full ${styles.dot}`} />
|
|
58
|
+
<span className={`text-lg font-semibold ${styles.title}`}>
|
|
59
|
+
{props.title}
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
{props.rightElement && <div>{props.rightElement}</div>}
|
|
63
|
+
</div>
|
|
64
|
+
{props.children && <div className="mt-2">{props.children}</div>}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default AlertBanner;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
2
|
+
import ExpandableText from "../ExpandableText/ExpandableText";
|
|
3
|
+
|
|
4
|
+
export interface Condition {
|
|
5
|
+
type: string;
|
|
6
|
+
status: string;
|
|
7
|
+
reason?: string | undefined;
|
|
8
|
+
message?: string | undefined;
|
|
9
|
+
lastTransitionTime?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ComponentProps {
|
|
13
|
+
conditions: Array<Condition>;
|
|
14
|
+
negativeTypes?: Array<string> | undefined;
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Default condition types where "True" is bad
|
|
19
|
+
const defaultNegativeTypes: Array<string> = [
|
|
20
|
+
"MemoryPressure",
|
|
21
|
+
"DiskPressure",
|
|
22
|
+
"PIDPressure",
|
|
23
|
+
"NetworkUnavailable",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function isConditionBad(
|
|
27
|
+
condition: Condition,
|
|
28
|
+
negativeTypes: Array<string>,
|
|
29
|
+
): boolean {
|
|
30
|
+
if (negativeTypes.includes(condition.type)) {
|
|
31
|
+
return condition.status === "True";
|
|
32
|
+
}
|
|
33
|
+
return condition.status === "False";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getStatusStyle(
|
|
37
|
+
condition: Condition,
|
|
38
|
+
negativeTypes: Array<string>,
|
|
39
|
+
): string {
|
|
40
|
+
const isNegativeType: boolean = negativeTypes.includes(condition.type);
|
|
41
|
+
if (condition.status === "True") {
|
|
42
|
+
return isNegativeType
|
|
43
|
+
? "bg-gradient-to-r from-red-50 to-red-100 text-red-800 ring-1 ring-inset ring-red-200/80"
|
|
44
|
+
: "bg-gradient-to-r from-emerald-50 to-emerald-100 text-emerald-800 ring-1 ring-inset ring-emerald-200/80";
|
|
45
|
+
}
|
|
46
|
+
if (condition.status === "False") {
|
|
47
|
+
return isNegativeType
|
|
48
|
+
? "bg-gradient-to-r from-emerald-50 to-emerald-100 text-emerald-800 ring-1 ring-inset ring-emerald-200/80"
|
|
49
|
+
: "bg-gradient-to-r from-red-50 to-red-100 text-red-800 ring-1 ring-inset ring-red-200/80";
|
|
50
|
+
}
|
|
51
|
+
return "bg-gradient-to-r from-amber-50 to-amber-100 text-amber-800 ring-1 ring-inset ring-amber-200/80";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatRelativeTime(timestamp: string): string {
|
|
55
|
+
if (!timestamp) {
|
|
56
|
+
return "-";
|
|
57
|
+
}
|
|
58
|
+
const date: Date = new Date(timestamp);
|
|
59
|
+
const now: Date = new Date();
|
|
60
|
+
const diffMs: number = now.getTime() - date.getTime();
|
|
61
|
+
if (diffMs < 0) {
|
|
62
|
+
return timestamp;
|
|
63
|
+
}
|
|
64
|
+
const diffSec: number = Math.floor(diffMs / 1000);
|
|
65
|
+
if (diffSec < 60) {
|
|
66
|
+
return `${diffSec}s ago`;
|
|
67
|
+
}
|
|
68
|
+
const diffMin: number = Math.floor(diffSec / 60);
|
|
69
|
+
if (diffMin < 60) {
|
|
70
|
+
return `${diffMin}m ago`;
|
|
71
|
+
}
|
|
72
|
+
const diffHrs: number = Math.floor(diffMin / 60);
|
|
73
|
+
if (diffHrs < 24) {
|
|
74
|
+
return `${diffHrs}h ago`;
|
|
75
|
+
}
|
|
76
|
+
const diffDays: number = Math.floor(diffHrs / 24);
|
|
77
|
+
return `${diffDays}d ago`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ConditionsTable: FunctionComponent<ComponentProps> = (
|
|
81
|
+
props: ComponentProps,
|
|
82
|
+
): ReactElement => {
|
|
83
|
+
const negativeTypes: Array<string> =
|
|
84
|
+
props.negativeTypes || defaultNegativeTypes;
|
|
85
|
+
|
|
86
|
+
if (props.conditions.length === 0) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="text-gray-500 text-sm p-4">No conditions available.</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className={`overflow-x-auto ${props.className || ""}`}>
|
|
94
|
+
<table className="min-w-full divide-y divide-gray-200">
|
|
95
|
+
<thead className="bg-gray-50">
|
|
96
|
+
<tr>
|
|
97
|
+
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
|
98
|
+
Type
|
|
99
|
+
</th>
|
|
100
|
+
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
|
101
|
+
Status
|
|
102
|
+
</th>
|
|
103
|
+
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
|
104
|
+
Reason
|
|
105
|
+
</th>
|
|
106
|
+
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
|
107
|
+
Message
|
|
108
|
+
</th>
|
|
109
|
+
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
|
110
|
+
Last Transition
|
|
111
|
+
</th>
|
|
112
|
+
</tr>
|
|
113
|
+
</thead>
|
|
114
|
+
<tbody className="bg-white divide-y divide-gray-200">
|
|
115
|
+
{props.conditions.map((condition: Condition, index: number) => {
|
|
116
|
+
const isBad: boolean = isConditionBad(condition, negativeTypes);
|
|
117
|
+
return (
|
|
118
|
+
<tr key={index} className={isBad ? "bg-red-50/50" : ""}>
|
|
119
|
+
<td className="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
120
|
+
{condition.type}
|
|
121
|
+
</td>
|
|
122
|
+
<td className="px-4 py-3 whitespace-nowrap text-sm">
|
|
123
|
+
<span
|
|
124
|
+
className={`inline-flex px-2 py-0.5 text-xs font-semibold rounded-full ${getStatusStyle(condition, negativeTypes)}`}
|
|
125
|
+
>
|
|
126
|
+
{condition.status}
|
|
127
|
+
</span>
|
|
128
|
+
</td>
|
|
129
|
+
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">
|
|
130
|
+
{condition.reason || "-"}
|
|
131
|
+
</td>
|
|
132
|
+
<td className="px-4 py-3 text-sm max-w-md">
|
|
133
|
+
<ExpandableText text={condition.message || "-"} />
|
|
134
|
+
</td>
|
|
135
|
+
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
|
|
136
|
+
<span title={condition.lastTransitionTime || ""}>
|
|
137
|
+
{formatRelativeTime(condition.lastTransitionTime || "")}
|
|
138
|
+
</span>
|
|
139
|
+
</td>
|
|
140
|
+
</tr>
|
|
141
|
+
);
|
|
142
|
+
})}
|
|
143
|
+
</tbody>
|
|
144
|
+
</table>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export default ConditionsTable;
|
|
@@ -40,22 +40,42 @@ const DictionaryOfStringsViewer: FunctionComponent<ComponentProps> = (
|
|
|
40
40
|
);
|
|
41
41
|
}, [props.value]);
|
|
42
42
|
|
|
43
|
+
if (data.length === 0) {
|
|
44
|
+
return (
|
|
45
|
+
<div className="text-gray-400 text-sm py-2">No items to display.</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
return (
|
|
44
|
-
<div>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
<div className="overflow-x-auto">
|
|
51
|
+
<table className="min-w-full divide-y divide-gray-200">
|
|
52
|
+
<thead className="bg-gray-50">
|
|
53
|
+
<tr>
|
|
54
|
+
<th className="px-4 py-2.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
55
|
+
Key
|
|
56
|
+
</th>
|
|
57
|
+
<th className="px-4 py-2.5 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
58
|
+
Value
|
|
59
|
+
</th>
|
|
60
|
+
</tr>
|
|
61
|
+
</thead>
|
|
62
|
+
<tbody className="bg-white divide-y divide-gray-100">
|
|
63
|
+
{data.map((item: Item, index: number) => {
|
|
64
|
+
return (
|
|
65
|
+
<tr key={index} className="hover:bg-gray-50/50">
|
|
66
|
+
<td className="px-4 py-2 text-sm font-mono font-medium text-indigo-700 whitespace-nowrap">
|
|
67
|
+
{item.key}
|
|
68
|
+
</td>
|
|
69
|
+
<td className="px-4 py-2 text-sm font-mono text-gray-600 break-all">
|
|
70
|
+
{item.value || (
|
|
71
|
+
<span className="text-gray-400 italic">empty</span>
|
|
72
|
+
)}
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</tbody>
|
|
78
|
+
</table>
|
|
59
79
|
</div>
|
|
60
80
|
);
|
|
61
81
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ComponentProps {
|
|
4
|
+
text: string;
|
|
5
|
+
maxLength?: number | undefined;
|
|
6
|
+
className?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const ExpandableText: FunctionComponent<ComponentProps> = (
|
|
10
|
+
props: ComponentProps,
|
|
11
|
+
): ReactElement => {
|
|
12
|
+
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
|
13
|
+
const maxLength: number = props.maxLength || 80;
|
|
14
|
+
|
|
15
|
+
if (!props.text || props.text === "-") {
|
|
16
|
+
return <span className="text-gray-400">-</span>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isLong: boolean = props.text.length > maxLength;
|
|
20
|
+
|
|
21
|
+
if (!isLong) {
|
|
22
|
+
return (
|
|
23
|
+
<span className={props.className || "text-gray-600"}>{props.text}</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<span className={props.className || "text-gray-600"}>
|
|
29
|
+
{isExpanded ? props.text : props.text.substring(0, maxLength) + "..."}
|
|
30
|
+
<button
|
|
31
|
+
onClick={() => {
|
|
32
|
+
setIsExpanded(!isExpanded);
|
|
33
|
+
}}
|
|
34
|
+
className="ml-1.5 text-xs text-indigo-600 hover:text-indigo-800 font-medium"
|
|
35
|
+
>
|
|
36
|
+
{isExpanded ? "Less" : "More"}
|
|
37
|
+
</button>
|
|
38
|
+
</span>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default ExpandableText;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
export interface FilterButtonOption {
|
|
4
|
+
label: string;
|
|
5
|
+
value: string;
|
|
6
|
+
badge?: number | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ComponentProps {
|
|
10
|
+
options: Array<FilterButtonOption>;
|
|
11
|
+
selectedValue: string;
|
|
12
|
+
onSelect: (value: string) => void;
|
|
13
|
+
className?: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const FilterButtons: FunctionComponent<ComponentProps> = (
|
|
17
|
+
props: ComponentProps,
|
|
18
|
+
): ReactElement => {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
className={`inline-flex gap-1 ${props.className || ""}`}
|
|
22
|
+
role="radiogroup"
|
|
23
|
+
aria-label="Filter options"
|
|
24
|
+
>
|
|
25
|
+
{props.options.map((option: FilterButtonOption) => {
|
|
26
|
+
const isActive: boolean = props.selectedValue === option.value;
|
|
27
|
+
return (
|
|
28
|
+
<button
|
|
29
|
+
key={option.value}
|
|
30
|
+
onClick={() => {
|
|
31
|
+
props.onSelect(option.value);
|
|
32
|
+
}}
|
|
33
|
+
className={`px-3 py-1.5 text-xs rounded-md font-medium transition-all duration-150 ${
|
|
34
|
+
isActive
|
|
35
|
+
? "bg-indigo-100 text-indigo-800 ring-1 ring-inset ring-indigo-200"
|
|
36
|
+
: "bg-white text-gray-600 ring-1 ring-inset ring-gray-200 hover:bg-gray-50 hover:text-gray-800"
|
|
37
|
+
}`}
|
|
38
|
+
role="radio"
|
|
39
|
+
aria-checked={isActive}
|
|
40
|
+
>
|
|
41
|
+
{option.label}
|
|
42
|
+
{option.badge !== undefined && option.badge > 0 && (
|
|
43
|
+
<span
|
|
44
|
+
className={`ml-1.5 inline-flex min-w-[1.25rem] justify-center px-1 py-0 text-[10px] rounded-full ${
|
|
45
|
+
isActive
|
|
46
|
+
? "bg-indigo-200 text-indigo-900"
|
|
47
|
+
: "bg-gray-100 text-gray-500"
|
|
48
|
+
}`}
|
|
49
|
+
>
|
|
50
|
+
{option.badge}
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
53
|
+
</button>
|
|
54
|
+
);
|
|
55
|
+
})}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default FilterButtons;
|
|
@@ -4,8 +4,10 @@ import React, {
|
|
|
4
4
|
FunctionComponent,
|
|
5
5
|
MouseEventHandler,
|
|
6
6
|
ReactElement,
|
|
7
|
+
useRef,
|
|
7
8
|
useState,
|
|
8
9
|
} from "react";
|
|
10
|
+
import ReactDOM from "react-dom";
|
|
9
11
|
|
|
10
12
|
export interface GanttChartBar {
|
|
11
13
|
id: string;
|
|
@@ -33,6 +35,9 @@ const Bar: FunctionComponent<ComponentProps> = (
|
|
|
33
35
|
props: ComponentProps,
|
|
34
36
|
): ReactElement => {
|
|
35
37
|
const [isHovered, setIsHovered] = useState(false);
|
|
38
|
+
const barRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
|
|
39
|
+
null,
|
|
40
|
+
) as React.RefObject<HTMLDivElement>;
|
|
36
41
|
|
|
37
42
|
// calculate bar width.
|
|
38
43
|
let barWidth: number =
|
|
@@ -88,6 +93,7 @@ const Bar: FunctionComponent<ComponentProps> = (
|
|
|
88
93
|
}}
|
|
89
94
|
>
|
|
90
95
|
<div
|
|
96
|
+
ref={barRef}
|
|
91
97
|
className="chart-bar h-8 pt-1 pb-1 mt-2.5 mb-2.5 rounded absolute cursor-pointer ml-1 mr-1"
|
|
92
98
|
style={{
|
|
93
99
|
width: `${barWidth}px`,
|
|
@@ -105,13 +111,25 @@ const Bar: FunctionComponent<ComponentProps> = (
|
|
|
105
111
|
props.onSelect(props.bar.id);
|
|
106
112
|
}
|
|
107
113
|
}}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
/>
|
|
115
|
+
{isHovered &&
|
|
116
|
+
props.bar.tooltip &&
|
|
117
|
+
barRef.current &&
|
|
118
|
+
ReactDOM.createPortal(
|
|
119
|
+
<div
|
|
120
|
+
className="bar-tooltip cursor-pointer bg-white shadow-lg rounded p-2 w-fit"
|
|
121
|
+
style={{
|
|
122
|
+
position: "fixed",
|
|
123
|
+
zIndex: 9999,
|
|
124
|
+
top: barRef.current.getBoundingClientRect().bottom + 4,
|
|
125
|
+
left: barRef.current.getBoundingClientRect().left,
|
|
126
|
+
pointerEvents: "none",
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
111
129
|
{props.bar.tooltip}
|
|
112
|
-
</div
|
|
130
|
+
</div>,
|
|
131
|
+
document.body,
|
|
113
132
|
)}
|
|
114
|
-
</div>
|
|
115
133
|
|
|
116
134
|
<div
|
|
117
135
|
className="h-8 pt-1 pb-1 mt-2.5 mb-2.5"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Icon from "../Icon/Icon";
|
|
2
2
|
import IconProp from "../../../Types/Icon/IconProp";
|
|
3
3
|
import TinyFormDocumentation from "../TinyFormDocumentation/TinyFormDocumentation";
|
|
4
|
+
import DOMPurify from "dompurify";
|
|
4
5
|
import React, {
|
|
5
6
|
FunctionComponent,
|
|
6
7
|
ReactElement,
|
|
@@ -416,9 +417,11 @@ const MarkdownEditor: FunctionComponent<ComponentProps> = (
|
|
|
416
417
|
htmlContent = `<p class="mb-4">${htmlContent}</p>`;
|
|
417
418
|
}
|
|
418
419
|
|
|
420
|
+
const sanitizedContent: string = DOMPurify.sanitize(htmlContent);
|
|
421
|
+
|
|
419
422
|
return (
|
|
420
423
|
<div className="p-4 min-h-32 bg-white prose prose-sm max-w-none">
|
|
421
|
-
<div dangerouslySetInnerHTML={{ __html:
|
|
424
|
+
<div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
|
|
422
425
|
</div>
|
|
423
426
|
);
|
|
424
427
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ComponentProps {
|
|
4
|
+
label: string;
|
|
5
|
+
value: number; // percentage 0-100
|
|
6
|
+
valueLabel?: string | undefined;
|
|
7
|
+
secondaryLabel?: string | undefined;
|
|
8
|
+
heightClassName?: string | undefined;
|
|
9
|
+
className?: string | undefined;
|
|
10
|
+
labelWidthClassName?: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getBarColor(percent: number): string {
|
|
14
|
+
if (percent > 80) {
|
|
15
|
+
return "bg-red-500";
|
|
16
|
+
}
|
|
17
|
+
if (percent > 60) {
|
|
18
|
+
return "bg-amber-500";
|
|
19
|
+
}
|
|
20
|
+
return "bg-emerald-500";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ResourceUsageBar: FunctionComponent<ComponentProps> = (
|
|
24
|
+
props: ComponentProps,
|
|
25
|
+
): ReactElement => {
|
|
26
|
+
const percent: number = Math.min(Math.max(props.value, 0), 100);
|
|
27
|
+
const heightClass: string = props.heightClassName || "h-2";
|
|
28
|
+
const labelWidthClass: string = props.labelWidthClassName || "w-40";
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className={`flex items-center gap-3 ${props.className || ""}`}>
|
|
32
|
+
<div
|
|
33
|
+
className={`${labelWidthClass} truncate text-sm text-gray-800 font-medium`}
|
|
34
|
+
title={props.label}
|
|
35
|
+
>
|
|
36
|
+
{props.label}
|
|
37
|
+
</div>
|
|
38
|
+
{props.secondaryLabel && (
|
|
39
|
+
<span className="inline-flex px-1.5 py-0.5 text-xs rounded bg-blue-50 text-blue-700">
|
|
40
|
+
{props.secondaryLabel}
|
|
41
|
+
</span>
|
|
42
|
+
)}
|
|
43
|
+
<div className={`flex-1 bg-gray-100 rounded-full ${heightClass}`}>
|
|
44
|
+
<div
|
|
45
|
+
className={`${heightClass} rounded-full transition-all duration-300 ${getBarColor(percent)}`}
|
|
46
|
+
style={{ width: `${percent}%` }}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
{props.valueLabel && (
|
|
50
|
+
<span className="text-xs text-gray-600 w-16 text-right font-medium tabular-nums">
|
|
51
|
+
{props.valueLabel}
|
|
52
|
+
</span>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default ResourceUsageBar;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React, { FunctionComponent, ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
export interface StackedProgressBarSegment {
|
|
4
|
+
value: number;
|
|
5
|
+
color: string; // Tailwind bg class, e.g. "bg-green-500"
|
|
6
|
+
label: string;
|
|
7
|
+
tooltip?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ComponentProps {
|
|
11
|
+
segments: Array<StackedProgressBarSegment>;
|
|
12
|
+
totalValue?: number | undefined;
|
|
13
|
+
heightClassName?: string | undefined;
|
|
14
|
+
showLegend?: boolean | undefined;
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const StackedProgressBar: FunctionComponent<ComponentProps> = (
|
|
19
|
+
props: ComponentProps,
|
|
20
|
+
): ReactElement => {
|
|
21
|
+
const total: number =
|
|
22
|
+
props.totalValue ||
|
|
23
|
+
props.segments.reduce((sum: number, seg: StackedProgressBarSegment) => {
|
|
24
|
+
return sum + seg.value;
|
|
25
|
+
}, 0);
|
|
26
|
+
|
|
27
|
+
const heightClass: string = props.heightClassName || "h-4";
|
|
28
|
+
const showLegend: boolean = props.showLegend !== false;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className={props.className || ""}>
|
|
32
|
+
<div
|
|
33
|
+
className={`flex ${heightClass} rounded-full overflow-hidden bg-gray-100`}
|
|
34
|
+
role="progressbar"
|
|
35
|
+
aria-label="Stacked progress bar"
|
|
36
|
+
>
|
|
37
|
+
{props.segments.map(
|
|
38
|
+
(segment: StackedProgressBarSegment, index: number) => {
|
|
39
|
+
if (segment.value <= 0 || total <= 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const widthPercent: number = (segment.value / total) * 100;
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
key={index}
|
|
46
|
+
className={`${segment.color} ${heightClass} transition-all duration-300`}
|
|
47
|
+
style={{ width: `${widthPercent}%` }}
|
|
48
|
+
title={segment.tooltip || `${segment.label}: ${segment.value}`}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
)}
|
|
53
|
+
</div>
|
|
54
|
+
{showLegend && (
|
|
55
|
+
<div className="flex flex-wrap gap-x-5 gap-y-1 mt-2.5">
|
|
56
|
+
{props.segments
|
|
57
|
+
.filter((seg: StackedProgressBarSegment) => {
|
|
58
|
+
return seg.value > 0;
|
|
59
|
+
})
|
|
60
|
+
.map((segment: StackedProgressBarSegment, index: number) => {
|
|
61
|
+
return (
|
|
62
|
+
<div key={index} className="flex items-center gap-1.5">
|
|
63
|
+
<span
|
|
64
|
+
className={`inline-block w-2.5 h-2.5 rounded-full ${segment.color}`}
|
|
65
|
+
/>
|
|
66
|
+
<span className="text-sm text-gray-600">
|
|
67
|
+
{segment.label}{" "}
|
|
68
|
+
<span className="font-medium text-gray-800">
|
|
69
|
+
({segment.value})
|
|
70
|
+
</span>
|
|
71
|
+
</span>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default StackedProgressBar;
|