mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
update UI to react 18 (#17471)
This commit is contained in:
parent
8be1d4766f
commit
3c2e4b8f4a
@ -94,6 +94,7 @@ const DEFAULT_HOST_MOCK: IHost = {
|
||||
software: [],
|
||||
users: [],
|
||||
policies: [],
|
||||
device_mapping: [],
|
||||
};
|
||||
|
||||
const createMockHost = (overrides?: Partial<IHost>): IHost => {
|
||||
|
@ -1,6 +1,10 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import React, { FC, ReactNode, useContext, useEffect, useState } from "react";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { QueryClient, QueryClientProvider } from "react-query";
|
||||
import {
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
QueryClientProviderProps,
|
||||
} from "react-query";
|
||||
|
||||
import page_titles from "router/page_titles";
|
||||
import TableProvider from "context/table";
|
||||
@ -35,6 +39,14 @@ interface IAppProps {
|
||||
};
|
||||
}
|
||||
|
||||
// We create a CustomQueryClientProvider that takes the same props as the original
|
||||
// QueryClientProvider but adds the children prop as a ReactNode. This children
|
||||
// prop is not required explicitly in React 18. We do it this way to avoid
|
||||
// having to update the react-query package version and typings for now.
|
||||
// When we upgrade React Query we should be able to remove this.
|
||||
type ICustomQueryClientProviderProps = React.PropsWithChildren<QueryClientProviderProps>;
|
||||
const CustomQueryClientProvider: FC<ICustomQueryClientProviderProps> = QueryClientProvider;
|
||||
|
||||
const baseClass = "app";
|
||||
|
||||
const App = ({ children, location }: IAppProps): JSX.Element => {
|
||||
@ -170,7 +182,7 @@ const App = ({ children, location }: IAppProps): JSX.Element => {
|
||||
return isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<CustomQueryClientProvider client={queryClient}>
|
||||
<TableProvider>
|
||||
<QueryProvider>
|
||||
<PolicyProvider>
|
||||
@ -185,7 +197,7 @@ const App = ({ children, location }: IAppProps): JSX.Element => {
|
||||
</PolicyProvider>
|
||||
</QueryProvider>
|
||||
</TableProvider>
|
||||
</QueryClientProvider>
|
||||
</CustomQueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -316,8 +316,8 @@ const SelectTargets = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleRowRemove = (row: Row) => {
|
||||
const removedHost = row.original as IHost;
|
||||
const handleRowRemove = (row: Row<IHost>) => {
|
||||
const removedHost = row.original;
|
||||
setTargetedHosts((prevHosts) =>
|
||||
prevHosts.filter((h) => h.id !== removedHost.id)
|
||||
);
|
||||
|
@ -20,7 +20,7 @@ interface ITargetsInputProps {
|
||||
targetedHosts: IHost[];
|
||||
setSearchText: (value: string) => void;
|
||||
handleRowSelect: (value: Row) => void;
|
||||
handleRowRemove: (value: Row) => void;
|
||||
handleRowRemove: (value: Row<IHost>) => void;
|
||||
}
|
||||
|
||||
const baseClass = "targets-input";
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import React from "react";
|
||||
import { Row } from "react-table";
|
||||
import { Column, Row } from "react-table";
|
||||
|
||||
import { IDataColumn } from "interfaces/datatable_config";
|
||||
import { IStringCellProps } from "interfaces/datatable_config";
|
||||
import { IHost } from "interfaces/host";
|
||||
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
@ -11,26 +11,20 @@ import LiveQueryIssueCell from "components/TableContainer/DataTable/LiveQueryIss
|
||||
import StatusIndicator from "components/StatusIndicator";
|
||||
import Icon from "components/Icon/Icon";
|
||||
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
value: string;
|
||||
};
|
||||
row: {
|
||||
original: IHost;
|
||||
};
|
||||
}
|
||||
type ITargestInputhostTableConfig = Column<IHost>;
|
||||
type ITableStringCellProps = IStringCellProps<IHost>;
|
||||
|
||||
// NOTE: cellProps come from react-table
|
||||
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||
export const generateTableHeaders = (
|
||||
handleRowRemove?: (value: Row) => void
|
||||
): IDataColumn[] => {
|
||||
handleRowRemove?: (value: Row<IHost>) => void
|
||||
): ITargestInputhostTableConfig[] => {
|
||||
const deleteHeader = handleRowRemove
|
||||
? [
|
||||
{
|
||||
id: "delete",
|
||||
Header: "",
|
||||
Cell: (cellProps: { row: Row }): JSX.Element => (
|
||||
Cell: (cellProps: ITableStringCellProps) => (
|
||||
<div onClick={() => handleRowRemove(cellProps.row)}>
|
||||
<Icon name="close-filled" />
|
||||
</div>
|
||||
@ -42,10 +36,9 @@ export const generateTableHeaders = (
|
||||
|
||||
return [
|
||||
{
|
||||
title: "Host",
|
||||
Header: "Host",
|
||||
accessor: "display_name",
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: ITableStringCellProps) => {
|
||||
return (
|
||||
<LiveQueryIssueCell
|
||||
displayName={cellProps.cell.value}
|
||||
@ -59,32 +52,27 @@ export const generateTableHeaders = (
|
||||
// TODO: Consider removing status column from selected hosts table because
|
||||
// status info is not refreshed once a target has been selected
|
||||
{
|
||||
title: "Status",
|
||||
Header: "Status",
|
||||
disableSortBy: true,
|
||||
accessor: "status",
|
||||
Cell: (cellProps) => <StatusIndicator value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Private IP address",
|
||||
Header: "Private IP address",
|
||||
accessor: "primary_ip",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "MAC address",
|
||||
Header: "MAC address",
|
||||
accessor: "primary_mac",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "OS",
|
||||
Header: "OS",
|
||||
accessor: "os_version",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
},
|
||||
{
|
||||
title: "Osquery",
|
||||
Header: "Osquery",
|
||||
accessor: "osquery_version",
|
||||
Cell: (cellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
|
@ -24,7 +24,7 @@ function useActionCallback(
|
||||
callbackFn: (targetIds: number[]) => void | undefined
|
||||
) {
|
||||
return useCallback(
|
||||
(targetIds) => {
|
||||
(targetIds: any) => {
|
||||
callbackFn(targetIds);
|
||||
},
|
||||
[callbackFn]
|
||||
|
@ -316,7 +316,7 @@ const DataTable = ({
|
||||
}, [toggleAllPagesSelected, toggleAllRowsSelected]);
|
||||
|
||||
const onSelectRowClick = useCallback(
|
||||
(row) => {
|
||||
(row: any) => {
|
||||
if (disableMultiRowSelect) {
|
||||
row.toggleRowSelected();
|
||||
onSelectSingleRow && onSelectSingleRow(row);
|
||||
|
@ -3,9 +3,7 @@ import { FilterProps, TableInstance } from "react-table";
|
||||
|
||||
import SearchField from "components/forms/fields/SearchField";
|
||||
|
||||
const DefaultColumnFilter = ({
|
||||
column,
|
||||
}: FilterProps<TableInstance>): JSX.Element => {
|
||||
const DefaultColumnFilter = <T extends object>({ column }: FilterProps<T>) => {
|
||||
const { setFilter } = column;
|
||||
|
||||
// Remove last_fetched filter per design as it is confusing to filter by a non-displayed date-string
|
||||
|
@ -1,4 +1,4 @@
|
||||
import ReactDOM from "react-dom";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
// used for babel polyfills.
|
||||
import "core-js/stable";
|
||||
@ -11,6 +11,6 @@ import "./index.scss";
|
||||
if (typeof window !== "undefined") {
|
||||
const { document } = global;
|
||||
const app = document.getElementById("app");
|
||||
|
||||
ReactDOM.render(routes, app);
|
||||
const root = createRoot(app);
|
||||
root.render(routes);
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export interface ICampaign {
|
||||
};
|
||||
id: number;
|
||||
query_id: number;
|
||||
query_results: unknown[];
|
||||
query_results: Record<string, unknown>[];
|
||||
status: string;
|
||||
totals: {
|
||||
count: number;
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { Column } from "react-table";
|
||||
/** This file contains reusable types that can be used when creating new tables
|
||||
* using react-table.
|
||||
*/
|
||||
|
||||
import { CellProps, Column, HeaderProps } from "react-table";
|
||||
|
||||
export type IDataColumn = Column & {
|
||||
title?: string;
|
||||
@ -8,3 +12,42 @@ export type IDataColumn = Column & {
|
||||
preFilteredRows?: any;
|
||||
setFilter?: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for a column configuration in a table. This is a wrapper around the `Column` type from
|
||||
* `react-table`.
|
||||
* @param T - The type of the data in the column
|
||||
* @example
|
||||
* ```ts
|
||||
* type IHostTableColumnConfig = IColumnConfig<IHost>;
|
||||
*/
|
||||
export type IColumnConfig<T extends object> = Column<T>;
|
||||
|
||||
/**
|
||||
* Interface for a cell with a string value. This is a wrapper around the
|
||||
* `CellProps` type from `react-table`.
|
||||
*/
|
||||
export type IStringCellProps<T extends object> = CellProps<T, string>;
|
||||
|
||||
/**
|
||||
* Interface for a cell with a number value. This is a wrapper around the
|
||||
* `CellProps` type from `react-table`.
|
||||
*/
|
||||
export type INumberCellProps<T extends object> = CellProps<T, number>;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export type IBoolCellProps<T extends object> = CellProps<T, boolean>;
|
||||
|
||||
/**
|
||||
* Interface for a cell with a value that is an array of objects. This is a
|
||||
* wrapper around the `HeaderProps` type from `react-table`.
|
||||
*/
|
||||
export type IHeaderProps<T extends object> = HeaderProps<T>;
|
||||
|
||||
/**
|
||||
* The typing for web socket data is loose as we are getting the data is
|
||||
* not typed and is not guaranteed to have the same shape every time.
|
||||
* */
|
||||
export type IWebSocketData = Record<string, unknown>;
|
||||
|
@ -246,6 +246,11 @@ export interface IHostEncrpytionKeyResponse {
|
||||
};
|
||||
}
|
||||
|
||||
export interface IHostIssues {
|
||||
total_issues_count: number;
|
||||
failing_policies_count: number;
|
||||
}
|
||||
|
||||
export interface IHost {
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@ -293,10 +298,7 @@ export interface IHost {
|
||||
labels: ILabel[];
|
||||
packs: IPack[];
|
||||
software: ISoftware[];
|
||||
issues: {
|
||||
total_issues_count: number;
|
||||
failing_policies_count: number;
|
||||
};
|
||||
issues: IHostIssues;
|
||||
status: HostStatus;
|
||||
display_text: string;
|
||||
display_name: string;
|
||||
@ -310,6 +312,7 @@ export interface IHost {
|
||||
geolocation?: IGeoLocation;
|
||||
batteries?: IBattery[];
|
||||
disk_encryption_enabled?: boolean;
|
||||
device_mapping: IDeviceUser[] | null;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -486,7 +486,7 @@ const TAGGED_TEMPLATES = {
|
||||
|
||||
const activityType = lowerCase(activity.type).replace(" saved", "");
|
||||
|
||||
return !entityName ? (
|
||||
return !entityName || typeof entityName !== "string" ? (
|
||||
`${activityType}.`
|
||||
) : (
|
||||
<>
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Column } from "react-table";
|
||||
import { CellProps, Column, HeaderProps } from "react-table";
|
||||
import { InjectedRouter } from "react-router";
|
||||
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
@ -22,40 +22,21 @@ import LinkCell from "components/TableContainer/DataTable/LinkCell";
|
||||
|
||||
import VulnerabilitiesCell from "pages/SoftwarePage/components/VulnerabilitiesCell";
|
||||
import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
|
||||
import {
|
||||
INumberCellProps,
|
||||
IStringCellProps,
|
||||
} from "interfaces/datatable_config";
|
||||
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
value: number | string | ISoftwareVulnerability[];
|
||||
};
|
||||
row: {
|
||||
original: IOperatingSystemVersion;
|
||||
};
|
||||
}
|
||||
type ITableColumnConfig = Column<IOperatingSystemVersion>;
|
||||
|
||||
interface IStringCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface INumberCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface IVulnCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: ISoftwareVulnerability[];
|
||||
};
|
||||
}
|
||||
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
title: string;
|
||||
isSortedDesc: boolean;
|
||||
};
|
||||
}
|
||||
type INameCellProps = IStringCellProps<IOperatingSystemVersion>;
|
||||
type IVersionCellProps = IStringCellProps<IOperatingSystemVersion>;
|
||||
type IVulnCellProps = CellProps<
|
||||
IOperatingSystemVersion,
|
||||
ISoftwareVulnerability[]
|
||||
>;
|
||||
type IHostCountCellProps = INumberCellProps<IOperatingSystemVersion>;
|
||||
type IHostHeaderProps = HeaderProps<IOperatingSystemVersion>;
|
||||
|
||||
interface IOSTableConfigOptions {
|
||||
includeName?: boolean;
|
||||
@ -67,12 +48,12 @@ const generateDefaultTableHeaders = (
|
||||
teamId?: number,
|
||||
router?: InjectedRouter,
|
||||
configOptions?: IOSTableConfigOptions
|
||||
): Column[] => [
|
||||
): ITableColumnConfig[] => [
|
||||
{
|
||||
Header: "Name",
|
||||
disableSortBy: true,
|
||||
accessor: "name_only",
|
||||
Cell: (cellProps: IStringCellProps) => {
|
||||
Cell: (cellProps: INameCellProps) => {
|
||||
if (!configOptions?.includeIcon) {
|
||||
return (
|
||||
<TextCell
|
||||
@ -116,7 +97,7 @@ const generateDefaultTableHeaders = (
|
||||
Header: "Version",
|
||||
disableSortBy: true,
|
||||
accessor: "version",
|
||||
Cell: (cellProps: IStringCellProps) => (
|
||||
Cell: (cellProps: IVersionCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
@ -124,7 +105,7 @@ const generateDefaultTableHeaders = (
|
||||
Header: "Vulnerabilities",
|
||||
disableSortBy: true,
|
||||
accessor: "vulnerabilities",
|
||||
Cell: (cellProps: IVulnCellProps): JSX.Element => {
|
||||
Cell: (cellProps: IVulnCellProps) => {
|
||||
const platform = cellProps.row.original.platform;
|
||||
if (platform !== "darwin" && platform !== "windows") {
|
||||
return <TextCell value="Not supported" greyed />;
|
||||
@ -133,16 +114,17 @@ const generateDefaultTableHeaders = (
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => (
|
||||
Header: (cellProps: IHostHeaderProps) => (
|
||||
<HeaderCell
|
||||
value="Hosts"
|
||||
disableSortBy={false}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
|
||||
disableSortBy: false,
|
||||
accessor: "hosts_count",
|
||||
Cell: (cellProps: INumberCellProps): JSX.Element => {
|
||||
Cell: (cellProps: IHostCountCellProps) => {
|
||||
const { hosts_count, os_version_id } = cellProps.row.original;
|
||||
return (
|
||||
<span className="hosts-cell__wrapper">
|
||||
@ -168,7 +150,7 @@ const generateTableHeaders = (
|
||||
teamId?: number,
|
||||
router?: InjectedRouter,
|
||||
configOptions?: IOSTableConfigOptions
|
||||
): Column[] => {
|
||||
): ITableColumnConfig[] => {
|
||||
let tableConfig = generateDefaultTableHeaders(teamId, router, configOptions);
|
||||
|
||||
if (!configOptions?.includeName) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Column } from "react-table";
|
||||
import { CellProps, Column } from "react-table";
|
||||
import { InjectedRouter } from "react-router";
|
||||
|
||||
import {
|
||||
@ -8,55 +8,33 @@ import {
|
||||
formatSoftwareType,
|
||||
} from "interfaces/software";
|
||||
import PATHS from "router/paths";
|
||||
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config";
|
||||
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import LinkCell from "components/TableContainer/DataTable/LinkCell/LinkCell";
|
||||
import ViewAllHostsLink from "components/ViewAllHostsLink";
|
||||
|
||||
import VersionCell from "../../components/VersionCell";
|
||||
import VulnerabilitiesCell from "../../components/VulnerabilitiesCell";
|
||||
import SoftwareIcon from "../../components/icons/SoftwareIcon";
|
||||
|
||||
// NOTE: cellProps come from react-table
|
||||
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
value: number | string | ISoftwareTitleVersion[];
|
||||
};
|
||||
row: {
|
||||
original: ISoftwareTitle;
|
||||
};
|
||||
}
|
||||
interface IStringCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface IVersionCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: ISoftwareTitleVersion[];
|
||||
};
|
||||
}
|
||||
type ISoftwareTitlesTableConfig = Column<ISoftwareTitle>;
|
||||
type ITableStringCellProps = IStringCellProps<ISoftwareTitle>;
|
||||
type IVersionsCellProps = CellProps<ISoftwareTitle, ISoftwareTitle["versions"]>;
|
||||
type IVulnerabilitiesCellProps = IVersionsCellProps;
|
||||
type IHostCountCellProps = CellProps<
|
||||
ISoftwareTitle,
|
||||
ISoftwareTitle["hosts_count"]
|
||||
>;
|
||||
type IViewAllHostsLinkProps = CellProps<ISoftwareTitle>;
|
||||
|
||||
interface INumberCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface IVulnCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: ISoftwareTitleVersion[];
|
||||
};
|
||||
}
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
title: string;
|
||||
isSortedDesc: boolean;
|
||||
};
|
||||
}
|
||||
type ITableHeaderProps = IHeaderProps<ISoftwareTitle>;
|
||||
|
||||
const getVulnerabilities = (versions: ISoftwareTitleVersion[]) => {
|
||||
if (!versions) {
|
||||
@ -77,19 +55,15 @@ const getVulnerabilities = (versions: ISoftwareTitleVersion[]) => {
|
||||
const generateTableHeaders = (
|
||||
router: InjectedRouter,
|
||||
teamId?: number
|
||||
): Column[] => {
|
||||
const softwareTableHeaders = [
|
||||
): ISoftwareTitlesTableConfig[] => {
|
||||
const softwareTableHeaders: ISoftwareTitlesTableConfig[] = [
|
||||
{
|
||||
title: "Name",
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
Header: (cellProps: ITableHeaderProps) => (
|
||||
<HeaderCell value="Name" isSortedDesc={cellProps.column.isSortedDesc} />
|
||||
),
|
||||
disableSortBy: false,
|
||||
accessor: "name",
|
||||
Cell: (cellProps: IStringCellProps): JSX.Element => {
|
||||
Cell: (cellProps: ITableStringCellProps) => {
|
||||
const { id, name, source } = cellProps.row.original;
|
||||
|
||||
const teamQueryParam = buildQueryStringFromParams({ team_id: teamId });
|
||||
@ -120,20 +94,18 @@ const generateTableHeaders = (
|
||||
sortType: "caseInsensitive",
|
||||
},
|
||||
{
|
||||
title: "Version",
|
||||
Header: "Version",
|
||||
disableSortBy: true,
|
||||
accessor: "versions",
|
||||
Cell: (cellProps: IVersionCellProps): JSX.Element => (
|
||||
Cell: (cellProps: IVersionsCellProps) => (
|
||||
<VersionCell versions={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
Header: "Type",
|
||||
disableSortBy: true,
|
||||
accessor: "source",
|
||||
Cell: (cellProps: ICellProps): JSX.Element => (
|
||||
Cell: (cellProps: ITableStringCellProps) => (
|
||||
<TextCell value={formatSoftwareType(cellProps.row.original)} />
|
||||
),
|
||||
},
|
||||
@ -144,11 +116,9 @@ const generateTableHeaders = (
|
||||
// With the versions data, we can sum up the vulnerabilities to get the
|
||||
// total number of vulnerabilities for the software title
|
||||
{
|
||||
title: "Vulnerabilities",
|
||||
Header: "Vulnerabilities",
|
||||
disableSortBy: true,
|
||||
accessor: "vulnerabilities",
|
||||
Cell: (cellProps: IVulnCellProps): JSX.Element => {
|
||||
Cell: (cellProps: IVulnerabilitiesCellProps) => {
|
||||
const vulnerabilities = getVulnerabilities(
|
||||
cellProps.row.original.versions ?? []
|
||||
);
|
||||
@ -156,26 +126,23 @@ const generateTableHeaders = (
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Hosts",
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => (
|
||||
Header: (cellProps: ITableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Hosts"
|
||||
disableSortBy={false}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
disableSortBy: false,
|
||||
accessor: "hosts_count",
|
||||
Cell: (cellProps: INumberCellProps): JSX.Element => (
|
||||
Cell: (cellProps: IHostCountCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: IViewAllHostsLinkProps) => {
|
||||
return (
|
||||
<ViewAllHostsLink
|
||||
queryParams={{
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Column } from "react-table";
|
||||
import { CellProps, Column } from "react-table";
|
||||
import { InjectedRouter } from "react-router";
|
||||
|
||||
import { buildQueryStringFromParams } from "utilities/url";
|
||||
@ -8,6 +8,7 @@ import {
|
||||
ISoftwareVersion,
|
||||
ISoftwareVulnerability,
|
||||
} from "interfaces/software";
|
||||
import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config";
|
||||
import PATHS from "router/paths";
|
||||
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
@ -19,60 +20,29 @@ import SoftwareIcon from "../../components/icons/SoftwareIcon";
|
||||
|
||||
// NOTE: cellProps come from react-table
|
||||
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
value: number | string | ISoftwareVulnerability[];
|
||||
};
|
||||
row: {
|
||||
original: ISoftwareVersion;
|
||||
};
|
||||
}
|
||||
interface IStringCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface IVersionCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
type ISoftwareVersionsTableConfig = Column<ISoftwareVersion>;
|
||||
type ITableStringCellProps = IStringCellProps<ISoftwareVersion>;
|
||||
type IVulnerabilitiesCellProps = CellProps<
|
||||
ISoftwareVersion,
|
||||
ISoftwareVulnerability[] | null
|
||||
>;
|
||||
type IHostCountCellProps = CellProps<ISoftwareVersion, number | undefined>;
|
||||
|
||||
interface INumberCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface IVulnCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: ISoftwareVulnerability[];
|
||||
};
|
||||
}
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
title: string;
|
||||
isSortedDesc: boolean;
|
||||
};
|
||||
}
|
||||
type ITableHeaderProps = IHeaderProps<ISoftwareVersion>;
|
||||
|
||||
const generateTableHeaders = (
|
||||
router: InjectedRouter,
|
||||
teamId?: number
|
||||
): Column[] => {
|
||||
const softwareTableHeaders = [
|
||||
): ISoftwareVersionsTableConfig[] => {
|
||||
const softwareTableHeaders: ISoftwareVersionsTableConfig[] = [
|
||||
{
|
||||
title: "Name",
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
Header: (cellProps: ITableHeaderProps) => (
|
||||
<HeaderCell value="Name" isSortedDesc={cellProps.column.isSortedDesc} />
|
||||
),
|
||||
disableSortBy: false,
|
||||
accessor: "name",
|
||||
Cell: (cellProps: IStringCellProps): JSX.Element => {
|
||||
Cell: (cellProps: ITableStringCellProps) => {
|
||||
const { id, name, source } = cellProps.row.original;
|
||||
|
||||
const teamQueryParam = buildQueryStringFromParams({
|
||||
@ -105,53 +75,47 @@ const generateTableHeaders = (
|
||||
sortType: "caseInsensitive",
|
||||
},
|
||||
{
|
||||
title: "Version",
|
||||
Header: "Version",
|
||||
disableSortBy: true,
|
||||
accessor: "version",
|
||||
Cell: (cellProps: IVersionCellProps): JSX.Element => (
|
||||
Cell: (cellProps: ITableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Type",
|
||||
Header: "Type",
|
||||
disableSortBy: true,
|
||||
accessor: "source",
|
||||
Cell: (cellProps: ICellProps): JSX.Element => (
|
||||
Cell: (cellProps: ITableStringCellProps) => (
|
||||
<TextCell value={formatSoftwareType(cellProps.row.original)} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Vulnerabilities",
|
||||
Header: "Vulnerabilities",
|
||||
disableSortBy: true,
|
||||
accessor: "vulnerabilities",
|
||||
Cell: (cellProps: IVulnCellProps): JSX.Element => (
|
||||
Cell: (cellProps: IVulnerabilitiesCellProps) => (
|
||||
<VulnerabilitiesCell vulnerabilities={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Hosts",
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => (
|
||||
Header: (cellProps: ITableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Hosts"
|
||||
disableSortBy={false}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
disableSortBy: false,
|
||||
accessor: "hosts_count",
|
||||
Cell: (cellProps: INumberCellProps): JSX.Element => (
|
||||
Cell: (cellProps: IHostCountCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
Header: "",
|
||||
accessor: "linkToFilteredHosts",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: ITableStringCellProps) => {
|
||||
return (
|
||||
<>
|
||||
{cellProps.row.original && (
|
||||
|
@ -33,7 +33,7 @@ const SoftwareVulnOSVersions = ({
|
||||
);
|
||||
|
||||
const onSelectSingleRow = useCallback(
|
||||
({ original: { os_version_id } }) => {
|
||||
({ original: { os_version_id } }: any) => {
|
||||
if (!os_version_id) {
|
||||
return;
|
||||
}
|
||||
|
@ -11,30 +11,27 @@ import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import ViewAllHostsLink from "components/ViewAllHostsLink";
|
||||
import { InjectedRouter } from "react-router";
|
||||
import {
|
||||
INumberCellProps,
|
||||
IStringCellProps,
|
||||
} from "interfaces/datatable_config";
|
||||
|
||||
interface ICellProps {
|
||||
row: {
|
||||
original: IVulnerabilityOSVersion;
|
||||
};
|
||||
}
|
||||
type ISWVulnTableColumnConfig = Column<IVulnerabilityOSVersion>;
|
||||
|
||||
interface INumberCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: number;
|
||||
};
|
||||
}
|
||||
type ITableStringCellProps = IStringCellProps<IVulnerabilityOSVersion>;
|
||||
type ITableNumberCellProps = INumberCellProps<IVulnerabilityOSVersion>;
|
||||
|
||||
const generateColumnConfigs = (
|
||||
isPremiumTier: boolean,
|
||||
router: InjectedRouter,
|
||||
teamIdForApi?: number
|
||||
): Column[] => {
|
||||
const configs = [
|
||||
): ISWVulnTableColumnConfig[] => {
|
||||
const configs: ISWVulnTableColumnConfig[] = [
|
||||
{
|
||||
Header: "Name",
|
||||
disableSortBy: true,
|
||||
accessor: "name_only",
|
||||
Cell: ({ row }: ICellProps) => {
|
||||
Cell: ({ row }: ITableStringCellProps) => {
|
||||
const { name, os_version_id, platform } = row.original;
|
||||
const endpoint = PATHS.SOFTWARE_OS_DETAILS(os_version_id);
|
||||
// since No Teams not supported on this page, falsiness of 0 is okay
|
||||
@ -65,7 +62,9 @@ const generateColumnConfigs = (
|
||||
Header: "Version",
|
||||
disableSortBy: true,
|
||||
accessor: "version",
|
||||
Cell: ({ cell }: INumberCellProps) => <TextCell value={cell.value} />,
|
||||
Cell: ({ cell }: ITableStringCellProps) => (
|
||||
<TextCell value={cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: () => (
|
||||
@ -75,13 +74,15 @@ const generateColumnConfigs = (
|
||||
),
|
||||
disableSortBy: true,
|
||||
accessor: "resolved_in_version",
|
||||
Cell: ({ cell }: INumberCellProps) => <TextCell value={cell.value} />,
|
||||
Cell: ({ cell }: ITableStringCellProps) => (
|
||||
<TextCell value={cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: "Hosts",
|
||||
disableSortBy: true,
|
||||
accessor: "hosts_count",
|
||||
Cell: ({ row }: ICellProps) => {
|
||||
Cell: ({ row }: ITableNumberCellProps) => {
|
||||
const { hosts_count, os_version_id } = row.original;
|
||||
return (
|
||||
<>
|
||||
|
@ -35,7 +35,7 @@ const SoftwareVulnSoftwareVersions = ({
|
||||
);
|
||||
|
||||
const onSelectSingleRow = useCallback(
|
||||
({ original: { id: software_version_id } }) => {
|
||||
({ original: { id: software_version_id } }: any) => {
|
||||
if (!software_version_id) {
|
||||
return;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import { Column } from "react-table";
|
||||
import { CellProps, Column } from "react-table";
|
||||
|
||||
import { IVulnerabilitySoftware } from "interfaces/software";
|
||||
import { IStringCellProps } from "interfaces/datatable_config";
|
||||
|
||||
import PATHS from "router/paths";
|
||||
|
||||
@ -11,32 +12,25 @@ import ViewAllHostsLink from "components/ViewAllHostsLink";
|
||||
import SoftwareIcon from "pages/SoftwarePage/components/icons/SoftwareIcon";
|
||||
import { InjectedRouter } from "react-router";
|
||||
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
value: number | string | IVulnerabilitySoftware;
|
||||
};
|
||||
row: {
|
||||
original: IVulnerabilitySoftware;
|
||||
};
|
||||
}
|
||||
type SwVulnTableColumnConfig = Column<IVulnerabilitySoftware>;
|
||||
|
||||
interface IStringCellProps extends ICellProps {
|
||||
cell: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
type ITableStringCellProps = IStringCellProps<IVulnerabilitySoftware>;
|
||||
type IHostCountCellProps = CellProps<
|
||||
IVulnerabilitySoftware,
|
||||
number | undefined
|
||||
>;
|
||||
|
||||
const generateColumnConfigs = (
|
||||
isPremiumTier: boolean,
|
||||
router: InjectedRouter,
|
||||
teamIdForApi?: number
|
||||
): Column[] => {
|
||||
const configs = [
|
||||
): SwVulnTableColumnConfig[] => {
|
||||
const configs: SwVulnTableColumnConfig[] = [
|
||||
{
|
||||
Header: "Name",
|
||||
disableSortBy: true,
|
||||
accessor: "name",
|
||||
Cell: ({ row }: ICellProps) => {
|
||||
Cell: ({ row }: ITableStringCellProps) => {
|
||||
const { name, id } = row.original;
|
||||
const endpoint = PATHS.SOFTWARE_VERSION_DETAILS(id.toString());
|
||||
// since No Teams not supported on this page, falsiness of 0 is okay
|
||||
@ -67,7 +61,9 @@ const generateColumnConfigs = (
|
||||
Header: "Version",
|
||||
disableSortBy: true,
|
||||
accessor: "version",
|
||||
Cell: ({ cell }: IStringCellProps) => <TextCell value={cell.value} />,
|
||||
Cell: ({ cell }: ITableStringCellProps) => (
|
||||
<TextCell value={cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: () => (
|
||||
@ -77,13 +73,15 @@ const generateColumnConfigs = (
|
||||
),
|
||||
disableSortBy: true,
|
||||
accessor: "resolved_in_version",
|
||||
Cell: ({ cell }: IStringCellProps) => <TextCell value={cell.value} />,
|
||||
Cell: ({ cell }: ITableStringCellProps) => (
|
||||
<TextCell value={cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: "Hosts",
|
||||
disableSortBy: true,
|
||||
accessor: "hosts_count",
|
||||
Cell: ({ row }: ICellProps) => {
|
||||
Cell: ({ row }: IHostCountCellProps) => {
|
||||
const { hosts_count, id } = row.original;
|
||||
return (
|
||||
<>
|
||||
|
@ -8,7 +8,7 @@ import ReactTooltip from "react-tooltip";
|
||||
|
||||
const baseClass = "version-cell";
|
||||
|
||||
const generateText = (versions: ISoftwareTitleVersion[]) => {
|
||||
const generateText = (versions: ISoftwareTitleVersion[] | null) => {
|
||||
if (!versions) {
|
||||
return <TextCell value="Unavailable" greyed />;
|
||||
}
|
||||
@ -40,7 +40,7 @@ const generateTooltip = (
|
||||
};
|
||||
|
||||
interface IVersionCellProps {
|
||||
versions: ISoftwareTitleVersion[];
|
||||
versions: ISoftwareTitleVersion[] | null;
|
||||
}
|
||||
|
||||
const VersionCell = ({ versions }: IVersionCellProps) => {
|
||||
|
@ -27,7 +27,7 @@ const AddUsersModal = ({
|
||||
const [selectedUsers, setSelectedUsers] = useState([]);
|
||||
|
||||
const onChangeDropdown = useCallback(
|
||||
(values) => {
|
||||
(values: any) => {
|
||||
setSelectedUsers(values);
|
||||
},
|
||||
[setSelectedUsers]
|
||||
|
@ -70,7 +70,7 @@ const AutocompleteDropdown = ({
|
||||
|
||||
// We disable any filtering client side as the server filters the results
|
||||
// for us.
|
||||
const filterOptions = useCallback((options) => {
|
||||
const filterOptions = useCallback((options: any) => {
|
||||
return options;
|
||||
}, []);
|
||||
|
||||
|
@ -41,7 +41,7 @@ const CreateTeamModal = ({
|
||||
);
|
||||
|
||||
const onFormSubmit = useCallback(
|
||||
(evt) => {
|
||||
(evt: any) => {
|
||||
evt.preventDefault();
|
||||
onSubmit({
|
||||
name,
|
||||
|
@ -2,7 +2,7 @@
|
||||
// disable this rule as it was throwing an error in Header and Cell component
|
||||
// definitions for the selection row for some reason when we dont really need it.
|
||||
import React from "react";
|
||||
import { Column } from "react-table";
|
||||
import { CellProps, Column } from "react-table";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
import { IDeviceUser, IHost } from "interfaces/host";
|
||||
@ -25,60 +25,30 @@ import {
|
||||
hostTeamName,
|
||||
} from "utilities/helpers";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
import { IDataColumn } from "interfaces/datatable_config";
|
||||
import {
|
||||
IHeaderProps,
|
||||
IStringCellProps,
|
||||
INumberCellProps,
|
||||
} from "interfaces/datatable_config";
|
||||
import PATHS from "router/paths";
|
||||
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
|
||||
import getHostStatusTooltipText from "../helpers";
|
||||
|
||||
interface IGetToggleAllRowsSelectedProps {
|
||||
checked: boolean;
|
||||
indeterminate: boolean;
|
||||
title: string;
|
||||
onChange: () => void;
|
||||
style: { cursor: string };
|
||||
}
|
||||
type IHostTableColumnConfig = Column<IHost> & {
|
||||
// This is used to prevent these columns from being hidden. This will be
|
||||
// used in EditColumnsModal to prevent these columns from being hidden.
|
||||
disableHidden?: boolean;
|
||||
// We add title in the column config to be able to use it in the EditColumnsModal
|
||||
// as well
|
||||
title?: string;
|
||||
};
|
||||
|
||||
interface IRow {
|
||||
original: IHost;
|
||||
getToggleRowSelectedProps: () => IGetToggleAllRowsSelectedProps;
|
||||
toggleRowSelected: () => void;
|
||||
}
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
title: string;
|
||||
isSortedDesc: boolean;
|
||||
};
|
||||
getToggleAllRowsSelectedProps: () => IGetToggleAllRowsSelectedProps;
|
||||
toggleAllRowsSelected: () => void;
|
||||
rows: IRow[];
|
||||
}
|
||||
|
||||
interface ICellProps {
|
||||
cell: {
|
||||
value: string;
|
||||
};
|
||||
row: IRow;
|
||||
}
|
||||
|
||||
interface INumberCellProps {
|
||||
cell: {
|
||||
value: number;
|
||||
};
|
||||
row: {
|
||||
original: IHost;
|
||||
getToggleRowSelectedProps: () => IGetToggleAllRowsSelectedProps;
|
||||
toggleRowSelected: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
interface IDeviceUserCellProps {
|
||||
cell: {
|
||||
value: IDeviceUser[];
|
||||
};
|
||||
row: {
|
||||
original: IHost;
|
||||
};
|
||||
}
|
||||
type IHostTableHeaderProps = IHeaderProps<IHost>;
|
||||
type IHostTableStringCellProps = IStringCellProps<IHost>;
|
||||
type IHostTableNumberCellProps = INumberCellProps<IHost>;
|
||||
type ISelectionCellProps = CellProps<IHost>;
|
||||
type IIssuesCellProps = CellProps<IHost, IHost["issues"]>;
|
||||
type IDeviceUserCellProps = CellProps<IHost, IHost["device_mapping"]>;
|
||||
|
||||
const condenseDeviceUsers = (users: IDeviceUser[]): string[] => {
|
||||
if (!users?.length) {
|
||||
@ -117,13 +87,13 @@ const lastSeenTime = (status: string, seenTime: string): string => {
|
||||
return "Online";
|
||||
};
|
||||
|
||||
const allHostTableHeaders: IDataColumn[] = [
|
||||
const allHostTableHeaders: IHostTableColumnConfig[] = [
|
||||
// We are using React Table useRowSelect functionality for the selection header.
|
||||
// More information on its API can be found here
|
||||
// https://react-table.tanstack.com/docs/api/useRowSelect
|
||||
{
|
||||
id: "selection",
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => {
|
||||
Header: (cellProps: IHostTableHeaderProps) => {
|
||||
const props = cellProps.getToggleAllRowsSelectedProps();
|
||||
const checkboxProps = {
|
||||
value: props.checked,
|
||||
@ -132,7 +102,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
};
|
||||
return <Checkbox {...checkboxProps} />;
|
||||
},
|
||||
Cell: (cellProps: ICellProps): JSX.Element => {
|
||||
Cell: (cellProps: ISelectionCellProps) => {
|
||||
const props = cellProps.row.getToggleRowSelectedProps();
|
||||
const checkboxProps = {
|
||||
value: props.checked,
|
||||
@ -143,15 +113,11 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
disableHidden: true,
|
||||
},
|
||||
{
|
||||
title: "Host",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell value="Hosts" isSortedDesc={cellProps.column.isSortedDesc} />
|
||||
),
|
||||
accessor: "display_name",
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: IHostTableStringCellProps) => {
|
||||
if (
|
||||
// if the host is pending, we want to disable the link to host details
|
||||
cellProps.row.original.mdm.enrollment_status === "Pending" &&
|
||||
@ -202,49 +168,50 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
{
|
||||
title: "Hostname",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Hostname"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "hostname",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Computer name",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Computer name"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "computer_name",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell value="Team" isSortedDesc={cellProps.column.isSortedDesc} />
|
||||
),
|
||||
accessor: "team_name",
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
Cell: (cellProps) => (
|
||||
<TextCell value={cellProps.cell.value} formatter={hostTeamName} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => {
|
||||
Header: (cellProps: IHostTableHeaderProps) => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
Online hosts will respond to a live query. Offline hosts won’t
|
||||
respond to a live query because they may be shut down, asleep, or
|
||||
not connected to the internet.
|
||||
Online hosts will respond to a live query. Offline hosts
|
||||
won't respond to a live query because they may be shut down,
|
||||
asleep, or not connected to the internet.
|
||||
</>
|
||||
}
|
||||
className="status-header"
|
||||
@ -261,7 +228,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
disableSortBy: true,
|
||||
accessor: "status",
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: IHostTableStringCellProps) => {
|
||||
const value = cellProps.cell.value;
|
||||
const tooltip = {
|
||||
tooltipText: getHostStatusTooltipText(value),
|
||||
@ -274,7 +241,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
Header: "Issues",
|
||||
disableSortBy: true,
|
||||
accessor: "issues",
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
Cell: (cellProps: IIssuesCellProps) => (
|
||||
<IssueCell
|
||||
issues={cellProps.row.original.issues}
|
||||
rowId={cellProps.row.original.id}
|
||||
@ -283,14 +250,14 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
{
|
||||
title: "Disk space available",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Disk space available"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "gigs_disk_space_available",
|
||||
Cell: (cellProps: INumberCellProps) => {
|
||||
Cell: (cellProps: IHostTableNumberCellProps) => {
|
||||
const {
|
||||
id,
|
||||
platform,
|
||||
@ -312,32 +279,36 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
{
|
||||
title: "Operating system",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Operating system"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "os_version",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Osquery",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Osquery"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "osquery_version",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Used by",
|
||||
Header: "Used by",
|
||||
disableSortBy: true,
|
||||
accessor: "device_mapping",
|
||||
Cell: (cellProps: IDeviceUserCellProps): JSX.Element => {
|
||||
Cell: (cellProps: IDeviceUserCellProps) => {
|
||||
const numUsers = cellProps.cell.value?.length || 0;
|
||||
const users = condenseDeviceUsers(cellProps.cell.value || []);
|
||||
if (users.length) {
|
||||
@ -372,18 +343,20 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
{
|
||||
title: "Private IP address",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Private IP address"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "primary_ip",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "MDM status",
|
||||
Header: (): JSX.Element => {
|
||||
Header: () => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
@ -400,13 +373,13 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
return <HeaderCell value={titleWithToolTip} disableSortBy />;
|
||||
},
|
||||
disableSortBy: true,
|
||||
accessor: "mdm.enrollment_status",
|
||||
accessor: (originalRow) => originalRow.mdm.enrollment_status,
|
||||
id: "mdm_enrollment_status",
|
||||
Cell: HostMdmStatusCell,
|
||||
},
|
||||
{
|
||||
title: "MDM server URL",
|
||||
Header: (): JSX.Element => {
|
||||
Header: () => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
@ -423,9 +396,9 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
return <HeaderCell value={titleWithToolTip} disableSortBy />;
|
||||
},
|
||||
disableSortBy: true,
|
||||
accessor: "mdm.server_url",
|
||||
accessor: (originalRow) => originalRow.mdm.server_url,
|
||||
id: "mdm_server_url",
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: IHostTableStringCellProps) => {
|
||||
if (cellProps.row.original.platform === "chrome") {
|
||||
return NotSupported;
|
||||
}
|
||||
@ -437,7 +410,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
{
|
||||
title: "Public IP address",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={
|
||||
<TooltipWrapper tipContent="The IP address the host uses to connect to Fleet.">
|
||||
@ -448,7 +421,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
/>
|
||||
),
|
||||
accessor: "public_ip",
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: IHostTableStringCellProps) => {
|
||||
return (
|
||||
<TextCell value={cellProps.cell.value ?? DEFAULT_EMPTY_CELL_VALUE} />
|
||||
);
|
||||
@ -456,7 +429,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
{
|
||||
title: "Last fetched",
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => {
|
||||
Header: (cellProps: IHostTableHeaderProps) => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
@ -477,7 +450,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
);
|
||||
},
|
||||
accessor: "detail_updated_at",
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell
|
||||
value={{ timeString: cellProps.cell.value }}
|
||||
formatter={HumanTimeDiffWithFleetLaunchCutoff}
|
||||
@ -486,7 +459,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
{
|
||||
title: "Last seen",
|
||||
Header: (cellProps: IHeaderProps): JSX.Element => {
|
||||
Header: (cellProps: IHostTableHeaderProps) => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
@ -507,7 +480,7 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
);
|
||||
},
|
||||
accessor: "seen_time",
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell
|
||||
value={{ timeString: cellProps.cell.value }}
|
||||
formatter={HumanTimeDiffWithFleetLaunchCutoff}
|
||||
@ -516,27 +489,24 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
},
|
||||
{
|
||||
title: "UUID",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell value="UUID" isSortedDesc={cellProps.column.isSortedDesc} />
|
||||
),
|
||||
accessor: "uuid",
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TooltipTruncatedTextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Last restarted",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Last restarted"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "last_restarted_at",
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: IHostTableStringCellProps) => {
|
||||
const { platform, last_restarted_at } = cellProps.row.original;
|
||||
|
||||
if (platform === "chrome") {
|
||||
@ -557,53 +527,58 @@ const allHostTableHeaders: IDataColumn[] = [
|
||||
Header: "CPU",
|
||||
disableSortBy: true,
|
||||
accessor: "cpu_type",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "RAM",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell value="RAM" isSortedDesc={cellProps.column.isSortedDesc} />
|
||||
),
|
||||
accessor: "memory",
|
||||
Cell: (cellProps: ICellProps) => (
|
||||
Cell: (cellProps: IHostTableNumberCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} formatter={humanHostMemory} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "MAC address",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="MAC address"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "primary_mac",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Serial number",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Serial number"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "hardware_serial",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Hardware model",
|
||||
Header: (cellProps: IHeaderProps) => (
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={cellProps.column.title}
|
||||
value="Hardware model"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: "hardware_model",
|
||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||
Cell: (cellProps: IHostTableStringCellProps) => (
|
||||
<TextCell value={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@ -635,9 +610,9 @@ const generateAvailableTableHeaders = ({
|
||||
}: {
|
||||
isFreeTier: boolean | undefined;
|
||||
isOnlyObserver: boolean | undefined;
|
||||
}): IDataColumn[] => {
|
||||
}): IHostTableColumnConfig[] => {
|
||||
return allHostTableHeaders.reduce(
|
||||
(columns: Column[], currentColumn: Column) => {
|
||||
(columns: Column<IHost>[], currentColumn: Column<IHost>) => {
|
||||
// skip over column headers that are not shown in free observer tier
|
||||
if (isFreeTier && isOnlyObserver) {
|
||||
if (
|
||||
@ -650,8 +625,8 @@ const generateAvailableTableHeaders = ({
|
||||
} else if (isFreeTier) {
|
||||
if (
|
||||
currentColumn.accessor === "team_name" ||
|
||||
currentColumn.accessor === "mdm_server_url" ||
|
||||
currentColumn.accessor === "mdm_enrollment_status"
|
||||
currentColumn.id === "mdm_server_url" ||
|
||||
currentColumn.id === "mdm_enrollment_status"
|
||||
) {
|
||||
return columns;
|
||||
}
|
||||
@ -681,7 +656,7 @@ const generateVisibleTableColumns = ({
|
||||
hiddenColumns: string[];
|
||||
isFreeTier: boolean | undefined;
|
||||
isOnlyObserver: boolean | undefined;
|
||||
}): IDataColumn[] => {
|
||||
}): IHostTableColumnConfig[] => {
|
||||
// remove columns set as hidden by the user.
|
||||
return generateAvailableTableHeaders({ isFreeTier, isOnlyObserver }).filter(
|
||||
(column) => {
|
||||
|
@ -164,7 +164,7 @@ const HQRTable = ({
|
||||
customControl={renderTableButtons}
|
||||
setExportRows={setFilteredResults}
|
||||
emptyComponent={() => null}
|
||||
defaultSortHeader={columnConfigs[0].title}
|
||||
defaultSortHeader={columnConfigs[0].id}
|
||||
defaultSortDirection="asc"
|
||||
/>
|
||||
)}
|
||||
|
@ -1,60 +1,53 @@
|
||||
import DefaultColumnFilter from "components/TableContainer/DataTable/DefaultColumnFilter";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import {
|
||||
IHeaderProps,
|
||||
IStringCellProps,
|
||||
IWebSocketData,
|
||||
} from "interfaces/datatable_config";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
CellProps,
|
||||
ColumnInstance,
|
||||
ColumnInterface,
|
||||
HeaderProps,
|
||||
TableInstance,
|
||||
} from "react-table";
|
||||
import { CellProps, Column } from "react-table";
|
||||
import {
|
||||
getUniqueColumnNamesFromRows,
|
||||
humanHostLastSeen,
|
||||
internallyTruncateText,
|
||||
} from "utilities/helpers";
|
||||
|
||||
type IHeaderProps = HeaderProps<TableInstance> & {
|
||||
column: ColumnInstance & IDataColumn;
|
||||
};
|
||||
type IHQRTTableColumn = Column<IWebSocketData>;
|
||||
type ITableHeaderProps = IHeaderProps<IWebSocketData>;
|
||||
type ITableStringCellProps = CellProps<IWebSocketData, string | unknown>;
|
||||
|
||||
type ICellProps = CellProps<TableInstance>;
|
||||
|
||||
interface IDataColumn extends ColumnInterface {
|
||||
title?: string;
|
||||
accessor: string;
|
||||
}
|
||||
|
||||
const generateColumnConfigs = (rows: Record<string, string>[]) =>
|
||||
const generateColumnConfigs = (rows: IWebSocketData[]): IHQRTTableColumn[] =>
|
||||
// casting necessary because of loose typing of below method
|
||||
// see note there for more details
|
||||
(getUniqueColumnNamesFromRows(rows) as string[]).map((colName) => {
|
||||
getUniqueColumnNamesFromRows(rows).map<IHQRTTableColumn>((colName) => {
|
||||
return {
|
||||
id: colName,
|
||||
title: colName,
|
||||
Header: (headerProps: IHeaderProps) => (
|
||||
Header: (headerProps: ITableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={
|
||||
// Sentence case last fetched
|
||||
headerProps.column.title === "last_fetched"
|
||||
headerProps.column.id === "last_fetched"
|
||||
? "Last fetched"
|
||||
: headerProps.column.title || headerProps.column.id
|
||||
: headerProps.column.id || headerProps.column.id
|
||||
}
|
||||
isSortedDesc={headerProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: colName,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
Cell: (cellProps: ITableStringCellProps) => {
|
||||
if (typeof cellProps?.cell?.value !== "string") return null;
|
||||
|
||||
// Sorts chronologically by date, but UI displays readable last fetched
|
||||
if (cellProps.column.id === "last_fetched") {
|
||||
return humanHostLastSeen(cellProps?.cell?.value);
|
||||
return <>{humanHostLastSeen(cellProps?.cell?.value)}</>;
|
||||
}
|
||||
// truncate columns longer than 300 characters
|
||||
const val = cellProps?.cell?.value;
|
||||
return !!val?.length && val.length > 300
|
||||
? internallyTruncateText(val)
|
||||
: val ?? null;
|
||||
: <>val</> ?? null;
|
||||
},
|
||||
Filter: DefaultColumnFilter, // Component hides filter for last_fetched
|
||||
filterType: "text",
|
||||
|
@ -48,7 +48,7 @@ const PacksTable = ({
|
||||
}, [packs, searchString, setFilteredPacks]);
|
||||
|
||||
const onQueryChange = useCallback(
|
||||
(queryData) => {
|
||||
(queryData: any) => {
|
||||
const { searchQuery } = queryData;
|
||||
setSearchString(searchQuery);
|
||||
},
|
||||
|
@ -354,7 +354,7 @@ const ManageQueriesPage = ({
|
||||
};
|
||||
|
||||
const onSaveQueryAutomations = useCallback(
|
||||
async (newAutomatedQueryIds) => {
|
||||
async (newAutomatedQueryIds: any) => {
|
||||
setIsUpdatingAutomations(true);
|
||||
|
||||
// Query ids added to turn on automations
|
||||
|
@ -14,13 +14,13 @@ describe("QueryReport", () => {
|
||||
{
|
||||
host_id: 1,
|
||||
host_name: "host1",
|
||||
last_fetched: "2021-01-01",
|
||||
last_fetched: "2020-01-01",
|
||||
columns: { col1: "value1", col2: "value2" },
|
||||
},
|
||||
{
|
||||
host_id: 2,
|
||||
host_name: "host2",
|
||||
last_fetched: "2021-01-01",
|
||||
last_fetched: "2020-01-01",
|
||||
columns: { col1: "value3", col2: "value4" },
|
||||
},
|
||||
],
|
||||
|
@ -3,14 +3,7 @@
|
||||
// definitions for the selection row for some reason when we dont really need it.
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
CellProps,
|
||||
Column,
|
||||
ColumnInstance,
|
||||
ColumnInterface,
|
||||
HeaderProps,
|
||||
TableInstance,
|
||||
} from "react-table";
|
||||
import { CellProps, Column } from "react-table";
|
||||
|
||||
import DefaultColumnFilter from "components/TableContainer/DataTable/DefaultColumnFilter";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
@ -20,19 +13,13 @@ import {
|
||||
humanHostLastSeen,
|
||||
internallyTruncateText,
|
||||
} from "utilities/helpers";
|
||||
import { IHeaderProps, IWebSocketData } from "interfaces/datatable_config";
|
||||
|
||||
type IHeaderProps = HeaderProps<TableInstance> & {
|
||||
column: ColumnInstance & IDataColumn;
|
||||
};
|
||||
type IQueryReportTableColumnConfig = Column<IWebSocketData>;
|
||||
type ITableHeaderProps = IHeaderProps<IWebSocketData>;
|
||||
type ITableCellProps = CellProps<IWebSocketData, string | unknown>;
|
||||
|
||||
type ICellProps = CellProps<TableInstance>;
|
||||
|
||||
interface IDataColumn extends ColumnInterface {
|
||||
title?: string;
|
||||
accessor: string;
|
||||
}
|
||||
|
||||
const _unshiftHostname = (headers: IDataColumn[]) => {
|
||||
const _unshiftHostname = (headers: IQueryReportTableColumnConfig[]) => {
|
||||
const newHeaders = [...headers];
|
||||
const displayNameIndex = headers.findIndex(
|
||||
(h) => h.id === "host_display_name"
|
||||
@ -41,7 +28,7 @@ const _unshiftHostname = (headers: IDataColumn[]) => {
|
||||
// remove hostname header from headers
|
||||
const [displayNameHeader] = newHeaders.splice(displayNameIndex, 1);
|
||||
// reformat title and insert at start of headers array
|
||||
newHeaders.unshift({ ...displayNameHeader, title: "Host" });
|
||||
newHeaders.unshift({ ...displayNameHeader, id: "Host" });
|
||||
}
|
||||
// TODO: Remove after v5 when host_hostname is removed rom API response.
|
||||
const hostNameIndex = headers.findIndex((h) => h.id === "host_hostname");
|
||||
@ -52,45 +39,50 @@ const _unshiftHostname = (headers: IDataColumn[]) => {
|
||||
return newHeaders;
|
||||
};
|
||||
|
||||
const generateReportColumnConfigsFromResults = (results: any[]): Column[] => {
|
||||
const generateReportColumnConfigsFromResults = (
|
||||
results: IWebSocketData[]
|
||||
): IQueryReportTableColumnConfig[] => {
|
||||
/* Results include an array of objects, each representing a table row
|
||||
Each key value pair in an object represents a column name and value
|
||||
To create headers, use JS set to create an array of all unique column names */
|
||||
const uniqueColumnNames = getUniqueColumnNamesFromRows(results);
|
||||
|
||||
const columnConfigs = uniqueColumnNames.map((key) => {
|
||||
const columnConfigs = uniqueColumnNames.map<IQueryReportTableColumnConfig>(
|
||||
(key) => {
|
||||
return {
|
||||
id: key as string,
|
||||
title: key as string,
|
||||
Header: (headerProps: IHeaderProps) => (
|
||||
id: key,
|
||||
Header: (headerProps: ITableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value={
|
||||
// Sentence case last fetched
|
||||
headerProps.column.title === "last_fetched"
|
||||
headerProps.column.id === "last_fetched"
|
||||
? "Last fetched"
|
||||
: headerProps.column.title || headerProps.column.id
|
||||
: headerProps.column.id
|
||||
}
|
||||
isSortedDesc={headerProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: key as string,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
accessor: key,
|
||||
Cell: (cellProps: ITableCellProps) => {
|
||||
if (typeof cellProps.cell.value !== "string") return null;
|
||||
|
||||
// Sorts chronologically by date, but UI displays readable last fetched
|
||||
if (cellProps.column.id === "last_fetched") {
|
||||
return humanHostLastSeen(cellProps?.cell?.value);
|
||||
return <>{humanHostLastSeen(cellProps?.cell?.value)}</>;
|
||||
}
|
||||
// truncate columns longer than 300 characters
|
||||
const val = cellProps?.cell?.value;
|
||||
return !!val?.length && val.length > 300
|
||||
? internallyTruncateText(val)
|
||||
: val ?? null;
|
||||
: <>{val}</> ?? null;
|
||||
},
|
||||
Filter: DefaultColumnFilter, // Component hides filter for last_fetched
|
||||
filterType: "text",
|
||||
disableSortBy: false,
|
||||
sortType: "caseInsensitive",
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
return _unshiftHostname(columnConfigs);
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
generateCSVFilename,
|
||||
generateCSVQueryResults,
|
||||
} from "utilities/generate_csv";
|
||||
import { ICampaign } from "interfaces/campaign";
|
||||
import { ICampaign, ICampaignError } from "interfaces/campaign";
|
||||
import { ITarget } from "interfaces/target";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
@ -67,7 +67,9 @@ const QueryResults = ({
|
||||
const [resultsColumnConfigs, setResultsColumnConfigs] = useState<Column[]>(
|
||||
[]
|
||||
);
|
||||
const [errorColumnConfigs, setErrorColumnConfigs] = useState<Column[]>([]);
|
||||
const [errorColumnConfigs, setErrorColumnConfigs] = useState<
|
||||
Column<ICampaignError>[]
|
||||
>([]);
|
||||
const [queryResultsForTableRender, setQueryResultsForTableRender] = useState(
|
||||
queryResults
|
||||
);
|
||||
|
@ -3,14 +3,7 @@
|
||||
// definitions for the selection row for some reason when we dont really need it.
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
CellProps,
|
||||
Column,
|
||||
ColumnInstance,
|
||||
ColumnInterface,
|
||||
HeaderProps,
|
||||
TableInstance,
|
||||
} from "react-table";
|
||||
import { CellProps, Column, HeaderProps } from "react-table";
|
||||
|
||||
import DefaultColumnFilter from "components/TableContainer/DataTable/DefaultColumnFilter";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
@ -19,18 +12,7 @@ import {
|
||||
internallyTruncateText,
|
||||
} from "utilities/helpers";
|
||||
|
||||
type IHeaderProps = HeaderProps<TableInstance> & {
|
||||
column: ColumnInstance & IDataColumn;
|
||||
};
|
||||
|
||||
type ICellProps = CellProps<TableInstance>;
|
||||
|
||||
interface IDataColumn extends ColumnInterface {
|
||||
title?: string;
|
||||
accessor: string;
|
||||
}
|
||||
|
||||
const _unshiftHostname = (columns: IDataColumn[]) => {
|
||||
const _unshiftHostname = <T extends object>(columns: Column<T>[]) => {
|
||||
const newHeaders = [...columns];
|
||||
const displayNameIndex = columns.findIndex(
|
||||
(h) => h.id === "host_display_name"
|
||||
@ -39,7 +21,7 @@ const _unshiftHostname = (columns: IDataColumn[]) => {
|
||||
// remove hostname header from headers
|
||||
const [displayNameHeader] = newHeaders.splice(displayNameIndex, 1);
|
||||
// reformat title and insert at start of headers array
|
||||
newHeaders.unshift({ ...displayNameHeader, title: "Host" });
|
||||
newHeaders.unshift({ ...displayNameHeader, id: "Host" });
|
||||
}
|
||||
// TODO: Remove after v5 when host_hostname is removed rom API response.
|
||||
const hostNameIndex = columns.findIndex((h) => h.id === "host_hostname");
|
||||
@ -50,24 +32,23 @@ const _unshiftHostname = (columns: IDataColumn[]) => {
|
||||
return newHeaders;
|
||||
};
|
||||
|
||||
const generateColumnConfigsFromRows = (
|
||||
const generateColumnConfigsFromRows = <T extends Record<keyof T, unknown>>(
|
||||
// TODO - narrow typing down this entire chain of logic
|
||||
// typed as any[] to accomodate loose typing of websocket API
|
||||
results: any[] // {col:val, ...} for each row of query results
|
||||
): Column[] => {
|
||||
results: T[] // {col:val, ...} for each row of query results
|
||||
): Column<T>[] => {
|
||||
const uniqueColumnNames = getUniqueColumnNamesFromRows(results);
|
||||
const columnsConfigs = uniqueColumnNames.map((colName) => {
|
||||
const columnsConfigs = uniqueColumnNames.map<Column<T>>((colName) => {
|
||||
return {
|
||||
id: colName as string,
|
||||
title: colName as string,
|
||||
Header: (headerProps: IHeaderProps) => (
|
||||
Header: (headerProps: HeaderProps<T>) => (
|
||||
<HeaderCell
|
||||
value={headerProps.column.title || headerProps.column.id}
|
||||
value={headerProps.column.id}
|
||||
isSortedDesc={headerProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
accessor: colName as string,
|
||||
Cell: (cellProps: ICellProps) => {
|
||||
accessor: colName,
|
||||
Cell: (cellProps: CellProps<T>) => {
|
||||
const val = cellProps?.cell?.value;
|
||||
return !!val?.length && val.length > 300
|
||||
? internallyTruncateText(val)
|
||||
|
@ -70,8 +70,8 @@ interface IWrapperComponentProps {
|
||||
}
|
||||
|
||||
const createWrapperComponent = (
|
||||
CurrentWrapper: React.FC<any>, // TODO: types
|
||||
WrapperComponent: React.FC<any>, // TODO: types
|
||||
CurrentWrapper: React.FC<React.PropsWithChildren<any>>, // TODO: types
|
||||
WrapperComponent: React.FC<React.PropsWithChildren<any>>, // TODO: types
|
||||
props: IWrapperComponentProps
|
||||
) => {
|
||||
return ({ children }: IChildrenProp) => (
|
||||
|
@ -9,7 +9,7 @@ interface IResult {
|
||||
distributed_query_execution_id: number;
|
||||
error: string | null;
|
||||
host: IHost;
|
||||
rows: unknown[];
|
||||
rows: Record<string, unknown>[];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,10 @@ export const generateCSVFilename = (descriptor: string) => {
|
||||
};
|
||||
|
||||
// Live query results, live query errors, and query report
|
||||
export const generateCSVQueryResults = (
|
||||
export const generateCSVQueryResults = <T extends object>(
|
||||
rows: Row[],
|
||||
filename: string,
|
||||
tableHeaders: Column[] | string[],
|
||||
tableHeaders: Column<T>[] | string[],
|
||||
omitHostDisplayName?: boolean
|
||||
) => {
|
||||
return new global.window.File(
|
||||
|
@ -858,14 +858,18 @@ export const internallyTruncateText = (
|
||||
original: string,
|
||||
prefixLength = 280,
|
||||
suffixLength = 10
|
||||
) => (
|
||||
): JSX.Element => (
|
||||
<>
|
||||
{original.slice(0, prefixLength)}...
|
||||
{original.slice(original.length - suffixLength)} <em>(truncated)</em>
|
||||
</>
|
||||
);
|
||||
|
||||
export const getUniqueColumnNamesFromRows = (rows: any[]) =>
|
||||
export const getUniqueColumnNamesFromRows = <
|
||||
T extends Record<keyof T, unknown>
|
||||
>(
|
||||
rows: T[]
|
||||
) =>
|
||||
// rows of type {col:val, col:val, ...}[]
|
||||
// cannot type more narrowly due to loose typing of websocket API and use of this function
|
||||
// by QueryResultsTableConfig, where results come from that API
|
||||
@ -873,11 +877,10 @@ export const getUniqueColumnNamesFromRows = (rows: any[]) =>
|
||||
Array.from(
|
||||
rows.reduce(
|
||||
(accOuter, row) =>
|
||||
Object.keys(row).reduce(
|
||||
(accInner, colNameInRow) => accInner.add(colNameInRow),
|
||||
accOuter
|
||||
),
|
||||
new Set()
|
||||
Object.keys(row).reduce((accInner, colNameInRow) => {
|
||||
return accInner.add(colNameInRow as keyof T);
|
||||
}, accOuter),
|
||||
new Set<keyof T>()
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -37,10 +37,10 @@
|
||||
"prop-types": "15.8.1",
|
||||
"proxy-middleware": "0.15.0",
|
||||
"rc-pagination": "1.16.3",
|
||||
"react": "16.14.0",
|
||||
"react": "18.2.0",
|
||||
"react-accessible-accordion": "3.3.5",
|
||||
"react-ace": "9.3.0",
|
||||
"react-dom": "16.14.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-error-boundary": "3.1.4",
|
||||
"react-markdown": "8.0.3",
|
||||
"react-query": "3.34.16",
|
||||
@ -106,7 +106,8 @@
|
||||
"@types/mocha": "2.2.48",
|
||||
"@types/node": "14.18.12",
|
||||
"@types/prop-types": "15.7.4",
|
||||
"@types/react": "17.0.40",
|
||||
"@types/react": "18.2.63",
|
||||
"@types/react-dom": "18.2.0",
|
||||
"@types/react-router": "3.0.28",
|
||||
"@types/react-select": "1.3.0",
|
||||
"@types/react-table": "7.7.7",
|
||||
|
56
yarn.lock
56
yarn.lock
@ -5203,9 +5203,16 @@
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-dom@*":
|
||||
version "17.0.13"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.13.tgz"
|
||||
integrity sha512-wEP+B8hzvy6ORDv1QBhcQia4j6ea4SFIBttHYpXKPFZRviBvknq0FRh3VrIxeXUmsPkwuXVZrVGG7KUVONmXCQ==
|
||||
version "18.2.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.19.tgz#b84b7c30c635a6c26c6a6dfbb599b2da9788be58"
|
||||
integrity sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dom@18.2.0":
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.0.tgz#374f28074bb117f56f58c4f3f71753bebb545156"
|
||||
integrity sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
@ -5226,7 +5233,7 @@
|
||||
|
||||
"@types/react-table@7.7.7":
|
||||
version "7.7.7"
|
||||
resolved "https://registry.npmjs.org/@types/react-table/-/react-table-7.7.7.tgz"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.7.7.tgz#503be1ce2e06857c11b281629f539358d32671f9"
|
||||
integrity sha512-3l2TP4detx9n5Jt44XhdH7Ku6PYwz6kB83P8W+YcBMUkIHtiw2gsCCcq9c4wyCIcdSwcTlWZ9WqH4PF7Yfbprg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
@ -5252,10 +5259,10 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@17.0.40":
|
||||
version "17.0.40"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.40.tgz"
|
||||
integrity sha512-UrXhD/JyLH+W70nNSufXqMZNuUD2cXHu6UjCllC6pmOQgBX4SGXOH8fjRka0O0Ee0HrFxapDD8Bwn81Kmiz6jQ==
|
||||
"@types/react@*", "@types/react@18.2.63":
|
||||
version "18.2.63"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.63.tgz#4637c56146ad90f96d0583171edab953f7e6fe57"
|
||||
integrity sha512-ppaqODhs15PYL2nGUOaOu2RSCCB4Difu4UFrP4I3NHLloXC/ESQzQMi9nvjfT1+rudd0d2L3fQPJxRSey+rGlQ==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
@ -15074,15 +15081,13 @@ react-docgen@^5.0.0:
|
||||
node-dir "^0.1.10"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
react-dom@16.14.0:
|
||||
version "16.14.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89"
|
||||
integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==
|
||||
react-dom@18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
scheduler "^0.19.1"
|
||||
scheduler "^0.23.0"
|
||||
|
||||
react-element-to-jsx-string@^15.0.0:
|
||||
version "15.0.0"
|
||||
@ -15283,7 +15288,7 @@ react-style-singleton@^2.2.1:
|
||||
|
||||
react-table@7.7.0:
|
||||
version "7.7.0"
|
||||
resolved "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz"
|
||||
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912"
|
||||
integrity sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==
|
||||
|
||||
react-tabs@3.2.3:
|
||||
@ -15320,14 +15325,12 @@ react-transition-group@^4.3.0:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react@16.14.0:
|
||||
version "16.14.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
|
||||
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
|
||||
react@18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
read-pkg-up@^7.0.1:
|
||||
version "7.0.1"
|
||||
@ -15882,13 +15885,12 @@ saxes@^6.0.0:
|
||||
dependencies:
|
||||
xmlchars "^2.2.0"
|
||||
|
||||
scheduler@^0.19.1:
|
||||
version "0.19.1"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"
|
||||
integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==
|
||||
scheduler@^0.23.0:
|
||||
version "0.23.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
|
||||
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
schema-utils@2.7.0:
|
||||
version "2.7.0"
|
||||
|
Loading…
Reference in New Issue
Block a user