UNPKG

@kinvolk/headlamp-plugin

Version:

The needed infrastructure for building Headlamp plugins.

130 lines (129 loc) 5.67 kB
import { jsx as _jsx, jsxs as _jsxs } 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 Box from '@mui/material/Box'; import { JSONPath } from 'jsonpath-plus'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import CRD from '../../lib/k8s/crd'; import { localeDate } from '../../lib/util'; import { CreateResourceButton, Link, Loader, PageGrid, SectionHeader } from '../common'; import BackLink from '../common/BackLink'; import Empty from '../common/EmptyContent'; import ResourceListView from '../common/Resource/ResourceListView'; 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(CustomResourceListRenderer, { 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 CustomResourceListRenderer(props) { const { crd } = props; const { t } = useTranslation('glossary'); const CRClass = crd.makeCRClass(); return (_jsxs(PageGrid, { children: [_jsx(BackLink, {}), _jsx(SectionHeader, { title: crd.spec.names.kind, titleSideActions: [ _jsx(CreateResourceButton, { resourceClass: CRClass, resourceName: crd.spec.names.kind }), ], actions: [ _jsx(Box, { mr: 2, children: _jsx(Link, { routeName: "crd", params: { name: crd.metadata.name }, activeCluster: crd.cluster, children: t('glossary|CRD: {{ crdName }}', { crdName: crd.metadata.name }) }) }), ] }), _jsx(CustomResourceListTable, { crd: crd })] })); } 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 = '' } = props; const apiGroup = React.useMemo(() => { return crd.getMainAPIGroup(); }, [crd]); const CRClass = React.useMemo(() => { return crd.makeCRClass(); }, [crd]); if (!CRClass) { return _jsx(Empty, { children: t('translation|No custom resources found') }); } 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 }), }, ...additionalPrinterCols, 'age', ]; if (crd.isNamespacedScope) { colsToDisplay.splice(1, 0, 'namespace'); } return colsToDisplay; }, [crd, additionalPrinterCols]); return (_jsx(ResourceListView, { title: title, headerProps: { noNamespaceFilter: !crd.isNamespaced, titleSideActions: [], }, resourceClass: CRClass, columns: cols })); }