@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
178 lines (177 loc) • 7.34 kB
JavaScript
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 { Icon } from '@iconify/react';
import { useTheme } from '@mui/material/styles';
import { styled } from '@mui/material/styles';
import { alpha } from '@mui/system/colorManipulator';
import { Handle, Position } from '@xyflow/react';
import { memo, useEffect, useState } from 'react';
import { getMainNode } from '../graph/graphGrouping';
import { useGraphView, useNode } from '../GraphView';
import { KubeIcon } from '../kubeIcon/KubeIcon';
import { KubeObjectGlance } from '../KubeObjectGlance/KubeObjectGlance';
import { GroupNodeComponent } from './GroupNode';
import { getStatus } from './KubeObjectStatus';
const Container = styled('div')(({ theme, isSelected, isExpanded, childrenCount }) => ({
display: 'flex',
flexDirection: 'column',
width: isExpanded ? 'auto' : '100%!important',
minWidth: '100%',
height: isExpanded ? 'auto' : '100%',
minHeight: '100%',
boxSizing: 'border-box',
position: 'absolute',
background: theme.palette.background.paper,
borderRadius: '10px',
border: '1px solid',
borderColor: isSelected ? theme.palette.action.active : theme.palette.divider,
padding: '10px',
willChange: 'width, height, border-color, box-shadow',
':hover': {
borderColor: isSelected ? undefined : alpha(theme.palette.action.active, 0.2),
boxShadow: '4px 4px 6px rgba(0,0,0,0.06)',
},
'::before': childrenCount > 1
? {
content: `''`,
width: '100%',
height: '100%',
position: 'absolute',
left: '5px',
top: '5px',
background: theme.palette.background.paper,
border: '1px solid',
borderColor: theme.palette.divider,
borderRadius: '10px',
zIndex: '-1',
}
: undefined,
'::after': childrenCount > 2
? {
content: `''`,
width: '100%',
height: '100%',
position: 'absolute',
left: '9px',
top: '9px',
background: theme.palette.background.paper,
border: '1px solid',
borderColor: theme.palette.divider,
borderRadius: '10px',
zIndex: '-2',
}
: undefined,
}));
const CircleBadge = styled('div')(({ theme }) => ({
position: 'absolute',
right: 0,
top: 0,
width: '32px',
height: '32px',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
margin: theme.spacing(-1),
justifyContent: 'center',
background: theme.palette.background.paper,
boxShadow: '1px 1px 5px rgba(0,0,0,0.08)',
transition: 'top 0.05s',
transitionTimingFunction: 'linear',
border: '1px solid #e1e1e1',
borderColor: theme.palette.divider,
color: theme.typography.caption.color,
}));
const TextContainer = styled('div')({
display: 'flex',
alignItems: 'center',
gap: '8px',
height: '48px',
});
const LabelContainer = styled('div')(({ isCollapsed }) => ({
display: 'flex',
flexDirection: isCollapsed ? 'column' : 'row',
overflow: 'hidden',
justifyContent: 'center',
alignItems: isCollapsed ? undefined : 'center',
}));
const Subtitle = styled('div')({
opacity: 0.8,
fontSize: 14,
});
const Title = styled('div')({
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
});
const EXPAND_DELAY = 450;
export const KubeObjectNodeComponent = memo(({ id }) => {
const node = useNode(id);
const [isHovered, setHovered] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
const theme = useTheme();
const graph = useGraphView();
const kubeObject = node?.kubeObject ?? getMainNode(node?.nodes ?? [])?.kubeObject;
const isSelected = id === graph.nodeSelection;
const isCollapsed = node?.nodes?.length ? node?.collapsed : true;
let status = 'success';
if (kubeObject) {
status = getStatus(kubeObject) ?? 'success';
}
if (node?.nodes) {
const errors = node?.nodes?.filter(it => it.kubeObject && getStatus(it.kubeObject) === 'error')?.length ?? 0;
const warnings = node?.nodes?.filter(it => it.kubeObject && getStatus(it.kubeObject) === 'warning')?.length ??
0;
if (warnings) {
status = 'warning';
}
if (errors) {
status = 'error';
}
}
useEffect(() => {
if (!isHovered) {
setIsExpanded(false);
return;
}
const id = setTimeout(() => setIsExpanded(true), EXPAND_DELAY);
return () => clearInterval(id);
}, [isHovered]);
const icon = kubeObject ? (_jsx(KubeIcon, { width: "42px", height: "42px", kind: kubeObject.kind })) : (node?.icon ?? null);
const openDetails = () => {
graph.setNodeSelection(id);
setHovered(false);
};
const label = node?.label ?? kubeObject?.metadata?.name;
const subtitle = node?.subtitle ?? kubeObject?.kind;
if (!node) {
return null;
}
if (node?.nodes?.length && !node.collapsed) {
return _jsx(GroupNodeComponent, { id: id });
}
return (_jsxs(Container, { tabIndex: 0, role: "button", isFaded: false, childrenCount: node.nodes?.length ?? 0, isSelected: isSelected, isExpanded: isExpanded, onClick: openDetails, onFocus: () => setHovered(true), onBlur: () => setHovered(false), onPointerEnter: () => setHovered(true), onPointerLeave: () => {
setHovered(false);
}, onKeyDown: e => {
if (e.key === 'Enter' || e.key === 'Space') {
openDetails();
}
}, children: [_jsx(Handle, { type: "target", position: Position.Top, style: { opacity: 0 } }), _jsx(Handle, { type: "source", position: Position.Bottom, style: { opacity: 0 } }), isCollapsed && status !== 'success' && (_jsx(CircleBadge, { style: { right: node.nodes?.length ? '36px' : undefined }, children: _jsx(Icon, { color: theme.palette[status].main, icon: status === 'error' ? 'mdi:exclamation' : 'mdi:information', width: "22px", height: "22px" }) })), node.collapsed && (node.nodes?.length ?? 0) > 0 && (_jsx(CircleBadge, { children: node.nodes?.length ?? 0 })), _jsxs(TextContainer, { children: [icon, _jsxs(LabelContainer, { isCollapsed: isCollapsed, children: [_jsx(Subtitle, { children: subtitle }), _jsx(Title, { sx: {
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
}, children: label })] })] }), isExpanded && kubeObject && _jsx(KubeObjectGlance, { resource: kubeObject })] }));
});