UNPKG

@kinvolk/headlamp-plugin

Version:

The needed infrastructure for building Headlamp plugins.

122 lines (121 loc) 5.26 kB
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 })); }