diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 33a16633b..2d85c64bf 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -188,6 +188,7 @@ "Manage columns": "Manage columns", "Selected columns will appear in the table.": "Selected columns will appear in the table.", "Click and drag the items to reorder the columns in the table.": "Click and drag the items to reorder the columns in the table.", + "Clear filters": "Clear filters", "Unselect all": "Unselect all", "Select all": "Select all", "Restore default columns": "Restore default columns", diff --git a/web/src/components/filters/filters-toolbar.css b/web/src/components/filters/filters-toolbar.css index d59fad06e..d87eec629 100644 --- a/web/src/components/filters/filters-toolbar.css +++ b/web/src/components/filters/filters-toolbar.css @@ -189,3 +189,31 @@ div#filter-toolbar-search-filters { #clear-all-filters-button { padding: 0; } + +.custom-chip.buttonless>p { + margin-right: 1rem; +} + +.custom-chip.selected, +.custom-chip.selected>button, +.custom-chip.selected>*, +.pf-theme-dark .custom-chip.selected, +.pf-theme-dark .custom-chip.selected>button, +.pf-theme-dark .custom-chip.selected>* { + color: #fff; + background-color: #06c; +} + +.custom-chip.unselected, +.custom-chip.unselected>button, +.custom-chip.unselected>* { + color: #000; + background-color: #fff; +} + +.pf-theme-dark .custom-chip.unselected, +.pf-theme-dark .custom-chip.unselected>button, +.pf-theme-dark .custom-chip.unselected>* { + color: #000; + background-color: #D2D2D2; +} \ No newline at end of file diff --git a/web/src/components/modals/columns-modal.css b/web/src/components/modals/columns-modal.css index 6f40d15e4..9567b488a 100644 --- a/web/src/components/modals/columns-modal.css +++ b/web/src/components/modals/columns-modal.css @@ -24,4 +24,20 @@ label { margin-top: auto; margin-bottom: auto; padding: inherit; +} + +.popup-header-margin { + margin-top: 1rem; +} + +.flex-center { + align-self: center; +} + +.flex-gap { + gap: 0.5rem; +} + +.custom-chip.pointer { + cursor: pointer; } \ No newline at end of file diff --git a/web/src/components/modals/columns-modal.tsx b/web/src/components/modals/columns-modal.tsx index f37bbdcd4..79ce99537 100644 --- a/web/src/components/modals/columns-modal.tsx +++ b/web/src/components/modals/columns-modal.tsx @@ -11,6 +11,8 @@ import { DragDrop, Draggable, Droppable, + Flex, + FlexItem, Text, TextContent, TextVariants, @@ -24,6 +26,8 @@ import { Column, ColumnSizeMap, getDefaultColumns, getFullColumnName } from '../ import './columns-modal.css'; import Modal from './modal'; +export const COLUMN_FILTER_KEYS = ['source', 'destination', 'time', 'host', 'namespace', 'owner', 'ip', 'dns']; + export const ColumnsModal: React.FC<{ isModalOpen: boolean; setModalOpen: (v: boolean) => void; @@ -35,10 +39,15 @@ export const ColumnsModal: React.FC<{ }> = ({ id, config, isModalOpen, setModalOpen, columns, setColumns, setColumnSizes }) => { const [resetClicked, setResetClicked] = React.useState(false); const [updatedColumns, setUpdatedColumns] = React.useState([]); - const [isSaveDisabled, setSaveDisabled] = React.useState(true); - const [isAllSelected, setAllSelected] = React.useState(false); + const [filterKeys, setFilterKeys] = React.useState([]); const { t } = useTranslation('plugin__netobserv-plugin'); + React.useEffect(() => { + if (isModalOpen) { + setFilterKeys([]); + } + }, [isModalOpen]); + React.useEffect(() => { if (!isModalOpen || _.isEmpty(updatedColumns)) { setUpdatedColumns(_.cloneDeep(columns)); @@ -46,18 +55,6 @@ export const ColumnsModal: React.FC<{ // eslint-disable-next-line react-hooks/exhaustive-deps }, [columns, isModalOpen]); - React.useEffect(() => { - let allSelected = true; - _.forEach(updatedColumns, (col: Column) => { - if (!col.isSelected) { - allSelected = false; - return false; - } - }); - setAllSelected(allSelected); - setSaveDisabled(_.isEmpty(updatedColumns.filter(col => col.isSelected))); - }, [updatedColumns]); - const onDrop = React.useCallback( (source, dest) => { if (dest) { @@ -91,13 +88,49 @@ export const ColumnsModal: React.FC<{ setUpdatedColumns(getDefaultColumns(config.columns).filter(c => columns.some(existing => existing.id === c.id))); }, [columns, config.columns]); + const isSaveDisabled = React.useCallback(() => { + return _.isEmpty(updatedColumns.filter(c => c.isSelected)); + }, [updatedColumns]); + + const isFilteredColumn = React.useCallback((c: Column, fks: string[]) => { + return ( + _.isEmpty(fks) || + _.reduce( + fks, + (acc, fk) => + (acc = + acc && + (c.id.toLowerCase().includes(fk) || + c.name.toLowerCase().includes(fk) || + c.group?.toLowerCase().includes(fk) || + false)), + true + ) + ); + }, []); + + const getColumnFilterKeys = React.useCallback(() => { + return COLUMN_FILTER_KEYS.filter(fk => columns.some(c => isFilteredColumn(c, [fk]))); + }, [columns, isFilteredColumn]); + + const filteredColumns = React.useCallback(() => { + return updatedColumns.filter(c => isFilteredColumn(c, filterKeys)); + }, [filterKeys, isFilteredColumn, updatedColumns]); + + const isAllSelected = React.useCallback(() => { + return _.reduce(filteredColumns(), (acc, c) => (acc = acc && c.isSelected), true); + }, [filteredColumns]); + const onSelectAll = React.useCallback(() => { + const allSelected = isAllSelected(); const result = [...updatedColumns]; - _.forEach(result, (col: Column) => { - col.isSelected = !isAllSelected; + _.forEach(result, (c: Column) => { + if (isFilteredColumn(c, filterKeys)) { + c.isSelected = !allSelected; + } }); setUpdatedColumns(result); - }, [updatedColumns, setUpdatedColumns, isAllSelected]); + }, [isAllSelected, updatedColumns, isFilteredColumn, filterKeys]); const onClose = React.useCallback(() => { setResetClicked(false); @@ -113,7 +146,18 @@ export const ColumnsModal: React.FC<{ onClose(); }, [resetClicked, setColumns, updatedColumns, onClose, setColumnSizes]); - const draggableItems = updatedColumns.map((column, i) => ( + const toggleChip = React.useCallback( + (key: string) => { + if (filterKeys.includes(key)) { + setFilterKeys(filterKeys.filter(k => k !== key)); + } else { + setFilterKeys(COLUMN_FILTER_KEYS.filter(f => f === key || filterKeys.includes(f))); + } + }, + [filterKeys] + ); + + const draggableItems = filteredColumns().map((column, i) => ( - - {t('Selected columns will appear in the table.')}  - {t('Click and drag the items to reorder the columns in the table.')} - - - + <> + + + {t('Selected columns will appear in the table.')}  + {t('Click and drag the items to reorder the columns in the table.')} + + + + + + {getColumnFilterKeys().map(key => { + return ( + toggleChip(key)} + className={`custom-chip ${ + filterKeys.includes(key) ? 'selected' : 'unselected' + } buttonless gap pointer`} + > + {key} + + ); + })} + + + + {_.isEmpty(filteredColumns()) ? ( + + ) : ( + + )} + + + } footer={
@@ -171,10 +246,10 @@ export const ColumnsModal: React.FC<{ - + - + <> + + + {t('Selected panels will appear in the overview tab.')}  + {t('Click and drag the items to reorder the panels in the overview tab.')} + + + + + + {PANEL_FILTER_KEYS.map(key => { + return ( + + toggleChip(key)}> + {key} + + + ); + })} + + + + {_.isEmpty(filteredPanels()) ? ( + + ) : ( + + )} + + + } footer={
@@ -170,10 +230,10 @@ export const OverviewPanelsModal: React.FC<{ - +