@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
757 lines (756 loc) • 35.5 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { Icon } from '@iconify/react';
import Editor from '@monaco-editor/react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import IconButton from '@mui/material/IconButton';
import Input from '@mui/material/Input';
import InputLabel from '@mui/material/InputLabel';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import { useTheme } from '@mui/system';
import _, { has } from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath, useLocation } from 'react-router-dom';
import YAML from 'yaml';
import { labelSelectorToQuery, ResourceClasses, useCluster } from '../../../lib/k8s';
import Pod from '../../../lib/k8s/pod';
import { METRIC_REFETCH_INTERVAL_MS, PodMetrics } from '../../../lib/k8s/PodMetrics';
import { createRouteURL } from '../../../lib/router';
import { getThemeName } from '../../../lib/themes';
import { localeDate, useId } from '../../../lib/util';
import { HeadlampEventType, useEventCallback } from '../../../redux/headlampEventSlice';
import { useTypedSelector } from '../../../redux/reducers/reducers';
import { useHasPreviousRoute } from '../../App/RouteSwitcher';
import { SectionBox } from '../../common/SectionBox';
import SimpleTable, { NameValueTable } from '../../common/SimpleTable';
import { DefaultDetailsViewSection, } from '../../DetailsViewSection/detailsViewSectionSlice';
import { PodListRenderer } from '../../pod/List';
import { LightTooltip, Loader, ObjectEventList } from '..';
import BackLink from '../BackLink';
import Empty from '../EmptyContent';
import ErrorBoundary from '../ErrorBoundary';
import InnerTable from '../InnerTable';
import { DateLabel, HoverInfoLabel, StatusLabel, ValueLabel } from '../Label';
import Link from '../Link';
import { metadataStyles } from '.';
import { MainInfoSection } from './MainInfoSection/MainInfoSection';
import { MainInfoHeader } from './MainInfoSection/MainInfoSectionHeader';
import { MetadataDictGrid, MetadataDisplay } from './MetadataDisplay';
import PortForward from './PortForward';
export { MainInfoSection };
export function ResourceLink(props) {
const { resource, routeName = props.resource.kind, routeParams = { ...props.resource.metadata }, name = props.resource.metadata.name, state, } = props;
if (!!resource.cluster) {
routeParams.cluster = resource.cluster;
}
return (_jsx(Link, { routeName: routeName, params: routeParams, state: state, children: name }));
}
/** Renders the different parts that constibute an actual resource's details view.
* Those are: the back link, the header, the main info section, the extra sections, and the events section.
*/
export function DetailsGrid(props) {
const { sectionsFunc, resourceType, name, namespace, cluster, children, withEvents, extraSections, onResourceUpdate, ...otherMainInfoSectionProps } = props;
const selectedCluster = useCluster();
const { t } = useTranslation();
const location = useLocation();
const hasPreviousRoute = useHasPreviousRoute();
const detailViews = useTypedSelector(state => state.detailsViewSection.detailsViewSections);
const detailViewsProcessors = useTypedSelector(state => state.detailsViewSection.detailsViewSectionsProcessors);
const dispatchHeadlampEvent = useEventCallback();
// This component used to have a MainInfoSection with all these props passed to it, so we're
// using them to accomplish the same behavior.
const { extraInfo, actions, noDefaultActions, headerStyle, backLink, title, headerSection } = otherMainInfoSectionProps;
const [item, error] = resourceType.useGet(name, namespace, {
cluster: cluster ?? selectedCluster ?? undefined,
});
const prevItemRef = React.useRef({});
React.useEffect(() => {
if (item) {
dispatchHeadlampEvent({
type: HeadlampEventType.DETAILS_VIEW,
data: {
title: item?.jsonData.kind,
resource: item,
error: error || undefined,
},
});
}
}, [item]);
React.useEffect(() => {
// We cannot call this callback more than once on each version of the item, in order to avoid
// infinite loops.
const prevItem = prevItemRef.current;
if (prevItem?.uid === item?.metadata?.uid &&
prevItem?.version === item?.metadata?.resourceVersion &&
error === prevItem.error) {
return;
}
prevItemRef.current = {
uid: item?.metadata?.uid,
version: item?.metadata?.resourceVersion,
error,
};
onResourceUpdate?.(item, error);
}, [item, error]);
const actualBackLink = React.useMemo(() => {
if (!!backLink || backLink === '') {
return backLink;
}
const stateLink = location.state?.backLink || null;
if (!!stateLink) {
return generatePath(stateLink.pathname);
}
if (!!hasPreviousRoute) {
// Will make it go back to the previous route
return '';
}
let route;
if (!!item) {
route = item.listRoute;
}
else {
try {
route = new resourceType({}).listRoute;
}
catch (err) {
console.error(`Error creating route for details grid (resource type=${resourceType}): ${err}`);
// Let the MainInfoSection handle it.
return undefined;
}
}
return createRouteURL(route);
}, [item]);
const sections = [];
// Back link
if (!!actualBackLink || actualBackLink === '') {
sections.push({
id: DefaultDetailsViewSection.BACK_LINK,
section: _jsx(BackLink, { to: actualBackLink }),
});
}
// Title / Header
sections.push({
id: DefaultDetailsViewSection.MAIN_HEADER,
section: (_jsx(MainInfoHeader, { title: title, resource: item, actions: actions, noDefaultActions: noDefaultActions, headerStyle: headerStyle })),
});
// Error / Loading or Metadata
if (item === null) {
sections.push(!!error
? {
id: DefaultDetailsViewSection.ERROR,
section: (_jsx(Paper, { variant: "outlined", children: _jsx(Empty, { color: "error", children: error.toString() }) })),
}
: {
id: DefaultDetailsViewSection.LOADING,
section: _jsx(Loader, { title: t('translation|Loading resource data') }),
});
}
else {
const mainInfoHeader = typeof headerSection === 'function' ? headerSection(item) : headerSection;
sections.push({
id: DefaultDetailsViewSection.METADATA,
section: (_jsxs(SectionBox, { "aria-busy": item === null, "aria-live": "polite", children: [mainInfoHeader, _jsx(MetadataDisplay, { resource: item, extraRows: extraInfo })] })),
});
}
// Other sections
if (!!sectionsFunc) {
console.info(`Using legacy sectionsFunc in DetailsGrid for ${title || resourceType + '/' + namespace + '/' + name}. Please use the children, or set up a details view processor.`);
sections.push({
id: 'LEGACY_SECTIONS_FUNC',
section: sectionsFunc(item),
});
}
if (!!extraSections) {
let actualExtraSections = [];
if (Array.isArray(extraSections)) {
actualExtraSections = extraSections;
}
else if (typeof extraSections === 'function') {
const extraSectionsResult = extraSections(item) || [];
if (Array.isArray(extraSectionsResult)) {
actualExtraSections = extraSectionsResult;
}
}
sections.push(...actualExtraSections);
}
// Children
if (!!children) {
sections.push({
id: DefaultDetailsViewSection.CHILDREN,
section: children,
});
}
// Plugin appended details views
if (!!detailViews) {
sections.push(...detailViews);
}
// Events
if (withEvents && item) {
sections.push({
id: DefaultDetailsViewSection.EVENTS,
section: _jsx(ObjectEventList, { object: item }),
});
}
let sectionsProcessed = [...sections];
for (const detailViewsProcessor of detailViewsProcessors) {
let processorsSections = sectionsProcessed;
try {
processorsSections = detailViewsProcessor.processor(item, sectionsProcessed);
if (!Array.isArray(processorsSections)) {
throw new Error(`Invalid return value: ${processorsSections}`);
}
}
catch (err) {
console.error(`Error processing details view sections for ${resourceType}/${namespace}/${name}: ${err}`);
continue;
}
sectionsProcessed = processorsSections;
}
return (_jsx(PageGrid, { sx: theme => ({
marginBottom: theme.spacing(2),
}), children: React.Children.toArray(sectionsProcessed.map(section => {
const Section = has(section, 'section')
? section.section
: section;
if (React.isValidElement(Section)) {
return _jsx(ErrorBoundary, { children: Section });
}
else if (Section === null) {
return null;
}
else if (typeof Section === 'function') {
return (_jsx(ErrorBoundary, { children: _jsx(Section, { resource: item }) }));
}
})) }));
}
export function PageGrid(props) {
const { sections = [], children = [], ...other } = props;
const childrenArray = React.Children.toArray(children).concat(React.Children.toArray(sections));
return (_jsx(Grid, { container: true, spacing: 1, justifyContent: "flex-start", alignItems: "stretch", ...other, children: childrenArray.map((section, i) => (_jsx(Grid, { item: true, xs: 12, children: _jsx(Box, { mt: [4, 0, 0], children: section }) }, i))) }));
}
export function SectionGrid(props) {
const { items } = props;
return (_jsx(Grid, { container: true, justifyContent: "space-between", children: items.map((item, i) => {
return (_jsx(Grid, { item: true, md: 12, xs: 12, children: item }, i));
}) }));
}
export function DataField(props) {
const { disableLabel, label, value, onSave, onChange } = props;
// Make sure we reload after a theme change
useTheme();
const themeName = getThemeName();
const [data, setData] = React.useState(value);
const handleChange = (newValue) => {
if (newValue !== undefined) {
setData(newValue);
onChange?.(newValue);
}
};
function handleEditorDidMount(editor) {
const editorElement = editor.getDomNode();
if (!editorElement) {
return;
}
const lineCount = editor.getModel()?.getLineCount() || 1;
if (lineCount < 2) {
editorElement.style.height = '3vh';
}
else if (lineCount <= 10) {
editorElement.style.height = '10vh';
}
else {
editorElement.style.height = '40vh';
}
editor.layout();
}
let language = label.split('.').pop();
if (language !== 'json') {
language = 'yaml';
}
const editorComponent = (_jsx(Editor, { value: data, language: language, onChange: handleChange, onMount: handleEditorDidMount, options: { lineNumbers: 'off', automaticLayout: true }, theme: themeName === 'dark' ? 'vs-dark' : 'light' }));
const content = (_jsxs(Box, { borderTop: 0, border: 1, children: [!disableLabel && (_jsxs(Box, { display: "flex", children: [_jsx(Box, { width: "10%", borderTop: 1, height: '1px' }), _jsx(Box, { pb: 1, mt: -1, px: 0.5, children: _jsx(InputLabel, { children: label }) }), _jsx(Box, { width: "100%", borderTop: 1, height: '1px' })] })), _jsx(Box, { mt: 1, px: 1, pb: 1, children: editorComponent })] }));
return (_jsxs(Box, { children: [content, onSave && (_jsx(Box, { mt: 1, display: "flex", justifyContent: "flex-end", children: _jsx(Button, { variant: "contained", color: "primary", onClick: () => onSave && onSave(data), children: "Save" }) }))] }));
}
export function SecretField(props) {
const { value, ...other } = props;
const [showPassword, setShowPassword] = React.useState(false);
const { t } = useTranslation();
function handleClickShowPassword() {
setShowPassword(!showPassword);
}
return (_jsxs(Grid, { container: true, alignItems: "stretch", spacing: 2, children: [_jsx(Grid, { item: true, children: _jsx(IconButton, { edge: "end", "aria-label": t('toggle field visibility'), onClick: handleClickShowPassword, onMouseDown: event => event.preventDefault(), size: "medium", children: _jsx(Icon, { icon: showPassword ? 'mdi:eye-off' : 'mdi:eye' }) }) }), _jsx(Grid, { item: true, xs: true, children: _jsx(Input, { readOnly: !showPassword, type: "password", fullWidth: true, multiline: showPassword, maxRows: "20", value: showPassword ? value : '******', ...other }) })] }));
}
export function ConditionsTable(props) {
const { resource, showLastUpdate = true } = props;
const { t } = useTranslation(['glossary', 'translation']);
function makeStatusLabel(condition) {
let status = '';
if (condition.type === 'Available') {
status = condition.status === 'True' ? 'success' : 'error';
}
return _jsx(StatusLabel, { status: status, children: condition.type });
}
function getColumns() {
const cols = [
{
label: t('Condition'),
getter: makeStatusLabel,
},
{
label: t('translation|Status'),
getter: condition => condition.status,
},
{
label: t('Last Transition'),
getter: condition => _jsx(DateLabel, { date: condition.lastTransitionTime }),
},
{
label: t('Last Update'),
getter: condition => condition.lastUpdateTime ? _jsx(DateLabel, { date: condition.lastUpdateTime }) : '-',
hide: !showLastUpdate,
},
{
label: t('translation|Reason'),
getter: condition => condition.reason ? (_jsx(HoverInfoLabel, { label: condition.reason, hoverInfo: condition.message })) : ('-'),
},
];
// Allow to filter the columns by using a hide field
return cols.filter(col => !col.hide);
}
return (_jsx(SimpleTable, { data: (resource && resource.status && resource.status.conditions) || [], columns: getColumns() }));
}
export function VolumeMounts(props) {
const { mounts } = props;
const { t } = useTranslation();
if (!mounts) {
return null;
}
return (_jsx(InnerTable, { columns: [
{
label: t('translation|Mount Path'),
getter: (data) => data.mountPath,
},
{
label: t('translation|from'),
getter: (data) => data.name,
},
{
label: t('translation|I/O'),
getter: (data) => (data.readOnly ? 'ReadOnly' : 'ReadWrite'),
},
], data: mounts }));
}
export function LivenessProbes(props) {
const { liveness } = props;
function LivenessProbeItem(props) {
return props.children ? (_jsx(Box, { p: 0.5, children: _jsx(Typography, { sx: metadataStyles, display: "inline", children: props.children }) })) : null;
}
return (_jsxs(Box, { display: "flex", flexDirection: "column", children: [_jsx(LivenessProbeItem, { children: `http-get, path: ${liveness?.httpGet?.path}, port: ${liveness?.httpGet?.port},
scheme: ${liveness?.httpGet?.scheme}` }), _jsx(LivenessProbeItem, { children: liveness?.exec?.command && `exec[${liveness?.exec?.command.join(' ')}]` }), _jsx(LivenessProbeItem, { children: liveness?.successThreshold && `success = ${liveness?.successThreshold}` }), _jsx(LivenessProbeItem, { children: liveness?.failureThreshold && `failure = ${liveness?.failureThreshold}` }), _jsx(LivenessProbeItem, { children: liveness?.initialDelaySeconds && `delay = ${liveness?.initialDelaySeconds}s` }), _jsx(LivenessProbeItem, { children: liveness?.timeoutSeconds && `timeout = ${liveness?.timeoutSeconds}s` }), _jsx(LivenessProbeItem, { children: liveness?.periodSeconds && `period = ${liveness?.periodSeconds}s` })] }));
}
export function ContainerInfo(props) {
const { container, status, resource } = props;
const { t } = useTranslation(['glossary', 'translation']);
const [startedDate, finishDate, lastStateStartedDate, lastStateFinishDate] = React.useMemo(() => {
function getStartedDate(state) {
let startedDate = state?.running?.startedAt || state?.terminated?.startedAt || '';
if (!!startedDate) {
startedDate = localeDate(startedDate);
}
return startedDate;
}
function getFinishDate(state) {
let finishDate = state?.terminated?.finishedAt || '';
if (!!finishDate) {
finishDate = localeDate(finishDate);
}
return finishDate;
}
return [
getStartedDate(status?.state),
getFinishDate(status?.state),
getStartedDate(status?.lastState),
getFinishDate(status?.lastState),
];
}, [status]);
function ContainerStatusLabel(props) {
const { state, container } = props;
const [stateDetails, label, statusType] = React.useMemo(() => {
let stateDetails = null;
let label = t('translation|Ready');
let statusType = '';
if (!state) {
return [stateDetails, label, statusType];
}
if (!!state.waiting) {
stateDetails = state.waiting;
statusType = 'warning';
label = t('translation|Waiting');
}
else if (!!state.running) {
statusType = 'success';
label = t('translation|Running');
}
else if (!!state.terminated) {
stateDetails = state.terminated;
if (state.terminated.exitCode === 0) {
statusType = '';
label = state.terminated.reason;
}
else {
statusType = 'error';
label = t('translation|Error');
}
}
return [stateDetails, label, statusType];
}, [state]);
if (!state || !container) {
return null;
}
const tooltipID = 'container-state-message-' + (container?.name ?? '');
return (_jsx(Box, { children: _jsxs(Box, { children: [_jsx(StatusLabel, { status: statusType, "aria-describedby": !!stateDetails?.message ? tooltipID : undefined, children: label + (stateDetails?.reason ? ` (${stateDetails.reason})` : '') }), !!stateDetails && stateDetails.message && (_jsx(LightTooltip, { role: "tooltip", title: stateDetails.message, interactive: true, id: tooltipID, children: _jsx(Box, { "aria-label": "hidden", display: "inline", px: 1, style: { verticalAlign: 'bottom' }, children: _jsx(Icon, { icon: "mdi:alert-outline", width: "1.3rem", height: "1.3rem", "aria-label": "hidden" }) }) }))] }) }));
}
function StatusValue(props) {
const { rows } = props;
const id = useId('status-value-');
const rowsToDisplay = React.useMemo(() => {
return rows.filter(({ hide }) => !hide);
}, [rows]);
if (rowsToDisplay.length === 0) {
return null;
}
return (_jsx(Box, { children: rowsToDisplay.map(({ name, value }, idx) => {
const rowId = `${id}-${idx}`;
return (_jsxs(Grid, { container: true, spacing: 2, direction: "row", children: [_jsx(Grid, { item: true, children: _jsx(Typography, { id: rowId, color: "textSecondary", children: name }) }), _jsx(Grid, { item: true, children: _jsx(Typography, { "aria-labelledby": rowId, children: value }) })] }, rowId));
}) }));
}
function containerRows() {
const env = {};
(container.env || []).forEach(envVar => {
let value = '';
if (envVar.value) {
value = envVar.value;
}
else if (envVar.valueFrom) {
if (envVar.valueFrom.fieldRef) {
value = envVar.valueFrom.fieldRef.fieldPath;
}
else if (envVar.valueFrom.secretKeyRef) {
value = envVar.valueFrom.secretKeyRef.key;
}
}
env[envVar.name] = value;
});
return [
{
name: container.name,
withHighlightStyle: true,
},
{
name: t('translation|Status'),
value: _jsx(ContainerStatusLabel, { state: status?.state, container: container }),
hide: !status,
},
{
name: t('translation|Exit Code'),
value: status?.state?.terminated?.exitCode,
hide: !status?.state?.terminated,
},
{
name: t('translation|Started'),
value: startedDate,
hide: !startedDate,
},
{
name: t('translation|Finished'),
value: finishDate,
hide: !finishDate,
},
{
name: t('translation|Restart Count'),
value: status?.restartCount,
hide: !status,
},
{
name: t('translation|Last State'),
value: (_jsxs(Grid, { container: true, direction: "column", spacing: 1, children: [_jsx(Grid, { item: true, children: _jsx(ContainerStatusLabel, { state: status?.lastState, container: container }) }), _jsx(Grid, { item: true, children: _jsx(StatusValue, { rows: [
{
name: t('translation|Exit Code'),
value: status?.lastState?.terminated?.exitCode,
hide: !status?.lastState?.terminated,
},
{
name: t('translation|Started'),
value: lastStateStartedDate,
hide: !lastStateStartedDate,
},
{
name: t('translation|Finished'),
value: lastStateFinishDate,
hide: !lastStateFinishDate,
},
] }) })] })),
hide: Object.keys(status?.lastState ?? {}).length === 0,
},
{
name: t('Container ID'),
value: status?.containerID,
hide: !status,
},
{
name: t('Image Pull Policy'),
value: container.imagePullPolicy,
},
{
name: t('Image'),
value: (_jsxs(_Fragment, { children: [_jsx(Typography, { children: container.image }), status?.imageID && (_jsxs(Typography, { sx: theme => ({
paddingTop: theme.spacing(1),
fontSize: '.95rem',
}), children: [_jsx(Typography, { component: "span", style: { fontWeight: 'bold' }, children: "ID:" }), ' ', status?.imageID] }))] })),
},
{
name: t('Args'),
value: container.args && (_jsx(MetadataDictGrid, { dict: container.args, showKeys: false })),
hide: !container.args,
},
{
name: t('Command'),
value: (container.command || []).join(' '),
hide: !container.command,
},
{
name: t('Environment'),
value: _jsx(MetadataDictGrid, { dict: env }),
hide: _.isEmpty(env),
},
{
name: t('Liveness Probes'),
value: _jsx(LivenessProbes, { liveness: container.livenessProbe }),
hide: _.isEmpty(container.livenessProbe),
},
{
name: t('Ports'),
value: (_jsx(Grid, { container: true, children: container.ports?.map(({ containerPort, protocol }, index) => (_jsxs(_Fragment, { children: [_jsx(Grid, { item: true, xs: 12, children: _jsxs(Box, { display: "flex", alignItems: 'center', children: [_jsxs(Box, { px: 0.5, minWidth: 120, children: [_jsx(ValueLabel, { children: `${protocol}:` }), _jsx(ValueLabel, { children: containerPort })] }), !!resource && ['Service', 'Pod'].includes(resource.kind) && (_jsx(PortForward, { containerPort: containerPort, resource: resource }))] }) }, `port_line_${index}`), index < container.ports.length - 1 && (_jsx(Grid, { item: true, xs: 12, children: _jsx(Box, { mt: 2, mb: 2, children: _jsx(Divider, {}) }) }))] }))) })),
hide: _.isEmpty(container.ports),
},
{
name: t('Volume Mounts'),
value: _jsx(VolumeMounts, { mounts: container?.volumeMounts || undefined }),
valueCellProps: { sm: 12 },
hide: _.isEmpty(container?.volumeMounts),
},
];
}
return (_jsx(Box, { pb: 1, children: _jsx(NameValueTable, { rows: containerRows() }) }));
}
export function OwnedPodsSection(props) {
const { resource, hideColumns, noSearch } = props;
let namespace;
if (resource.kind === 'Namespace') {
namespace = resource.metadata.name;
}
else {
namespace = resource.metadata.namespace;
}
const queryData = {
namespace,
labelSelector: resource?.jsonData?.spec?.selector
? labelSelectorToQuery(resource?.jsonData?.spec?.selector)
: '',
fieldSelector: resource.kind === 'Node' ? `spec.nodeName=${resource.metadata.name}` : undefined,
cluster: resource.cluster,
};
const { items: pods, errors } = Pod.useList(queryData);
const { items: podMetrics } = PodMetrics.useList({
...queryData,
refetchInterval: METRIC_REFETCH_INTERVAL_MS,
});
const onlyOneNamespace = !!resource.metadata.namespace || resource.kind === 'Namespace';
const hideNamespaceFilter = onlyOneNamespace || noSearch;
return (_jsx(PodListRenderer, { hideColumns: hideColumns || onlyOneNamespace ? ['namespace'] : undefined, pods: pods, errors: errors, metrics: podMetrics, noNamespaceFilter: hideNamespaceFilter }));
}
export function ContainersSection(props) {
const { resource } = props;
const { t } = useTranslation('glossary');
let title = '…';
function getContainers() {
if (!resource) {
return [];
}
let containers = [];
if (resource.spec) {
if (resource.spec.containers) {
title = t('Containers');
containers = resource.spec.containers;
}
else if (resource.spec.template && resource.spec.template.spec) {
title = t('Container Spec');
containers = resource.spec.template.spec.containers;
}
}
return containers;
}
function getInitContainers() {
return resource?.spec?.initContainers || [];
}
function getEphemeralContainers() {
return resource?.spec?.ephemeralContainers || [];
}
function getStatuses(statusKind) {
if (!resource || resource.kind !== 'Pod') {
return {};
}
const statuses = {};
(resource.status[statusKind] || []).forEach(containerStatus => {
const { name, ...status } = containerStatus;
statuses[name] = { ...status };
});
return statuses;
}
const containers = getContainers();
const initContainers = getInitContainers();
const ephemContainers = getEphemeralContainers();
const statuses = getStatuses('containerStatuses');
const initStatuses = getStatuses('initContainerStatuses');
const ephemStatuses = getStatuses('ephemeralContainerStatuses');
const numContainers = containers.length;
return (_jsxs(_Fragment, { children: [_jsx(SectionBox, { title: title, children: numContainers === 0 ? (_jsx(Empty, { children: t('translation|No data to be shown.') })) : (containers.map((container) => (_jsx(ContainerInfo, { resource: resource, container: container, status: statuses[container.name] }, `container_${container.name}`)))) }), ephemContainers.length > 0 && (_jsx(SectionBox, { title: t('glossary|Ephemeral Containers'), children: ephemContainers.map((ephemContainer) => (_jsx(ContainerInfo, { resource: resource, container: ephemContainer, status: ephemStatuses[ephemContainer.name] }, `ephem_container_${ephemContainer.name}`))) })), initContainers.length > 0 && (_jsx(SectionBox, { title: t('translation|Init Containers'), children: initContainers.map((initContainer, i) => (_jsx(ContainerInfo, { resource: resource, container: initContainer, status: initStatuses[initContainer.name] }, `init_container_${i}`))) }))] }));
}
export function ConditionsSection(props) {
const { resource } = props;
const { t } = useTranslation(['glossary']);
if (!resource) {
return null;
}
return (_jsx(SectionBox, { title: t('translation|Conditions'), children: _jsx(ConditionsTable, { resource: resource }) }));
}
export function VolumeSection(props) {
const { t } = useTranslation('glossary');
const { resource } = props;
const volumes = resource?.spec?.volumes;
if (!volumes) {
return null;
}
const namespace = resource?.metadata?.namespace;
/*
* printVolumeLink will print a working link that is set within the router using fields from the resource as params
*/
function PrintVolumeLink(props) {
const { volumeName, volumeKind, volume } = props;
const resourceClasses = ResourceClasses;
const classList = Object.keys(resourceClasses);
for (const kind of classList) {
if (kind.toLowerCase() === volumeKind.toLowerCase()) {
const volumeClass = resourceClasses[kind];
const volumeRoute = volumeClass.detailsRoute;
const volumeNamespace = volumeClass.isNamespaced ? namespace : null;
const volumeKindNames = {
configMap: 'name',
secret: 'secretName',
persistentVolumeClaim: 'claimName',
};
const volumeNameKey = volumeKindNames[volumeKind];
if (!!volumeNameKey) {
const detailName = volume[volumeKind][volumeNameKey];
if (!!detailName) {
return (_jsx(Link, { routeName: volumeRoute, params: { namespace: volumeNamespace, name: detailName }, children: volumeName }));
}
}
}
}
return _jsx(Typography, { children: volumeName });
}
function volumeRows(volume) {
const { name, ...objWithVolumeKind } = volume;
const volumeKind = Object.keys(objWithVolumeKind)[0] || '';
if (!volume) {
return [];
}
function printVolumeDetails(volume) {
const { ...vol } = volume[volumeKind];
// array for items that are printable
const directPrint = [];
// array for items that are not printable and need to be printed to yaml
const yamlPrint = [];
// loop over volumeKeys and check if the value is a string, number, or bool
for (const key in vol) {
if (!(vol[key] === '')) {
if (typeof vol[key] === 'string' ||
typeof vol[key] === 'number' ||
typeof vol[key] === 'boolean') {
directPrint.push({
volKey: key,
volValue: typeof vol[key] === 'boolean' ? vol[key].toString() : vol[key],
});
}
else {
yamlPrint.push({
volKey: key,
volValue: vol[key],
});
}
}
}
const volumePrints = {
directPrint,
yamlPrint,
};
return volumePrints;
}
const volumeDetails = printVolumeDetails(volume);
return [
{
name: name,
withHighlightStyle: true,
},
...(volumeKind
? [
{
name: 'Kind',
value: volumeKind,
},
]
: []),
{
name: 'Source',
value: _jsx(PrintVolumeLink, { volumeName: name, volumeKind: volumeKind, volume: volume }),
},
...(volumeDetails.directPrint
? volumeDetails.directPrint.map(({ volKey, volValue }) => {
return {
name: volKey,
value: volValue,
};
})
: []),
...(volumeDetails.yamlPrint
? volumeDetails.yamlPrint.map(({ volKey, volValue }) => {
return {
name: volKey,
value: (_jsx(Typography, { component: "pre", variant: "body2", children: YAML.stringify(volValue) })),
};
})
: []),
];
}
return (_jsx(SectionBox, { title: t('translation|Volumes'), children: volumes.map((volume) => (_jsx(NameValueTable, { rows: volumeRows(volume) }, volume.name))) }));
}