mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Fleet UI: macOS dashboard MDM solutions (#7014)
Co-authored-by: gillespi314 <73313222+gillespi314@users.noreply.github.com>
This commit is contained in:
parent
8d4ad6ce9f
commit
18852aae66
1
changes/issue-6928-mdm-solutions
Normal file
1
changes/issue-6928-mdm-solutions
Normal file
@ -0,0 +1 @@
|
|||||||
|
* MacOS dashboard view includes MDM solutions table and filters for hosts by MDM
|
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
interface ITextCellProps {
|
interface ITextCellProps {
|
||||||
value: string | number | boolean;
|
value: string | number | boolean;
|
||||||
formatter?: (val: any) => string; // string, number, or null
|
formatter?: (val: any) => string; // string, number, or null
|
||||||
greyed?: string;
|
greyed?: boolean;
|
||||||
classes?: string;
|
classes?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const TextCell = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={`text-cell ${classes} ${greyed || ""}`}>
|
<span className={`text-cell ${classes} ${greyed && "grey-cell"}`}>
|
||||||
{formatter(val)}
|
{formatter(val)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -186,6 +186,10 @@
|
|||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
.grey-cell {
|
||||||
|
color: $ui-fleet-black-50;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.highlight-on-hover:hover {
|
.highlight-on-hover:hover {
|
||||||
background-color: $ui-off-white;
|
background-color: $ui-off-white;
|
||||||
|
@ -14,10 +14,18 @@ export interface IMDMAggregateStatus {
|
|||||||
unenrolled_hosts_count: number;
|
unenrolled_hosts_count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IMDMSolution {
|
||||||
|
id: number;
|
||||||
|
name: string | null;
|
||||||
|
server_url: string;
|
||||||
|
hosts_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IMacadminAggregate {
|
export interface IMacadminAggregate {
|
||||||
macadmins: {
|
macadmins: {
|
||||||
counts_updated_at: string;
|
counts_updated_at: string;
|
||||||
munki_versions: IMunkiAggregate[];
|
munki_versions: IMunkiAggregate[];
|
||||||
mobile_device_management_enrollment_status: IMDMAggregateStatus;
|
mobile_device_management_enrollment_status: IMDMAggregateStatus;
|
||||||
|
mobile_device_management_solution: IMDMSolution[] | null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,7 @@ const Homepage = (): JSX.Element => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const MDMCard = useInfoCard({
|
const MDMCard = useInfoCard({
|
||||||
title: "Mobile device management (MDM) enrollment",
|
title: "Mobile device management (MDM)",
|
||||||
showTitle: showMDMUI,
|
showTitle: showMDMUI,
|
||||||
description: (
|
description: (
|
||||||
<p>
|
<p>
|
||||||
|
@ -1,14 +1,24 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
|
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
|
||||||
|
|
||||||
import macadminsAPI from "services/entities/macadmins";
|
import macadminsAPI from "services/entities/macadmins";
|
||||||
import { IMacadminAggregate, IDataTableMDMFormat } from "interfaces/macadmins";
|
import {
|
||||||
|
IMacadminAggregate,
|
||||||
|
IDataTableMDMFormat,
|
||||||
|
IMDMSolution,
|
||||||
|
} from "interfaces/macadmins";
|
||||||
|
|
||||||
|
import TabsWrapper from "components/TabsWrapper";
|
||||||
import TableContainer from "components/TableContainer";
|
import TableContainer from "components/TableContainer";
|
||||||
import Spinner from "components/Spinner";
|
import Spinner from "components/Spinner";
|
||||||
import TableDataError from "components/DataError";
|
import TableDataError from "components/DataError";
|
||||||
import LastUpdatedText from "components/LastUpdatedText";
|
import LastUpdatedText from "components/LastUpdatedText";
|
||||||
import generateTableHeaders from "./MDMTableConfig";
|
import {
|
||||||
|
generateSolutionsTableHeaders,
|
||||||
|
generateSolutionsDataSet,
|
||||||
|
} from "./MDMSolutionsTableConfig";
|
||||||
|
import generateEnrollmentTableHeaders from "./MDMEnrollmentTableConfig";
|
||||||
|
|
||||||
interface IMDMCardProps {
|
interface IMDMCardProps {
|
||||||
showMDMUI: boolean;
|
showMDMUI: boolean;
|
||||||
@ -18,13 +28,14 @@ interface IMDMCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SORT_DIRECTION = "desc";
|
const DEFAULT_SORT_DIRECTION = "desc";
|
||||||
const DEFAULT_SORT_HEADER = "hosts_count";
|
const SOLUTIONS_DEFAULT_SORT_HEADER = "hosts_count";
|
||||||
|
const ENROLLMENT_DEFAULT_SORT_HEADER = "hosts";
|
||||||
const PAGE_SIZE = 8;
|
const PAGE_SIZE = 8;
|
||||||
const baseClass = "home-mdm";
|
const baseClass = "home-mdm";
|
||||||
|
|
||||||
const EmptyMDM = (): JSX.Element => (
|
const EmptyMDMEnrollment = (): JSX.Element => (
|
||||||
<div className={`${baseClass}__empty-mdm`}>
|
<div className={`${baseClass}__empty-mdm`}>
|
||||||
<h1>Unable to detect MDM enrollment.</h1>
|
<h1>Unable to detect MDM enrollment</h1>
|
||||||
<p>
|
<p>
|
||||||
To see MDM versions, deploy
|
To see MDM versions, deploy
|
||||||
<a
|
<a
|
||||||
@ -39,15 +50,27 @@ const EmptyMDM = (): JSX.Element => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const EmptyMDMSolutions = (): JSX.Element => (
|
||||||
|
<div className={`${baseClass}__empty-mdm`}>
|
||||||
|
<h1>No MDM solutions detected</h1>
|
||||||
|
<p>
|
||||||
|
This report is updated every hour to protect the performance of your
|
||||||
|
devices.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const MDM = ({
|
const MDM = ({
|
||||||
showMDMUI,
|
showMDMUI,
|
||||||
currentTeamId,
|
currentTeamId,
|
||||||
setShowMDMUI,
|
setShowMDMUI,
|
||||||
setTitleDetail,
|
setTitleDetail,
|
||||||
}: IMDMCardProps): JSX.Element => {
|
}: IMDMCardProps): JSX.Element => {
|
||||||
|
const [navTabIndex, setNavTabIndex] = useState<number>(0);
|
||||||
const [formattedMDMData, setFormattedMDMData] = useState<
|
const [formattedMDMData, setFormattedMDMData] = useState<
|
||||||
IDataTableMDMFormat[]
|
IDataTableMDMFormat[]
|
||||||
>([]);
|
>([]);
|
||||||
|
const [solutions, setSolutions] = useState<IMDMSolution[] | null>([]);
|
||||||
|
|
||||||
const { isFetching: isMDMFetching, error: errorMDM } = useQuery<
|
const { isFetching: isMDMFetching, error: errorMDM } = useQuery<
|
||||||
IMacadminAggregate,
|
IMacadminAggregate,
|
||||||
@ -58,6 +81,7 @@ const MDM = ({
|
|||||||
const {
|
const {
|
||||||
counts_updated_at,
|
counts_updated_at,
|
||||||
mobile_device_management_enrollment_status,
|
mobile_device_management_enrollment_status,
|
||||||
|
mobile_device_management_solution,
|
||||||
} = data.macadmins;
|
} = data.macadmins;
|
||||||
const {
|
const {
|
||||||
enrolled_manual_hosts_count,
|
enrolled_manual_hosts_count,
|
||||||
@ -84,13 +108,20 @@ const MDM = ({
|
|||||||
},
|
},
|
||||||
{ status: "Unenrolled", hosts: unenrolled_hosts_count },
|
{ status: "Unenrolled", hosts: unenrolled_hosts_count },
|
||||||
]);
|
]);
|
||||||
|
setSolutions(mobile_device_management_solution);
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
setShowMDMUI(true);
|
setShowMDMUI(true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableHeaders = generateTableHeaders();
|
const onTabChange = (index: number) => {
|
||||||
|
setNavTabIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const solutionsTableHeaders = generateSolutionsTableHeaders();
|
||||||
|
const enrollmentTableHeaders = generateEnrollmentTableHeaders();
|
||||||
|
const solutionsDataSet = generateSolutionsDataSet(solutions);
|
||||||
|
|
||||||
// Renders opaque information as host information is loading
|
// Renders opaque information as host information is loading
|
||||||
const opacity = showMDMUI ? { opacity: 1 } : { opacity: 0 };
|
const opacity = showMDMUI ? { opacity: 1 } : { opacity: 0 };
|
||||||
@ -103,26 +134,58 @@ const MDM = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div style={opacity}>
|
<div style={opacity}>
|
||||||
{errorMDM ? (
|
<TabsWrapper>
|
||||||
<TableDataError card />
|
<Tabs selectedIndex={navTabIndex} onSelect={onTabChange}>
|
||||||
) : (
|
<TabList>
|
||||||
<TableContainer
|
<Tab>Solutions</Tab>
|
||||||
columns={tableHeaders}
|
<Tab>Enrollment</Tab>
|
||||||
data={formattedMDMData}
|
</TabList>
|
||||||
isLoading={isMDMFetching}
|
<TabPanel>
|
||||||
defaultSortHeader={DEFAULT_SORT_HEADER}
|
{errorMDM ? (
|
||||||
defaultSortDirection={DEFAULT_SORT_DIRECTION}
|
<TableDataError card />
|
||||||
hideActionButton
|
) : (
|
||||||
resultsTitle={"MDM"}
|
<TableContainer
|
||||||
emptyComponent={EmptyMDM}
|
columns={solutionsTableHeaders}
|
||||||
showMarkAllPages={false}
|
data={solutionsDataSet}
|
||||||
isAllPagesSelected={false}
|
isLoading={isMDMFetching}
|
||||||
disableCount
|
defaultSortHeader={SOLUTIONS_DEFAULT_SORT_HEADER}
|
||||||
disableActionButton
|
defaultSortDirection={DEFAULT_SORT_DIRECTION}
|
||||||
disablePagination
|
hideActionButton
|
||||||
pageSize={PAGE_SIZE}
|
resultsTitle={"MDM"}
|
||||||
/>
|
emptyComponent={EmptyMDMSolutions}
|
||||||
)}
|
showMarkAllPages={false}
|
||||||
|
isAllPagesSelected={false}
|
||||||
|
isClientSidePagination
|
||||||
|
disableCount
|
||||||
|
disableActionButton
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
{errorMDM ? (
|
||||||
|
<TableDataError card />
|
||||||
|
) : (
|
||||||
|
<TableContainer
|
||||||
|
columns={enrollmentTableHeaders}
|
||||||
|
data={formattedMDMData}
|
||||||
|
isLoading={isMDMFetching}
|
||||||
|
defaultSortHeader={ENROLLMENT_DEFAULT_SORT_HEADER}
|
||||||
|
defaultSortDirection={DEFAULT_SORT_DIRECTION}
|
||||||
|
hideActionButton
|
||||||
|
resultsTitle={"MDM"}
|
||||||
|
emptyComponent={EmptyMDMEnrollment}
|
||||||
|
showMarkAllPages={false}
|
||||||
|
isAllPagesSelected={false}
|
||||||
|
disableCount
|
||||||
|
disableActionButton
|
||||||
|
disablePagination
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
|
</Tabs>
|
||||||
|
</TabsWrapper>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
125
frontend/pages/Homepage/cards/MDM/MDMEnrollmentTableConfig.tsx
Normal file
125
frontend/pages/Homepage/cards/MDM/MDMEnrollmentTableConfig.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router";
|
||||||
|
|
||||||
|
import { IDataTableMDMFormat } from "interfaces/macadmins";
|
||||||
|
|
||||||
|
import PATHS from "router/paths";
|
||||||
|
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||||
|
import TooltipWrapper from "components/TooltipWrapper";
|
||||||
|
import Chevron from "../../../../../assets/images/icon-chevron-right-9x6@2x.png";
|
||||||
|
|
||||||
|
// NOTE: cellProps come from react-table
|
||||||
|
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||||
|
interface ICellProps {
|
||||||
|
cell: {
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
row: {
|
||||||
|
original: IDataTableMDMFormat;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IHeaderProps {
|
||||||
|
column: {
|
||||||
|
title: string;
|
||||||
|
isSortedDesc: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IStringCellProps extends ICellProps {
|
||||||
|
cell: {
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDataColumn {
|
||||||
|
title: string;
|
||||||
|
Header: ((props: IHeaderProps) => JSX.Element) | string;
|
||||||
|
accessor: string;
|
||||||
|
Cell: (props: ICellProps) => JSX.Element;
|
||||||
|
disableHidden?: boolean;
|
||||||
|
disableSortBy?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enrollmentTableHeaders = [
|
||||||
|
{
|
||||||
|
title: "Status",
|
||||||
|
Header: "Status",
|
||||||
|
disableSortBy: true,
|
||||||
|
accessor: "status",
|
||||||
|
Cell: (cellProps: IStringCellProps) => {
|
||||||
|
const tooltipText = (status: string): string => {
|
||||||
|
if (status === "Enrolled (automatic)") {
|
||||||
|
return `
|
||||||
|
<span>
|
||||||
|
Hosts automatically enrolled to an MDM solution <br/>
|
||||||
|
the first time the host is used. Administrators <br />
|
||||||
|
might have a higher level of control over these <br />
|
||||||
|
hosts.
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<span>
|
||||||
|
Hosts manually enrolled to an MDM solution by a<br />
|
||||||
|
user or administrator.
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cellProps.cell.value === "Unenrolled") {
|
||||||
|
return <TextCell value={cellProps.cell.value} />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span className="name-container">
|
||||||
|
<TooltipWrapper tipContent={tooltipText(cellProps.cell.value)}>
|
||||||
|
{cellProps.cell.value}
|
||||||
|
</TooltipWrapper>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
sortType: "caseInsensitive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Hosts",
|
||||||
|
Header: "Hosts",
|
||||||
|
disableSortBy: true,
|
||||||
|
accessor: "hosts",
|
||||||
|
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
Header: "",
|
||||||
|
disableSortBy: true,
|
||||||
|
disableGlobalFilter: true,
|
||||||
|
accessor: "linkToFilteredHosts",
|
||||||
|
Cell: (cellProps: IStringCellProps) => {
|
||||||
|
const statusParam = () => {
|
||||||
|
switch (cellProps.row.original.status) {
|
||||||
|
case "Enrolled (automatic)":
|
||||||
|
return "automatic";
|
||||||
|
case "Enrolled (manual)":
|
||||||
|
return "manual";
|
||||||
|
default:
|
||||||
|
return "unenrolled";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`${PATHS.MANAGE_HOSTS}?mdm_enrollment_status=${statusParam()}`}
|
||||||
|
className={`mdm-solution-link`}
|
||||||
|
>
|
||||||
|
View all hosts{" "}
|
||||||
|
<img alt="link to hosts filtered by MDM solution" src={Chevron} />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
disableHidden: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const generateEnrollmentTableHeaders = (): IDataColumn[] => {
|
||||||
|
return enrollmentTableHeaders;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default generateEnrollmentTableHeaders;
|
121
frontend/pages/Homepage/cards/MDM/MDMSolutionsTableConfig.tsx
Normal file
121
frontend/pages/Homepage/cards/MDM/MDMSolutionsTableConfig.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router";
|
||||||
|
|
||||||
|
import { IMDMSolution } from "interfaces/macadmins";
|
||||||
|
|
||||||
|
import PATHS from "router/paths";
|
||||||
|
import { greyCell } from "utilities/helpers";
|
||||||
|
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||||
|
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||||
|
import Chevron from "../../../../../assets/images/icon-chevron-right-9x6@2x.png";
|
||||||
|
|
||||||
|
// NOTE: cellProps come from react-table
|
||||||
|
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
||||||
|
interface ICellProps {
|
||||||
|
cell: {
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
row: {
|
||||||
|
original: IMDMSolution;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IHeaderProps {
|
||||||
|
column: {
|
||||||
|
title: string;
|
||||||
|
isSortedDesc: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IStringCellProps extends ICellProps {
|
||||||
|
cell: {
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDataColumn {
|
||||||
|
title: string;
|
||||||
|
Header: ((props: IHeaderProps) => JSX.Element) | string;
|
||||||
|
accessor: string;
|
||||||
|
Cell: (props: ICellProps) => JSX.Element;
|
||||||
|
disableHidden?: boolean;
|
||||||
|
disableSortBy?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const solutionsTableHeaders = [
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
Header: "Name",
|
||||||
|
disableSortBy: true,
|
||||||
|
accessor: "name",
|
||||||
|
Cell: (cellProps: ICellProps) => (
|
||||||
|
<TextCell
|
||||||
|
greyed={greyCell(cellProps.cell.value)}
|
||||||
|
value={cellProps.cell.value}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Server URL",
|
||||||
|
Header: "Server URL",
|
||||||
|
disableSortBy: true,
|
||||||
|
accessor: "server_url",
|
||||||
|
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Hosts",
|
||||||
|
Header: (cellProps: IHeaderProps) => (
|
||||||
|
<HeaderCell
|
||||||
|
value={cellProps.column.title}
|
||||||
|
isSortedDesc={cellProps.column.isSortedDesc}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
accessor: "hosts_count",
|
||||||
|
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
Header: "",
|
||||||
|
disableSortBy: true,
|
||||||
|
disableGlobalFilter: true,
|
||||||
|
accessor: "linkToFilteredHosts",
|
||||||
|
Cell: (cellProps: IStringCellProps) => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`${PATHS.MANAGE_HOSTS}?mdm_id=${cellProps.row.original.id}`}
|
||||||
|
className={`mdm-solution-link`}
|
||||||
|
>
|
||||||
|
View all hosts{" "}
|
||||||
|
<img alt="link to hosts filtered by MDM solution" src={Chevron} />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
disableHidden: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const generateSolutionsTableHeaders = (): IDataColumn[] => {
|
||||||
|
return solutionsTableHeaders;
|
||||||
|
};
|
||||||
|
|
||||||
|
const enhanceSolutionsData = (solutions: IMDMSolution[]): IMDMSolution[] => {
|
||||||
|
return Object.values(solutions).map((solution) => {
|
||||||
|
return {
|
||||||
|
id: solution.id,
|
||||||
|
name: solution.name || "Unknown",
|
||||||
|
server_url: solution.server_url,
|
||||||
|
hosts_count: solution.hosts_count,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateSolutionsDataSet = (
|
||||||
|
solutions: IMDMSolution[] | null
|
||||||
|
): IMDMSolution[] => {
|
||||||
|
if (!solutions) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [...enhanceSolutionsData(solutions)];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { generateSolutionsTableHeaders, generateSolutionsDataSet };
|
@ -1,54 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import { IDataTableMDMFormat } from "interfaces/macadmins";
|
|
||||||
|
|
||||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
|
||||||
|
|
||||||
// NOTE: cellProps come from react-table
|
|
||||||
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
|
|
||||||
interface ICellProps {
|
|
||||||
cell: {
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
row: {
|
|
||||||
original: IDataTableMDMFormat;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IHeaderProps {
|
|
||||||
column: {
|
|
||||||
title: string;
|
|
||||||
isSortedDesc: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IDataColumn {
|
|
||||||
title: string;
|
|
||||||
Header: ((props: IHeaderProps) => JSX.Element) | string;
|
|
||||||
accessor: string;
|
|
||||||
Cell: (props: ICellProps) => JSX.Element;
|
|
||||||
disableHidden?: boolean;
|
|
||||||
disableSortBy?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const munkiTableHeaders = [
|
|
||||||
{
|
|
||||||
title: "Status",
|
|
||||||
Header: "Status",
|
|
||||||
disableSortBy: true,
|
|
||||||
accessor: "status",
|
|
||||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Hosts",
|
|
||||||
Header: "Hosts",
|
|
||||||
accessor: "hosts",
|
|
||||||
Cell: (cellProps: ICellProps) => <TextCell value={cellProps.cell.value} />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const generateTableHeaders = (): IDataColumn[] => {
|
|
||||||
return munkiTableHeaders;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default generateTableHeaders;
|
|
@ -2,14 +2,12 @@
|
|||||||
margin-top: $pad-large;
|
margin-top: $pad-large;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.data-table__wrapper {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
.component__tabs-wrapper .table-container__header {
|
.component__tabs-wrapper .table-container__header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
&__empty-mdm {
|
&__empty-mdm {
|
||||||
margin: $pad-medium auto 0;
|
width: 364px;
|
||||||
|
margin: $pad-large auto 0;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: $small;
|
font-size: $small;
|
||||||
@ -36,39 +34,44 @@
|
|||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
.name__header {
|
.name__header,
|
||||||
width: 60%;
|
.status__header {
|
||||||
}
|
|
||||||
.version__header {
|
|
||||||
width: 30%;
|
width: 30%;
|
||||||
padding-right: 0;
|
|
||||||
}
|
}
|
||||||
.hosts_count__header {
|
.server_url__header {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
.hosts_count__header,
|
||||||
|
.hosts__header {
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
.id__header {
|
.linkToFilteredHosts__header {
|
||||||
padding: 0;
|
width: 140px;
|
||||||
border-left: 0;
|
|
||||||
width: 40px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody {
|
tbody {
|
||||||
.name__cell,
|
.name__cell {
|
||||||
.version__cell {
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.id__cell {
|
.mdm-solution-link {
|
||||||
padding: 0;
|
visibility: hidden;
|
||||||
width: 40px;
|
text-overflow: none;
|
||||||
}
|
|
||||||
.vulnerabilities__cell {
|
|
||||||
img {
|
img {
|
||||||
transform: scale(0.5);
|
vertical-align: text-top;
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
&:hover {
|
||||||
|
.mdm-solution-link {
|
||||||
|
visibility: visible;
|
||||||
|
text-overflow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,6 @@
|
|||||||
min-width: 542px;
|
min-width: 542px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grey-cell {
|
|
||||||
color: $ui-fleet-black-50;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal__modal_container {
|
.modal__modal_container {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
a {
|
a {
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
import { IApiError } from "interfaces/errors";
|
import { IApiError } from "interfaces/errors";
|
||||||
import { IHost } from "interfaces/host";
|
import { IHost } from "interfaces/host";
|
||||||
import { ILabel, ILabelFormData } from "interfaces/label";
|
import { ILabel, ILabelFormData } from "interfaces/label";
|
||||||
|
import { IMDMSolution } from "interfaces/macadmins";
|
||||||
import { IOperatingSystemVersion } from "interfaces/operating_system";
|
import { IOperatingSystemVersion } from "interfaces/operating_system";
|
||||||
import { IPolicy } from "interfaces/policy";
|
import { IPolicy } from "interfaces/policy";
|
||||||
import { ISoftware } from "interfaces/software";
|
import { ISoftware } from "interfaces/software";
|
||||||
@ -248,6 +249,10 @@ const ManageHostsPage = ({
|
|||||||
const [softwareDetails, setSoftwareDetails] = useState<ISoftware | null>(
|
const [softwareDetails, setSoftwareDetails] = useState<ISoftware | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
const [
|
||||||
|
mdmSolutionDetails,
|
||||||
|
setMDMSolutionDetails,
|
||||||
|
] = useState<IMDMSolution | null>(null);
|
||||||
const [tableQueryData, setTableQueryData] = useState<ITableQueryProps>();
|
const [tableQueryData, setTableQueryData] = useState<ITableQueryProps>();
|
||||||
const [
|
const [
|
||||||
currentQueryOptions,
|
currentQueryOptions,
|
||||||
@ -268,6 +273,11 @@ const ManageHostsPage = ({
|
|||||||
queryParams?.software_id !== undefined
|
queryParams?.software_id !== undefined
|
||||||
? parseInt(queryParams?.software_id, 10)
|
? parseInt(queryParams?.software_id, 10)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const mdmId =
|
||||||
|
queryParams?.mdm_id !== undefined
|
||||||
|
? parseInt(queryParams?.mdm_id, 10)
|
||||||
|
: undefined;
|
||||||
|
const mdmEnrollmentStatus = queryParams?.mdm_enrollment_status;
|
||||||
const operatingSystemId =
|
const operatingSystemId =
|
||||||
queryParams?.operating_system_id !== undefined
|
queryParams?.operating_system_id !== undefined
|
||||||
? parseInt(queryParams?.operating_system_id, 10)
|
? parseInt(queryParams?.operating_system_id, 10)
|
||||||
@ -445,11 +455,15 @@ const ManageHostsPage = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { hosts: returnedHosts, software } = await hostsAPI.loadHosts(
|
const {
|
||||||
options
|
hosts: returnedHosts,
|
||||||
);
|
software,
|
||||||
|
mobile_device_management_solution,
|
||||||
|
} = await hostsAPI.loadHosts(options);
|
||||||
setHosts(returnedHosts);
|
setHosts(returnedHosts);
|
||||||
software && setSoftwareDetails(software);
|
software && setSoftwareDetails(software);
|
||||||
|
mobile_device_management_solution &&
|
||||||
|
setMDMSolutionDetails(mobile_device_management_solution);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setHasHostErrors(true);
|
setHasHostErrors(true);
|
||||||
@ -534,6 +548,8 @@ const ManageHostsPage = ({
|
|||||||
policyId,
|
policyId,
|
||||||
policyResponse,
|
policyResponse,
|
||||||
softwareId,
|
softwareId,
|
||||||
|
mdmId,
|
||||||
|
mdmEnrollmentStatus,
|
||||||
operatingSystemId,
|
operatingSystemId,
|
||||||
page: tableQueryData ? tableQueryData.pageIndex : 0,
|
page: tableQueryData ? tableQueryData.pageIndex : 0,
|
||||||
perPage: tableQueryData ? tableQueryData.pageSize : 100,
|
perPage: tableQueryData ? tableQueryData.pageSize : 100,
|
||||||
@ -641,10 +657,18 @@ const ManageHostsPage = ({
|
|||||||
|
|
||||||
const handleClearSoftwareFilter = () => {
|
const handleClearSoftwareFilter = () => {
|
||||||
router.replace(PATHS.MANAGE_HOSTS);
|
router.replace(PATHS.MANAGE_HOSTS);
|
||||||
setCurrentTeam(undefined);
|
|
||||||
setSoftwareDetails(null);
|
setSoftwareDetails(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClearMDMSolutionFilter = () => {
|
||||||
|
router.replace(PATHS.MANAGE_HOSTS);
|
||||||
|
setMDMSolutionDetails(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearMDMEnrollmentFilter = () => {
|
||||||
|
router.replace(PATHS.MANAGE_HOSTS);
|
||||||
|
};
|
||||||
|
|
||||||
const handleTeamSelect = (teamId: number) => {
|
const handleTeamSelect = (teamId: number) => {
|
||||||
const { MANAGE_HOSTS } = PATHS;
|
const { MANAGE_HOSTS } = PATHS;
|
||||||
const teamIdParam = getValidatedTeamId(
|
const teamIdParam = getValidatedTeamId(
|
||||||
@ -769,10 +793,25 @@ const ManageHostsPage = ({
|
|||||||
newQueryParams.policy_response = policyResponse;
|
newQueryParams.policy_response = policyResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (softwareId && !policyId) {
|
if (softwareId && !policyId && !mdmId && !mdmEnrollmentStatus) {
|
||||||
newQueryParams.software_id = softwareId;
|
newQueryParams.software_id = softwareId;
|
||||||
}
|
}
|
||||||
if (operatingSystemId && !softwareId && !policyId) {
|
|
||||||
|
if (mdmId && !policyId && !softwareId && !mdmEnrollmentStatus) {
|
||||||
|
newQueryParams.mdm_id = mdmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mdmEnrollmentStatus && !policyId && !softwareId && !mdmId) {
|
||||||
|
newQueryParams.mdm_enrollment_status = mdmEnrollmentStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
operatingSystemId &&
|
||||||
|
!softwareId &&
|
||||||
|
!policyId &&
|
||||||
|
!mdmEnrollmentStatus &&
|
||||||
|
!mdmId
|
||||||
|
) {
|
||||||
newQueryParams.operating_system_id = operatingSystemId;
|
newQueryParams.operating_system_id = operatingSystemId;
|
||||||
}
|
}
|
||||||
router.replace(
|
router.replace(
|
||||||
@ -791,6 +830,8 @@ const ManageHostsPage = ({
|
|||||||
policyId,
|
policyId,
|
||||||
queryParams,
|
queryParams,
|
||||||
softwareId,
|
softwareId,
|
||||||
|
mdmId,
|
||||||
|
mdmEnrollmentStatus,
|
||||||
operatingSystemId,
|
operatingSystemId,
|
||||||
sortBy,
|
sortBy,
|
||||||
]
|
]
|
||||||
@ -1029,6 +1070,8 @@ const ManageHostsPage = ({
|
|||||||
policyId,
|
policyId,
|
||||||
policyResponse,
|
policyResponse,
|
||||||
softwareId,
|
softwareId,
|
||||||
|
mdmId,
|
||||||
|
mdmEnrollmentStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
toggleTransferHostModal();
|
toggleTransferHostModal();
|
||||||
@ -1074,6 +1117,8 @@ const ManageHostsPage = ({
|
|||||||
policyId,
|
policyId,
|
||||||
policyResponse,
|
policyResponse,
|
||||||
softwareId,
|
softwareId,
|
||||||
|
mdmId,
|
||||||
|
mdmEnrollmentStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
refetchLabels();
|
refetchLabels();
|
||||||
@ -1188,12 +1233,12 @@ const ManageHostsPage = ({
|
|||||||
>
|
>
|
||||||
{buttonText}
|
{buttonText}
|
||||||
<Button
|
<Button
|
||||||
className={`${baseClass}__clear-policies-filter`}
|
className={`${baseClass}__clear-software-filter`}
|
||||||
onClick={handleClearSoftwareFilter}
|
onClick={handleClearSoftwareFilter}
|
||||||
variant={"small-text-icon"}
|
variant={"small-text-icon"}
|
||||||
title={buttonText}
|
title={buttonText}
|
||||||
>
|
>
|
||||||
<img src={CloseIcon} alt="Remove policy filter" />
|
<img src={CloseIcon} alt="Remove software filter" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
@ -1216,6 +1261,126 @@ const ManageHostsPage = ({
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderMDMSolutionFilterBlock = () => {
|
||||||
|
if (mdmSolutionDetails) {
|
||||||
|
const { name, server_url } = mdmSolutionDetails;
|
||||||
|
const buttonText = `${name !== "Unknown" && name} ${server_url}`;
|
||||||
|
return (
|
||||||
|
<div className={`${baseClass}__mdm-solution-filter-block`}>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
data-tip
|
||||||
|
data-for="mdm-solution-filter-tooltip"
|
||||||
|
data-tip-disable={!name || !server_url}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${baseClass}__mdm-solution-filter-name-card tooltip`}
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
<Button
|
||||||
|
className={`${baseClass}__clear-mdm-solution-filter`}
|
||||||
|
onClick={handleClearMDMSolutionFilter}
|
||||||
|
variant={"small-text-icon"}
|
||||||
|
title={buttonText}
|
||||||
|
>
|
||||||
|
<img src={CloseIcon} alt="Remove MDM solution filter" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<ReactTooltip
|
||||||
|
place="bottom"
|
||||||
|
effect="solid"
|
||||||
|
backgroundColor="#3e4771"
|
||||||
|
id="mdm-solution-filter-tooltip"
|
||||||
|
data-html
|
||||||
|
>
|
||||||
|
<span className={`tooltip__tooltip-text`}>
|
||||||
|
Host enrolled
|
||||||
|
{name !== "Unknown" && ` to ${name}`}
|
||||||
|
<br /> at {server_url}
|
||||||
|
</span>
|
||||||
|
</ReactTooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderMDMEnrollmentFilterBlock = () => {
|
||||||
|
if (mdmEnrollmentStatus) {
|
||||||
|
const buttonText = () => {
|
||||||
|
switch (mdmEnrollmentStatus) {
|
||||||
|
case "automatic":
|
||||||
|
return "MDM enrolled (automatic)";
|
||||||
|
case "manual":
|
||||||
|
return "MDM enrolled (manual)";
|
||||||
|
default:
|
||||||
|
return "Unenrolled";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const tooltipText = () => {
|
||||||
|
switch (mdmEnrollmentStatus) {
|
||||||
|
case "automatic":
|
||||||
|
return (
|
||||||
|
<span className={`tooltip__tooltip-text`}>
|
||||||
|
Hosts automatically enrolled <br />
|
||||||
|
to an MDM solution the first time <br />
|
||||||
|
the host is used. Administrators <br />
|
||||||
|
might have a higher level of control <br />
|
||||||
|
over these hosts.
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case "manual":
|
||||||
|
return (
|
||||||
|
<span className={`tooltip__tooltip-text`}>
|
||||||
|
Hosts manually enrolled to an <br />
|
||||||
|
MDM solution by a user or <br />
|
||||||
|
administrator.
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<span className={`tooltip__tooltip-text`}>
|
||||||
|
Hosts not enrolled to <br /> an MDM solution.
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={`${baseClass}__mdm-enrollment-status-filter-block`}>
|
||||||
|
<div>
|
||||||
|
<span data-tip data-for="mdm-enrollment-status-filter-tooltip">
|
||||||
|
<div
|
||||||
|
className={`${baseClass}__mdm-enrollment-status-filter-name-card tooltip`}
|
||||||
|
>
|
||||||
|
{buttonText()}
|
||||||
|
<Button
|
||||||
|
className={`${baseClass}__clear-mdm-enrollment-status-filter`}
|
||||||
|
onClick={handleClearMDMEnrollmentFilter}
|
||||||
|
variant={"small-text-icon"}
|
||||||
|
title={buttonText()}
|
||||||
|
>
|
||||||
|
<img src={CloseIcon} alt="Remove MDM enrollment filter" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<ReactTooltip
|
||||||
|
place="bottom"
|
||||||
|
effect="solid"
|
||||||
|
backgroundColor="#3e4771"
|
||||||
|
id="mdm-enrollment-status-filter-tooltip"
|
||||||
|
data-html
|
||||||
|
>
|
||||||
|
{tooltipText()}
|
||||||
|
</ReactTooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const renderEditColumnsModal = () => {
|
const renderEditColumnsModal = () => {
|
||||||
if (!config || !currentUser) {
|
if (!config || !currentUser) {
|
||||||
return null;
|
return null;
|
||||||
@ -1404,6 +1569,8 @@ const ManageHostsPage = ({
|
|||||||
policyId,
|
policyId,
|
||||||
policyResponse,
|
policyResponse,
|
||||||
softwareId,
|
softwareId,
|
||||||
|
mdmId,
|
||||||
|
mdmEnrollmentStatus,
|
||||||
visibleColumns,
|
visibleColumns,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1471,22 +1638,47 @@ const ManageHostsPage = ({
|
|||||||
selectedLabel &&
|
selectedLabel &&
|
||||||
selectedLabel.type !== "all" &&
|
selectedLabel.type !== "all" &&
|
||||||
selectedLabel.type !== "status";
|
selectedLabel.type !== "status";
|
||||||
if (policyId || softwareId || showSelectedLabel || operatingSystemId) {
|
if (
|
||||||
|
policyId ||
|
||||||
|
softwareId ||
|
||||||
|
showSelectedLabel ||
|
||||||
|
mdmId ||
|
||||||
|
mdmEnrollmentStatus ||
|
||||||
|
operatingSystemId
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<div className={`${baseClass}__labels-active-filter-wrap`}>
|
<div className={`${baseClass}__labels-active-filter-wrap`}>
|
||||||
{showSelectedLabel && renderHeaderLabelBlock()}
|
{showSelectedLabel && renderHeaderLabelBlock()}
|
||||||
{!!policyId &&
|
{!!policyId &&
|
||||||
!softwareId &&
|
!softwareId &&
|
||||||
|
!mdmId &&
|
||||||
|
!mdmEnrollmentStatus &&
|
||||||
!showSelectedLabel &&
|
!showSelectedLabel &&
|
||||||
renderPoliciesFilterBlock()}
|
renderPoliciesFilterBlock()}
|
||||||
{!!softwareId &&
|
{!!softwareId &&
|
||||||
!policyId &&
|
!policyId &&
|
||||||
|
!mdmId &&
|
||||||
|
!mdmEnrollmentStatus &&
|
||||||
!showSelectedLabel &&
|
!showSelectedLabel &&
|
||||||
renderSoftwareFilterBlock()}
|
renderSoftwareFilterBlock()}
|
||||||
|
{!!mdmId &&
|
||||||
|
!policyId &&
|
||||||
|
!softwareId &&
|
||||||
|
!mdmEnrollmentStatus &&
|
||||||
|
!showSelectedLabel &&
|
||||||
|
renderMDMSolutionFilterBlock()}
|
||||||
|
{!!mdmEnrollmentStatus &&
|
||||||
|
!policyId &&
|
||||||
|
!softwareId &&
|
||||||
|
!mdmId &&
|
||||||
|
!showSelectedLabel &&
|
||||||
|
renderMDMEnrollmentFilterBlock()}
|
||||||
{!!operatingSystemId &&
|
{!!operatingSystemId &&
|
||||||
!policyId &&
|
!policyId &&
|
||||||
!softwareId &&
|
!softwareId &&
|
||||||
!showSelectedLabel &&
|
!showSelectedLabel &&
|
||||||
|
!mdmId &&
|
||||||
|
!mdmEnrollmentStatus &&
|
||||||
renderOSFilterBlock()}
|
renderOSFilterBlock()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -1587,10 +1779,18 @@ const ManageHostsPage = ({
|
|||||||
!isHostsLoading &&
|
!isHostsLoading &&
|
||||||
teamSync
|
teamSync
|
||||||
) {
|
) {
|
||||||
const { software_id, policy_id, operating_system_id } = queryParams || {};
|
const {
|
||||||
const includesSoftwareOrPolicyOrOSFilter = !!(
|
software_id,
|
||||||
|
policy_id,
|
||||||
|
mdm_id,
|
||||||
|
mdm_enrollment_status,
|
||||||
|
operating_system_id,
|
||||||
|
} = queryParams || {};
|
||||||
|
const includesNameCardFilter = !!(
|
||||||
software_id ||
|
software_id ||
|
||||||
policy_id ||
|
policy_id ||
|
||||||
|
mdm_id ||
|
||||||
|
mdm_enrollment_status ||
|
||||||
operating_system_id
|
operating_system_id
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1598,7 +1798,7 @@ const ManageHostsPage = ({
|
|||||||
<NoHosts
|
<NoHosts
|
||||||
toggleAddHostsModal={toggleAddHostsModal}
|
toggleAddHostsModal={toggleAddHostsModal}
|
||||||
canEnrollHosts={canEnrollHosts}
|
canEnrollHosts={canEnrollHosts}
|
||||||
includesSoftwareOrPolicyFilter={includesSoftwareOrPolicyOrOSFilter}
|
includesNameCardFilter={includesNameCardFilter}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -199,6 +199,7 @@
|
|||||||
&__policies-filter-block {
|
&__policies-filter-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: $pad-medium;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: $xx-small;
|
font-size: $xx-small;
|
||||||
@ -208,14 +209,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__policies-filter-name-card,
|
&__policies-filter-name-card,
|
||||||
&__software-filter-name-card {
|
&__software-filter-name-card,
|
||||||
|
&__mdm-solution-filter-name-card,
|
||||||
|
&__mdm-enrollment-status-filter-name-card {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
border: 1px solid $ui-fleet-black-25;
|
border: 1px solid $ui-fleet-black-25;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
margin-left: $pad-medium;
|
|
||||||
color: $core-fleet-black;
|
color: $core-fleet-black;
|
||||||
font-size: $xx-small;
|
font-size: $xx-small;
|
||||||
font-weight: $bold;
|
font-weight: $bold;
|
||||||
@ -240,10 +242,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__software-filter-name-card {
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__enroll-hosts {
|
&__enroll-hosts {
|
||||||
padding: $pad-small;
|
padding: $pad-small;
|
||||||
margin-right: $pad-small;
|
margin-right: $pad-small;
|
||||||
|
@ -9,7 +9,7 @@ import RoboDogImage from "../../../../../../assets/images/robo-dog-176x144@2x.pn
|
|||||||
interface INoHostsProps {
|
interface INoHostsProps {
|
||||||
toggleAddHostsModal: () => void;
|
toggleAddHostsModal: () => void;
|
||||||
canEnrollHosts?: boolean;
|
canEnrollHosts?: boolean;
|
||||||
includesSoftwareOrPolicyFilter?: boolean;
|
includesNameCardFilter?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseClass = "no-hosts";
|
const baseClass = "no-hosts";
|
||||||
@ -17,10 +17,10 @@ const baseClass = "no-hosts";
|
|||||||
const NoHosts = ({
|
const NoHosts = ({
|
||||||
toggleAddHostsModal,
|
toggleAddHostsModal,
|
||||||
canEnrollHosts,
|
canEnrollHosts,
|
||||||
includesSoftwareOrPolicyFilter,
|
includesNameCardFilter,
|
||||||
}: INoHostsProps): JSX.Element => {
|
}: INoHostsProps): JSX.Element => {
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (includesSoftwareOrPolicyFilter) {
|
if (includesNameCardFilter) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>No hosts match the current criteria</h1>
|
<h1>No hosts match the current criteria</h1>
|
||||||
@ -64,9 +64,7 @@ const NoHosts = ({
|
|||||||
return (
|
return (
|
||||||
<div className={`${baseClass}`}>
|
<div className={`${baseClass}`}>
|
||||||
<div className={`${baseClass}__inner`}>
|
<div className={`${baseClass}__inner`}>
|
||||||
{!includesSoftwareOrPolicyFilter && (
|
{!includesNameCardFilter && <img src={RoboDogImage} alt="No Hosts" />}
|
||||||
<img src={RoboDogImage} alt="No Hosts" />
|
|
||||||
)}
|
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,6 +17,8 @@ export interface IHostCountLoadOptions {
|
|||||||
policyId?: number;
|
policyId?: number;
|
||||||
policyResponse?: string;
|
policyResponse?: string;
|
||||||
softwareId?: number;
|
softwareId?: number;
|
||||||
|
mdmId?: number;
|
||||||
|
mdmEnrollmentStatus?: string;
|
||||||
operatingSystemId?: number;
|
operatingSystemId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +32,8 @@ export default {
|
|||||||
const policyResponse = options?.policyResponse || null;
|
const policyResponse = options?.policyResponse || null;
|
||||||
const selectedLabels = options?.selectedLabels || [];
|
const selectedLabels = options?.selectedLabels || [];
|
||||||
const softwareId = options?.softwareId || null;
|
const softwareId = options?.softwareId || null;
|
||||||
|
const mdmId = options?.mdmId || null;
|
||||||
|
const mdmEnrollmentStatus = options?.mdmEnrollmentStatus || null;
|
||||||
const operatingSystemId = options?.operatingSystemId || null;
|
const operatingSystemId = options?.operatingSystemId || null;
|
||||||
|
|
||||||
const labelPrefix = "labels/";
|
const labelPrefix = "labels/";
|
||||||
@ -70,7 +74,22 @@ export default {
|
|||||||
queryString += `&software_id=${softwareId}`;
|
queryString += `&software_id=${softwareId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!label && !policyId && !softwareId && operatingSystemId) {
|
if (!label && !policyId && mdmId) {
|
||||||
|
queryString += `&mdm_id=${mdmId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!label && !policyId && mdmEnrollmentStatus) {
|
||||||
|
queryString += `&mdm_enrollment_status=${mdmEnrollmentStatus}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!label &&
|
||||||
|
!policyId &&
|
||||||
|
!softwareId &&
|
||||||
|
!mdmId &&
|
||||||
|
!mdmEnrollmentStatus &&
|
||||||
|
operatingSystemId
|
||||||
|
) {
|
||||||
queryString += `&operating_system_id=${operatingSystemId}`;
|
queryString += `&operating_system_id=${operatingSystemId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ export interface ILoadHostsOptions {
|
|||||||
policyId?: number;
|
policyId?: number;
|
||||||
policyResponse?: string;
|
policyResponse?: string;
|
||||||
softwareId?: number;
|
softwareId?: number;
|
||||||
|
mdmId?: number;
|
||||||
|
mdmEnrollmentStatus?: string;
|
||||||
operatingSystemId?: number;
|
operatingSystemId?: number;
|
||||||
device_mapping?: boolean;
|
device_mapping?: boolean;
|
||||||
columns?: string;
|
columns?: string;
|
||||||
@ -35,6 +37,8 @@ export interface IExportHostsOptions {
|
|||||||
policyId?: number;
|
policyId?: number;
|
||||||
policyResponse?: string;
|
policyResponse?: string;
|
||||||
softwareId?: number;
|
softwareId?: number;
|
||||||
|
mdmId?: number;
|
||||||
|
mdmEnrollmentStatus?: string;
|
||||||
operatingSystemId?: number;
|
operatingSystemId?: number;
|
||||||
device_mapping?: boolean;
|
device_mapping?: boolean;
|
||||||
columns?: string;
|
columns?: string;
|
||||||
@ -101,9 +105,37 @@ const getPolicyParams = (
|
|||||||
const getSoftwareParam = (
|
const getSoftwareParam = (
|
||||||
label?: string,
|
label?: string,
|
||||||
policyId?: number,
|
policyId?: number,
|
||||||
softwareId?: number
|
softwareId?: number,
|
||||||
|
mdmId?: number,
|
||||||
|
mdmEnrollmentStatus?: string
|
||||||
) => {
|
) => {
|
||||||
return label === undefined && policyId === undefined ? softwareId : undefined;
|
return !label && !policyId && !mdmId && !mdmEnrollmentStatus
|
||||||
|
? softwareId
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMDMSolutionParam = (
|
||||||
|
label?: string,
|
||||||
|
policyId?: number,
|
||||||
|
softwareId?: number,
|
||||||
|
mdmId?: number,
|
||||||
|
mdmEnrollmentStatus?: string
|
||||||
|
) => {
|
||||||
|
return !label && !policyId && !softwareId && !mdmEnrollmentStatus
|
||||||
|
? mdmId
|
||||||
|
: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMDMEnrollmentStatusParam = (
|
||||||
|
label?: string,
|
||||||
|
policyId?: number,
|
||||||
|
softwareId?: number,
|
||||||
|
mdmId?: number,
|
||||||
|
mdmEnrollmentStatus?: string
|
||||||
|
) => {
|
||||||
|
return !label && !policyId && !softwareId && !mdmId
|
||||||
|
? mdmEnrollmentStatus
|
||||||
|
: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOperatingSystemParam = (
|
const getOperatingSystemParam = (
|
||||||
@ -156,6 +188,8 @@ export default {
|
|||||||
const policyId = options?.policyId || null;
|
const policyId = options?.policyId || null;
|
||||||
const policyResponse = options?.policyResponse || "passing";
|
const policyResponse = options?.policyResponse || "passing";
|
||||||
const softwareId = options?.softwareId || null;
|
const softwareId = options?.softwareId || null;
|
||||||
|
const mdmId = options?.mdmId || null;
|
||||||
|
const mdmEnrollmentStatus = options?.mdmEnrollmentStatus || null;
|
||||||
const visibleColumns = options?.visibleColumns || null;
|
const visibleColumns = options?.visibleColumns || null;
|
||||||
|
|
||||||
if (!sortBy.length) {
|
if (!sortBy.length) {
|
||||||
@ -186,7 +220,7 @@ export default {
|
|||||||
path += `&team_id=${teamId}`;
|
path += `&team_id=${teamId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label OR policy_id OR software_id are valid filters.
|
// label OR policy_id OR software_id OR mdm_id OR mdm_enrollment_status are valid filters.
|
||||||
if (label) {
|
if (label) {
|
||||||
const lid = label.substr(labelPrefix.length);
|
const lid = label.substr(labelPrefix.length);
|
||||||
path += `&label_id=${parseInt(lid, 10)}`;
|
path += `&label_id=${parseInt(lid, 10)}`;
|
||||||
@ -197,10 +231,18 @@ export default {
|
|||||||
path += `&policy_response=${policyResponse}`;
|
path += `&policy_response=${policyResponse}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!label && !policyId && softwareId) {
|
if (!label && !policyId && !mdmId && !mdmEnrollmentStatus && softwareId) {
|
||||||
path += `&software_id=${softwareId}`;
|
path += `&software_id=${softwareId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!label && !policyId && !softwareId && !mdmEnrollmentStatus && mdmId) {
|
||||||
|
path += `&mdm_id=${mdmId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!label && !policyId && !softwareId && !mdmId && mdmEnrollmentStatus) {
|
||||||
|
path += `&mdm_enrollment_status=${mdmEnrollmentStatus}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (visibleColumns) {
|
if (visibleColumns) {
|
||||||
path += `&columns=${visibleColumns}`;
|
path += `&columns=${visibleColumns}`;
|
||||||
}
|
}
|
||||||
@ -217,6 +259,8 @@ export default {
|
|||||||
policyId,
|
policyId,
|
||||||
policyResponse = "passing",
|
policyResponse = "passing",
|
||||||
softwareId,
|
softwareId,
|
||||||
|
mdmId,
|
||||||
|
mdmEnrollmentStatus,
|
||||||
operatingSystemId,
|
operatingSystemId,
|
||||||
device_mapping,
|
device_mapping,
|
||||||
selectedLabels,
|
selectedLabels,
|
||||||
@ -237,6 +281,20 @@ export default {
|
|||||||
policy_id: policyParams.policy_id,
|
policy_id: policyParams.policy_id,
|
||||||
policy_response: policyParams.policy_response,
|
policy_response: policyParams.policy_response,
|
||||||
software_id: getSoftwareParam(label, policyId, softwareId),
|
software_id: getSoftwareParam(label, policyId, softwareId),
|
||||||
|
mdm_id: getMDMSolutionParam(
|
||||||
|
label,
|
||||||
|
policyId,
|
||||||
|
softwareId,
|
||||||
|
mdmId,
|
||||||
|
mdmEnrollmentStatus
|
||||||
|
),
|
||||||
|
mdm_enrollment_status: getMDMEnrollmentStatusParam(
|
||||||
|
label,
|
||||||
|
policyId,
|
||||||
|
softwareId,
|
||||||
|
mdmId,
|
||||||
|
mdmEnrollmentStatus
|
||||||
|
),
|
||||||
operating_system_id: getOperatingSystemParam(
|
operating_system_id: getOperatingSystemParam(
|
||||||
label,
|
label,
|
||||||
policyId,
|
policyId,
|
||||||
@ -248,6 +306,7 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const queryString = buildQueryStringFromParams(queryParams);
|
const queryString = buildQueryStringFromParams(queryParams);
|
||||||
|
|
||||||
const endpoint = getHostEndpoint(selectedLabels);
|
const endpoint = getHostEndpoint(selectedLabels);
|
||||||
const path = `${endpoint}?${queryString}`;
|
const path = `${endpoint}?${queryString}`;
|
||||||
return sendRequest("GET", path);
|
return sendRequest("GET", path);
|
||||||
|
@ -8,6 +8,7 @@ export default {
|
|||||||
const { MACADMINS } = endpoints;
|
const { MACADMINS } = endpoints;
|
||||||
const queryString = buildQueryStringFromParams({ team_id: teamId });
|
const queryString = buildQueryStringFromParams({ team_id: teamId });
|
||||||
const path = `${MACADMINS}?${queryString}`;
|
const path = `${MACADMINS}?${queryString}`;
|
||||||
|
|
||||||
return sendRequest("GET", path);
|
return sendRequest("GET", path);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -358,4 +358,8 @@ const labels = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default { count, hosts, labels };
|
export default {
|
||||||
|
count,
|
||||||
|
hosts,
|
||||||
|
labels,
|
||||||
|
};
|
||||||
|
@ -547,16 +547,12 @@ export const generateTeam = (
|
|||||||
return `${teams.length + 1} teams`; // global role and one or more teams
|
return `${teams.length + 1} teams`; // global role and one or more teams
|
||||||
};
|
};
|
||||||
|
|
||||||
export const greyCell = (roleOrTeamText: string): string => {
|
export const greyCell = (roleOrTeamText: string): boolean => {
|
||||||
const GREYED_TEXT = ["Global", "Unassigned", "Various", "No Team"];
|
const GREYED_TEXT = ["Global", "Unassigned", "Various", "No Team", "Unknown"];
|
||||||
|
|
||||||
if (
|
return (
|
||||||
GREYED_TEXT.includes(roleOrTeamText) ||
|
GREYED_TEXT.includes(roleOrTeamText) || roleOrTeamText.includes(" teams")
|
||||||
roleOrTeamText.includes(" teams")
|
);
|
||||||
) {
|
|
||||||
return "grey-cell";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupData = (formData: any) => {
|
const setupData = (formData: any) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user