@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
146 lines (145 loc) • 7.44 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 FormControlLabel from '@mui/material/FormControlLabel';
import Grid from '@mui/material/Grid';
import Switch from '@mui/material/Switch';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router';
import Event from '../../lib/k8s/event';
import Node from '../../lib/k8s/node';
import Pod from '../../lib/k8s/pod';
import { useFilterFunc } from '../../lib/util';
import { useTypedSelector } from '../../redux/reducers/reducers';
import { DateLabel, Link, PageGrid, StatusLabel } from '../common';
import ResourceListView from '../common/Resource/ResourceListView';
import { SectionBox } from '../common/SectionBox';
import ShowHideLabel from '../common/ShowHideLabel';
import { LightTooltip } from '../common/Tooltip';
import { CpuCircularChart, MemoryCircularChart, NodesStatusCircleChart, PodsStatusCircleChart, } from './Charts';
import { ClusterGroupErrorMessage } from './ClusterGroupErrorMessage';
export default function Overview() {
const { t } = useTranslation(['translation']);
const [pods] = Pod.useList();
const [nodes] = Node.useList();
const [nodeMetrics, metricsError] = Node.useMetrics();
const chartProcessors = useTypedSelector(state => state.overviewCharts.processors);
const noMetrics = metricsError?.status === 404;
const noPermissions = metricsError?.status === 403;
// Process the default charts through any registered processors
const defaultCharts = [
{
id: 'cpu',
component: () => (_jsx(CpuCircularChart, { items: nodes, itemsMetrics: nodeMetrics, noMetrics: noMetrics })),
},
{
id: 'memory',
component: () => (_jsx(MemoryCircularChart, { items: nodes, itemsMetrics: nodeMetrics, noMetrics: noMetrics })),
},
{
id: 'pods',
component: () => _jsx(PodsStatusCircleChart, { items: pods }),
},
{
id: 'nodes',
component: () => _jsx(NodesStatusCircleChart, { items: nodes }),
},
];
const charts = chartProcessors.reduce((currentCharts, p) => p.processor(currentCharts), defaultCharts);
return (_jsxs(PageGrid, { children: [_jsx(SectionBox, { title: t('translation|Overview'), py: 2, mt: [4, 0, 0], children: noPermissions ? (_jsx(ClusterGroupErrorMessage, { errors: [metricsError] })) : (_jsx(Grid, { container: true, justifyContent: "flex-start", alignItems: "stretch", spacing: 4, children: charts.map(chart => (_jsx(Grid, { item: true, xs: true, sx: { maxWidth: '300px' }, children: _jsx(chart.component, {}) }, chart.id))) })) }), _jsx(EventsSection, {})] }));
}
function EventsSection() {
const EVENT_WARNING_SWITCH_FILTER_STORAGE_KEY = 'EVENT_WARNING_SWITCH_FILTER_STORAGE_KEY';
const EVENT_WARNING_SWITCH_DEFAULT = true;
const { t } = useTranslation(['translation', 'glossary']);
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const eventsFilter = queryParams.get('eventsFilter');
const filterFunc = useFilterFunc(['.jsonData.involvedObject.kind']);
const [isWarningEventSwitchChecked, setIsWarningEventSwitchChecked] = React.useState(Boolean(JSON.parse(localStorage.getItem(EVENT_WARNING_SWITCH_FILTER_STORAGE_KEY) ||
EVENT_WARNING_SWITCH_DEFAULT.toString())));
const { items: events, errors: eventsErrors } = Event.useList({ limit: Event.maxLimit });
const warningActionFilterFunc = (event, search) => {
if (!filterFunc(event, search)) {
return false;
}
if (isWarningEventSwitchChecked) {
return event.jsonData.type === 'Warning';
}
// Return true because if we reach this point, it means we're only filtering by
// the default filterFunc (and its result was 'true').
return true;
};
const numWarnings = React.useMemo(() => events?.filter(e => e.type === 'Warning').length ?? '?', [events]);
function makeStatusLabel(event) {
return (_jsx(StatusLabel, { status: event.type === 'Normal' ? '' : 'warning', sx: (theme) => ({
[theme.breakpoints.up('md')]: {
display: 'unset',
},
}), children: event.reason }));
}
function makeObjectLink(event) {
const obj = event.involvedObjectInstance;
if (!!obj) {
return _jsx(Link, { kubeObject: obj });
}
return event.involvedObject.name;
}
return (_jsx(ResourceListView, { title: t('glossary|Events'), headerProps: {
noNamespaceFilter: false,
titleSideActions: [
_jsx(FormControlLabel, { checked: isWarningEventSwitchChecked, label: t('Only warnings ({{ numWarnings }})', { numWarnings }), control: _jsx(Switch, { color: "primary" }), onChange: (event, checked) => {
localStorage.setItem(EVENT_WARNING_SWITCH_FILTER_STORAGE_KEY, checked.toString());
setIsWarningEventSwitchChecked(checked);
} }, "warning-toggle"),
],
}, defaultGlobalFilter: eventsFilter ?? undefined, data: events, errors: eventsErrors, columns: [
{
label: t('Type'),
gridTemplate: 'min-content',
getValue: event => event.involvedObject.kind,
},
{
label: t('Name'),
getValue: event => event.involvedObjectInstance?.getName() ?? event.involvedObject.name,
render: event => makeObjectLink(event),
gridTemplate: 'auto',
},
'namespace',
'cluster',
{
label: t('Reason'),
gridTemplate: 'min-content',
getValue: event => event.reason,
render: event => (_jsx(LightTooltip, { title: event.reason, interactive: true, children: makeStatusLabel(event) })),
},
{
label: t('Message'),
getValue: event => event.message ?? '',
render: event => (_jsx(ShowHideLabel, { labelId: event.metadata?.uid || '', children: event.message || '' })),
gridTemplate: 'auto',
},
{
id: 'last-seen',
label: t('Last Seen'),
gridTemplate: 'min-content',
cellProps: { align: 'right' },
getValue: event => -new Date(event.lastOccurrence).getTime(),
render: event => _jsx(DateLabel, { date: event.lastOccurrence, format: "mini" }),
},
], filterFunction: warningActionFilterFunc, defaultSortingColumn: { id: 'last-seen', desc: false }, id: "headlamp-cluster.overview.events" }));
}