@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
127 lines (126 loc) • 6.46 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, 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 { Box, Button, Typography } from '@mui/material';
import { groupBy, uniq } from 'lodash';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useClustersConf } from '../../lib/k8s';
import Namespace from '../../lib/k8s/namespace';
import { StatusLabel } from '../common';
import Link from '../common/Link';
import Table from '../common/Table/Table';
import { NewProjectPopup } from './NewProjectPopup';
import { getHealthIcon, getResourcesHealth, PROJECT_ID_LABEL } from './projectUtils';
import { useProjectItems } from './useProjectResources';
const useProjects = () => {
const clusterConf = useClustersConf();
const clusters = Object.values(clusterConf ?? {});
const { items: namespaces } = Namespace.useList({
clusters: clusters.map(c => c.name),
labelSelector: PROJECT_ID_LABEL,
});
const projects = useMemo(() => Object.entries(groupBy(namespaces, n => n.metadata.labels[PROJECT_ID_LABEL])).map(([name, namespaces]) => ({
id: name,
namespaces: uniq(namespaces.map(it => it.metadata.name)),
clusters: uniq(namespaces.map(it => it.cluster)),
})), [namespaces]);
return projects;
};
export const useProject = (name) => {
const clusterConf = useClustersConf();
const clusters = Object.values(clusterConf ?? {});
const { items: namespaces, isLoading } = Namespace.useList({
clusters: clusters.map(c => c.name),
labelSelector: PROJECT_ID_LABEL + '=' + name,
});
return useMemo(() => ({
isLoading,
project: namespaces
? {
clusters: uniq(namespaces.map(it => it.cluster)),
namespaces: uniq(namespaces.map(it => it.metadata.name)),
id: name,
}
: undefined,
}), [namespaces, name, isLoading]);
};
export default function ProjectList() {
const { t } = useTranslation();
const [showCreate, setShowCreate] = useState(false);
const projects = useProjects();
const handleCreateProject = () => {
setShowCreate(true);
};
const columns = useMemo(() => {
const columns = [
{
id: 'name',
header: t('Name'),
accessorFn: it => it.id,
Cell: ({ row: { original } }) => (_jsx(_Fragment, { children: _jsx(Link, { routeName: "projectDetails", params: { name: original.id }, children: original.id }) })),
},
{
id: 'resources',
header: t('Resources'),
Cell: ({ row: { original } }) => {
const { items } = useProjectItems(original, { disableWatch: true });
return items.length;
},
gridTemplate: 'min-content',
},
{
id: 'health',
header: t('Health'),
Cell: ({ row: { original } }) => {
const { items } = useProjectItems(original, { disableWatch: true });
const projectHealth = getResourcesHealth(items);
return (_jsxs(StatusLabel, { status: projectHealth.error > 0
? 'error'
: projectHealth.warning > 0
? 'warning'
: 'success', children: [_jsx(Icon, { icon: getHealthIcon(projectHealth.success, projectHealth.error, projectHealth.warning), style: {
fontSize: 24,
} }), items.length === 0
? t('No Resources')
: projectHealth.error > 0
? t('Unhealthy')
: projectHealth.warning > 0
? t('Degraded')
: t('Healthy')] }));
},
gridTemplate: 'min-content',
},
{
id: 'clusters',
header: t('Clusters'),
accessorFn: it => it.clusters.join(', '),
},
{
id: 'namespaces',
header: t('Namespaces'),
accessorFn: it => it.namespaces.join(', '),
},
];
return columns;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (projects.length === 0) {
return (_jsxs(_Fragment, { children: [showCreate && _jsx(NewProjectPopup, { open: showCreate, onClose: () => setShowCreate(false) }), _jsxs(Box, { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", minHeight: "400px", textAlign: "center", children: [_jsx(Icon, { icon: "mdi:folder-multiple", style: { fontSize: 64, color: '#ccc', marginBottom: 16 } }), _jsx(Typography, { variant: "h6", gutterBottom: true, children: t('No projects found') }), _jsx(Typography, { variant: "body2", color: "text.secondary", paragraph: true, children: t('Create your first project to organize your Kubernetes resources') }), _jsx(Button, { variant: "contained", startIcon: _jsx(Icon, { icon: "mdi:plus" }), onClick: handleCreateProject, children: t('Create Project') })] })] }));
}
return (_jsxs(_Fragment, { children: [showCreate && _jsx(NewProjectPopup, { open: showCreate, onClose: () => setShowCreate(false) }), _jsx(Box, { display: "flex", justifyContent: "flex-end", mb: 2, mt: 2, children: _jsx(Button, { variant: "contained", startIcon: _jsx(Icon, { icon: "mdi:plus" }), onClick: handleCreateProject, children: t('Create Project') }) }), _jsx(Table, { columns: columns, data: projects })] }));
}