mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Enhance KubernetesResourceTable with dynamic column rendering and integrate Table component
This commit is contained in:
@@ -3,9 +3,14 @@ import KubernetesResourceUtils, {
|
||||
KubernetesResource,
|
||||
} from "../../Pages/Kubernetes/Utils/KubernetesResourceUtils";
|
||||
import Card from "Common/UI/Components/Card/Card";
|
||||
import Table from "Common/UI/Components/Table/Table";
|
||||
import FieldType from "Common/UI/Components/Types/FieldType";
|
||||
import Link from "Common/UI/Components/Link/Link";
|
||||
import SortOrder from "Common/Types/BaseDatabase/SortOrder";
|
||||
import Route from "Common/Types/API/Route";
|
||||
import Column from "Common/UI/Components/Table/Types/Column";
|
||||
|
||||
export interface Column {
|
||||
export interface ResourceColumn {
|
||||
title: string;
|
||||
key: string;
|
||||
getValue?: (resource: KubernetesResource) => string;
|
||||
@@ -15,7 +20,7 @@ export interface ComponentProps {
|
||||
resources: Array<KubernetesResource>;
|
||||
title: string;
|
||||
description: string;
|
||||
columns?: Array<Column>;
|
||||
columns?: Array<ResourceColumn>;
|
||||
showNamespace?: boolean;
|
||||
getViewRoute?: (resource: KubernetesResource) => Route;
|
||||
emptyMessage?: string;
|
||||
@@ -26,120 +31,135 @@ const KubernetesResourceTable: FunctionComponent<ComponentProps> = (
|
||||
): ReactElement => {
|
||||
const showNamespace: boolean = props.showNamespace !== false;
|
||||
|
||||
const tableColumns: Array<Column<KubernetesResource>> = [
|
||||
{
|
||||
title: "Name",
|
||||
type: FieldType.Element,
|
||||
key: "name",
|
||||
disableSort: true,
|
||||
getElement: (resource: KubernetesResource): ReactElement => {
|
||||
return (
|
||||
<span className="font-medium text-gray-900">{resource.name}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (showNamespace) {
|
||||
tableColumns.push({
|
||||
title: "Namespace",
|
||||
type: FieldType.Element,
|
||||
key: "namespace",
|
||||
disableSort: true,
|
||||
getElement: (resource: KubernetesResource): ReactElement => {
|
||||
return (
|
||||
<span className="inline-flex px-2 py-0.5 text-xs font-medium rounded bg-blue-50 text-blue-700">
|
||||
{resource.namespace || "default"}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (props.columns) {
|
||||
for (const col of props.columns) {
|
||||
tableColumns.push({
|
||||
title: col.title,
|
||||
type: FieldType.Element,
|
||||
key: col.key as keyof KubernetesResource,
|
||||
disableSort: true,
|
||||
getElement: (resource: KubernetesResource): ReactElement => {
|
||||
const value: string = col.getValue
|
||||
? col.getValue(resource)
|
||||
: resource.additionalAttributes[col.key] || "";
|
||||
return <span>{value}</span>;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tableColumns.push(
|
||||
{
|
||||
title: "CPU",
|
||||
type: FieldType.Element,
|
||||
key: "cpuUtilization",
|
||||
disableSort: true,
|
||||
getElement: (resource: KubernetesResource): ReactElement => {
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex px-2 py-0.5 text-xs font-medium rounded ${
|
||||
resource.cpuUtilization !== null && resource.cpuUtilization > 80
|
||||
? "bg-red-50 text-red-700"
|
||||
: resource.cpuUtilization !== null &&
|
||||
resource.cpuUtilization > 60
|
||||
? "bg-yellow-50 text-yellow-700"
|
||||
: "bg-green-50 text-green-700"
|
||||
}`}
|
||||
>
|
||||
{KubernetesResourceUtils.formatCpuValue(resource.cpuUtilization)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Memory",
|
||||
type: FieldType.Element,
|
||||
key: "memoryUsageBytes",
|
||||
disableSort: true,
|
||||
getElement: (resource: KubernetesResource): ReactElement => {
|
||||
return (
|
||||
<span>
|
||||
{KubernetesResourceUtils.formatMemoryValue(
|
||||
resource.memoryUsageBytes,
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (props.getViewRoute) {
|
||||
tableColumns.push({
|
||||
title: "Actions",
|
||||
type: FieldType.Element,
|
||||
key: "name",
|
||||
disableSort: true,
|
||||
getElement: (resource: KubernetesResource): ReactElement => {
|
||||
return (
|
||||
<Link
|
||||
to={props.getViewRoute!(resource)}
|
||||
className="text-indigo-600 hover:text-indigo-900 font-medium"
|
||||
>
|
||||
View
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Card title={props.title} description={props.description}>
|
||||
{props.resources.length === 0 ? (
|
||||
<p className="text-gray-500 text-sm p-4">
|
||||
{props.emptyMessage ||
|
||||
"No resources found. Resources will appear here once the kubernetes-agent is sending data."}
|
||||
</p>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Name
|
||||
</th>
|
||||
{showNamespace && (
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Namespace
|
||||
</th>
|
||||
)}
|
||||
{props.columns?.map((column: Column) => {
|
||||
return (
|
||||
<th
|
||||
key={column.key}
|
||||
className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
{column.title}
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
CPU
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Memory
|
||||
</th>
|
||||
{props.getViewRoute && (
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Actions
|
||||
</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{props.resources.map(
|
||||
(resource: KubernetesResource, index: number) => {
|
||||
return (
|
||||
<tr
|
||||
key={index}
|
||||
className="hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<td className="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
{resource.name}
|
||||
</td>
|
||||
{showNamespace && (
|
||||
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
|
||||
<span className="inline-flex px-2 py-0.5 text-xs font-medium rounded bg-blue-50 text-blue-700">
|
||||
{resource.namespace || "default"}
|
||||
</span>
|
||||
</td>
|
||||
)}
|
||||
{props.columns?.map((column: Column) => {
|
||||
return (
|
||||
<td
|
||||
key={column.key}
|
||||
className="px-4 py-3 whitespace-nowrap text-sm text-gray-500"
|
||||
>
|
||||
{column.getValue
|
||||
? column.getValue(resource)
|
||||
: resource.additionalAttributes[column.key] ||
|
||||
""}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
|
||||
<span
|
||||
className={`inline-flex px-2 py-0.5 text-xs font-medium rounded ${
|
||||
resource.cpuUtilization !== null &&
|
||||
resource.cpuUtilization > 80
|
||||
? "bg-red-50 text-red-700"
|
||||
: resource.cpuUtilization !== null &&
|
||||
resource.cpuUtilization > 60
|
||||
? "bg-yellow-50 text-yellow-700"
|
||||
: "bg-green-50 text-green-700"
|
||||
}`}
|
||||
>
|
||||
{KubernetesResourceUtils.formatCpuValue(
|
||||
resource.cpuUtilization,
|
||||
)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
|
||||
{KubernetesResourceUtils.formatMemoryValue(
|
||||
resource.memoryUsageBytes,
|
||||
)}
|
||||
</td>
|
||||
{props.getViewRoute && (
|
||||
<td className="px-4 py-3 whitespace-nowrap text-sm">
|
||||
<a
|
||||
href={props.getViewRoute(resource).toString()}
|
||||
className="text-indigo-600 hover:text-indigo-900 font-medium"
|
||||
>
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
<Table<KubernetesResource>
|
||||
id={`kubernetes-${props.title.toLowerCase().replace(/\s+/g, "-")}-table`}
|
||||
columns={tableColumns}
|
||||
data={props.resources}
|
||||
singularLabel={props.title}
|
||||
pluralLabel={props.title}
|
||||
isLoading={false}
|
||||
error=""
|
||||
disablePagination={true}
|
||||
currentPageNumber={1}
|
||||
totalItemsCount={props.resources.length}
|
||||
itemsOnPage={props.resources.length}
|
||||
onNavigateToPage={() => {}}
|
||||
sortBy={null}
|
||||
sortOrder={SortOrder.Ascending}
|
||||
onSortChanged={() => {}}
|
||||
noItemsMessage={
|
||||
props.emptyMessage ||
|
||||
"No resources found. Resources will appear here once the kubernetes-agent is sending data."
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,8 +4,7 @@ If you are using OneUptime.com and want to whitelist our IP's for security reaso
|
||||
|
||||
Please whitelist the following IP's in your firewall to allow oneuptime.com to reach your resources.
|
||||
|
||||
- 172.174.206.132
|
||||
- 57.151.99.117
|
||||
{{IP_WHITELIST}}
|
||||
|
||||
These IP's can change, we will let you know in advance if this happens.
|
||||
|
||||
@@ -21,7 +20,7 @@ This returns a JSON response:
|
||||
|
||||
```json
|
||||
{
|
||||
"ipWhitelist": ["172.174.206.132", "57.151.99.117"]
|
||||
"ipWhitelist": ["<list of IPs>"]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import Response from "Common/Server/Utils/Response";
|
||||
import LocalFile from "Common/Server/Utils/LocalFile";
|
||||
import logger from "Common/Server/Utils/Logger";
|
||||
import "ejs";
|
||||
import { IsBillingEnabled } from "Common/Server/EnvironmentConfig";
|
||||
import { IsBillingEnabled, IpWhitelist } from "Common/Server/EnvironmentConfig";
|
||||
|
||||
const DocsFeatureSet: FeatureSet = {
|
||||
init: async (): Promise<void> => {
|
||||
@@ -78,6 +78,21 @@ const DocsFeatureSet: FeatureSet = {
|
||||
// Remove first line (title) from content as it is already present in the navigation
|
||||
contentInMarkdown = contentInMarkdown.split("\n").slice(1).join("\n");
|
||||
|
||||
// Replace dynamic placeholders in markdown content
|
||||
if (contentInMarkdown.includes("{{IP_WHITELIST}}")) {
|
||||
const ipList: string = IpWhitelist
|
||||
? IpWhitelist.split(",")
|
||||
.map((ip: string) => {
|
||||
return `- ${ip.trim()}`;
|
||||
})
|
||||
.filter((line: string) => {
|
||||
return line.length > 2;
|
||||
})
|
||||
.join("\n")
|
||||
: "- No IP addresses configured.";
|
||||
contentInMarkdown = contentInMarkdown.replace("{{IP_WHITELIST}}", ipList);
|
||||
}
|
||||
|
||||
// Render Markdown content to HTML
|
||||
const renderedContent: string =
|
||||
await DocsRender.render(contentInMarkdown);
|
||||
|
||||
Reference in New Issue
Block a user