@oneuptime/common 10.0.34 → 10.0.36
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 +3 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +640 -0
- package/Server/API/IPWhitelistAPI.ts +31 -0
- package/Server/API/Index.ts +2 -0
- package/Server/EnvironmentConfig.ts +2 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1774000000000-MigrationName.ts +80 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1774000000001-MigrationName.ts +137 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/KubernetesClusterService.ts +109 -0
- package/Server/Services/UserService.ts +6 -0
- package/Server/Services/UserSessionService.ts +23 -0
- package/Server/Types/Markdown.ts +11 -3
- package/Server/Utils/Express.ts +0 -6
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +4 -1
- package/Server/Utils/StartServer.ts +7 -3
- package/Server/Utils/VM/VMRunner.ts +3 -0
- package/Types/Code/CodeType.ts +1 -1
- package/Types/Icon/IconProp.ts +1 -0
- 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/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/Icon/Icon.tsx +51 -0
- package/UI/Components/LogsViewer/LogsViewer.tsx +2 -0
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +25 -0
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +4 -1
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +85 -53
- package/UI/Components/Navbar/NavBarMenu.tsx +2 -2
- 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/Utils/Traces/CriticalPath.ts +348 -0
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +661 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -0
- package/build/dist/Server/API/IPWhitelistAPI.js +24 -0
- package/build/dist/Server/API/IPWhitelistAPI.js.map +1 -0
- package/build/dist/Server/API/Index.js +2 -0
- package/build/dist/Server/API/Index.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +1 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000000-MigrationName.js +35 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000000-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000001-MigrationName.js +52 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1774000000001-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +117 -0
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -0
- package/build/dist/Server/Services/UserService.js +5 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSessionService.js +20 -0
- package/build/dist/Server/Services/UserSessionService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +10 -2
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Express.js +1 -42
- package/build/dist/Server/Utils/Express.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/Server/Utils/StartServer.js +4 -3
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +3 -0
- package/build/dist/Server/Utils/VM/VMRunner.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/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.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/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/Icon/Icon.js +27 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +4 -0
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.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/Markdown.tsx/MarkdownViewer.js +50 -43
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenu.js +2 -2
- 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/build/dist/Utils/Traces/CriticalPath.js +240 -0
- package/build/dist/Utils/Traces/CriticalPath.js.map +1 -0
- package/package.json +1 -1
package/Types/Permission.ts
CHANGED
|
@@ -705,6 +705,11 @@ enum Permission {
|
|
|
705
705
|
DeleteAlertSeverity = "DeleteAlertSeverity",
|
|
706
706
|
ReadAlertSeverity = "ReadAlertSeverity",
|
|
707
707
|
|
|
708
|
+
CreateKubernetesCluster = "CreateKubernetesCluster",
|
|
709
|
+
DeleteKubernetesCluster = "DeleteKubernetesCluster",
|
|
710
|
+
EditKubernetesCluster = "EditKubernetesCluster",
|
|
711
|
+
ReadKubernetesCluster = "ReadKubernetesCluster",
|
|
712
|
+
|
|
708
713
|
CreateService = "CreateService",
|
|
709
714
|
DeleteService = "DeleteService",
|
|
710
715
|
EditService = "EditService",
|
|
@@ -4324,6 +4329,43 @@ export class PermissionHelper {
|
|
|
4324
4329
|
group: PermissionGroup.AIAgent,
|
|
4325
4330
|
},
|
|
4326
4331
|
|
|
4332
|
+
{
|
|
4333
|
+
permission: Permission.CreateKubernetesCluster,
|
|
4334
|
+
title: "Create Kubernetes Cluster",
|
|
4335
|
+
description:
|
|
4336
|
+
"This permission can create Kubernetes Cluster in this project.",
|
|
4337
|
+
isAssignableToTenant: true,
|
|
4338
|
+
isAccessControlPermission: true,
|
|
4339
|
+
group: PermissionGroup.Telemetry,
|
|
4340
|
+
},
|
|
4341
|
+
{
|
|
4342
|
+
permission: Permission.DeleteKubernetesCluster,
|
|
4343
|
+
title: "Delete Kubernetes Cluster",
|
|
4344
|
+
description:
|
|
4345
|
+
"This permission can delete Kubernetes Cluster of this project.",
|
|
4346
|
+
isAssignableToTenant: true,
|
|
4347
|
+
isAccessControlPermission: false,
|
|
4348
|
+
group: PermissionGroup.Telemetry,
|
|
4349
|
+
},
|
|
4350
|
+
{
|
|
4351
|
+
permission: Permission.EditKubernetesCluster,
|
|
4352
|
+
title: "Edit Kubernetes Cluster",
|
|
4353
|
+
description:
|
|
4354
|
+
"This permission can edit Kubernetes Cluster of this project.",
|
|
4355
|
+
isAssignableToTenant: true,
|
|
4356
|
+
isAccessControlPermission: false,
|
|
4357
|
+
group: PermissionGroup.Telemetry,
|
|
4358
|
+
},
|
|
4359
|
+
{
|
|
4360
|
+
permission: Permission.ReadKubernetesCluster,
|
|
4361
|
+
title: "Read Kubernetes Cluster",
|
|
4362
|
+
description:
|
|
4363
|
+
"This permission can read Kubernetes Cluster of this project.",
|
|
4364
|
+
isAssignableToTenant: true,
|
|
4365
|
+
isAccessControlPermission: false,
|
|
4366
|
+
group: PermissionGroup.Telemetry,
|
|
4367
|
+
},
|
|
4368
|
+
|
|
4327
4369
|
{
|
|
4328
4370
|
permission: Permission.CreateService,
|
|
4329
4371
|
title: "Create Service",
|
|
@@ -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;
|
|
@@ -2733,6 +2733,57 @@ const Icon: FunctionComponent<ComponentProps> = ({
|
|
|
2733
2733
|
d="m15 11.25 1.5 1.5.75-.75V8.758l2.276-.61a3 3 0 1 0-3.675-3.675l-.61 2.277H12l-.75.75 1.5 1.5M15 11.25l-8.47 8.47c-.34.34-.8.53-1.28.53s-.94-.19-1.28-.53a1.818 1.818 0 0 1 0-2.56l8.47-8.47M15 11.25 12 8.25"
|
|
2734
2734
|
/>,
|
|
2735
2735
|
);
|
|
2736
|
+
} else if (icon === IconProp.Kubernetes) {
|
|
2737
|
+
// Kubernetes helm wheel — 7-sided shape with 7 spokes, matching the official logo
|
|
2738
|
+
const cx: number = 12;
|
|
2739
|
+
const cy: number = 12;
|
|
2740
|
+
const outerR: number = 9.5;
|
|
2741
|
+
const innerR: number = 2.2;
|
|
2742
|
+
const spokeEnd: number = 8;
|
|
2743
|
+
const sides: number = 7;
|
|
2744
|
+
const offsetAngle: number = -Math.PI / 2; // start from top
|
|
2745
|
+
|
|
2746
|
+
const outerPoints: string[] = [];
|
|
2747
|
+
const spokes: React.ReactElement[] = [];
|
|
2748
|
+
|
|
2749
|
+
for (let i: number = 0; i < sides; i++) {
|
|
2750
|
+
const angle: number = offsetAngle + (2 * Math.PI * i) / sides;
|
|
2751
|
+
const ox: number = cx + outerR * Math.cos(angle);
|
|
2752
|
+
const oy: number = cy + outerR * Math.sin(angle);
|
|
2753
|
+
outerPoints.push(`${ox.toFixed(2)},${oy.toFixed(2)}`);
|
|
2754
|
+
|
|
2755
|
+
const sx: number = cx + innerR * Math.cos(angle);
|
|
2756
|
+
const sy: number = cy + innerR * Math.sin(angle);
|
|
2757
|
+
const ex: number = cx + spokeEnd * Math.cos(angle);
|
|
2758
|
+
const ey: number = cy + spokeEnd * Math.sin(angle);
|
|
2759
|
+
|
|
2760
|
+
spokes.push(
|
|
2761
|
+
<line
|
|
2762
|
+
key={`spoke-${i}`}
|
|
2763
|
+
x1={sx.toFixed(2)}
|
|
2764
|
+
y1={sy.toFixed(2)}
|
|
2765
|
+
x2={ex.toFixed(2)}
|
|
2766
|
+
y2={ey.toFixed(2)}
|
|
2767
|
+
stroke="currentColor"
|
|
2768
|
+
strokeWidth="1.5"
|
|
2769
|
+
strokeLinecap="round"
|
|
2770
|
+
/>,
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
return getSvgWrapper(
|
|
2775
|
+
<>
|
|
2776
|
+
<polygon
|
|
2777
|
+
points={outerPoints.join(" ")}
|
|
2778
|
+
fill="none"
|
|
2779
|
+
stroke="currentColor"
|
|
2780
|
+
strokeWidth="1.5"
|
|
2781
|
+
strokeLinejoin="round"
|
|
2782
|
+
/>
|
|
2783
|
+
<circle cx={cx} cy={cy} r={innerR} />
|
|
2784
|
+
{spokes}
|
|
2785
|
+
</>,
|
|
2786
|
+
);
|
|
2736
2787
|
}
|
|
2737
2788
|
|
|
2738
2789
|
return <></>;
|
|
@@ -102,6 +102,7 @@ export interface ComponentProps {
|
|
|
102
102
|
onEditSavedView?: ((viewId: string) => void) | undefined;
|
|
103
103
|
onDeleteSavedView?: ((viewId: string) => void) | undefined;
|
|
104
104
|
onUpdateCurrentSavedView?: (() => void) | undefined;
|
|
105
|
+
onShowDocumentation?: (() => void) | undefined;
|
|
105
106
|
viewMode?: LogsViewMode | undefined;
|
|
106
107
|
onViewModeChange?: ((mode: LogsViewMode) => void) | undefined;
|
|
107
108
|
analyticsServiceIds?: Array<string> | undefined;
|
|
@@ -805,6 +806,7 @@ const LogsViewer: FunctionComponent<ComponentProps> = (
|
|
|
805
806
|
return !prev;
|
|
806
807
|
});
|
|
807
808
|
},
|
|
809
|
+
onShowDocumentation: props.onShowDocumentation,
|
|
808
810
|
};
|
|
809
811
|
|
|
810
812
|
const showSidebar: boolean =
|
|
@@ -37,6 +37,7 @@ export interface LogsViewerToolbarProps {
|
|
|
37
37
|
onExportJSON?: (() => void) | undefined;
|
|
38
38
|
showKeyboardShortcuts?: boolean | undefined;
|
|
39
39
|
onToggleKeyboardShortcuts?: (() => void) | undefined;
|
|
40
|
+
onShowDocumentation?: (() => void) | undefined;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
const LogsViewerToolbar: FunctionComponent<LogsViewerToolbarProps> = (
|
|
@@ -155,6 +156,30 @@ const LogsViewerToolbar: FunctionComponent<LogsViewerToolbarProps> = (
|
|
|
155
156
|
/>
|
|
156
157
|
)}
|
|
157
158
|
|
|
159
|
+
{props.onShowDocumentation && (
|
|
160
|
+
<button
|
|
161
|
+
type="button"
|
|
162
|
+
className="inline-flex items-center gap-1.5 rounded-md border border-gray-200 bg-white px-2.5 py-1.5 text-xs font-medium text-gray-700 shadow-sm transition-colors hover:border-gray-300 hover:bg-gray-50"
|
|
163
|
+
onClick={props.onShowDocumentation}
|
|
164
|
+
title="Setup Documentation"
|
|
165
|
+
>
|
|
166
|
+
<svg
|
|
167
|
+
className="h-3.5 w-3.5"
|
|
168
|
+
fill="none"
|
|
169
|
+
viewBox="0 0 24 24"
|
|
170
|
+
strokeWidth={1.5}
|
|
171
|
+
stroke="currentColor"
|
|
172
|
+
>
|
|
173
|
+
<path
|
|
174
|
+
strokeLinecap="round"
|
|
175
|
+
strokeLinejoin="round"
|
|
176
|
+
d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"
|
|
177
|
+
/>
|
|
178
|
+
</svg>
|
|
179
|
+
Docs
|
|
180
|
+
</button>
|
|
181
|
+
)}
|
|
182
|
+
|
|
158
183
|
{props.onToggleKeyboardShortcuts && (
|
|
159
184
|
<div className="relative">
|
|
160
185
|
<button
|
|
@@ -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
|
};
|