UNPKG

@kinvolk/headlamp-plugin

Version:

The needed infrastructure for building Headlamp plugins.

127 lines (126 loc) 6.46 kB
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 })] })); }