@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
97 lines (96 loc) • 5.42 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 { Box } from '@mui/material';
import { useQuery } from '@tanstack/react-query';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelectedClusters } from '../../lib/k8s';
import { apiDiscovery } from '../../lib/k8s/api/v2/apiDiscovery';
import { apiResourceId } from '../../lib/k8s/api/v2/ApiResource';
import { SectionHeader } from '../common';
import Loader from '../common/Loader';
import { NamespacesAutocomplete } from '../common/NamespacesAutocomplete';
import { useLocalStorageState } from '../globalSearch/useLocalStorageState';
import { ApiResourcesView } from './ApiResourcePicker';
import { EmptyResults } from './EmptyResults';
import { ResourceSearch } from './ResourceSearch';
import { SearchSettings } from './SearchSettings';
const emptyList = [];
export const ADVANCED_SEARCH_QUERY_KEY = 'advanced-search-query';
/**
* AdvancedSearch component provides an interface for searching Kubernetes resources
* with advanced filtering capabilities.
*/
export function AdvancedSearch() {
const { t } = useTranslation();
const selectedClusters = useSelectedClusters();
const [maxItemsPerResource, setMaxItemsPerResource] = useState(10000);
const [refetchIntervalMs, setRefetchIntervalMs] = useState(60000);
// Store selected resources in query parameter
const [selectedResourcesState, setSelectedResourcesState] = useLocalStorageState('resources', '');
// Selected Resources is a set of selected API resource ID's
// undefined means that the selection wasn't initialized yet
const [selectedResources, setSelectedResources] = useState(() => selectedResourcesState?.length && selectedResourcesState !== 'all'
? new Set(selectedResourcesState.split('+'))
: undefined);
const [rawQuery, setRawQueryState] = useLocalStorageState(ADVANCED_SEARCH_QUERY_KEY, '');
const setRawQuery = useCallback((query) => {
setRawQueryState(() => query ?? '');
}, [setRawQueryState]);
const { data: resources, isLoading } = useQuery({
queryFn: () => apiDiscovery([...selectedClusters]),
queryKey: ['api-discovery', ...selectedClusters],
});
// Select every resource
if (selectedResources === undefined && selectedResourcesState === 'all' && resources) {
setSelectedResources(new Set(resources.map(resource => apiResourceId(resource))));
}
// Sync selected resources query parameter
useEffect(() => {
if (!selectedResources)
return;
setSelectedResourcesState(() => selectedResources.size === resources?.length
? 'all'
: selectedResources.size === 0
? ''
: [...selectedResources].join('+'));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedResources, resources]);
const resourcesList = useMemo(() => resources?.filter(resource => selectedResources?.has(apiResourceId(resource))), [resources, selectedResources]);
if (isLoading) {
return _jsx(Loader, { title: t('Loading') });
}
return (_jsxs(Box, { sx: {
display: 'flex',
flexDirection: 'column',
boxSizing: 'border-box',
maxWidth: '1100px',
gap: 2,
padding: 2,
paddingTop: 4,
height: 'calc(100vh - 70px)',
overflow: 'hidden',
margin: '0 auto',
}, children: [_jsx(SectionHeader, { title: t('Advanced Search (Beta)'), noPadding: true }), _jsxs(Box, { sx: {
display: 'flex',
gap: 2,
}, children: [_jsx(ApiResourcesView, { resources: resources ?? emptyList, selectedResources: selectedResources, setSelectedResources: setSelectedResources }), _jsx(SearchSettings, { maxItemsPerResource: maxItemsPerResource, setMaxItemsPerResource: setMaxItemsPerResource, refetchIntervalMs: refetchIntervalMs, setRefetchIntervalMs: setRefetchIntervalMs }), _jsx(Box, { sx: { marginLeft: 'auto' }, children: _jsx(NamespacesAutocomplete, {}) })] }), resources && (_jsx(ResourceSearch, { resources: resourcesList ?? emptyList, selectedClusters: selectedClusters, maxItemsPerResource: maxItemsPerResource, refetchIntervalMs: refetchIntervalMs, rawQuery: rawQuery ?? '', setRawQuery: setRawQuery }, resourcesList?.map(it => it.pluralName + it.groupName).join(', ') +
selectedClusters.join(', '))), resources && (rawQuery ?? '').length === 0 && (_jsx(EmptyResults, { resources: resources, onQuerySelected: (resources, query) => {
setSelectedResources(new Set(resources.map(it => apiResourceId(it))));
setRawQuery(query);
} }))] }));
}