mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Column filters
Signed-off-by: Pavel Ashevskii <pashevskii@mirantis.com>
This commit is contained in:
parent
8c70e055ee
commit
43dd1bbd81
@ -28,6 +28,7 @@ export interface UserPreferences {
|
|||||||
downloadBinariesPath?: string;
|
downloadBinariesPath?: string;
|
||||||
kubectlBinariesPath?: string;
|
kubectlBinariesPath?: string;
|
||||||
openAtLogin?: boolean;
|
openAtLogin?: boolean;
|
||||||
|
hiddenTableColumns?: Record<string, string[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserStore extends BaseStore<UserStoreModel> {
|
export class UserStore extends BaseStore<UserStoreModel> {
|
||||||
@ -54,6 +55,7 @@ export class UserStore extends BaseStore<UserStoreModel> {
|
|||||||
downloadMirror: "default",
|
downloadMirror: "default",
|
||||||
downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version
|
downloadKubectlBinaries: true, // Download kubectl binaries matching cluster version
|
||||||
openAtLogin: false,
|
openAtLogin: false,
|
||||||
|
hiddenTableColumns: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
protected async handleOnLoad() {
|
protected async handleOnLoad() {
|
||||||
|
|||||||
@ -74,6 +74,8 @@ export class Pods extends React.Component<Props> {
|
|||||||
<KubeObjectListLayout
|
<KubeObjectListLayout
|
||||||
className="Pods" store={podsStore}
|
className="Pods" store={podsStore}
|
||||||
dependentStores={[volumeClaimStore, eventStore]}
|
dependentStores={[volumeClaimStore, eventStore]}
|
||||||
|
tableId = "workloads_pods"
|
||||||
|
isConfigurable
|
||||||
sortingCallbacks={{
|
sortingCallbacks={{
|
||||||
[sortBy.name]: (pod: Pod) => pod.getName(),
|
[sortBy.name]: (pod: Pod) => pod.getName(),
|
||||||
[sortBy.namespace]: (pod: Pod) => pod.getNs(),
|
[sortBy.namespace]: (pod: Pod) => pod.getNs(),
|
||||||
|
|||||||
@ -18,6 +18,11 @@ import { PageFiltersList } from "./page-filters-list";
|
|||||||
import { PageFiltersSelect } from "./page-filters-select";
|
import { PageFiltersSelect } from "./page-filters-select";
|
||||||
import { NamespaceSelectFilter } from "../+namespaces/namespace-select";
|
import { NamespaceSelectFilter } from "../+namespaces/namespace-select";
|
||||||
import { themeStore } from "../../theme.store";
|
import { themeStore } from "../../theme.store";
|
||||||
|
import { MenuActions} from "../menu/menu-actions";
|
||||||
|
import { MenuItem } from "../menu";
|
||||||
|
import { Checkbox } from "../checkbox";
|
||||||
|
import { userStore } from "../../../common/user-store";
|
||||||
|
import logger from "../../../main/logger";
|
||||||
|
|
||||||
// todo: refactor, split to small re-usable components
|
// todo: refactor, split to small re-usable components
|
||||||
|
|
||||||
@ -32,6 +37,7 @@ interface IHeaderPlaceholders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
||||||
|
tableId?: string;
|
||||||
className: IClassName;
|
className: IClassName;
|
||||||
store: ItemStore<T>;
|
store: ItemStore<T>;
|
||||||
dependentStores?: ItemStore[];
|
dependentStores?: ItemStore[];
|
||||||
@ -50,6 +56,7 @@ export interface ItemListLayoutProps<T extends ItemObject = ItemObject> {
|
|||||||
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
|
isSearchable?: boolean; // apply search-filter & add search-input
|
||||||
|
isConfigurable?: boolean;
|
||||||
copyClassNameFromHeadCells?: boolean;
|
copyClassNameFromHeadCells?: boolean;
|
||||||
sortingCallbacks?: { [sortBy: string]: TableSortCallback };
|
sortingCallbacks?: { [sortBy: string]: TableSortCallback };
|
||||||
tableProps?: Partial<TableProps>; // low-level table configuration
|
tableProps?: Partial<TableProps>; // low-level table configuration
|
||||||
@ -74,6 +81,7 @@ const defaultProps: Partial<ItemListLayoutProps> = {
|
|||||||
showHeader: true,
|
showHeader: true,
|
||||||
isSearchable: true,
|
isSearchable: true,
|
||||||
isSelectable: true,
|
isSelectable: true,
|
||||||
|
isConfigurable: false,
|
||||||
copyClassNameFromHeadCells: true,
|
copyClassNameFromHeadCells: true,
|
||||||
dependentStores: [],
|
dependentStores: [],
|
||||||
filterItems: [],
|
filterItems: [],
|
||||||
@ -89,7 +97,7 @@ interface ItemListLayoutUserSettings {
|
|||||||
@observer
|
@observer
|
||||||
export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
||||||
static defaultProps = defaultProps as object;
|
static defaultProps = defaultProps as object;
|
||||||
|
@observable hiddenColumnNames = new Set<string>();
|
||||||
@observable isUnmounting = false;
|
@observable isUnmounting = false;
|
||||||
|
|
||||||
// default user settings (ui show-hide tweaks mostly)
|
// default user settings (ui show-hide tweaks mostly)
|
||||||
@ -111,7 +119,9 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const { store, dependentStores, isClusterScoped } = this.props;
|
const { store, dependentStores, isClusterScoped, isConfigurable, tableId, renderTableHeader } = this.props;
|
||||||
|
if (this.canBeConfigured) this.hiddenColumnNames = new Set(userStore.preferences?.hiddenTableColumns?.[tableId]);
|
||||||
|
|
||||||
const stores = [store, ...dependentStores];
|
const stores = [store, ...dependentStores];
|
||||||
|
|
||||||
if (!isClusterScoped) stores.push(namespaceStore);
|
if (!isClusterScoped) stores.push(namespaceStore);
|
||||||
@ -129,9 +139,11 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.isUnmounting = true;
|
this.isUnmounting = true;
|
||||||
const { store, isSelectable } = this.props;
|
const { store, isSelectable, isConfigurable, tableId } = this.props;
|
||||||
|
|
||||||
if (isSelectable) store.resetSelection();
|
if (isSelectable) store.resetSelection();
|
||||||
|
if (this.canBeConfigured) {
|
||||||
|
userStore.preferences.hiddenTableColumns[tableId] = Array.from(this.hiddenColumnNames);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterCallbacks: { [type: string]: ItemsFilter } = {
|
private filterCallbacks: { [type: string]: ItemsFilter } = {
|
||||||
@ -215,6 +227,35 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
return this.applyFilters(filterItems, allItems);
|
return this.applyFilters(filterItems, allItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateColumnFilter(checkboxValue: boolean, columnName: string) {
|
||||||
|
if (checkboxValue){
|
||||||
|
this.hiddenColumnNames.delete(columnName);
|
||||||
|
} else {
|
||||||
|
this.hiddenColumnNames.add(columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showColumn(index: number): boolean {
|
||||||
|
const {isConfigurable, tableId , renderTableHeader} = this.props;
|
||||||
|
if (!this.canBeConfigured) return true;
|
||||||
|
return !this.hiddenColumnNames.has(renderTableHeader[index].className);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed get canBeConfigured(): boolean {
|
||||||
|
const { isConfigurable, tableId, renderTableHeader } = this.props;
|
||||||
|
|
||||||
|
if (!isConfigurable || !tableId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!renderTableHeader?.every(({ className }) => className)) {
|
||||||
|
logger.warning("[ItemObjectList]: cannot configure an object list without all headers being identifiable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@autobind()
|
@autobind()
|
||||||
getRow(uid: string) {
|
getRow(uid: string) {
|
||||||
const {
|
const {
|
||||||
@ -257,8 +298,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
cellProps.className = cssNames(cellProps.className, headCell.className);
|
cellProps.className = cssNames(cellProps.className, headCell.className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return this.showColumn(index) ? <TableCell key={index} {...cellProps} /> : null;
|
||||||
return <TableCell key={index} {...cellProps} />;
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
{renderItemMenu && (
|
{renderItemMenu && (
|
||||||
@ -395,8 +435,9 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
|
|
||||||
renderList() {
|
renderList() {
|
||||||
const {
|
const {
|
||||||
isSelectable, tableProps = {}, renderTableHeader, renderItemMenu,
|
isSelectable, tableProps = {}, renderTableHeader,
|
||||||
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem
|
store, hasDetailsView, addRemoveButtons = {}, virtual, sortingCallbacks, detailsItem,
|
||||||
|
isConfigurable, tableId
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isReady, removeItemsDialog, items } = this;
|
const { isReady, removeItemsDialog, items } = this;
|
||||||
const { selectedItems } = store;
|
const { selectedItems } = store;
|
||||||
@ -430,14 +471,19 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
onClick={prevDefault(() => store.toggleSelectionAll(items))}
|
onClick={prevDefault(() => store.toggleSelectionAll(items))}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{renderTableHeader.map((cellProps, index) => <TableCell key={index} {...cellProps} />)}
|
{renderTableHeader.map((cellProps, index) => this.showColumn(index) ? <TableCell key={index} {...cellProps} /> : null)}
|
||||||
{renderItemMenu && <TableCell className="menu" />}
|
{
|
||||||
|
<TableCell className="menu" >
|
||||||
|
{this.canBeConfigured && this.renderColumnMenu()}
|
||||||
|
</TableCell>
|
||||||
|
}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
{
|
{
|
||||||
!virtual && items.map(item => this.getRow(item.getId()))
|
!virtual && items.map(item => this.getRow(item.getId()))
|
||||||
}
|
}
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
<AddRemoveButtons
|
<AddRemoveButtons
|
||||||
onRemove={selectedItems.length ? removeItemsDialog : null}
|
onRemove={selectedItems.length ? removeItemsDialog : null}
|
||||||
@ -448,6 +494,25 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderColumnMenu() {
|
||||||
|
const { renderTableHeader} = this.props;
|
||||||
|
return(
|
||||||
|
<MenuActions
|
||||||
|
toolbar = {false}
|
||||||
|
autoCloseOnSelect = {false}
|
||||||
|
className={cssNames("KubeObjectMenu")}
|
||||||
|
>
|
||||||
|
{renderTableHeader.map((cellProps, index) => (
|
||||||
|
<MenuItem key={index}>
|
||||||
|
<Checkbox label = {cellProps.title ? cellProps.title : `<${cellProps.className}>`}
|
||||||
|
value ={!this.hiddenColumnNames.has(cellProps.className)}
|
||||||
|
onChange = {v =>this.updateColumnFilter(v, cellProps.className)} />
|
||||||
|
</MenuItem>))
|
||||||
|
}
|
||||||
|
</MenuActions>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderFooter() {
|
renderFooter() {
|
||||||
if (this.props.renderFooter) {
|
if (this.props.renderFooter) {
|
||||||
return this.props.renderFooter(this);
|
return this.props.renderFooter(this);
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import isString from "lodash/isString";
|
|||||||
export interface MenuActionsProps extends Partial<MenuProps> {
|
export interface MenuActionsProps extends Partial<MenuProps> {
|
||||||
className?: string;
|
className?: string;
|
||||||
toolbar?: boolean; // display menu as toolbar with icons
|
toolbar?: boolean; // display menu as toolbar with icons
|
||||||
|
autoCloseOnSelect?: boolean;
|
||||||
triggerIcon?: string | IconProps | React.ReactNode;
|
triggerIcon?: string | IconProps | React.ReactNode;
|
||||||
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
|
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
|
||||||
updateAction?(): void;
|
updateAction?(): void;
|
||||||
@ -80,7 +81,7 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
className, toolbar, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage,
|
className, toolbar, autoCloseOnSelect, children, updateAction, removeAction, triggerIcon, removeConfirmationMessage,
|
||||||
...menuProps
|
...menuProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const menuClassName = cssNames("MenuActions flex", className, {
|
const menuClassName = cssNames("MenuActions flex", className, {
|
||||||
@ -98,7 +99,7 @@ export class MenuActions extends React.Component<MenuActionsProps> {
|
|||||||
className={menuClassName}
|
className={menuClassName}
|
||||||
usePortal={autoClose}
|
usePortal={autoClose}
|
||||||
closeOnScroll={autoClose}
|
closeOnScroll={autoClose}
|
||||||
closeOnClickItem={autoClose}
|
closeOnClickItem={autoCloseOnSelect ?? autoClose }
|
||||||
closeOnClickOutside={autoClose}
|
closeOnClickOutside={autoClose}
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
>
|
>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user