Add edit column modal feature (#351)
This adds the column toggling to the feature to the host data table. Co-authored-by: Noah Talerman <noahtal@umich.edu>
This commit is contained in:
co-authored by
Noah Talerman
parent
a6c8987200
commit
4de0090bd3
@@ -35,3 +35,4 @@ helm-temp
|
||||
|
||||
#editors
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 426 B |
@@ -0,0 +1,90 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Checkbox from '../../../../../components/forms/fields/Checkbox';
|
||||
import Button from '../../../../../components/buttons/Button';
|
||||
|
||||
const useCheckboxListStateManagement = (allColumns, hiddenColumns) => {
|
||||
const [columnItems, setColumnItems] = useState(() => {
|
||||
return allColumns.map((column) => {
|
||||
return {
|
||||
name: column.title,
|
||||
accessor: column.accessor,
|
||||
isChecked: !hiddenColumns.includes(column.accessor),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const updateColumnItems = (columnAccessor) => {
|
||||
setColumnItems((prevState) => {
|
||||
const selectedColumn = columnItems.find(column => column.accessor === columnAccessor);
|
||||
const updatedColumn = {
|
||||
...selectedColumn,
|
||||
isChecked: !selectedColumn.isChecked,
|
||||
};
|
||||
|
||||
// this is replacing the column object with the updatedColumn we just created.
|
||||
const newState = prevState.map((currentColumn) => {
|
||||
return currentColumn.accessor === columnAccessor ? updatedColumn : currentColumn;
|
||||
});
|
||||
return newState;
|
||||
});
|
||||
};
|
||||
|
||||
return [columnItems, updateColumnItems];
|
||||
};
|
||||
|
||||
const getHiddenColumns = (columns) => {
|
||||
return columns.filter(column => !column.isChecked)
|
||||
.map(column => column.accessor);
|
||||
};
|
||||
|
||||
const EditColumnsModal = (props) => {
|
||||
const { columns, hiddenColumns, onSaveColumns, onCancelColumns } = props;
|
||||
const [columnItems, updateColumnItems] = useCheckboxListStateManagement(columns, hiddenColumns);
|
||||
|
||||
return (
|
||||
<div className={'edit-column-modal'}>
|
||||
<p>Choose which columns you see</p>
|
||||
<div className={'modal-items'}>
|
||||
{columnItems.map((column) => {
|
||||
return (
|
||||
<div key={column.accessor}>
|
||||
<Checkbox
|
||||
name={column.name}
|
||||
value={column.isChecked}
|
||||
onChange={() => updateColumnItems(column.accessor)}
|
||||
>
|
||||
<span>{column.name}</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className={'button-actions'}>
|
||||
<Button
|
||||
onClick={onCancelColumns}
|
||||
variant={'inverse'}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className={'save-button'}
|
||||
onClick={() => onSaveColumns(getHiddenColumns(columnItems))}
|
||||
variant={'default'}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
EditColumnsModal.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object), // TODO: create proper interface for this
|
||||
hiddenColumns: PropTypes.arrayOf(PropTypes.string),
|
||||
onSaveColumns: PropTypes.func,
|
||||
onCancelColumns: PropTypes.func,
|
||||
};
|
||||
|
||||
export default EditColumnsModal;
|
||||
@@ -0,0 +1,11 @@
|
||||
.edit-column-modal {
|
||||
|
||||
.button-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.save-button {
|
||||
margin-left: $pad-half;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
import labelInterface from 'interfaces/label';
|
||||
import Button from 'components/buttons/Button';
|
||||
import InputField from 'components/forms/fields/InputField';
|
||||
import KolideIcon from 'components/icons/KolideIcon';
|
||||
import Modal from 'components/modals/Modal';
|
||||
import RoboDogImage from '../../../../../../assets/images/robo-dog-176x144@2x.png';
|
||||
import EditColumnsIcon from '../../../../../../assets/images/icon-edit-columns-20x20@2x.png';
|
||||
|
||||
import { hostDataHeaders, defaultHiddenColumns } from './HostTableConfig';
|
||||
import HostsDataTable from '../HostsDataTable/HostsDataTable';
|
||||
import EditColumnsModal from '../EditColumnsModal/EditColumnsModal';
|
||||
|
||||
const baseClass = 'host-container';
|
||||
|
||||
@@ -20,13 +26,18 @@ class HostContainer extends Component {
|
||||
|
||||
static defaultProps = {
|
||||
selectedLabel: { count: undefined },
|
||||
};
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
// For now we persist using localstorage. May do server side persistence later.
|
||||
const storedHiddenColumns = JSON.parse(localStorage.getItem('hostHiddenColumns'));
|
||||
|
||||
this.state = {
|
||||
searchQuery: '',
|
||||
showEditColumnsModal: false,
|
||||
hiddenColumns: storedHiddenColumns !== null ? storedHiddenColumns : defaultHiddenColumns,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,14 +47,55 @@ class HostContainer extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
onEditColumnsClick = () => {
|
||||
this.setState({
|
||||
showEditColumnsModal: true,
|
||||
});
|
||||
}
|
||||
|
||||
onCancelColumns = () => {
|
||||
this.setState({
|
||||
showEditColumnsModal: false,
|
||||
});
|
||||
}
|
||||
|
||||
onSaveColumns = (newHiddenColumns) => {
|
||||
localStorage.setItem('hostHiddenColumns', JSON.stringify(newHiddenColumns));
|
||||
this.setState({
|
||||
hiddenColumns: newHiddenColumns,
|
||||
showEditColumnsModal: false,
|
||||
});
|
||||
}
|
||||
|
||||
renderEditColumnsModal = () => {
|
||||
const { showEditColumnsModal, hiddenColumns } = this.state;
|
||||
|
||||
if (!showEditColumnsModal) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Edit Columns"
|
||||
onExit={() => this.setState({ showEditColumnsModal: false })}
|
||||
className={`${baseClass}__invite-modal`}
|
||||
>
|
||||
<EditColumnsModal
|
||||
columns={hostDataHeaders}
|
||||
hiddenColumns={hiddenColumns}
|
||||
onSaveColumns={this.onSaveColumns}
|
||||
onCancelColumns={this.onCancelColumns}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { onSearchQueryChange } = this;
|
||||
const { onSearchQueryChange, renderEditColumnsModal } = this;
|
||||
const { selectedFilter, selectedLabel } = this.props;
|
||||
const { searchQuery } = this.state;
|
||||
const { searchQuery, hiddenColumns } = this.state;
|
||||
|
||||
if (selectedFilter === 'all-hosts' && selectedLabel.count === 0) {
|
||||
return (
|
||||
<div className={`${baseClass} ${baseClass}--no-hosts`}>
|
||||
<div className={`${baseClass} ${baseClass}--no-hosts`}>
|
||||
<div className={`${baseClass}--no-hosts__inner`}>
|
||||
<img src={RoboDogImage} alt="No Hosts" />
|
||||
<div>
|
||||
@@ -62,20 +114,33 @@ class HostContainer extends Component {
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}`}>
|
||||
<div className={`${baseClass}__search-input`}>
|
||||
<InputField
|
||||
placeholder="Search hosts by hostname"
|
||||
name=""
|
||||
onChange={onSearchQueryChange}
|
||||
value={searchQuery}
|
||||
inputWrapperClass={`${baseClass}__input-wrapper`}
|
||||
/>
|
||||
<KolideIcon name="search" />
|
||||
{/* TODO: find a way to move these controls into the table component */}
|
||||
<div className={`${baseClass}__table-controls`}>
|
||||
<Button onClick={this.onEditColumnsClick} variant="unstyled" className={`${baseClass}__edit-columns-button`}>
|
||||
<img src={EditColumnsIcon} alt="edit columns icon" />
|
||||
Edit columns
|
||||
</Button>
|
||||
<div data-tip data-for="search" className={`${baseClass}__search-input`}>
|
||||
<InputField
|
||||
placeholder="Search hostname, UUID, serial number, or IPv4"
|
||||
name=""
|
||||
onChange={onSearchQueryChange}
|
||||
value={searchQuery}
|
||||
inputWrapperClass={`${baseClass}__input-wrapper`}
|
||||
/>
|
||||
<KolideIcon name="search" />
|
||||
</div>
|
||||
<ReactTooltip place="bottom" type="dark" effect="solid" id="search" backgroundColor="#3e4771">
|
||||
<span className={`${baseClass}__tooltip-text`}>Search by hostname, UUID, serial number, or IPv4</span>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
<HostsDataTable
|
||||
selectedFilter={selectedFilter}
|
||||
searchQuery={searchQuery}
|
||||
tableColumns={hostDataHeaders}
|
||||
hiddenColumns={hiddenColumns}
|
||||
/>
|
||||
{renderEditColumnsModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
|
||||
import HeaderCell from '../HeaderCell/HeaderCell';
|
||||
import LinkCell from '../LinkCell/LinkCell';
|
||||
import StatusCell from '../StatusCell/StatusCell';
|
||||
import TextCell from '../TextCell/TextCell';
|
||||
import { humanHostMemory, humanHostUptime, humanHostLastSeen } from '../../../../../kolide/helpers';
|
||||
|
||||
const hostDataHeaders = [
|
||||
{
|
||||
title: 'Hostname',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'hostname',
|
||||
Cell: cellProps => <LinkCell value={cellProps.cell.value} host={cellProps.row.original} />,
|
||||
canHide: false,
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
Header: 'Status',
|
||||
disableSortBy: true,
|
||||
accessor: 'status',
|
||||
Cell: cellProps => <StatusCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: 'OS',
|
||||
Header: cellProps => <HeaderCell all={cellProps.column} value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'os_version',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: 'Osquery',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'osquery_version',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: 'IPv4',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'primary_ip',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: 'Last Seen',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'seen_time',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} formatter={humanHostLastSeen} />,
|
||||
},
|
||||
{
|
||||
title: 'UUID',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'uuid',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: 'Uptime',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'uptime',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} formatter={humanHostUptime} />,
|
||||
},
|
||||
{
|
||||
title: 'CPU',
|
||||
Header: 'CPU',
|
||||
disableSortBy: true,
|
||||
accessor: 'host_cpu',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: 'Memory',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'memory',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} formatter={humanHostMemory} />,
|
||||
},
|
||||
{
|
||||
title: 'MAC Address',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'primary_mac',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: 'Serial Number',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'hardware_serial',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: 'Hardware Model',
|
||||
Header: cellProps => <HeaderCell value={cellProps.column.title} isSortedDesc={cellProps.column.isSortedDesc} />,
|
||||
accessor: 'hardware_model',
|
||||
Cell: cellProps => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
];
|
||||
|
||||
const defaultHiddenColumns = [
|
||||
'primary_mac',
|
||||
'host_cpu',
|
||||
'memory',
|
||||
'uptime',
|
||||
'uuid',
|
||||
'seen_time',
|
||||
'hardware_model',
|
||||
'hardware_serial',
|
||||
];
|
||||
|
||||
export { hostDataHeaders, defaultHiddenColumns };
|
||||
@@ -3,15 +3,8 @@
|
||||
&--no-hosts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-top: 35px;
|
||||
font-size: 15px;
|
||||
font-weight: $regular;
|
||||
line-height: 2;
|
||||
letter-spacing: normal;
|
||||
color: rgba(32, 37, 50, 0.66);
|
||||
border-top: 1px solid $ui-borders;
|
||||
margin-top: $pad-half;
|
||||
align-items: center;
|
||||
margin-top: 80px;
|
||||
|
||||
h1 {
|
||||
font-size: $large;
|
||||
@@ -47,8 +40,13 @@
|
||||
|
||||
&__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
|
||||
h1 {
|
||||
font-size: $small;
|
||||
font-weight: $bold;
|
||||
margin-bottom: $pad-medium;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 176px;
|
||||
@@ -61,6 +59,12 @@
|
||||
font-size: $x-small;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.no-filter-results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
.host-pagination__pager-wrap {
|
||||
@@ -96,11 +100,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__table-controls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__edit-columns-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: $x-small;
|
||||
color: $core-blue;
|
||||
|
||||
img {
|
||||
width: 20px;
|
||||
margin-right: $pad-half;
|
||||
}
|
||||
}
|
||||
|
||||
&__edit-columns-button:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: $core-blue-over;
|
||||
}
|
||||
|
||||
&__search-input {
|
||||
position: relative;
|
||||
color: $core-dark-blue-grey;
|
||||
width: 344px;
|
||||
margin-left: auto;
|
||||
margin-left: $pad-medium;
|
||||
|
||||
.host-container__input-wrapper {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -3,18 +3,11 @@ import PropTypes from 'prop-types';
|
||||
import { useTable, useGlobalFilter, useSortBy, useAsyncDebounce } from 'react-table';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
|
||||
// TODO: move this file closer to HostsDataTable
|
||||
import { humanHostMemory, humanHostUptime } from 'kolide/helpers';
|
||||
import { getHostTableData } from 'redux/nodes/components/ManageHostsPage/actions';
|
||||
|
||||
import Spinner from 'components/loaders/Spinner';
|
||||
import HostPagination from 'components/hosts/HostPagination';
|
||||
|
||||
import HeaderCell from '../HeaderCell/HeaderCell';
|
||||
import TextCell from '../TextCell/TextCell';
|
||||
import StatusCell from '../StatusCell/StatusCell';
|
||||
import LinkCell from '../LinkCell/LinkCell';
|
||||
import scrollToTop from '../../../../../utilities/scroll_to_top';
|
||||
|
||||
// TODO: pass in as props
|
||||
@@ -42,6 +35,8 @@ const HostsDataTable = (props) => {
|
||||
// component cannot access the router state.
|
||||
selectedFilter,
|
||||
searchQuery,
|
||||
hiddenColumns,
|
||||
tableColumns,
|
||||
} = props;
|
||||
|
||||
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||
@@ -59,20 +54,9 @@ const HostsDataTable = (props) => {
|
||||
|
||||
const pageIndexChangeRef = useRef();
|
||||
|
||||
// TODO: maybe pass as props?
|
||||
const columns = useMemo(() => {
|
||||
return [
|
||||
{ Header: cellProps => <HeaderCell value={'Hostname'} isSortedDesc={cellProps.column.isSortedDesc} />, accessor: 'hostname', Cell: cellProps => <LinkCell value={cellProps.cell.value} host={cellProps.row.original} /> },
|
||||
{ Header: 'Status', disableSortBy: true, accessor: 'status', Cell: cellProps => <StatusCell value={cellProps.cell.value} /> },
|
||||
{ Header: cellProps => <HeaderCell all={cellProps.column} value={'OS'} isSortedDesc={cellProps.column.isSortedDesc} />, accessor: 'os_version', Cell: cellProps => <TextCell value={cellProps.cell.value} /> },
|
||||
{ Header: cellProps => <HeaderCell value={'Osquery'} isSortedDesc={cellProps.column.isSortedDesc} />, accessor: 'osquery_version', Cell: cellProps => <TextCell value={cellProps.cell.value} /> },
|
||||
{ Header: cellProps => <HeaderCell value={'IPv4'} isSortedDesc={cellProps.column.isSortedDesc} />, accessor: 'primary_ip', Cell: cellProps => <TextCell value={cellProps.cell.value} /> },
|
||||
{ Header: cellProps => <HeaderCell value={'Physical Address'} isSortedDesc={cellProps.column.isSortedDesc} />, accessor: 'primary_mac', Cell: cellProps => <TextCell value={cellProps.cell.value} /> },
|
||||
{ Header: 'CPU', disableSortBy: true, accessor: 'host_cpu', Cell: cellProps => <TextCell value={cellProps.cell.value} /> },
|
||||
{ Header: cellProps => <HeaderCell value={'Memory'} isSortedDesc={cellProps.column.isSortedDesc} />, accessor: 'memory', Cell: cellProps => <TextCell value={cellProps.cell.value} formatter={humanHostMemory} /> },
|
||||
{ Header: cellProps => <HeaderCell value={'Uptime'} isSortedDesc={cellProps.column.isSortedDesc} />, accessor: 'uptime', Cell: cellProps => <TextCell value={cellProps.cell.value} formatter={humanHostUptime} /> },
|
||||
];
|
||||
}, []);
|
||||
return tableColumns;
|
||||
}, [tableColumns]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
return hostAPIOrder.map((id) => {
|
||||
@@ -85,13 +69,16 @@ const HostsDataTable = (props) => {
|
||||
rows,
|
||||
prepareRow,
|
||||
setGlobalFilter,
|
||||
setHiddenColumns,
|
||||
state: tableState,
|
||||
} = useTable(
|
||||
{ columns,
|
||||
data,
|
||||
initialState: {
|
||||
sortBy: [{ id: DEFAULT_SORT_KEY, desc: true }],
|
||||
hiddenColumns,
|
||||
},
|
||||
autoResetHiddenColumns: false,
|
||||
disableMultiSort: true,
|
||||
manualGlobalFilter: true,
|
||||
manualSortBy: true,
|
||||
@@ -110,28 +97,34 @@ const HostsDataTable = (props) => {
|
||||
|
||||
const onPaginationChange = useCallback((newPage) => {
|
||||
if (newPage > pageIndex) {
|
||||
// pageIndexChangeRef.current = pageIndex;
|
||||
setPageIndex(pageIndex + 1);
|
||||
} else {
|
||||
// pageIndpageIndexChangeRefexRef.current = pageIndex;
|
||||
setPageIndex(pageIndex - 1);
|
||||
}
|
||||
pageIndexChangeRef.current = true;
|
||||
scrollToTop();
|
||||
}, [pageIndex, setPageIndex]);
|
||||
|
||||
// Since searchQuery is feed in from the parent, we want to debounce the globalfilter change
|
||||
// Since searchQuery is passed in from the parent, we want to debounce the globalFilter change
|
||||
// when we see it change.
|
||||
useEffect(() => {
|
||||
debouncedGlobalFilter(searchQuery);
|
||||
}, [debouncedGlobalFilter, searchQuery]);
|
||||
|
||||
// Any changes to these relevent table search params will fire off an action to get the new
|
||||
// Track hidden columns changing and update the table accordingly.
|
||||
useEffect(() => {
|
||||
setHiddenColumns(hiddenColumns);
|
||||
}, [setHiddenColumns, hiddenColumns]);
|
||||
|
||||
// Any changes to these relevant table search params will fire off an action to get the new
|
||||
// hosts data.
|
||||
useEffect(() => {
|
||||
if (pageIndexChangeRef.current) { // the pageIndex has changed
|
||||
dispatch(getHostTableData(pageIndex, pageSize, selectedFilter, globalFilter, sortBy));
|
||||
} else {
|
||||
} else { // something besides pageIndex changed. we want to get results starting at the first page
|
||||
// NOTE: currently this causes the request to fire twice if the user is not on the first page
|
||||
// of results. Need to come back to this and figure out how to get it to
|
||||
// only fire once.
|
||||
setPageIndex(0);
|
||||
dispatch(getHostTableData(0, pageSize, selectedFilter, globalFilter, sortBy));
|
||||
}
|
||||
@@ -144,18 +137,11 @@ const HostsDataTable = (props) => {
|
||||
return (
|
||||
<div className={`${containerClass} ${containerClass}--no-hosts`}>
|
||||
<div className={`${containerClass}--no-hosts__inner`}>
|
||||
<div>
|
||||
<h1>No hosts match the current search criteria</h1>
|
||||
<div className={'no-filter-results'}>
|
||||
<h1>No hosts match the current criteria</h1>
|
||||
<p>Expecting to see new hosts? Try again in a few seconds as the system catches up</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<HostPagination
|
||||
hostOnCurrentPage={100}
|
||||
currentPage={pageIndex}
|
||||
hostsPerPage={pageSize}
|
||||
onPaginationChange={onPaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -166,6 +152,11 @@ const HostsDataTable = (props) => {
|
||||
<p className={'manage-hosts__host-count'}>{generateHostCountText(pageIndex, pageSize, rows.length)}</p>
|
||||
</div>
|
||||
<div className={'hosts-table hosts-table__wrapper'}>
|
||||
{loadingHosts &&
|
||||
<div className={'loading-overlay'}>
|
||||
<Spinner />
|
||||
</div>
|
||||
}
|
||||
<table className={'hosts-table__table'}>
|
||||
<thead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
@@ -179,22 +170,20 @@ const HostsDataTable = (props) => {
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{loadingHosts
|
||||
? <tr><td><Spinner /></td></tr>
|
||||
: rows.map((row) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell) => {
|
||||
return (
|
||||
<td {...cell.getCellProps()}>
|
||||
{cell.render('Cell')}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
{rows.map((row) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell) => {
|
||||
return (
|
||||
<td {...cell.getCellProps()}>
|
||||
{cell.render('Cell')}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -213,6 +202,8 @@ const HostsDataTable = (props) => {
|
||||
HostsDataTable.propTypes = {
|
||||
selectedFilter: PropTypes.string,
|
||||
searchQuery: PropTypes.string,
|
||||
tableColumns: PropTypes.arrayOf(PropTypes.object), // TODO: create proper interface for this
|
||||
hiddenColumns: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
export default HostsDataTable;
|
||||
|
||||
@@ -86,5 +86,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user