mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Make customizing ItemListLayout header explicitly reducable (#2956)
- Add search placeholder text for helm releases Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
757fed47fa
commit
ae2fa15b20
@ -30,7 +30,6 @@ import type { HelmChart } from "../../api/endpoints/helm-charts.api";
|
|||||||
import { HelmChartDetails } from "./helm-chart-details";
|
import { HelmChartDetails } from "./helm-chart-details";
|
||||||
import { navigation } from "../../navigation";
|
import { navigation } from "../../navigation";
|
||||||
import { ItemListLayout } from "../item-object-list/item-list-layout";
|
import { ItemListLayout } from "../item-object-list/item-list-layout";
|
||||||
import { SearchInputUrl } from "../input";
|
|
||||||
|
|
||||||
enum columnId {
|
enum columnId {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -92,9 +91,12 @@ export class HelmCharts extends Component<Props> {
|
|||||||
(chart: HelmChart) => chart.getAppVersion(),
|
(chart: HelmChart) => chart.getAppVersion(),
|
||||||
(chart: HelmChart) => chart.getKeywords(),
|
(chart: HelmChart) => chart.getKeywords(),
|
||||||
]}
|
]}
|
||||||
customizeHeader={() => (
|
customizeHeader={({ searchProps }) => ({
|
||||||
<SearchInputUrl placeholder="Search Helm Charts" />
|
searchProps: {
|
||||||
)}
|
...searchProps,
|
||||||
|
placeholder: "Search Helm Charts...",
|
||||||
|
},
|
||||||
|
})}
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ className: "icon", showWithColumn: columnId.name },
|
{ className: "icon", showWithColumn: columnId.name },
|
||||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||||
|
|||||||
@ -117,16 +117,20 @@ export class HelmReleases extends Component<Props> {
|
|||||||
(release: HelmRelease) => release.getStatus(),
|
(release: HelmRelease) => release.getStatus(),
|
||||||
(release: HelmRelease) => release.getVersion(),
|
(release: HelmRelease) => release.getVersion(),
|
||||||
]}
|
]}
|
||||||
renderHeaderTitle="Releases"
|
customizeHeader={({ filters, searchProps, ...headerPlaceholders }) => ({
|
||||||
customizeHeader={({ filters, ...headerPlaceholders }) => ({
|
|
||||||
filters: (
|
filters: (
|
||||||
<>
|
<>
|
||||||
{filters}
|
{filters}
|
||||||
<NamespaceSelectFilter />
|
<NamespaceSelectFilter />
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
searchProps: {
|
||||||
|
...searchProps,
|
||||||
|
placeholder: "Search Releases...",
|
||||||
|
},
|
||||||
...headerPlaceholders,
|
...headerPlaceholders,
|
||||||
})}
|
})}
|
||||||
|
renderHeaderTitle="Releases"
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||||
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
|
{ title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
|
||||||
|
|||||||
@ -205,7 +205,6 @@ export class Catalog extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<ItemListLayout
|
<ItemListLayout
|
||||||
renderHeaderTitle={this.catalogEntityStore.activeCategory?.metadata.name ?? "Browse All"}
|
renderHeaderTitle={this.catalogEntityStore.activeCategory?.metadata.name ?? "Browse All"}
|
||||||
isSearchable={true}
|
|
||||||
isSelectable={false}
|
isSelectable={false}
|
||||||
className="CatalogItemList"
|
className="CatalogItemList"
|
||||||
store={this.catalogEntityStore}
|
store={this.catalogEntityStore}
|
||||||
@ -242,7 +241,6 @@ export class Catalog extends React.Component<Props> {
|
|||||||
return (
|
return (
|
||||||
<ItemListLayout
|
<ItemListLayout
|
||||||
renderHeaderTitle={this.catalogEntityStore.activeCategory?.metadata.name ?? "Browse All"}
|
renderHeaderTitle={this.catalogEntityStore.activeCategory?.metadata.name ?? "Browse All"}
|
||||||
isSearchable={true}
|
|
||||||
isSelectable={false}
|
isSelectable={false}
|
||||||
className="CatalogItemList"
|
className="CatalogItemList"
|
||||||
store={this.catalogEntityStore}
|
store={this.catalogEntityStore}
|
||||||
|
|||||||
@ -42,4 +42,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.SearchInput {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ export class CrdList extends React.Component {
|
|||||||
sortingCallbacks={sortingCallbacks}
|
sortingCallbacks={sortingCallbacks}
|
||||||
searchFilters={Object.values(sortingCallbacks)}
|
searchFilters={Object.values(sortingCallbacks)}
|
||||||
renderHeaderTitle="Custom Resources"
|
renderHeaderTitle="Custom Resources"
|
||||||
customizeHeader={() => {
|
customizeHeader={({ filters, ...headerPlaceholders }) => {
|
||||||
let placeholder = <>All groups</>;
|
let placeholder = <>All groups</>;
|
||||||
|
|
||||||
if (selectedGroups.length == 1) placeholder = <>Group: {selectedGroups[0]}</>;
|
if (selectedGroups.length == 1) placeholder = <>Group: {selectedGroups[0]}</>;
|
||||||
@ -104,6 +104,8 @@ export class CrdList extends React.Component {
|
|||||||
return {
|
return {
|
||||||
// todo: move to global filters
|
// todo: move to global filters
|
||||||
filters: (
|
filters: (
|
||||||
|
<>
|
||||||
|
{filters}
|
||||||
<Select
|
<Select
|
||||||
className="group-select"
|
className="group-select"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
@ -123,7 +125,9 @@ export class CrdList extends React.Component {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
</>
|
||||||
|
),
|
||||||
|
...headerPlaceholders,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
|
|||||||
@ -101,6 +101,13 @@ export class CrdResources extends React.Component<Props> {
|
|||||||
(item: KubeObject) => item.getSearchFields(),
|
(item: KubeObject) => item.getSearchFields(),
|
||||||
]}
|
]}
|
||||||
renderHeaderTitle={crd.getResourceTitle()}
|
renderHeaderTitle={crd.getResourceTitle()}
|
||||||
|
customizeHeader={({ searchProps, ...headerPlaceholders }) => ({
|
||||||
|
searchProps: {
|
||||||
|
...searchProps,
|
||||||
|
placeholder: `Search ${crd.getResourceTitle()}...`,
|
||||||
|
},
|
||||||
|
...headerPlaceholders
|
||||||
|
})}
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
{ title: "Name", className: "name", sortBy: columnId.name, id: columnId.name },
|
||||||
isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
|
isNamespaced && { title: "Namespace", className: "namespace", sortBy: columnId.namespace, id: columnId.namespace },
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import { EventStore, eventStore } from "./event.store";
|
|||||||
import { getDetailsUrl, KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object";
|
import { getDetailsUrl, KubeObjectListLayout, KubeObjectListLayoutProps } from "../kube-object";
|
||||||
import type { KubeEvent } from "../../api/endpoints/events.api";
|
import type { KubeEvent } from "../../api/endpoints/events.api";
|
||||||
import type { TableSortCallbacks, TableSortParams, TableProps } from "../table";
|
import type { TableSortCallbacks, TableSortParams, TableProps } from "../table";
|
||||||
import type { IHeaderPlaceholders } from "../item-object-list";
|
import type { HeaderCustomizer } from "../item-object-list";
|
||||||
import { Tooltip } from "../tooltip";
|
import { Tooltip } from "../tooltip";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { cssNames, IClassName, stopPropagation } from "../../utils";
|
import { cssNames, IClassName, stopPropagation } from "../../utils";
|
||||||
@ -112,19 +112,21 @@ export class Events extends React.Component<Props> {
|
|||||||
return this.items;
|
return this.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
customizeHeader = ({ info, title }: IHeaderPlaceholders) => {
|
customizeHeader: HeaderCustomizer = ({ info, title, ...headerPlaceholders }) => {
|
||||||
const { compact } = this.props;
|
const { compact } = this.props;
|
||||||
const { store, items, visibleItems } = this;
|
const { store, items, visibleItems } = this;
|
||||||
const allEventsAreShown = visibleItems.length === items.length;
|
const allEventsAreShown = visibleItems.length === items.length;
|
||||||
|
|
||||||
// handle "compact"-mode header
|
// handle "compact"-mode header
|
||||||
if (compact) {
|
if (compact) {
|
||||||
if (allEventsAreShown) return title; // title == "Events"
|
if (allEventsAreShown) {
|
||||||
|
return { title };
|
||||||
|
}
|
||||||
|
|
||||||
return <>
|
return {
|
||||||
{title}
|
title,
|
||||||
<span> ({visibleItems.length} of <Link to={eventsURL()}>{items.length}</Link>)</span>
|
info: <span> ({visibleItems.length} of <Link to={eventsURL()}>{items.length}</Link>)</span>,
|
||||||
</>;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -136,7 +138,9 @@ export class Events extends React.Component<Props> {
|
|||||||
className="help-icon"
|
className="help-icon"
|
||||||
tooltip={`Limited to ${store.limit}`}
|
tooltip={`Limited to ${store.limit}`}
|
||||||
/>
|
/>
|
||||||
</>
|
</>,
|
||||||
|
title,
|
||||||
|
...headerPlaceholders
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -32,12 +32,12 @@ export const searchUrlParam = createPageParam({
|
|||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
interface Props extends InputProps {
|
export interface SearchInputUrlProps extends InputProps {
|
||||||
compact?: boolean; // show only search-icon when not focused
|
compact?: boolean; // show only search-icon when not focused
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class SearchInputUrl extends React.Component<Props> {
|
export class SearchInputUrl extends React.Component<SearchInputUrlProps> {
|
||||||
@observable inputVal = ""; // fix: use empty string on init to avoid react warnings
|
@observable inputVal = ""; // fix: use empty string on init to avoid react warnings
|
||||||
|
|
||||||
@disposeOnUnmount
|
@disposeOnUnmount
|
||||||
@ -62,7 +62,7 @@ export class SearchInputUrl extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: SearchInputUrlProps) {
|
||||||
super(props);
|
super(props);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons";
|
|||||||
import { NoItems } from "../no-items";
|
import { NoItems } from "../no-items";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import type { ItemObject, ItemStore } from "../../item.store";
|
import type { ItemObject, ItemStore } from "../../item.store";
|
||||||
import { SearchInputUrl } from "../input";
|
import { SearchInputUrlProps, SearchInputUrl } from "../input";
|
||||||
import { Filter, FilterType, pageFilters } from "./page-filters.store";
|
import { Filter, FilterType, pageFilters } from "./page-filters.store";
|
||||||
import { PageFiltersList } from "./page-filters-list";
|
import { PageFiltersList } from "./page-filters-list";
|
||||||
import { ThemeStore } from "../../theme.store";
|
import { ThemeStore } from "../../theme.store";
|
||||||
@ -41,21 +41,21 @@ import { MenuItem } from "../menu";
|
|||||||
import { Checkbox } from "../checkbox";
|
import { Checkbox } from "../checkbox";
|
||||||
import { UserStore } from "../../../common/user-store";
|
import { UserStore } from "../../../common/user-store";
|
||||||
import { namespaceStore } from "../+namespaces/namespace.store";
|
import { namespaceStore } from "../+namespaces/namespace.store";
|
||||||
import { KubeObjectStore } from "../../kube-object.store";
|
|
||||||
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
|
|
||||||
|
|
||||||
// todo: refactor, split to small re-usable components
|
|
||||||
|
|
||||||
export type SearchFilter<T extends ItemObject = any> = (item: T) => string | number | (string | number)[];
|
export type SearchFilter<T extends ItemObject = any> = (item: T) => string | number | (string | number)[];
|
||||||
export type ItemsFilter<T extends ItemObject = any> = (items: T[]) => T[];
|
export type ItemsFilter<T extends ItemObject = any> = (items: T[]) => T[];
|
||||||
|
|
||||||
export interface IHeaderPlaceholders {
|
export interface HeaderPlaceholders {
|
||||||
title: ReactNode;
|
title?: ReactNode;
|
||||||
search: ReactNode;
|
searchProps?: SearchInputUrlProps;
|
||||||
filters: ReactNode;
|
filters?: ReactNode;
|
||||||
info: ReactNode;
|
info?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HeaderCustomizer = (placeholders: HeaderPlaceholders) => HeaderPlaceholders;
|
||||||
|
|
||||||
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
||||||
tableId?: string;
|
tableId?: string;
|
||||||
className: IClassName;
|
className: IClassName;
|
||||||
@ -72,12 +72,11 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
|||||||
showHeader?: boolean;
|
showHeader?: boolean;
|
||||||
headerClassName?: IClassName;
|
headerClassName?: IClassName;
|
||||||
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout) => ReactNode);
|
renderHeaderTitle?: ReactNode | ((parent: ItemListLayout) => ReactNode);
|
||||||
customizeHeader?: (placeholders: IHeaderPlaceholders, content: ReactNode) => Partial<IHeaderPlaceholders> | ReactNode;
|
customizeHeader?: HeaderCustomizer | HeaderCustomizer[];
|
||||||
|
|
||||||
// items list configuration
|
// items list configuration
|
||||||
isReady?: boolean; // show loading indicator while not ready
|
isReady?: boolean; // show loading indicator while not ready
|
||||||
isSelectable?: boolean; // show checkbox in rows for selecting items
|
isSelectable?: boolean; // show checkbox in rows for selecting items
|
||||||
isSearchable?: boolean; // apply search-filter & add search-input
|
|
||||||
isConfigurable?: boolean;
|
isConfigurable?: boolean;
|
||||||
copyClassNameFromHeadCells?: boolean;
|
copyClassNameFromHeadCells?: boolean;
|
||||||
sortingCallbacks?: { [sortBy: string]: TableSortCallback };
|
sortingCallbacks?: { [sortBy: string]: TableSortCallback };
|
||||||
@ -101,12 +100,13 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
|||||||
|
|
||||||
const defaultProps: Partial<ItemListLayoutProps> = {
|
const defaultProps: Partial<ItemListLayoutProps> = {
|
||||||
showHeader: true,
|
showHeader: true,
|
||||||
isSearchable: true,
|
|
||||||
isSelectable: true,
|
isSelectable: true,
|
||||||
isConfigurable: false,
|
isConfigurable: false,
|
||||||
copyClassNameFromHeadCells: true,
|
copyClassNameFromHeadCells: true,
|
||||||
preloadStores: true,
|
preloadStores: true,
|
||||||
dependentStores: [],
|
dependentStores: [],
|
||||||
|
searchFilters: [],
|
||||||
|
customizeHeader: [],
|
||||||
filterItems: [],
|
filterItems: [],
|
||||||
hasDetailsView: true,
|
hasDetailsView: true,
|
||||||
onDetails: noop,
|
onDetails: noop,
|
||||||
@ -160,10 +160,10 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
|
|
||||||
private filterCallbacks: { [type: string]: ItemsFilter } = {
|
private filterCallbacks: { [type: string]: ItemsFilter } = {
|
||||||
[FilterType.SEARCH]: items => {
|
[FilterType.SEARCH]: items => {
|
||||||
const { searchFilters, isSearchable } = this.props;
|
const { searchFilters } = this.props;
|
||||||
const search = pageFilters.getValues(FilterType.SEARCH)[0] || "";
|
const search = pageFilters.getValues(FilterType.SEARCH)[0] || "";
|
||||||
|
|
||||||
if (search && isSearchable && searchFilters) {
|
if (search && searchFilters.length) {
|
||||||
const normalizeText = (text: string) => String(text).toLowerCase();
|
const normalizeText = (text: string) => String(text).toLowerCase();
|
||||||
const searchTexts = [search].map(normalizeText);
|
const searchTexts = [search].map(normalizeText);
|
||||||
|
|
||||||
@ -190,9 +190,9 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
|
|
||||||
@computed get filters() {
|
@computed get filters() {
|
||||||
let { activeFilters } = pageFilters;
|
let { activeFilters } = pageFilters;
|
||||||
const { isSearchable, searchFilters } = this.props;
|
const { searchFilters } = this.props;
|
||||||
|
|
||||||
if (!(isSearchable && searchFilters)) {
|
if (searchFilters.length === 0) {
|
||||||
activeFilters = activeFilters.filter(({ type }) => type !== FilterType.SEARCH);
|
activeFilters = activeFilters.filter(({ type }) => type !== FilterType.SEARCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,18 +348,22 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
return this.items.map(item => this.getRow(item.getId()));
|
return this.items.map(item => this.getRow(item.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeaderContent(placeholders: IHeaderPlaceholders): ReactNode {
|
renderHeaderContent(placeholders: HeaderPlaceholders): ReactNode {
|
||||||
const { isSearchable, searchFilters } = this.props;
|
const { searchFilters } = this.props;
|
||||||
const { title, filters, search, info } = placeholders;
|
const { title, filters, searchProps, info } = placeholders;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{title}
|
{title}
|
||||||
|
{
|
||||||
|
info && (
|
||||||
<div className="info-panel box grow">
|
<div className="info-panel box grow">
|
||||||
{info}
|
{info}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
{filters}
|
{filters}
|
||||||
{isSearchable && searchFilters && search}
|
{searchFilters.length > 0 && searchProps && <SearchInputUrl {...searchProps} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -385,28 +389,15 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const showNamespaceSelectFilter = this.props.store instanceof KubeObjectStore && this.props.store.api.isNamespaced;
|
|
||||||
const title = typeof renderHeaderTitle === "function" ? renderHeaderTitle(this) : renderHeaderTitle;
|
const title = typeof renderHeaderTitle === "function" ? renderHeaderTitle(this) : renderHeaderTitle;
|
||||||
const placeholders: IHeaderPlaceholders = {
|
const customizeHeaders = [customizeHeader].flat().filter(Boolean);
|
||||||
|
const initialPlaceholders: HeaderPlaceholders = {
|
||||||
title: <h5 className="title">{title}</h5>,
|
title: <h5 className="title">{title}</h5>,
|
||||||
info: this.renderInfo(),
|
info: this.renderInfo(),
|
||||||
filters: showNamespaceSelectFilter && <NamespaceSelectFilter />,
|
searchProps: {},
|
||||||
search: <SearchInputUrl />,
|
|
||||||
};
|
};
|
||||||
let header = this.renderHeaderContent(placeholders);
|
const headerPlaceholders = customizeHeaders.reduce((prevPlaceholders, customizer) => customizer(prevPlaceholders), initialPlaceholders);
|
||||||
|
const header = this.renderHeaderContent(headerPlaceholders);
|
||||||
if (customizeHeader) {
|
|
||||||
const modifiedHeader = customizeHeader(placeholders, header) ?? {};
|
|
||||||
|
|
||||||
if (isReactNode(modifiedHeader)) {
|
|
||||||
header = modifiedHeader;
|
|
||||||
} else {
|
|
||||||
header = this.renderHeaderContent({
|
|
||||||
...placeholders,
|
|
||||||
...modifiedHeader as IHeaderPlaceholders,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cssNames("header flex gaps align-center", headerClassName)}>
|
<div className={cssNames("header flex gaps align-center", headerClassName)}>
|
||||||
|
|||||||
@ -30,6 +30,8 @@ import { KubeObjectMenu } from "./kube-object-menu";
|
|||||||
import { kubeSelectedUrlParam, showDetails } from "./kube-object-details";
|
import { kubeSelectedUrlParam, showDetails } from "./kube-object-details";
|
||||||
import { kubeWatchApi } from "../../api/kube-watch-api";
|
import { kubeWatchApi } from "../../api/kube-watch-api";
|
||||||
import { clusterContext } from "../context";
|
import { clusterContext } from "../context";
|
||||||
|
import { NamespaceSelectFilter } from "../+namespaces/namespace-select-filter";
|
||||||
|
import { ResourceKindMap, ResourceNames } from "../../utils/rbac";
|
||||||
|
|
||||||
export interface KubeObjectListLayoutProps extends ItemListLayoutProps {
|
export interface KubeObjectListLayoutProps extends ItemListLayoutProps {
|
||||||
store: KubeObjectStore;
|
store: KubeObjectStore;
|
||||||
@ -66,7 +68,8 @@ export class KubeObjectListLayout extends React.Component<KubeObjectListLayoutPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, store, items = store.contextItems, ...layoutProps } = this.props;
|
const { className, customizeHeader, store, items = store.contextItems, ...layoutProps } = this.props;
|
||||||
|
const placeholderString = ResourceNames[ResourceKindMap[store.api.kind]] || store.api.kind;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemListLayout
|
<ItemListLayout
|
||||||
@ -76,6 +79,22 @@ export class KubeObjectListLayout extends React.Component<KubeObjectListLayoutPr
|
|||||||
items={items}
|
items={items}
|
||||||
preloadStores={false} // loading handled in kubeWatchApi.subscribeStores()
|
preloadStores={false} // loading handled in kubeWatchApi.subscribeStores()
|
||||||
detailsItem={this.selectedItem}
|
detailsItem={this.selectedItem}
|
||||||
|
customizeHeader={[
|
||||||
|
({ filters, searchProps, ...headerPlaceHolders }) => ({
|
||||||
|
filters: (
|
||||||
|
<>
|
||||||
|
{filters}
|
||||||
|
{store.api.isNamespaced && <NamespaceSelectFilter />}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
searchProps: {
|
||||||
|
...searchProps,
|
||||||
|
placeholder: `Search ${placeholderString}...`,
|
||||||
|
},
|
||||||
|
...headerPlaceHolders,
|
||||||
|
}),
|
||||||
|
...[customizeHeader].flat(),
|
||||||
|
]}
|
||||||
renderItemMenu={(item: KubeObject) => <KubeObjectMenu object={item} />} // safe because we are dealing with KubeObjects here
|
renderItemMenu={(item: KubeObject) => <KubeObjectMenu object={item} />} // safe because we are dealing with KubeObjects here
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { KubeResource } from "../../common/rbac";
|
import { apiResourceRecord, KubeResource } from "../../common/rbac";
|
||||||
|
|
||||||
export const ResourceNames: Record<KubeResource, string> = {
|
export const ResourceNames: Record<KubeResource, string> = {
|
||||||
"namespaces": "Namespaces",
|
"namespaces": "Namespaces",
|
||||||
@ -53,3 +53,8 @@ export const ResourceNames: Record<KubeResource, string> = {
|
|||||||
"clusterroles": "Cluster Roles",
|
"clusterroles": "Cluster Roles",
|
||||||
"serviceaccounts": "Service Accounts"
|
"serviceaccounts": "Service Accounts"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ResourceKindMap: Record<string, KubeResource> = Object.fromEntries(
|
||||||
|
Object.entries(apiResourceRecord)
|
||||||
|
.map(([resource, { kind }]) => [kind, resource as KubeResource])
|
||||||
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user