@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
122 lines (121 loc) • 5.26 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
/*
* Copyright 2025 The Kubernetes Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { JSONPath } from 'jsonpath-plus';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { useSelectedClusters } from '../../lib/k8s';
import CRD from '../../lib/k8s/crd';
import { localeDate } from '../../lib/util';
import { EmptyContent as Empty, Link, Loader, ResourceListView } from '../common';
export default function CustomResourceList() {
const { t } = useTranslation(['glossary', 'translation']);
const { crd: crdName } = useParams();
const [crd, error] = CRD.useGet(crdName);
if (!crd && !error) {
return _jsx(Loader, { title: t('translation|Loading custom resource definition') });
}
if (!!error) {
return (_jsx(Empty, { color: "error", children: t('translation|Error getting custom resource definition {{ crdName }}: {{ errorMessage }}', {
crdName,
errorMessage: error,
}) }));
}
return _jsx(CustomResourceListTable, { crd: crd });
}
function CustomResourceLink(props) {
const { resource, crd, ...otherProps } = props;
return (_jsx(Link, { sx: { cursor: 'pointer' }, routeName: "customresource", params: {
crName: resource.metadata.name,
crd: crd.metadata.name,
namespace: resource.metadata.namespace || '-',
}, activeCluster: resource.cluster, ...otherProps, children: resource.metadata.name }));
}
function getValueWithJSONPath(item, jsonPath) {
let value;
try {
// Extract the value from the json item
value = JSONPath({ path: '$' + jsonPath, json: item.jsonData });
}
catch (err) {
console.error(`Failed to get value from JSONPath ${jsonPath} on CR item ${item}`);
}
// Make sure the value will be represented in string form (to account for
// e.g. cases where we may get an array).
return value?.toString() || '';
}
export function CustomResourceListTable(props) {
const { t } = useTranslation(['glossary', 'translation']);
const { crd, title = crd.spec.names.kind, includeCRDLink } = props;
const apiGroup = React.useMemo(() => {
return crd.getMainAPIGroup();
}, [crd]);
const CRClass = React.useMemo(() => {
return crd.makeCRClass();
}, [crd]);
const clusters = useSelectedClusters();
const isMultiCluster = clusters.length > 1;
const additionalPrinterCols = React.useMemo(() => {
const currentVersion = apiGroup[1];
const colsFromSpec = crd.jsonData.spec.versions.find((version) => version.name === currentVersion)?.additionalPrinterColumns || [];
const cols = [];
for (let i = 0; i < colsFromSpec.length; i++) {
const idx = i;
const colSpec = colsFromSpec[idx];
// Skip creation date because we already show it by default
if (colSpec.jsonPath === '.metadata.creationTimestamp') {
continue;
}
cols.push({
label: colSpec.name,
getValue: resource => {
let value = getValueWithJSONPath(resource, colSpec.jsonPath);
if (colSpec.type === 'date') {
value = localeDate(new Date(value));
}
return value;
},
});
}
return cols;
}, [crd, apiGroup]);
const cols = React.useMemo(() => {
const colsToDisplay = [
{
label: t('translation|Name'),
getValue: resource => resource.metadata.name,
render: resource => _jsx(CustomResourceLink, { resource: resource, crd: crd }),
},
...(isMultiCluster ? ['cluster'] : []),
...additionalPrinterCols,
'labels',
'age',
];
if (crd.isNamespacedScope) {
colsToDisplay.splice(1, 0, 'namespace');
}
return colsToDisplay;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [crd, additionalPrinterCols, isMultiCluster]);
if (!CRClass) {
return _jsx(Empty, { children: t('translation|No custom resources found') });
}
return (_jsx(ResourceListView, { title: title, headerProps: {
noNamespaceFilter: !crd.isNamespacedScope,
subtitle: (includeCRDLink ?? true) && (_jsx(Link, { routeName: "crd", params: { name: crd.metadata.name }, activeCluster: crd.cluster, children: t('glossary|CRD: {{ crdName }}', { crdName: crd.metadata.name }) })),
}, resourceClass: CRClass, columns: cols }));
}