react-table#Column TypeScript Examples
The following examples show how to use
react-table#Column.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts From grafana-chinese with Apache License 2.0 | 7 votes |
export function getColumns(data: DataFrame, availableWidth: number, columnMinWidth: number): Column[] {
const columns: Column[] = [];
let fieldCountWithoutWidth = data.fields.length;
for (const field of data.fields) {
const fieldTableOptions = (field.config.custom || {}) as TableFieldOptions;
if (fieldTableOptions.width) {
availableWidth -= fieldTableOptions.width;
fieldCountWithoutWidth -= 1;
}
const Cell = getCellComponent(fieldTableOptions.displayMode);
columns.push({
Cell,
Header: field.name,
accessor: field.name,
width: fieldTableOptions.width,
});
}
// divide up the rest of the space
const sharedWidth = availableWidth / fieldCountWithoutWidth;
for (const column of columns) {
if (!column.width) {
column.width = Math.max(sharedWidth, columnMinWidth);
}
}
return columns;
}
Example #2
Source File: types.tsx From admin with MIT License | 6 votes |
useTypesColumns = () => {
const columns = useMemo<Column<ProductType>[]>(() => {
return [
{
Header: () => (
<div className="flex items-center gap-1 min-w-[626px]">
Type <SortingIcon size={16} />
</div>
),
accessor: "value",
Cell: ({ row: { original } }) => {
return <span>{original.value}</span>
},
},
]
}, [])
return columns
}
Example #3
Source File: tags.tsx From admin with MIT License | 6 votes |
TagColumns: Column<ProductTag>[] = [
{
Header: () => (
<div className="flex items-center gap-1">
Tag <SortingIcon size={16} />
</div>
),
accessor: "value",
Cell: ({ row: { original } }) => {
return (
<div className="w-[220px]">
<span className="bg-grey-10 px-2 py-0.5 rounded-rounded">
#{original.value}
</span>
</div>
)
},
},
]
Example #4
Source File: groups.tsx From admin with MIT License | 6 votes |
useGroupColumns = () => {
const columns = useMemo<Column<CustomerGroup>[]>(() => {
return [
{
Header: () => (
<div className="flex items-center gap-1 min-w-[540px]">
Title <SortingIcon size={16} />
</div>
),
accessor: "name",
},
{
Header: () => (
<div className="flex justify-end items-center gap-1">
Members <SortingIcon size={16} />
</div>
),
id: "members",
accessor: (r) => r.customers?.length,
Cell: ({ cell: { value } }) => {
return <div className="text-right">{value}</div>
},
},
]
}, [])
return columns
}
Example #5
Source File: collection.tsx From admin with MIT License | 6 votes |
useCollectionColumns = () => {
const columns = useMemo<Column<ProductCollection>[]>(() => {
return [
{
Header: () => (
<div className="flex items-center gap-1 min-w-[546px]">
Title <SortingIcon size={16} />
</div>
),
accessor: "title",
Cell: ({ row: { original } }) => {
return <span>{original.title}</span>
},
},
{
Header: () => (
<div className="flex justify-end items-center gap-1">
Products <SortingIcon size={16} />
</div>
),
id: "products",
accessor: (row) => row.products.length,
Cell: ({ cell: { value } }) => {
return <div className="text-right">{value}</div>
},
},
]
}, [])
return columns
}
Example #6
Source File: columns.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 6 votes |
createGroupedColumns = () =>
[
{
Header: 'Vulnerability',
accessor: 'title',
Cell: ({ value, row }: CellProps<Vulnerability>) =>
row.original.cve ? (
<a
href={`https://nvd.nist.gov/vuln/detail/${row.original.cve}`}
target="_blank"
rel="noopener noreferrer"
>
{value} {extLink}
</a>
) : (
<p>{row.original.title}</p>
),
width: 800,
Filter: ColumnFilter
},
{
Header: 'Severity',
id: 'severity',
accessor: ({ severity }) => (
<span
style={{
borderBottom: `6px solid ${getSeverityColor({
id: severity ?? ''
})}`,
width: '80px'
}}
>
{severity}
</span>
),
width: 100,
Filter: selectFilter(['Low', 'Medium', 'High', 'Critical', 'None'])
},
{
Header: 'Count',
accessor: 'cnt',
disableFilters: true
},
{
Header: 'Description',
accessor: 'description',
disableFilters: true
}
] as Column<Vulnerability>[]
Example #7
Source File: config.tsx From admin with MIT License | 6 votes |
CUSTOMER_GROUPS_CUSTOMERS_LIST_TABLE_COLUMNS: Column<
Customer
>[] = [
{
Header: () => (
<div className="flex items-center gap-1">
Name <SortingIcon size={16} />
</div>
),
accessor: "customer",
Cell: ({ row }) => (
<CustomerAvatarItem customer={row.original} color={getColor(row.index)} />
),
},
{
Header: () => (
<div className="flex items-center gap-1">
Email <SortingIcon size={16} />
</div>
),
accessor: "email",
},
{
accessor: "groups",
disableSortBy: true,
Header: "Groups",
Cell: ({ cell: { value } }) => <CustomersGroupsSummary groups={value} />,
},
{
Header: "",
id: "settings-col",
},
]
Example #8
Source File: config.tsx From admin with MIT License | 6 votes |
CUSTOMER_GROUPS_CUSTOMERS_TABLE_COLUMNS: Column<Customer>[] = [
{
id: "selection",
Header: ({ getToggleAllPageRowsSelectedProps }) => (
<IndeterminateCheckbox {...getToggleAllPageRowsSelectedProps()} />
),
Cell: ({ row }) => {
return (
<Table.Cell onClick={(e) => e.stopPropagation()} className="w-[100px]">
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</Table.Cell>
)
},
},
{
Header: () => (
<div className="flex items-center gap-1">
Name <SortingIcon size={16} />
</div>
),
accessor: "customer",
Cell: ({ row }) => (
<CustomerAvatarItem customer={row.original} color={getColor(row.index)} />
),
},
{
Header: () => (
<div className="flex items-center gap-1">
Email <SortingIcon size={16} />
</div>
),
accessor: "email",
},
{
accessor: "groups",
Header: () => <div className="text-left">Segments</div>,
Cell: ({ cell: { value } }) => <CustomersGroupsSummary groups={value} />,
},
]
Example #9
Source File: config.tsx From admin with MIT License | 6 votes |
CUSTOMER_GROUPS_TABLE_COLUMNS: Column<CustomerGroup>[] = [
{
Header: () => (
<div className="flex items-center gap-1">
Title <SortingIcon size={16} />
</div>
),
accessor: "name",
},
{
Header: () => (
<div className="flex items-center gap-1">
Members <SortingIcon size={16} />
</div>
),
id: "members",
accessor: (r) => r.customers?.length,
},
]
Example #10
Source File: table.mappers.ts From master-frontend-lemoncode with MIT License | 5 votes |
mapColumnListFromStringToColumn = (columns: string[]): Column[] =>
mapToCollection(columns, mapColumnFromStringToColumn)
Example #11
Source File: ProposalTable.tsx From mysterium-vpn-desktop with MIT License | 5 votes |
ProposalTable: React.FC = observer(function ProposalTable() {
const { proposals } = useStores()
const columns = React.useMemo<Column<UIProposal>[]>(
() => [
{
Header: "",
accessor: "ipType",
width: 25,
Cell: (props) => {
if (props.value === "residential") {
return (
<span style={{ fontSize: 15 }}>
<FontAwesomeIcon icon={faRegistered} />
</span>
)
}
return <span />
},
disableSortBy: true,
},
{ Header: "Node", accessor: "shortId", width: 120 },
{
Header: "Country",
accessor: "countryName",
width: 124,
},
{
Header: "Price/h",
id: "priceHour",
accessor: (p): string => displayTokens4(p.price.perHourTokens),
width: 62,
sortType: "basic",
},
{
Header: "Price/GiB",
id: "priceGib",
accessor: (p): string => displayTokens4(p.price.perGibTokens),
width: 62,
sortType: "basic",
},
{
Header: "Price",
accessor: (p): number => proposals.priceTier(p),
width: 44,
Cell: (props: { value: number }) => <IconPriceTier tier={props.value} />,
},
{
Header: "Quality",
accessor: "qualityLevel",
width: 42,
sortDescFirst: true,
Cell: (props) => {
return (
<CellCenter>
<ProposalQuality level={props.value} />
</CellCenter>
)
},
},
],
[],
) as Column<UIProposal>[]
return (
<Styles>
<Table columns={columns} data={proposals.filteredProposals} />
</Styles>
)
})
Example #12
Source File: use-columns.tsx From admin with MIT License | 5 votes |
usePricesColumns = () => {
const columns = React.useMemo<Column<Product>[]>(
() => [
{
Header: <Table.HeadCell className="pl-4">Name</Table.HeadCell>,
accessor: "title",
Cell: ({ row: { original } }) => (
<div className="pl-4 flex items-center">
<div className="h-[40px] w-[30px] my-1.5 flex items-center mr-4">
{original.thumbnail ? (
<img
src={original.thumbnail}
className="h-full object-cover rounded-soft"
/>
) : (
<div className="flex items-center justify-center w-full h-full rounded-soft bg-grey-10">
<ImagePlaceholder size={16} />
</div>
)}
</div>
<div className="flex flex-col">
<span>{original.title}</span>
</div>
</div>
),
},
{
Header: (
<Table.HeadCell className="w-[400px]">Collection</Table.HeadCell>
),
accessor: "collection",
Cell: ({ cell: { value } }) => (
<Table.Cell>
{value?.title ? (
value.title
) : (
<span className="text-grey-40">No collection</span>
)}
</Table.Cell>
),
},
{
Header: "Variants",
Cell: ({ row: { original } }) => (
<Table.Cell>{original.variants.length}</Table.Cell>
),
},
],
[]
)
return columns
}
Example #13
Source File: products.tsx From admin with MIT License | 5 votes |
useProductColumns = () => {
const columns = useMemo<Column<Product>[]>(() => {
return [
{
Header: () => (
<div className="flex items-center gap-1 min-w-[443px]">
Title <SortingIcon size={16} />
</div>
),
accessor: "title",
Cell: ({ row: { original } }) => {
return (
<div className="flex items-center">
<div className="h-[40px] w-[30px] my-1.5 flex items-center mr-4">
{original.thumbnail ? (
<img
src={original.thumbnail}
className="h-full object-cover rounded-soft"
/>
) : (
<div className="flex items-center justify-center w-full h-full rounded-soft bg-grey-10">
<ImagePlaceholder size={16} />
</div>
)}
</div>
<div className="flex flex-col">
<span>{original.title}</span>
</div>
</div>
)
},
},
{
Header: () => (
<div className="flex items-center gap-1">
Status <SortingIcon size={16} />
</div>
),
accessor: "status",
Cell: ({ row: { original } }) => (
<StatusIndicator
title={`${original.status
.charAt(0)
.toUpperCase()}${original.status.slice(1)}`}
variant={getProductStatusVariant(original.status)}
/>
),
},
{
Header: () => (
<div className="flex justify-end items-center gap-1">
Variants <SortingIcon size={16} />
</div>
),
id: "variants",
accessor: (row) => row.variants.length,
Cell: ({ cell: { value } }) => {
return <div className="text-right">{value}</div>
},
},
]
}, [])
return columns
}
Example #14
Source File: use-price-list-columns.tsx From admin with MIT License | 5 votes |
usePriceListTableColumns = () => {
const columns = useMemo<Column<PriceList>[]>(
() => [
{
Header: <Table.HeadCell>Name</Table.HeadCell>,
accessor: "name",
Cell: ({ cell: { value } }) => (
<Table.Cell>
<span className="inter-small-regular">{value}</span>
</Table.Cell>
),
},
{
Header: "Description",
accessor: "description",
Cell: ({ cell: { value } }) => <Table.Cell>{value}</Table.Cell>,
},
{
Header: "Status",
accessor: "status",
Cell: ({ row: { original } }) => (
<Table.Cell>{getPriceListStatus(original)}</Table.Cell>
),
},
{
Header: () => <div className="">Groups</div>,
accessor: "customer_groups",
Cell: ({ cell: { value } }) => {
const groups: string[] = isArray(value)
? value.map((v) => v.name)
: []
const [group, other] = formatPriceListGroups(groups)
return (
<Table.Cell>
{group}
{other && <span className="text-grey-40"> + {other} more</span>}
</Table.Cell>
)
},
},
{
accessor: "created_at",
Cell: ({ row: { original: priceList } }) => {
const { getActions } = usePriceListActions(priceList)
return (
<Table.Cell
onClick={(e) => e.stopPropagation()}
className="w-full flex justify-end"
>
<div className="justify-end">
<Actionables forceDropdown actions={getActions()} />
</div>
</Table.Cell>
)
},
},
],
[]
)
return [columns] as const
}
Example #15
Source File: use-view-product-columns.tsx From admin with MIT License | 5 votes |
useViewProductColumns = () => {
const columns: Column<SimpleProductType>[] = useMemo(
() => [
{
id: "selection",
Cell: ({ row }) => {
return (
<Table.Cell className="w-[0%] pl-base pr-large">
<div>{row.index + 1}</div>
</Table.Cell>
)
},
},
{
accessor: "thumbnail",
Cell: ({ cell: { value } }) => (
<Table.Cell className="w-[0%] pr-base">
<div className="h-[40px] w-[30px] bg-grey-5 rounded-soft overflow-hidden my-xsmall">
{value ? (
<img
src={value}
alt="Thumbnail"
className="h-full w-full object-cover"
/>
) : null}
</div>
</Table.Cell>
),
},
{
accessor: "title",
Cell: ({ cell: { row, value } }) => (
<Table.Cell className="w-[20%]">
<Link to={`/a/products/${row.original.id}`}>{value}</Link>
</Table.Cell>
),
},
{
accessor: "status",
Cell: ({ cell: { value } }) => (
<Table.Cell className="w-[50%] justify-start">
{decideStatus(value)}
</Table.Cell>
),
},
],
[]
)
return columns
}
Example #16
Source File: use-collection-product-columns.tsx From admin with MIT License | 5 votes |
useCollectionProductColumns = () => {
const columns: Column<SimpleProductType>[] = useMemo(
() => [
{
accessor: "thumbnail",
Cell: ({ cell: { value } }) => (
<Table.Cell className="w-[5%]">
<div className="h-[40px] w-[30px] bg-grey-5 rounded-soft overflow-hidden my-xsmall">
{value ? (
<img
src={value}
alt="Thumbnail"
className="h-full w-full object-cover"
/>
) : null}
</div>
</Table.Cell>
),
},
{
accessor: "title",
Cell: ({ cell: { value } }) => (
<Table.Cell className="w-3/6">{value}</Table.Cell>
),
},
{
accessor: "status",
Cell: ({ cell: { value } }) => (
<Table.Cell className="w-[10%] pr-base">
<div className="flex items-center justify-end">
{decideStatus(value)}
</div>
</Table.Cell>
),
},
],
[]
)
return columns
}
Example #17
Source File: product-table-config.tsx From admin with MIT License | 5 votes |
columns: Column<Product>[] = [ { Header: <Table.HeadCell className="pl-4">Product Details</Table.HeadCell>, accessor: "title", Cell: ({ row: { original } }) => ( <div className="pl-4 flex items-center w-[400px]"> <div className="h-[40px] w-[30px] my-1.5 flex items-center mr-4"> {original.thumbnail ? ( <img src={original.thumbnail} className="h-full object-cover rounded-soft" /> ) : ( <div className="flex items-center justify-center w-full h-full rounded-soft bg-grey-10"> <ImagePlaceholderIcon size={16} /> </div> )} </div> <div className="flex flex-col"> <span> {original.title}{" "} {original.subtitle && ( <span className="text-grey-50">({original.subtitle})</span> )} </span> </div> </div> ), }, { Header: <Table.HeadCell>Status</Table.HeadCell>, accessor: "status", Cell: ({ cell: { value } }) => ( <Table.Cell className="w-[10%] pr-base"> <div className="flex items-center">{decideStatus(value)}</div> </Table.Cell> ), }, { Header: ( <Table.HeadCell className="flex justify-end items-center pr-4"> Variants </Table.HeadCell> ), accessor: "variants", Cell: ({ row: { original } }) => ( <Table.Cell className="flex justify-end items-center pr-4"> {original.variants.length} </Table.Cell> ), }, ]
Example #18
Source File: FeTable.tsx From frontegg-react with MIT License | 5 votes |
FeTable: FC<TableProps> = <T extends object>(props: TableProps<T>) => { const tableRef = useRef<HTMLDivElement>(null); const firstRender = useRef<boolean>(true); const columns = useMemo(() => { const columns = props.columns.map( ({ sortable, Filter, Header, ...rest }) => ({ ...rest, disableSortBy: !sortable, disableFilters: !Filter, Filter, Header: Header ?? <div style={{ minWidth: rest.minWidth, maxWidth: rest.maxWidth }} />, } as FeTableColumnOptions<T>) ); if (props.expandable) { columns.unshift({ id: 'fe-expander', minWidth: 60, maxWidth: '60px' as any, Header: <div style={{ minWidth: '2rem', maxWidth: '2rem' }} />, Cell: (cell: Cell<T>) => { const row = cell.row as Row<T> & UseExpandedRowProps<T>; return ( <FeButton className={classNames('fe-table__expand-button', { 'is-expanded': row.isExpanded })} {...row.getToggleRowExpandedProps()} variant={row.isExpanded ? 'primary' : undefined} > <FeIcon name='right-arrow' /> </FeButton> ); }, }); } if (props.selection) { columns.unshift({ id: 'fe-selection', minWidth: 60, maxWidth: '60px' as any, Cell: (cell: Cell<T>) => { const row = cell.row as Row<T> & UseRowSelectRowProps<T>; return ( <FeCheckbox {...row.getToggleRowSelectedProps()} checked={row.isSelected} onChange={(e) => onRowSelected(row.original, e.target.checked)} /> ); }, }); } return columns as Column<T>[]; }, [props.columns, props.expandable]); const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, state, // The page controls ;) page, canPreviousPage, canNextPage, pageOptions, pageCount, gotoPage, nextPage, previousPage, setPageSize, // select props toggleAllRowsSelected, isAllRowsSelected, selectedFlatRows, toggleRowSelected, } = useTable( { columns, data: props.data, getRowId: (row: any) => row[props.rowKey], manualSortBy: !!props.onSortChange, manualFilters: !!props.onFilterChange, manualPagination: !!props.onPageChange, manualRowSelectedKey: props.rowKey, pageCount: !!props.onPageChange ? props.pageCount : undefined, autoResetPage: !props.onPageChange, useControlledState: (state1: any, meta) => ({ ...state1, sortBy: props.sortBy ?? state1.sortBy, filters: props.filters ?? state1.filters, selectedRowIds: props.selectedRowIds ?? state1.selectedRowIds, } as FeTableState<T>), expandSubRows: false, autoResetExpanded: false, initialState: { pageIndex: 0, pageSize: props.pageSize, selectedRowIds: props.selectedRowIds || {}, }, } as FeUseTable<T>, useFilters, useSortBy, useExpanded, usePagination, useRowSelect, useFlexLayout ) as FeTableInstance<T>; checkTableProps(props); const tableState = state as UseSortByState<T> & UseFiltersState<T> & UsePaginationState<T> & UseRowSelectState<T>; const onSortChange = useCallback( (column: FeTableColumnProps<T>) => { if (props.hasOwnProperty('sortBy')) { const sortBy = props.isMultiSort ? tableState.sortBy.filter(({ id }) => id !== column.id) : []; if (!column.isSorted) { sortBy.push({ id: column.id, desc: false }); } else if (!column.isSortedDesc) { sortBy.push({ id: column.id, desc: true }); } props.onSortChange?.(sortBy); } else { if (column.isSorted && column.isSortedDesc) { column.clearSortBy(); } else { column.toggleSortBy(column.isSorted, props.isMultiSort ?? false); } } }, [props.onSortChange] ); const onFilterChange = useCallback( (column: FeTableColumnProps<T>, filterValue?: any) => { if (props.hasOwnProperty('filters')) { const filters = tableState.filters.filter(({ id }) => id !== column.id); if (filterValue != null) { filters.push({ id: column.id, value: filterValue }); } props.onFilterChange?.(filters); } else { column.setFilter(filterValue); } }, [props.onFilterChange, tableState] ); const onToggleAllRowsSelected = useCallback( (value: boolean) => { if (props.hasOwnProperty('selectedRowIds')) { const selectedIds = props.data.reduce((p, n: any) => ({ ...p, [n[props.rowKey]]: true }), {}); props.onRowSelected?.(value ? selectedIds : {}); } else { toggleAllRowsSelected(value); } }, [props.onRowSelected] ); const onRowSelected = useCallback( (row: any, value: boolean) => { const id = row[props.rowKey]; if (props.hasOwnProperty('selectedRowIds')) { const newSelectedRows: any = { ...props.selectedRowIds }; if (value) { newSelectedRows[id] = true; } else { delete newSelectedRows[id]; } props.onRowSelected?.(newSelectedRows); } else { toggleRowSelected(id, value); } }, [props.onRowSelected] ); const handleOnPageChange = useCallback(() => { if (pagination === 'pages') { tableRef.current?.querySelector(`.${prefixCls}__tbody`)?.scroll?.({ top: 0, left: 0, behavior: 'smooth' }); } props.onPageChange?.(tableState.pageSize, tableState.pageIndex); }, [tableState.pageIndex]); useEffect(() => { !props.hasOwnProperty('sortBy') && props.onSortChange?.(tableState.sortBy); }, [props.sortBy, tableState.sortBy]); useEffect(() => { !props.hasOwnProperty('filters') && props.onFilterChange?.(tableState.filters); }, [props.filters, tableState.filters]); useEffect(() => { firstRender.current ? (firstRender.current = false) : handleOnPageChange(); }, [tableState.pageIndex]); useEffect(() => { !props.hasOwnProperty('selectedRowIds') && props.onRowSelected?.(tableState.selectedRowIds as any); }, [tableState.selectedRowIds]); const tableHeadProps: FeTableTHeadProps<T> = { prefixCls, headerGroups, onSortChange, onFilterChange, toggleAllRowsSelected, isAllRowsSelected, selectedFlatRows, }; const tableRows: (Row<T> & UseExpandedRowProps<T>)[] = useMemo( () => (props.pagination ? page : rows) as (Row<T> & UseExpandedRowProps<T>)[], [page, rows, props.pagination] ); const tablePaginationProps: FeTablePaginationProps<T> = { pageIndex: tableState.pageIndex, pageSize: tableState.pageSize, canPreviousPage, canNextPage, pageOptions, pageCount, gotoPage, nextPage, previousPage, setPageSize, }; const { className, toolbar, loading, pagination, pageSize } = props; return ( <div className='fe-table__container'> <div ref={tableRef} className={classNames(prefixCls, className)} {...getTableProps()}> {toolbar && <FeTableToolbar />} <div className={classNames( `${prefixCls}__table-container`, loading && pagination === 'pages' && `${prefixCls}__table-container-loading` )} > <FeTableTBody pageSize={pageSize} pagination={pagination} onInfiniteScroll={handleOnPageChange} loading={props.loading} prefixCls={prefixCls} prepareRow={prepareRow} getTableBodyProps={getTableBodyProps} renderExpandedComponent={props.renderExpandedComponent} rows={tableRows} /> <FeTableTHead {...tableHeadProps} /> </div> {loading && pagination === 'pages' && rows.length > 0 && <FeLoader center size={24} />} {pagination === 'pages' && <FeTablePagination {...tablePaginationProps} />} </div> </div> ); }
Example #19
Source File: table.mappers.ts From master-frontend-lemoncode with MIT License | 5 votes |
mapColumnFromStringToColumn = (column: string): Column => ({
accessor: column,
Header: column,
})
Example #20
Source File: index.tsx From admin with MIT License | 4 votes |
CollectionProductTable: React.FC<CollectionProductTableProps> = ({
addedProducts,
setProducts,
}) => {
const [query, setQuery] = useState("")
const [limit, setLimit] = useState(10)
const [offset, setOffset] = useState(0)
const [numPages, setNumPages] = useState(0)
const [currentPage, setCurrentPage] = useState(0)
const [filteringOptions, setFilteringOptions] = useState<
FilteringOptionProps[]
>([])
const [selectedProducts, setSelectedProducts] = useState<any[]>([])
const debouncedSearchTerm = useDebounce(query, 500)
const { isLoading, count, products } = useAdminProducts({
q: debouncedSearchTerm,
limit: limit,
offset,
})
useEffect(() => {
setFilteringOptions([
{
title: "Sort by",
options: [
{
title: "All",
onClick: () => {},
},
{
title: "Newest",
onClick: () => {},
},
{
title: "Oldest",
onClick: () => {},
},
],
},
])
}, [products])
const columns = useCollectionProductColumns() as readonly Column<any[]>[]
const {
rows,
prepareRow,
getTableBodyProps,
getTableProps,
canPreviousPage,
canNextPage,
pageCount,
nextPage,
previousPage,
// Get the state from the instance
state: { pageIndex, pageSize, selectedRowIds },
} = useTable(
{
data: products || [],
columns: columns,
manualPagination: true,
initialState: {
pageIndex: currentPage,
pageSize: limit,
selectedRowIds: addedProducts?.reduce((prev, { id }) => {
prev[id] = true
return prev
}, {}),
},
pageCount: numPages,
autoResetSelectedRows: false,
autoResetPage: false,
getRowId: (row) => row.id,
},
usePagination,
useRowSelect,
(hooks) => {
hooks.visibleColumns.push((columns) => [
{
id: "selection",
Cell: ({ row }) => {
return (
<Table.Cell className="w-[5%] pl-base">
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</Table.Cell>
)
},
},
...columns,
])
}
)
useEffect(() => {
setSelectedProducts((selectedProducts) => [
...selectedProducts.filter(
(sv) => Object.keys(selectedRowIds).findIndex((id) => id === sv.id) > -1
),
...(products?.filter(
(p) =>
selectedProducts.findIndex((sv) => sv.id === p.id) < 0 &&
Object.keys(selectedRowIds).findIndex((id) => id === p.id) > -1
) || []),
])
}, [selectedRowIds])
const handleNext = () => {
if (canNextPage) {
setOffset((old) => old + pageSize)
setCurrentPage((old) => old + 1)
nextPage()
}
}
const handlePrev = () => {
if (canPreviousPage) {
setOffset((old) => old - pageSize)
setCurrentPage((old) => old - 1)
previousPage()
}
}
const handleSearch = (q) => {
setOffset(0)
setQuery(q)
}
useEffect(() => {
console.log("products", selectedProducts)
}, [selectedProducts])
return (
<div className="w-full h-full flex flex-col justify-between overflow-y-auto">
<Table
enableSearch
handleSearch={handleSearch}
searchPlaceholder="Search Products"
filteringOptions={filteringOptions}
{...getTableProps()}
className="h-full"
>
{isLoading || !products ? null : (
<Table.Body {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row)
return (
<Table.Row
color={"inherit"}
{...row.getRowProps()}
className="px-base"
>
{row.cells.map((cell, index) => {
return cell.render("Cell", { index })
})}
</Table.Row>
)
})}
</Table.Body>
)}
</Table>
<TablePagination
count={count!}
limit={limit}
offset={offset}
pageSize={offset + rows.length}
title="Products"
currentPage={pageIndex + 1}
pageCount={pageCount}
nextPage={handleNext}
prevPage={handlePrev}
hasNext={canNextPage}
hasPrev={canPreviousPage}
/>
</div>
)
}
Example #21
Source File: ValidationTableWidget.tsx From frontend-sample-showcase with MIT License | 4 votes |
ValidationTableWidget: React.FunctionComponent = () => {
const iModelConnection = useActiveIModelConnection();
const [validationResults, setValidationResults] = React.useState<ValidationResults>();
const [ruleData, setRuleData] = React.useState<Record<string, PropertyValueValidationRule | undefined>>();
const [selectedElement, setSelectedElement] = useState<string | undefined>();
useEffect(() => {
const removeDataListener = ValidationApi.onValidationDataChanged.addListener((dat) => {
setValidationResults(dat.validationData);
setRuleData(dat.ruleData);
});
const removeElementListener = ValidationApi.onMarkerClicked.addListener((elementId) => {
setSelectedElement(elementId);
});
if (iModelConnection) {
ValidationApi.getValidationData(iModelConnection.iTwinId!).catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});
}
return () => {
removeDataListener();
removeElementListener();
};
}, [iModelConnection]);
const columnDefinition = useMemo((): Column<TableRow>[] => [
{
Header: "Table",
columns: [
{
Header: "Element Id",
accessor: "elementId",
},
{
Header: "Element Label",
accessor: "elementLabel",
},
{
Header: "Rule Name",
accessor: "ruleName",
},
{
Header: "Legal Range",
accessor: "legalValues",
},
{
Header: "Invalid Value",
accessor: "badValue",
},
],
},
], []);
const data = useMemo(() => {
const rows: TableRow[] = [];
if (validationResults !== undefined && validationResults.result !== undefined && validationResults.ruleList !== undefined && ruleData !== undefined) {
const getLegalValue = (item: any) => {
const currentRuleData = ruleData[validationResults.ruleList[item.ruleIndex].id];
if (!currentRuleData)
return "";
if (currentRuleData.functionParameters.lowerBound) {
if (currentRuleData.functionParameters.upperBound) {
// Range of values
return `[${currentRuleData.functionParameters.lowerBound},${currentRuleData.functionParameters.upperBound}]`;
} else {
// Value has a lower bound
return `>${currentRuleData.functionParameters.lowerBound}`;
}
} else {
// Value needs to be defined
return "Must be Defined";
}
};
validationResults.result.forEach((rowData) => {
const row: TableRow = {
elementId: rowData.elementId,
elementLabel: rowData.elementLabel,
ruleName: validationResults.ruleList[rowData.ruleIndex].displayName,
legalValues: getLegalValue(rowData),
badValue: rowData.badValue,
};
rows.push(row);
});
}
return rows;
}, [validationResults, ruleData]);
const controlledState = useCallback(
(state: TableState<TableRow>, meta: MetaBase<TableRow>) => {
state.selectedRowIds = {};
if (selectedElement) {
const row = meta.instance.rows.find((r: { original: { elementId: string } }) => r.original.elementId === selectedElement);
if (row) {
state.selectedRowIds[row.id] = true;
}
}
return { ...state };
},
[selectedElement],
);
const tableStateReducer = (
newState: TableState<TableRow>,
action: ActionType,
_previousState: TableState<TableRow>,
instance?: TableInstance<TableRow> | undefined,
): TableState<TableRow> => {
switch (action.type) {
case actions.toggleRowSelected: {
newState.selectedRowIds = {};
if (action.value)
newState.selectedRowIds[action.id] = true;
if (instance) {
const elementId = instance.rowsById[action.id].original.elementId;
ValidationApi.visualizeViolation(elementId);
setSelectedElement(elementId);
}
break;
}
default:
break;
}
return newState;
};
return (
<Table<TableRow>
useControlledState={controlledState}
stateReducer={tableStateReducer}
isSelectable={true}
data={data}
columns={columnDefinition}
isLoading={!validationResults}
emptyTableContent="No data"
density="extra-condensed" />
);
}
Example #22
Source File: Table.tsx From opensaas with MIT License | 4 votes |
Table: React.FC<TableProps> = <T extends object>(props: TableProps<T>) => { const { expandable, selection, pagination, sortBy, pageCount, data, selectedRowIds, pageSize, rowKey, loading, isMultiSort, renderExpandedComponent, onPageChange, filters: propsFilters, onSortChange: propsOnSortChange, onRowSelected: propsOnRowSelected, onFilterChange: propsOnFilterChange, columns: propsColumns, } = props; const classes = useStyles(); const tableRef = useRef<HTMLTableElement>(null); const hasSortBy = props.hasOwnProperty('sortBy'); const hasFilters = props.hasOwnProperty('filters'); const hasPagination = props.hasOwnProperty('pagination'); const hasOnPageChange = props.hasOwnProperty('onPageChange'); const hasSelectedRowIds = props.hasOwnProperty('selectedRowIds'); const onRowSelected = useCallback( (row: UseRowSelectRowProps<T> & Row<T> & UseTableRowProps<T>, value: boolean) => { const id = (row.original as any)[rowKey]; if (hasSelectedRowIds) { const newSelectedRows: any = { ...selectedRowIds }; if (value) { newSelectedRows[id] = true; } else { delete newSelectedRows[id]; } propsOnRowSelected?.(newSelectedRows); } else { row.toggleRowSelected(value); } }, [hasSelectedRowIds, rowKey, selectedRowIds, propsOnRowSelected], ); const columns = useMemo(() => { const columns = propsColumns.map( ({ sortable, Filter, Header, ...rest }) => ({ ...rest, disableSortBy: !sortable, disableFilters: !Filter, Filter, Header: Header ?? <div style={{ minWidth: rest.minWidth, maxWidth: rest.maxWidth }} />, } as TableColumnOptions<T>), ); if (expandable) { columns.unshift({ id: 'expander', minWidth: 60, maxWidth: '60px' as any, Cell: (cell: UseTableCellProps<T>) => { const row = cell.row as Row<T> & UseExpandedRowProps<T>; return ( <IconButton className={classes.expandIcon} {...row.getToggleRowExpandedProps()}> {row.isExpanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} </IconButton> ); }, }); } if (selection) { columns.unshift({ id: 'selection', minWidth: 60, maxWidth: '60px' as any, Cell: (cell: UseTableCellProps<T>) => { const row = cell.row as UseRowSelectRowProps<T> & Row<T> & UseTableRowProps<T>; return ( <Checkbox className={classes.checkBox} {...row.getToggleRowSelectedProps()} checked={row.isSelected} onChange={(e) => onRowSelected(row, e.target.checked)} /> ); }, }); } return columns as Column<T>[]; }, [propsColumns, expandable, classes, selection, onRowSelected]); const tableHooks: PluginHook<T>[] = [useFilters, useSortBy]; if (expandable) { tableHooks.push(useExpanded); } if (pagination) { tableHooks.push(usePagination); } if (selection) { tableHooks.push(useRowSelect); } tableHooks.push(useFlexLayout); const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, state, page, pageOptions, gotoPage, nextPage, previousPage, toggleAllRowsSelected, isAllRowsSelected, selectedFlatRows, } = useTable( { columns, data, getRowId: (row: any) => row[rowKey], manualSortBy: !!propsOnSortChange, manualFilters: !!propsOnFilterChange, manualPagination: !!onPageChange, manualRowSelectedKey: rowKey, pageCount: pageCount ?? 0, useControlledState: (state1: any) => ({ ...state1, sortBy: sortBy ?? state1.sortBy, filters: propsFilters ?? state1.filters, selectedRowIds: selectedRowIds ?? state1.selectedRowIds, } as TableState<T> & UseFiltersState<T> & UseSortByState<T> & UseRowSelectState<T>), expandSubRows: false, initialState: { pageIndex: 0, pageSize: pageSize ?? 0, selectedRowIds: selectedRowIds || {}, }, } as UseTableOptions<T> & UseFiltersOptions<T> & UseSortByOptions<T> & UseExpandedOptions<T> & UseRowSelectOptions<T> & UsePaginationOptions<T>, ...tableHooks, ) as TableInstance<T> & UseTableInstanceProps<T> & UsePaginationInstanceProps<T> & UseRowSelectInstanceProps<T>; if (expandable && !renderExpandedComponent) { throw Error('Table: you must provide renderExpandedComponent property if the table is expandable'); } if (hasSortBy && !propsOnSortChange) { throw Error('Table: you must provide onSortChange property if sortBy is controlled'); } if (hasFilters && !propsOnFilterChange) { throw Error('Table: you must provide onFilterChange property if filters is controlled'); } if (hasPagination && !pageSize) { throw Error('Table: you must provide pageSize property if pagination enabled'); } if (hasOnPageChange && !pageCount) { throw Error('Table: you must provide pageCount property if onPageChange is controlled'); } const tableState = state as UseSortByState<T> & UseFiltersState<T> & UsePaginationState<T> & UseRowSelectState<T>; const onSortChange = useCallback( (column: TableColumnProps<T>) => { if (hasSortBy) { const sortBy = isMultiSort ? tableState.sortBy.filter(({ id }) => id !== column.id) : []; if (!column.isSorted) { sortBy.push({ id: column.id, desc: false }); } else if (!column.isSortedDesc) { sortBy.push({ id: column.id, desc: true }); } propsOnSortChange?.(sortBy); } else { if (column.isSorted && column.isSortedDesc) { column.clearSortBy(); } else { column.toggleSortBy(column.isSorted, isMultiSort ?? false); } } }, [hasSortBy, isMultiSort, tableState.sortBy, propsOnSortChange], ); const onFilterChange = useCallback( (column: TableColumnProps<T>, filterValue?: any) => { if (hasFilters) { const filters = tableState.filters.filter(({ id }) => id !== column.id); if (filterValue != null) { filters.push({ id: column.id, value: filterValue }); } propsOnFilterChange?.(filters); } else { column.setFilter(filterValue); } }, [propsOnFilterChange, hasFilters, tableState.filters], ); const onToggleAllRowsSelected = useCallback( (value: boolean) => { if (hasSelectedRowIds) { const selectedIds = data.reduce((p, n: any) => ({ ...p, [n[rowKey]]: true }), {}); propsOnRowSelected?.(value ? selectedIds : {}); } else { toggleAllRowsSelected(value); } }, [hasSelectedRowIds, data, rowKey, propsOnRowSelected, toggleAllRowsSelected], ); useEffect(() => { if (!hasSortBy) propsOnSortChange?.(tableState.sortBy); }, [hasSortBy, propsOnSortChange, tableState.sortBy]); useEffect(() => { if (!hasFilters) propsOnFilterChange?.(tableState.filters); }, [hasFilters, propsOnFilterChange, tableState.filters]); useEffect(() => { tableRef.current?.querySelector('.table-tbody')?.scroll?.({ top: 0, left: 0, behavior: 'smooth' }); onPageChange?.(tableState.pageSize, tableState.pageIndex); }, [onPageChange, tableState.pageSize, tableState.pageIndex]); useEffect(() => { if (!hasSelectedRowIds) propsOnRowSelected?.(tableState.selectedRowIds as any); }, [hasSelectedRowIds, propsOnRowSelected, tableState.selectedRowIds]); const onPageChangeHandler = (page: number) => { if (page > tableState.pageIndex) { nextPage(); } else { previousPage(); } }; return ( <Paper className={classes.paper}> <MaterialUITable className={classes.table} ref={tableRef} {...getTableProps()}> <TableHead headerGroups={headerGroups} onSortChange={onSortChange} onFilterChange={onFilterChange} toggleAllRowsSelected={onToggleAllRowsSelected} isAllRowsSelected={isAllRowsSelected} selectedFlatRows={selectedFlatRows} /> <TableBody getTableBodyProps={getTableBodyProps} prepareRow={prepareRow} loading={loading} rows={(pagination ? page : rows) as (Row<T> & UseExpandedRowProps<T>)[]} renderExpandedComponent={renderExpandedComponent} /> </MaterialUITable> {pagination === 'pages' && ( <TablePagination className={classes.footer} rowsPerPageOptions={[]} component='div' count={rows.length} rowsPerPage={tableState.pageSize} page={tableState.pageIndex} onChangePage={(e, page) => onPageChangeHandler(page)} ActionsComponent={(props) => ( <TablePaginationActions {...props} gotoPage={gotoPage} pageOptions={pageOptions} /> )} /> )} </Paper> ); }
Example #23
Source File: Table.tsx From frontegg-react with MIT License | 4 votes |
Table: FC<TableProps> = <T extends object>(props: TableProps<T>) => { const classes = useStyles(); const tableRef = useRef<HTMLTableElement>(null); const firstRender = useRef<boolean>(true); const columns = useMemo(() => { const columns = props.columns.map( ({ sortable, Filter, Header, ...rest }) => ({ ...rest, disableSortBy: !sortable, disableFilters: !Filter, Filter, Header: Header ?? <div style={{ minWidth: rest.minWidth, maxWidth: rest.maxWidth }} />, } as FeTableColumnOptions<T>) ); if (props.expandable) { columns.unshift({ id: 'fe-expander', minWidth: 60, maxWidth: '60px' as any, Header: <div style={{ minWidth: '1.5rem', maxWidth: '1.5rem' }} />, Cell: (cell: Cell<T>) => { const row = cell.row as Row<T> & UseExpandedRowProps<T>; return ( <IconButton className={classes.expandIcon} {...row.getToggleRowExpandedProps()}> {row.isExpanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} </IconButton> ); }, }); } if (props.selection) { columns.unshift({ id: 'fe-selection', minWidth: 60, maxWidth: '60px' as any, Cell: (cell: Cell<T>) => { const row = cell.row as Row<T> & UseRowSelectRowProps<T>; return ( <Checkbox className={classes.checkBox} {...row.getToggleRowSelectedProps()} checked={row.isSelected} onChange={(e) => onRowSelected(row.original, e.target.checked)} /> ); }, }); } return columns as Column<T>[]; }, [props.columns, props.expandable]); const tableHooks: PluginHook<T>[] = [useFilters, useSortBy]; if (props.expandable) { tableHooks.push(useExpanded); } if (props.pagination) { tableHooks.push(usePagination); } if (props.selection) { tableHooks.push(useRowSelect); } tableHooks.push(useFlexLayout); const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, state, // The page controls ;) page, // canPreviousPage, // canNextPage, pageOptions, pageCount, gotoPage, nextPage, previousPage, // setPageSize, // select props toggleAllRowsSelected, isAllRowsSelected, selectedFlatRows, toggleRowSelected, } = useTable( { columns, data: props.data, getRowId: (row: any) => row[props.rowKey], manualSortBy: !!props.onSortChange, manualFilters: !!props.onFilterChange, manualPagination: !!props.onPageChange, manualRowSelectedKey: props.rowKey, pageCount: !!props.onPageChange ? props.pageCount : undefined, autoResetPage: !props.onPageChange, useControlledState: (state1: any) => { return { ...state1, sortBy: props.sortBy ?? state1.sortBy, filters: props.filters ?? state1.filters, selectedRowIds: props.selectedRowIds ?? state1.selectedRowIds, } as TableState<T> & UseFiltersState<T> & UseSortByState<T> & UseRowSelectState<T>; }, expandSubRows: false, initialState: { pageIndex: 0, pageSize: props.pageSize ?? 0, selectedRowIds: props.selectedRowIds || {}, }, } as UseTableOptions<T> & UseFiltersOptions<T> & UseSortByOptions<T> & UseExpandedOptions<T> & UseRowSelectOptions<T> & UsePaginationOptions<T>, ...tableHooks ) as TableInstance<T> & UseTableInstanceProps<T> & UsePaginationInstanceProps<T> & UseRowSelectInstanceProps<T>; if (props.expandable && !props.renderExpandedComponent) { throw Error('FeTable: you must provide renderExpandedComponent property if the table is expandable'); } if (props.hasOwnProperty('sortBy') && !props.onSortChange) { throw Error('FeTable: you must provide onSortChange property if sortBy is controlled'); } if (props.hasOwnProperty('filters') && !props.onFilterChange) { throw Error('FeTable: you must provide onFilterChange property if filters is controlled'); } if (props.hasOwnProperty('pagination') && !props.pageSize) { throw Error('FeTable: you must provide pageSize property if pagination enabled'); } if (props.hasOwnProperty('onPageChange') && !props.pageCount) { throw Error('FeTable: you must provide pageCount property if onPageChange is controlled'); } const tableState = state as UseSortByState<T> & UseFiltersState<T> & UsePaginationState<T> & UseRowSelectState<T>; const onSortChange = useCallback( (column: FeTableColumnProps<T>) => { if (props.hasOwnProperty('sortBy')) { const sortBy = props.isMultiSort ? tableState.sortBy.filter(({ id }) => id !== column.id) : []; if (!column.isSorted) { sortBy.push({ id: column.id, desc: false }); } else if (!column.isSortedDesc) { sortBy.push({ id: column.id, desc: true }); } props.onSortChange?.(sortBy); } else { if (column.isSorted && column.isSortedDesc) { column.clearSortBy(); } else { column.toggleSortBy(column.isSorted, props.isMultiSort ?? false); } } }, [props.onSortChange] ); const onFilterChange = useCallback( (column: FeTableColumnProps<T>, filterValue?: any) => { if (props.hasOwnProperty('filters')) { const filters = tableState.filters.filter(({ id }) => id !== column.id); if (filterValue != null) { filters.push({ id: column.id, value: filterValue }); } props.onFilterChange?.(filters); } else { column.setFilter(filterValue); } }, [props.onFilterChange, tableState] ); const onToggleAllRowsSelected = useCallback( (value: boolean) => { if (props.hasOwnProperty('selectedRowIds')) { const selectedIds = props.data.reduce((p, n: any) => ({ ...p, [n[props.rowKey]]: true }), {}); props.onRowSelected?.(value ? selectedIds : {}); } else { toggleAllRowsSelected(value); } }, [props.onRowSelected] ); const onRowSelected = useCallback( (row: any, value: boolean) => { const id = row[props.rowKey]; if (props.hasOwnProperty('selectedRowIds')) { const newSelectedRows: any = { ...props.selectedRowIds }; if (value) { newSelectedRows[id] = true; } else { delete newSelectedRows[id]; } props.onRowSelected?.(newSelectedRows); } else { toggleRowSelected(id, value); } }, [props.onRowSelected] ); const handleOnPageChange = useCallback(() => { if (pagination === 'pages') { tableRef.current?.scroll?.({ top: 0, left: 0, behavior: 'smooth' }); } props.onPageChange?.(tableState.pageSize, tableState.pageIndex); }, [tableState.pageIndex]); useEffect(() => { !props.hasOwnProperty('sortBy') && props.onSortChange?.(tableState.sortBy); }, [props.sortBy, tableState.sortBy]); useEffect(() => { !props.hasOwnProperty('filters') && props.onFilterChange?.(tableState.filters); }, [props.filters, tableState.filters]); useEffect(() => { firstRender.current ? (firstRender.current = false) : handleOnPageChange(); }, [tableState.pageIndex]); useEffect(() => { !props.hasOwnProperty('selectedRowIds') && props.onRowSelected?.(tableState.selectedRowIds as any); }, [tableState.selectedRowIds]); const onPageChangeHandler = (page: number) => { if (page > tableState.pageIndex) { nextPage(); } else { previousPage(); } }; const { className, loading, pagination, totalData, pageSize } = props; return ( <Paper ref={tableRef} className={classes.paper}> <MaUTable className={classNames(classes.table, className)} {...getTableProps()}> <TableHead headerGroups={headerGroups} onSortChange={onSortChange} onFilterChange={onFilterChange} toggleAllRowsSelected={onToggleAllRowsSelected} isAllRowsSelected={isAllRowsSelected} selectedFlatRows={selectedFlatRows} /> <TableBody pageSize={pageSize} pagination={pagination} getTableBodyProps={getTableBodyProps} prepareRow={prepareRow} loading={loading} rows={(pagination ? page : rows) as (Row<T> & UseExpandedRowProps<T>)[]} renderExpandedComponent={props.renderExpandedComponent} onInfiniteScroll={handleOnPageChange} /> </MaUTable> {loading && pagination === 'pages' && rows.length > 0 && <Loader center size={24} />} {pagination === 'pages' && ( <TablePagination className={classes.footer} rowsPerPageOptions={[]} component='div' count={totalData || rows.length} rowsPerPage={tableState.pageSize} page={tableState.pageIndex} onChangePage={(e, page) => onPageChangeHandler(page)} ActionsComponent={(props) => ( <TablePaginationActions {...props} gotoPage={gotoPage} pageOptions={pageOptions} /> )} /> )} </Paper> ); }
Example #24
Source File: Table.spec.tsx From solo with MIT License | 4 votes |
describe("Table component", () => {
const columns: Column<TestDoc>[] = [
{
Header: "expand",
Cell: ({ row }) => (
<span data-testid="testexpand" {...row.getToggleRowExpandedProps()}>
expand
</span>
),
id: "expand"
},
{
Header: "test column",
accessor: "value"
}
];
const data = [{ value: "2" }];
const onRenderSubMock = jest.fn();
const fetchDataMock = jest.fn();
afterEach(() => {
onRenderSubMock.mockReset();
fetchDataMock.mockReset();
});
it("matches snapshot", () => {
const { asFragment } = render(
<Table<TestDoc>
columns={columns}
data={data}
renderSubComponent={onRenderSubMock}
fetchData={fetchDataMock}
/>
);
expect(asFragment()).toMatchSnapshot();
});
it("honors the initial sort by prop", async () => {
render(
<Table<TestDoc>
columns={columns}
data={data}
renderSubComponent={onRenderSubMock}
fetchData={fetchDataMock}
initialSortBy={[{ id: "value", desc: true }]}
/>
);
await wait(() => {
expect(fetchDataMock).toHaveBeenCalled();
expect(fetchDataMock.mock.calls[0][0]).toMatchObject({
sort: [{ id: "value", desc: true }]
});
});
});
it("calls re-fetches data on sort toggled", async () => {
const { getByText } = render(
<Table<TestDoc>
columns={columns}
data={data}
renderSubComponent={onRenderSubMock}
fetchData={fetchDataMock}
/>
);
await wait(() => {
expect(fetchDataMock).toHaveBeenCalledTimes(1);
});
const sortHeader = getByText("test column");
fireEvent.click(sortHeader);
await wait(() => {
expect(fetchDataMock).toHaveBeenCalledTimes(2);
expect(fetchDataMock.mock.calls[1][0]).toMatchObject({
sort: [{ id: "value" }]
});
});
fireEvent.click(sortHeader);
await wait(() => {
expect(fetchDataMock).toHaveBeenCalledTimes(3);
expect(fetchDataMock.mock.calls[2][0]).toMatchObject({
sort: [{ id: "value", desc: true }]
});
});
});
it("calls render subcomponent on click", async () => {
const { getByTestId } = render(
<Table<TestDoc>
columns={columns}
data={data}
renderSubComponent={onRenderSubMock}
fetchData={fetchDataMock}
/>
);
const toggleBtn = getByTestId("testexpand");
fireEvent.click(toggleBtn);
await wait(() => {
expect(onRenderSubMock).toHaveBeenCalled();
});
});
});
Example #25
Source File: columns.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
createColumns = (updateVulnerability: any) =>
[
{
Header: 'Vulnerability',
accessor: 'title',
Cell: ({ value, row }: CellProps<Vulnerability>) =>
row.original.cve ? (
<a
href={`https://nvd.nist.gov/vuln/detail/${row.original.cve}`}
target="_blank"
rel="noopener noreferrer"
>
{value} {extLink}
</a>
) : (
<p>{row.original.title}</p>
),
width: 800,
Filter: ColumnFilter
},
{
Header: 'Severity',
id: 'severity',
accessor: ({ severity, substate }) => (
<span
style={{
borderBottom: `6px solid ${getSeverityColor({
id: severity ?? ''
})}`,
width: '80px'
}}
// className={substate === 'unconfirmed' ? classes.severity : undefined}
>
{severity}
</span>
),
width: 100,
Filter: selectFilter(['Low', 'Medium', 'High', 'Critical', 'None'])
},
{
Header: 'KEV',
accessor: 'isKev',
Cell: ({ value, row }: CellProps<Vulnerability>) =>
value ? (
<a
href={`https://www.cisa.gov/known-exploited-vulnerabilities-catalog`}
target="_blank"
rel="noopener noreferrer"
>
Yes {extLink}
</a>
) : (
<p>No</p>
),
width: 50,
Filter: selectFilter([
{ value: 'true', label: 'Yes' },
{ value: 'false', label: 'No' }
])
},
{
Header: 'Domain',
id: 'domain',
accessor: ({ domain }) => (
<Link to={`/inventory/domain/${domain?.id}`}>{domain?.name}</Link>
),
width: 800,
Filter: ColumnFilter
},
{
Header: 'Product',
id: 'cpe',
accessor: ({ cpe, service }) => {
const product =
service &&
service.products.find(
(product) => cpe && product.cpe && cpe.includes(product.cpe)
);
if (product)
return product.name + (product.version ? ' ' + product.version : '');
else return cpe;
},
width: 100,
Filter: ColumnFilter
},
{
Header: 'Days Open',
id: 'createdAt',
accessor: ({ createdAt, actions = [] }) => {
// Calculates the total number of days a vulnerability has been open
let daysOpen = 0;
let lastOpenDate = createdAt;
let lastState = 'open';
actions.reverse();
for (const action of actions) {
if (action.state === 'closed' && lastState === 'open') {
daysOpen += differenceInCalendarDays(
parseISO(action.date),
parseISO(lastOpenDate)
);
lastState = 'closed';
} else if (action.state === 'open' && lastState === 'closed') {
lastOpenDate = action.date;
lastState = 'open';
}
}
if (lastState === 'open') {
daysOpen += differenceInCalendarDays(
new Date(),
parseISO(lastOpenDate)
);
}
return daysOpen;
},
disableFilters: true
},
{
Header: 'Status',
id: 'state',
width: 100,
maxWidth: 200,
accessor: 'state',
Filter: selectFilter([
'open',
'open (unconfirmed)',
'open (exploitable)',
'closed',
'closed (false positive)',
'closed (accepted risk)',
'closed (remediated)'
]),
Cell: ({ row }: CellProps<Vulnerability>) => (
<Dropdown
id="state-dropdown"
name="state-dropdown"
onChange={(e) => {
updateVulnerability(row.index, {
substate: e.target.value
});
}}
value={row.original.substate}
style={{ display: 'inline-block', width: '200px' }}
>
<option value="unconfirmed">Open (Unconfirmed)</option>
<option value="exploitable">Open (Exploitable)</option>
<option value="false-positive">Closed (False Positive)</option>
<option value="accepted-risk">Closed (Accepted Risk)</option>
<option value="remediated">Closed (Remediated)</option>
</Dropdown>
)
},
{
Header: 'Details',
Cell: ({ row }: CellProps<Vulnerability>) => (
<Link
to={`/inventory/vulnerability/${row.original.id}`}
style={{
fontSize: '14px',
cursor: 'pointer',
color: '#484D51',
textDecoration: 'none'
}}
>
DETAILS
</Link>
),
disableFilters: true
}
] as Column<Vulnerability>[]
Example #26
Source File: Users.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
Users: React.FC = () => {
const { apiGet, apiPost, apiDelete } = useAuthContext();
const [showModal, setShowModal] = useState<Boolean>(false);
const [selectedRow, setSelectedRow] = useState<number>(0);
const [users, setUsers] = useState<User[]>([]);
const columns: Column<User>[] = [
{
Header: 'Name',
accessor: 'fullName',
width: 200,
disableFilters: true,
id: 'name'
},
{
Header: 'Email',
accessor: 'email',
width: 150,
minWidth: 150,
id: 'email',
disableFilters: true
},
{
Header: 'Organizations',
accessor: ({ roles }) =>
roles &&
roles
.filter((role) => role.approved)
.map((role) => role.organization.name)
.join(', '),
id: 'organizations',
width: 200,
disableFilters: true
},
{
Header: 'User type',
accessor: ({ userType }) =>
userType === 'standard'
? 'Standard'
: userType === 'globalView'
? 'Global View'
: 'Global Admin',
width: 50,
minWidth: 50,
id: 'userType',
disableFilters: true
},
{
Header: 'Date ToU Signed',
accessor: ({ dateAcceptedTerms }) =>
dateAcceptedTerms
? `${formatDistanceToNow(parseISO(dateAcceptedTerms))} ago`
: 'None',
width: 50,
minWidth: 50,
id: 'dateAcceptedTerms',
disableFilters: true
},
{
Header: 'ToU Version',
accessor: 'acceptedTermsVersion',
width: 50,
minWidth: 50,
id: 'acceptedTermsVersion',
disableFilters: true
},
{
Header: 'Last Logged In',
accessor: ({ lastLoggedIn }) =>
lastLoggedIn
? `${formatDistanceToNow(parseISO(lastLoggedIn))} ago`
: 'None',
width: 50,
minWidth: 50,
id: 'lastLoggedIn',
disableFilters: true
},
{
Header: 'Delete',
id: 'delete',
Cell: ({ row }: { row: { index: number } }) => (
<span
onClick={() => {
setShowModal(true);
setSelectedRow(row.index);
}}
>
<FaTimes />
</span>
),
disableFilters: true
}
];
const [errors, setErrors] = useState<Errors>({});
const [values, setValues] = useState<{
firstName: string;
lastName: string;
email: string;
organization?: Organization;
userType: string;
}>({
firstName: '',
lastName: '',
email: '',
userType: ''
});
const fetchUsers = useCallback(async () => {
try {
const rows = await apiGet<User[]>('/users/');
setUsers(rows);
} catch (e) {
console.error(e);
}
}, [apiGet]);
const deleteRow = async (index: number) => {
try {
const row = users[index];
await apiDelete(`/users/${row.id}`, { body: {} });
setUsers(users.filter((user) => user.id !== row.id));
} catch (e) {
setErrors({
global:
e.status === 422 ? 'Unable to delete user' : e.message ?? e.toString()
});
console.log(e);
}
};
const onSubmit: React.FormEventHandler = async (e) => {
e.preventDefault();
try {
const body = {
firstName: values.firstName,
lastName: values.lastName,
email: values.email,
userType: values.userType
};
const user = await apiPost('/users/', {
body
});
setUsers(users.concat(user));
} catch (e) {
setErrors({
global:
e.status === 422
? 'Error when submitting user entry.'
: e.message ?? e.toString()
});
console.log(e);
}
};
const onTextChange: React.ChangeEventHandler<
HTMLInputElement | HTMLSelectElement
> = (e) => onChange(e.target.name, e.target.value);
const onChange = (name: string, value: any) => {
setValues((values) => ({
...values,
[name]: value
}));
};
React.useEffect(() => {
document.addEventListener('keyup', (e) => {
//Escape
if (e.keyCode === 27) {
setShowModal(false);
}
});
}, [apiGet]);
return (
<div className={classes.root}>
<h1>Users</h1>
<Table<User> columns={columns} data={users} fetchData={fetchUsers} />
<h2>Invite a user</h2>
<form onSubmit={onSubmit} className={classes.form}>
{errors.global && <p className={classes.error}>{errors.global}</p>}
<Label htmlFor="firstName">First Name</Label>
<TextInput
required
id="firstName"
name="firstName"
className={classes.textField}
type="text"
value={values.firstName}
onChange={onTextChange}
/>
<Label htmlFor="lastName">Last Name</Label>
<TextInput
required
id="lastName"
name="lastName"
className={classes.textField}
type="text"
value={values.lastName}
onChange={onTextChange}
/>
<Label htmlFor="email">Email</Label>
<TextInput
required
id="email"
name="email"
className={classes.textField}
type="text"
value={values.email}
onChange={onTextChange}
/>
<Label htmlFor="userType">User Type</Label>
<RadioGroup
aria-label="User Type"
name="userType"
value={values.userType}
onChange={onTextChange}
>
<FormControlLabel
value="standard"
control={<Radio color="primary" />}
label="Standard"
/>
<FormControlLabel
value="globalView"
control={<Radio color="primary" />}
label="Global View"
/>
<FormControlLabel
value="globalAdmin"
control={<Radio color="primary" />}
label="Global Administrator"
/>
</RadioGroup>
<br></br>
<Button type="submit">Invite User</Button>
</form>
<ImportExport<
| User
| {
roles: string;
}
>
name="users"
fieldsToExport={['firstName', 'lastName', 'email', 'roles', 'userType']}
onImport={async (results) => {
// TODO: use a batch call here instead.
const createdUsers = [];
for (const result of results) {
const parsedRoles: {
organization: string;
role: string;
}[] = JSON.parse(result.roles as string);
const body: any = result;
// For now, just create role with the first organization
if (parsedRoles.length > 0) {
body.organization = parsedRoles[0].organization;
body.organizationAdmin = parsedRoles[0].role === 'admin';
}
try {
createdUsers.push(
await apiPost('/users/', {
body
})
);
} catch (e) {
// Just continue when an error occurs
console.error(e);
}
}
setUsers(users.concat(...createdUsers));
}}
getDataToExport={() =>
users.map((user) => ({
...user,
roles: JSON.stringify(
user.roles.map((role) => ({
organization: role.organization.id,
role: role.role
}))
)
}))
}
/>
{showModal && (
<div>
<Overlay />
<ModalContainer>
<Modal
actions={
<>
<Button
outline
type="button"
onClick={() => {
setShowModal(false);
}}
>
Cancel
</Button>
<Button
type="button"
onClick={() => {
deleteRow(selectedRow);
setShowModal(false);
}}
>
Delete
</Button>
</>
}
title={<h2>Delete user?</h2>}
>
<p>
Are you sure you would like to delete{' '}
<code>{users[selectedRow].fullName}</code>?
</p>
</Modal>
</ModalContainer>
</div>
)}
</div>
);
}
Example #27
Source File: Settings.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
Settings: React.FC = () => {
const { logout, user, setUser, apiPost, apiDelete } = useAuthContext();
const [showModal, setShowModal] = useState<Boolean>(false);
const [apiKey, setApiKey] = useState<string>('');
const generateApiKey = async () => {
if (!user) return;
const apiKey = await apiPost<
ApiKey & {
key: string;
}
>('/api-keys');
setUser({ ...user, apiKeys: user.apiKeys.concat([apiKey]) });
setApiKey(apiKey.key);
setShowModal(true);
};
const deleteApiKey = async (key: string) => {
if (!user) return;
await apiDelete<ApiKey>('/api-keys/' + key);
setUser({
...user,
apiKeys: user.apiKeys.filter((k) => k.id !== key)
});
};
const columns: Column<ApiKey>[] = [
{
Header: 'Key',
accessor: ({ lastFour }) => '*'.repeat(12) + lastFour,
width: 200,
disableFilters: true,
id: 'key'
},
{
Header: 'Date Created',
accessor: ({ createdAt }) =>
`${formatDistanceToNow(parseISO(createdAt))} ago`,
width: 50,
minWidth: 50,
id: 'createdAt',
disableFilters: true
},
{
Header: 'Last Used',
accessor: ({ lastUsed }) =>
lastUsed ? `${formatDistanceToNow(parseISO(lastUsed))} ago` : 'None',
width: 50,
minWidth: 50,
id: 'lastUsed',
disableFilters: true
},
{
Header: 'Delete',
id: 'delete',
Cell: ({ row }: { row: { index: number } }) => (
<span
onClick={() => {
if (!user) return;
deleteApiKey(user.apiKeys[row.index].id);
}}
>
<FaTimes />
</span>
),
disableFilters: true
}
];
return (
<div className={classes.root}>
<h1>My Account</h1>
<h2>Name: {user && user.fullName}</h2>
<h2>Email: {user && user.email}</h2>
<h2>
Member of:{' '}
{user &&
(user.roles || [])
.filter((role) => role.approved)
.map((role) => role.organization.name)
.join(', ')}
</h2>
<h2>API Keys:</h2>
<p>
<a
href="https://docs.crossfeed.cyber.dhs.gov/api-reference"
rel="noopener noreferrer"
target="_blank"
>
Read API documentation
</a>
</p>
{(!user?.apiKeys || user.apiKeys.length === 0) && <p>No API Keys</p>}
{user?.apiKeys && user.apiKeys.length > 0 && (
<Table<ApiKey> columns={columns} data={user?.apiKeys} />
)}
<br></br>
<Button type="button" onClick={generateApiKey}>
Generate API Key
</Button>
<br></br>
<br></br>
{showModal && (
<div>
<Overlay />
<ModalContainer>
<Modal
actions={
<>
<Button
type="button"
onClick={() => {
setShowModal(false);
}}
>
Ok
</Button>
</>
}
title={<h2>Copy API Key</h2>}
>
<p>
Please copy your API key now, as you will not be able to see it
again:
</p>
<code>{apiKey}</code>
</Modal>
</ModalContainer>
</div>
)}
{user?.userType === 'globalAdmin' && (
<>
<a href={`${process.env.REACT_APP_API_URL}/matomo/index.php`}>
<Button type="button">Matomo</Button>
</a>
<br />
<br />
</>
)}
<Button type="button" onClick={logout}>
Logout
</Button>
</div>
);
}
Example #28
Source File: ScansView.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
ScansView: React.FC = () => {
const { apiGet, apiPost, apiDelete } = useAuthContext();
const [showModal, setShowModal] = useState<Boolean>(false);
const [selectedRow, setSelectedRow] = useState<number>(0);
const [scans, setScans] = useState<Scan[]>([]);
const [organizationOptions, setOrganizationOptions] = useState<
OrganizationOption[]
>([]);
const [tags, setTags] = useState<OrganizationTag[]>([]);
const [scanSchema, setScanSchema] = useState<ScanSchema>({});
const columns: Column<Scan>[] = [
{
Header: 'Run',
id: 'run',
Cell: ({ row }: { row: { index: number } }) => (
<div
style={{ textAlign: 'center' }}
onClick={() => {
runScan(row.index);
}}
>
<FaPlayCircle />
</div>
),
disableFilters: true
},
{
Header: 'Name',
accessor: 'name',
width: 200,
id: 'name',
disableFilters: true
},
{
Header: 'Tags',
accessor: ({ tags }) => tags.map((tag) => tag.name).join(', '),
width: 150,
minWidth: 150,
id: 'tags',
disableFilters: true
},
{
Header: 'Mode',
accessor: ({ name }) =>
scanSchema[name] && scanSchema[name].isPassive ? 'Passive' : 'Active',
width: 150,
minWidth: 150,
id: 'mode',
disableFilters: true
},
{
Header: 'Frequency',
accessor: ({ frequency, isSingleScan }) => {
let val, unit;
if (frequency < 60 * 60) {
val = frequency / 60;
unit = 'minute';
} else if (frequency < 60 * 60 * 24) {
val = frequency / (60 * 60);
unit = 'hour';
} else {
val = frequency / (60 * 60 * 24);
unit = 'day';
}
if (isSingleScan) {
return 'Single Scan';
}
return `Every ${val} ${unit}${val === 1 ? '' : 's'}`;
},
width: 200,
id: 'frequency',
disableFilters: true
},
{
Header: 'Last Run',
accessor: (args: Scan) => {
return !args.lastRun ||
new Date(args.lastRun).getTime() === new Date(0).getTime()
? 'None'
: `${formatDistanceToNow(parseISO(args.lastRun))} ago`;
},
width: 200,
id: 'lastRun',
disableFilters: true
},
{
Header: 'Edit',
id: 'edit',
Cell: ({ row }: CellProps<Scan>) => (
<Link to={`/scans/${row.original.id}`} style={{ color: 'black' }}>
<FaEdit />
</Link>
),
disableFilters: true
},
{
Header: 'Delete',
id: 'delete',
Cell: ({ row }: { row: { index: number } }) => (
<span
onClick={() => {
setShowModal(true);
setSelectedRow(row.index);
}}
>
<FaTimes />
</span>
),
disableFilters: true
},
{
Header: 'Description',
accessor: ({ name }) => scanSchema[name]?.description,
width: 200,
maxWidth: 200,
id: 'description',
disableFilters: true
}
];
const [errors, setErrors] = useState<Errors>({});
const [values] = useState<ScanFormValues>({
name: 'censys',
arguments: '{}',
organizations: [],
frequency: 1,
frequencyUnit: 'minute',
isGranular: false,
isUserModifiable: false,
isSingleScan: false,
tags: []
});
React.useEffect(() => {
document.addEventListener('keyup', (e) => {
//Escape
if (e.keyCode === 27) {
setShowModal(false);
}
});
}, [apiGet]);
const fetchScans = useCallback(async () => {
try {
const { scans, organizations, schema } = await apiGet<{
scans: Scan[];
organizations: Organization[];
schema: ScanSchema;
}>('/scans/');
const tags = await apiGet<OrganizationTag[]>(`/organizations/tags`);
setScans(scans);
setScanSchema(schema);
setOrganizationOptions(
organizations.map((e) => ({ label: e.name, value: e.id }))
);
setTags(tags);
} catch (e) {
console.error(e);
}
}, [apiGet]);
const deleteRow = async (index: number) => {
try {
const row = scans[index];
await apiDelete(`/scans/${row.id}`, { body: {} });
setScans(scans.filter((scan) => scan.id !== row.id));
} catch (e) {
setErrors({
global:
e.status === 422 ? 'Unable to delete scan' : e.message ?? e.toString()
});
console.log(e);
}
};
const onSubmit = async (body: ScanFormValues) => {
try {
// For now, parse the arguments as JSON. We'll want to add a GUI for this in the future
body.arguments = JSON.parse(body.arguments);
setFrequency(body);
const scan = await apiPost('/scans/', {
body: {
...body,
organizations: body.organizations
? body.organizations.map((e) => e.value)
: [],
tags: body.tags ? body.tags.map((e) => ({ id: e.value })) : []
}
});
setScans(scans.concat(scan));
} catch (e) {
setErrors({
global: e.message ?? e.toString()
});
console.log(e);
}
};
const invokeScheduler = async () => {
setErrors({ ...errors, scheduler: '' });
try {
await apiPost('/scheduler/invoke', { body: {} });
} catch (e) {
console.error(e);
setErrors({ ...errors, scheduler: 'Invocation failed.' });
}
};
/**
* Manually runs a single scan, then immediately invokes the
* scheduler so the scan is run.
* @param index Row index
*/
const runScan = async (index: number) => {
const row = scans[index];
try {
await apiPost(`/scans/${row.id}/run`, { body: {} });
} catch (e) {
console.error(e);
setErrors({ ...errors, scheduler: 'Run failed.' });
}
await invokeScheduler();
};
return (
<>
<Table<Scan> columns={columns} data={scans} fetchData={fetchScans} />
<br></br>
<Button type="submit" outline onClick={invokeScheduler}>
Manually run scheduler
</Button>
{errors.scheduler && <p className={classes.error}>{errors.scheduler}</p>}
<h2>Add a scan</h2>
{errors.global && <p className={classes.error}>{errors.global}</p>}
<ScanForm
organizationOption={organizationOptions}
tags={tags}
propValues={values}
onSubmit={onSubmit}
type="create"
scanSchema={scanSchema}
></ScanForm>
<ImportExport<Scan>
name="scans"
fieldsToExport={['name', 'arguments', 'frequency']}
onImport={async (results) => {
// TODO: use a batch call here instead.
const createdScans = [];
for (const result of results) {
createdScans.push(
await apiPost('/scans/', {
body: {
...result,
// These fields are initially parsed as strings, so they need
// to be converted to objects.
arguments: JSON.parse(
((result.arguments as unknown) as string) || ''
)
}
})
);
}
setScans(scans.concat(...createdScans));
}}
getDataToExport={() =>
scans.map((scan) => ({
...scan,
arguments: JSON.stringify(scan.arguments)
}))
}
/>
{showModal && (
<div>
<Overlay />
<ModalContainer>
<Modal
actions={
<>
<Button
outline
type="button"
onClick={() => {
setShowModal(false);
}}
>
Cancel
</Button>
<Button
type="button"
onClick={() => {
deleteRow(selectedRow);
setShowModal(false);
}}
>
Delete
</Button>
</>
}
title={<h2>Delete scan?</h2>}
>
<p>
Are you sure you would like to delete the{' '}
<code>{scans[selectedRow].name}</code> scan?
</p>
</Modal>
</ModalContainer>
</div>
)}
</>
);
}
Example #29
Source File: ScanTasksView.tsx From crossfeed with Creative Commons Zero v1.0 Universal | 4 votes |
ScanTasksView: React.FC = () => {
const { apiPost, token } = useAuthContext();
const [scanTasks, setScanTasks] = useState<ScanTask[]>([]);
const [totalResults, setTotalResults] = useState(0);
const [errors, setErrors] = useState<Errors>({});
const killScanTask = async (index: number) => {
try {
const row = scanTasks[index];
await apiPost(`/scan-tasks/${row.id}/kill`, { body: {} });
setScanTasks(
Object.assign([], scanTasks, {
[index]: {
...row,
status: 'failed'
}
})
);
} catch (e) {
setErrors({
global:
e.status === 422 ? 'Unable to kill scan' : e.message ?? e.toString()
});
console.log(e);
}
};
const renderExpanded = (row: Row<ScanTask>) => {
const { original } = row;
return (
<div className={classes.expandedRoot}>
{original.fargateTaskArn && (
<>
<h4>
Logs
{original.fargateTaskArn?.match('.*/(.*)') && (
<a
target="_blank"
rel="noopener noreferrer"
href={`https://us-east-1.console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/${process
.env
.REACT_APP_FARGATE_LOG_GROUP!}/log-events/worker$252Fmain$252F${
(original.fargateTaskArn.match('.*/(.*)') || [])[1]
}`}
>
{' '}
(View all on CloudWatch)
</a>
)}
</h4>
<Log
token={token ?? ''}
url={`${process.env.REACT_APP_API_URL}/scan-tasks/${original.id}/logs`}
/>
</>
)}
<h4>Input</h4>
<small>
<pre>{JSON.stringify(JSON.parse(original.input), null, 2)}</pre>
</small>
<h4>Output</h4>
<small>
<pre>{original.output || 'None'}</pre>
</small>
{row.original.status !== 'finished' &&
row.original.status !== 'failed' && (
<>
<h4>Actions</h4>
<a
href="# "
onClick={(e) => {
e.preventDefault();
killScanTask(row.index);
}}
>
Kill
</a>
</>
)}
</div>
);
};
const columns: Column<ScanTask>[] = [
{
Header: 'ID',
accessor: 'id',
Filter: ColumnFilter,
disableSortBy: true,
disableFilters: true
},
{
Header: 'Status',
accessor: 'status',
Filter: selectFilter([
'created',
'queued',
'requested',
'started',
'finished',
'failed'
]),
disableSortBy: true
},
{
Header: 'Name',
id: 'name',
accessor: ({ scan }) => scan?.name,
Filter: selectFilter([
// TODO: sync this with the SCAN_SCHEMA
'censys',
'amass',
'findomain',
'portscanner',
'wappalyzer',
'censysIpv4',
'censysCertificates',
'sslyze',
'searchSync',
'cve',
'dotgov',
'webscraper',
'intrigueIdent',
'shodan',
'hibp',
'lookingGlass',
'dnstwist',
'peCybersixgill',
'peHibpSync',
'peShodan',
'peDomMasq',
'rootDomainSync'
]),
disableSortBy: true
},
{
Header: 'Created At',
id: 'createdAt',
accessor: ({ createdAt }) => dateAccessor(createdAt),
disableFilters: true
},
{
Header: 'Finished At',
id: 'finishedAt',
accessor: ({ finishedAt }) => dateAccessor(finishedAt),
disableFilters: true
},
{
Header: 'Details',
Cell: ({ row }: CellProps<ScanTask>) => (
<span
{...row.getToggleRowExpandedProps()}
className="text-center display-block"
>
{row.isExpanded ? <FaMinus /> : <FaPlus />}
</span>
),
disableFilters: true
}
];
const PAGE_SIZE = 25;
const fetchScanTasks = useCallback(
async (query: Query<ScanTask>) => {
const { page, sort, filters } = query;
try {
const { result, count } = await apiPost<ApiResponse>(
'/scan-tasks/search',
{
body: {
page,
sort: sort[0]?.id ?? 'createdAt',
order: sort[0]?.desc ? 'DESC' : 'ASC',
filters: filters
.filter((f) => Boolean(f.value))
.reduce(
(accum, next) => ({
...accum,
[next.id]: next.value
}),
{}
)
}
}
);
setScanTasks(result);
setTotalResults(count);
} catch (e) {
console.error(e);
}
},
[apiPost]
);
const renderPagination = (table: TableInstance<ScanTask>) => (
<Paginator table={table} totalResults={totalResults} />
);
return (
<>
{errors.global && <p className={classes.error}>{errors.global}</p>}
<Table<ScanTask>
renderPagination={renderPagination}
columns={columns}
data={scanTasks}
pageCount={Math.ceil(totalResults / PAGE_SIZE)}
fetchData={fetchScanTasks}
pageSize={PAGE_SIZE}
initialSortBy={[
{
id: 'createdAt',
desc: true
}
]}
renderExpanded={renderExpanded}
/>
</>
);
}