mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 17:08:53 +00:00
Add mobile device management and device-user mapping information to host details to UI (#3499)
* Add mdm, munki and device-user mapping to UI
This commit is contained in:
parent
e776c2ea36
commit
6d2d28d5a8
10 changed files with 220 additions and 143 deletions
2
changes/issues-3317-3350-mdm-munki-device-mapping-ui
Normal file
2
changes/issues-3317-3350-mdm-munki-device-mapping-ui
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
* Surface mobile device management and munki information from macadmins osquery extension on host details page
|
||||
* Surface google chrome profile information from macadmins osquery extension on host details page
|
||||
|
|
@ -64,18 +64,27 @@ export default PropTypes.shape({
|
|||
|
||||
export interface IDeviceUser {
|
||||
email: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface IDeviceMappingResponse {
|
||||
device_mapping: IDeviceUser[];
|
||||
}
|
||||
|
||||
export interface IMunkiData {
|
||||
version: string;
|
||||
last_run_time: string;
|
||||
packages_intalled_count: number;
|
||||
errors_count: number;
|
||||
}
|
||||
|
||||
export interface IMDMData {
|
||||
health: string;
|
||||
enrollment_url: string;
|
||||
enrollment_status: string;
|
||||
server_url: string;
|
||||
}
|
||||
|
||||
export interface IMacadminsResponse {
|
||||
macadmins: null | {
|
||||
munki: null | IMunkiData;
|
||||
mobile_device_management: IMDMData;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPackStats {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ const WelcomeHost = (): JSX.Element => {
|
|||
refetch: fullyReloadHost,
|
||||
} = useQuery<IHostResponse, Error, IHost>(
|
||||
["host"],
|
||||
() => hostAPI.load(HOST_ID),
|
||||
() => hostAPI.loadHostDetails(HOST_ID),
|
||||
{
|
||||
select: (data: IHostResponse) => data.host,
|
||||
onSuccess: (returnedHost) => {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@ import queryAPI from "services/entities/queries";
|
|||
import teamAPI from "services/entities/teams";
|
||||
import { AppContext } from "context/app";
|
||||
import { PolicyContext } from "context/policy";
|
||||
import { IHost, IPackStats } from "interfaces/host";
|
||||
import {
|
||||
IHost,
|
||||
IDeviceMappingResponse,
|
||||
IMacadminsResponse,
|
||||
IPackStats,
|
||||
} from "interfaces/host";
|
||||
import { IQueryStats } from "interfaces/query_stats";
|
||||
import { ISoftware } from "interfaces/software";
|
||||
import { IHostPolicy } from "interfaces/policy";
|
||||
|
|
@ -46,7 +51,6 @@ import {
|
|||
AccordionItemPanel,
|
||||
} from "react-accessible-accordion";
|
||||
import {
|
||||
humanTimeAgo,
|
||||
humanHostUptime,
|
||||
humanHostLastSeen,
|
||||
humanHostEnrolled,
|
||||
|
|
@ -157,10 +161,7 @@ const HostDetailsPage = ({
|
|||
);
|
||||
|
||||
const [refetchStartTime, setRefetchStartTime] = useState<number | null>(null);
|
||||
const [
|
||||
showRefetchLoadingSpinner,
|
||||
setShowRefetchLoadingSpinner,
|
||||
] = useState<boolean>(false);
|
||||
const [showRefetchSpinner, setShowRefetchSpinner] = useState<boolean>(false);
|
||||
const [packsState, setPacksState] = useState<IPackStats[]>();
|
||||
const [scheduleState, setScheduleState] = useState<IQueryStats[]>();
|
||||
const [softwareState, setSoftwareState] = useState<ISoftware[]>([]);
|
||||
|
|
@ -181,60 +182,62 @@ const HostDetailsPage = ({
|
|||
select: (data: IFleetQueriesResponse) => data.queries,
|
||||
});
|
||||
|
||||
const { data: teams, error: teamsError } = useQuery<
|
||||
ITeamsResponse,
|
||||
Error,
|
||||
ITeam[]
|
||||
>("teams", () => teamAPI.loadAll(), {
|
||||
enabled: !!hostIdFromURL && !!isPremiumTier,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
select: (data: ITeamsResponse) => data.teams,
|
||||
});
|
||||
const { data: teams } = useQuery<ITeamsResponse, Error, ITeam[]>(
|
||||
"teams",
|
||||
() => teamAPI.loadAll(),
|
||||
{
|
||||
enabled: !!hostIdFromURL && !!isPremiumTier,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
select: (data: ITeamsResponse) => data.teams,
|
||||
}
|
||||
);
|
||||
|
||||
const { data: deviceMapping, refetch: refetchDeviceMapping } = useQuery(
|
||||
["deviceMapping", hostIdFromURL],
|
||||
() => hostAPI.loadHostDetailsExtension(hostIdFromURL, "device_mapping"),
|
||||
{
|
||||
enabled: !!hostIdFromURL,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
select: (data: IDeviceMappingResponse) => data.device_mapping,
|
||||
}
|
||||
);
|
||||
|
||||
const { data: macadmins, refetch: refetchMacadmins } = useQuery(
|
||||
["macadmins", hostIdFromURL],
|
||||
() => hostAPI.loadHostDetailsExtension(hostIdFromURL, "macadmins"),
|
||||
{
|
||||
enabled: !!hostIdFromURL,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
select: (data: IMacadminsResponse) => data.macadmins,
|
||||
}
|
||||
);
|
||||
|
||||
const refetchExtensions = () => {
|
||||
deviceMapping !== null && refetchDeviceMapping();
|
||||
macadmins !== null && refetchMacadmins();
|
||||
};
|
||||
|
||||
const {
|
||||
isLoading: isLoadingHost,
|
||||
data: host,
|
||||
refetch: fullyReloadHost,
|
||||
refetch: refetchHostDetails,
|
||||
} = useQuery<IHostResponse, Error, IHost>(
|
||||
["host", hostIdFromURL],
|
||||
() => hostAPI.load(hostIdFromURL),
|
||||
() => hostAPI.loadHostDetails(hostIdFromURL),
|
||||
{
|
||||
enabled: !!hostIdFromURL,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
select: (data: IHostResponse) => data.host,
|
||||
|
||||
// The onSuccess method below will run each time react-query successfully fetches data from
|
||||
// the hosts API through this useQuery hook.
|
||||
// This includes the initial page load as well as whenever we call react-query's refetch method,
|
||||
// which above we renamed to fullyReloadHost. For example, we use fullyReloadHost with the refetch
|
||||
// button and also after actions like team transfers.
|
||||
onSuccess: (returnedHost) => {
|
||||
setSoftwareState(returnedHost.software);
|
||||
setUsersState(returnedHost.users);
|
||||
if (returnedHost.pack_stats) {
|
||||
const packStatsByType = returnedHost.pack_stats.reduce(
|
||||
(
|
||||
dictionary: { packs: IPackStats[]; schedule: IQueryStats[] },
|
||||
pack: IPackStats
|
||||
) => {
|
||||
if (pack.type === "pack") {
|
||||
dictionary.packs.push(pack);
|
||||
} else {
|
||||
dictionary.schedule.push(...pack.query_stats);
|
||||
}
|
||||
return dictionary;
|
||||
},
|
||||
{ packs: [], schedule: [] }
|
||||
);
|
||||
setPacksState(packStatsByType.packs);
|
||||
setScheduleState(packStatsByType.schedule);
|
||||
}
|
||||
|
||||
setShowRefetchLoadingSpinner(returnedHost.refetch_requested);
|
||||
setShowRefetchSpinner(returnedHost.refetch_requested);
|
||||
if (returnedHost.refetch_requested) {
|
||||
// If the API reports that a Fleet refetch request is pending, we want to check back for fresh
|
||||
// host details. Here we set a one second timeout and poll the API again using
|
||||
|
|
@ -248,17 +251,19 @@ const HostDetailsPage = ({
|
|||
if (returnedHost.status === "online") {
|
||||
setRefetchStartTime(Date.now());
|
||||
setTimeout(() => {
|
||||
fullyReloadHost();
|
||||
refetchHostDetails();
|
||||
refetchExtensions();
|
||||
}, 1000);
|
||||
} else {
|
||||
setShowRefetchLoadingSpinner(false);
|
||||
setShowRefetchSpinner(false);
|
||||
}
|
||||
} else {
|
||||
const totalElapsedTime = Date.now() - refetchStartTime;
|
||||
if (totalElapsedTime < 60000) {
|
||||
if (returnedHost.status === "online") {
|
||||
setTimeout(() => {
|
||||
fullyReloadHost();
|
||||
refetchHostDetails();
|
||||
refetchExtensions();
|
||||
}, 1000);
|
||||
} else {
|
||||
dispatch(
|
||||
|
|
@ -267,7 +272,7 @@ const HostDetailsPage = ({
|
|||
`This host is offline. Please try refetching host vitals later.`
|
||||
)
|
||||
);
|
||||
setShowRefetchLoadingSpinner(false);
|
||||
setShowRefetchSpinner(false);
|
||||
}
|
||||
} else {
|
||||
dispatch(
|
||||
|
|
@ -276,9 +281,33 @@ const HostDetailsPage = ({
|
|||
`We're having trouble fetching fresh vitals for this host. Please try again later.`
|
||||
)
|
||||
);
|
||||
setShowRefetchLoadingSpinner(false);
|
||||
setShowRefetchSpinner(false);
|
||||
}
|
||||
}
|
||||
return; // exit early because refectch is pending so we can avoid unecessary steps below
|
||||
}
|
||||
setSoftwareState(returnedHost.software);
|
||||
setUsersState(returnedHost.users);
|
||||
if (returnedHost.pack_stats) {
|
||||
const packStatsByType = returnedHost.pack_stats.reduce(
|
||||
(
|
||||
dictionary: {
|
||||
packs: IPackStats[];
|
||||
schedule: IQueryStats[];
|
||||
},
|
||||
pack: IPackStats
|
||||
) => {
|
||||
if (pack.type === "pack") {
|
||||
dictionary.packs.push(pack);
|
||||
} else {
|
||||
dictionary.schedule.push(...pack.query_stats);
|
||||
}
|
||||
return dictionary;
|
||||
},
|
||||
{ packs: [], schedule: [] }
|
||||
);
|
||||
setPacksState(packStatsByType.packs);
|
||||
setScheduleState(packStatsByType.schedule);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
|
|
@ -439,16 +468,19 @@ const HostDetailsPage = ({
|
|||
// Once the user clicks to refetch, the refetch loading spinner should continue spinning
|
||||
// unless there is an error. The spinner state is also controlled in the fullyReloadHost
|
||||
// method.
|
||||
setShowRefetchLoadingSpinner(true);
|
||||
setShowRefetchSpinner(true);
|
||||
try {
|
||||
await hostAPI.refetch(host).then(() => {
|
||||
setRefetchStartTime(Date.now());
|
||||
setTimeout(() => fullyReloadHost(), 1000);
|
||||
setTimeout(() => {
|
||||
refetchHostDetails();
|
||||
refetchExtensions();
|
||||
}, 1000);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch(renderFlash("error", `Host "${host.hostname}" refetch error`));
|
||||
setShowRefetchLoadingSpinner(false);
|
||||
setShowRefetchSpinner(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -484,7 +516,7 @@ const HostDetailsPage = ({
|
|||
: `Host successfully transferred to ${team.name}.`;
|
||||
|
||||
dispatch(renderFlash("success", successMessage));
|
||||
fullyReloadHost();
|
||||
refetchHostDetails(); // Note: it is not necessary to `refetchExtensions` here because only team has changed
|
||||
setShowTransferHostModal(false);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
|
@ -970,19 +1002,19 @@ const HostDetailsPage = ({
|
|||
className="refetch"
|
||||
data-tip
|
||||
data-for="refetch-tooltip"
|
||||
data-tip-disable={isOnline || showRefetchLoadingSpinner}
|
||||
data-tip-disable={isOnline || showRefetchSpinner}
|
||||
>
|
||||
<Button
|
||||
className={`
|
||||
button
|
||||
button--unstyled
|
||||
${!isOnline ? "refetch-offline" : ""}
|
||||
${showRefetchLoadingSpinner ? "refetch-spinner" : "refetch-btn"}
|
||||
${showRefetchSpinner ? "refetch-spinner" : "refetch-btn"}
|
||||
`}
|
||||
disabled={!isOnline}
|
||||
onClick={onRefetchHost}
|
||||
>
|
||||
{showRefetchLoadingSpinner
|
||||
{showRefetchSpinner
|
||||
? "Fetching fresh vitals...this may take a moment"
|
||||
: "Refetch"}
|
||||
</Button>
|
||||
|
|
@ -1047,18 +1079,47 @@ const HostDetailsPage = ({
|
|||
);
|
||||
|
||||
const renderDeviceUser = () => {
|
||||
if (host?.device_users && host?.device_users.length > 0) {
|
||||
const numUsers = deviceMapping?.length;
|
||||
if (numUsers) {
|
||||
return (
|
||||
// max width is added here because this is the only div that needs it
|
||||
<div
|
||||
className="info-flex__item info-flex__item--title"
|
||||
style={{ maxWidth: 216 }}
|
||||
>
|
||||
<span className="info-flex__header">Device user</span>
|
||||
<span className="info-flex__data">{host.device_users[0].email}</span>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Device user</span>
|
||||
<span className="info-grid__data">
|
||||
{numUsers === 1 ? (
|
||||
deviceMapping[0].email || "---"
|
||||
) : (
|
||||
<span className={`${baseClass}__device-mapping`}>
|
||||
<span
|
||||
className="device-user"
|
||||
data-tip
|
||||
data-for="device-user-tooltip"
|
||||
>
|
||||
{`${numUsers} users`}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id="device-user-tooltip"
|
||||
backgroundColor="#3e4771"
|
||||
>
|
||||
<div
|
||||
className={`${baseClass}__tooltip-text device-user-tooltip`}
|
||||
>
|
||||
{deviceMapping.map((user, i, arr) => (
|
||||
<span key={user.email}>{`${user.email}${
|
||||
i < arr.length - 1 ? ", " : ""
|
||||
}`}</span>
|
||||
))}
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderDiskSpace = () => {
|
||||
|
|
@ -1088,50 +1149,40 @@ const HostDetailsPage = ({
|
|||
return <span className="info-flex__data">No data available</span>;
|
||||
};
|
||||
|
||||
const renderMunkiData = () => {
|
||||
if (host?.munki) {
|
||||
return (
|
||||
<>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Munki last run</span>
|
||||
<span className="info-grid__data">
|
||||
{humanTimeAgo(host.munki.last_run_time)} days ago
|
||||
</span>
|
||||
</div>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Munki packages installed</span>
|
||||
<span className="info-grid__data">
|
||||
{host.munki.packages_intalled_count}
|
||||
</span>
|
||||
</div>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Munki errors</span>
|
||||
<span className="info-grid__data">{host.munki.errors_count}</span>
|
||||
</div>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Munki version</span>
|
||||
<span className="info-grid__data">{host.munki.version}</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
const renderMdmData = () => {
|
||||
if (!macadmins) {
|
||||
return null;
|
||||
}
|
||||
const { mobile_device_management: mdm } = macadmins;
|
||||
return mdm.enrollment_status !== "Unenrolled" ? (
|
||||
<>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">MDM enrollment</span>
|
||||
<span className="info-grid__data">
|
||||
{mdm.enrollment_status || "---"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">MDM server URL</span>
|
||||
<span className="info-grid__data">{mdm.server_url || "---"}</span>
|
||||
</div>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const renderMDMData = () => {
|
||||
if (host?.mdm) {
|
||||
return (
|
||||
<>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">MDM health</span>
|
||||
<span className="info-grid__data">{host.mdm?.health}</span>
|
||||
</div>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">MDM enrollment URL</span>
|
||||
<span className="info-grid__data">{host.mdm.enrollment_url}</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
const renderMunkiData = () => {
|
||||
if (!macadmins) {
|
||||
return null;
|
||||
}
|
||||
const { munki } = macadmins;
|
||||
return munki ? (
|
||||
<>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Munki version</span>
|
||||
<span className="info-grid__data">{munki.version || "---"}</span>
|
||||
</div>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
if (isLoadingHost) {
|
||||
|
|
@ -1173,7 +1224,6 @@ const HostDetailsPage = ({
|
|||
</div>
|
||||
{titleData.issues?.total_issues_count > 0 && renderIssues()}
|
||||
{isPremiumTier && renderHostTeam()}
|
||||
{renderDeviceUser()}
|
||||
<div className="info-flex__item info-flex__item--title">
|
||||
<span className="info-flex__header">Disk Space</span>
|
||||
{renderDiskSpace()}
|
||||
|
|
@ -1267,7 +1317,8 @@ const HostDetailsPage = ({
|
|||
</span>
|
||||
</div>
|
||||
{renderMunkiData()}
|
||||
{renderMDMData()}
|
||||
{renderMdmData()}
|
||||
{renderDeviceUser()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-2">
|
||||
|
|
|
|||
|
|
@ -386,6 +386,14 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
&__device-mapping {
|
||||
.device-user-tooltip {
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
border: solid 1px $ui-fleet-blue-15;
|
||||
border-radius: 6px;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import teamsAPI from "services/entities/teams";
|
|||
import globalPoliciesAPI from "services/entities/global_policies";
|
||||
import teamPoliciesAPI from "services/entities/team_policies";
|
||||
import hostsAPI, {
|
||||
IHostLoadOptions,
|
||||
ILoadHostsOptions,
|
||||
ISortOption,
|
||||
} from "services/entities/hosts";
|
||||
import hostCountAPI, {
|
||||
|
|
@ -131,9 +131,7 @@ const ManageHostsPage = ({
|
|||
config,
|
||||
isGlobalAdmin,
|
||||
isGlobalMaintainer,
|
||||
isAnyTeamMaintainer,
|
||||
isTeamMaintainer,
|
||||
isAnyTeamAdmin,
|
||||
isTeamAdmin,
|
||||
isOnGlobalTeam,
|
||||
isOnlyObserver,
|
||||
|
|
@ -270,7 +268,6 @@ const ManageHostsPage = ({
|
|||
const {
|
||||
isLoading: isTeamSecretsLoading,
|
||||
data: teamSecrets,
|
||||
error: teamSecretsError,
|
||||
refetch: refetchTeamSecrets,
|
||||
} = useQuery<IEnrollSecretsResponse, Error, IEnrollSecret[]>(
|
||||
["team secrets", currentTeam],
|
||||
|
|
@ -385,7 +382,7 @@ const ManageHostsPage = ({
|
|||
return selectedFilters.find((f) => !f.includes(LABEL_SLUG_PREFIX));
|
||||
};
|
||||
|
||||
const retrieveHosts = async (options: IHostLoadOptions = {}) => {
|
||||
const retrieveHosts = async (options: ILoadHostsOptions = {}) => {
|
||||
setIsHostsLoading(true);
|
||||
|
||||
options = {
|
||||
|
|
@ -403,7 +400,7 @@ const ManageHostsPage = ({
|
|||
}
|
||||
|
||||
try {
|
||||
const { hosts: returnedHosts, software } = await hostsAPI.loadAll(
|
||||
const { hosts: returnedHosts, software } = await hostsAPI.loadHosts(
|
||||
options
|
||||
);
|
||||
setHosts(returnedHosts);
|
||||
|
|
@ -444,7 +441,7 @@ const ManageHostsPage = ({
|
|||
}
|
||||
};
|
||||
|
||||
const refetchHosts = (options: IHostLoadOptions) => {
|
||||
const refetchHosts = (options: ILoadHostsOptions) => {
|
||||
retrieveHosts(options);
|
||||
if (options.sortBy) {
|
||||
delete options.sortBy;
|
||||
|
|
@ -473,7 +470,7 @@ const ManageHostsPage = ({
|
|||
setSelectedLabel(selected);
|
||||
|
||||
// get the hosts
|
||||
const options: IHostLoadOptions = {
|
||||
const options: ILoadHostsOptions = {
|
||||
selectedLabels: selectedFilters,
|
||||
globalFilter: searchQuery,
|
||||
sortBy,
|
||||
|
|
@ -510,7 +507,7 @@ const ManageHostsPage = ({
|
|||
setSelectedLabel(selected);
|
||||
|
||||
// get the hosts
|
||||
const options: IHostLoadOptions = {
|
||||
const options: ILoadHostsOptions = {
|
||||
selectedLabels: selectedFilters,
|
||||
globalFilter: searchQuery,
|
||||
sortBy,
|
||||
|
|
|
|||
|
|
@ -318,8 +318,6 @@ const ManagePolicyPage = (managePoliciesPageProps: {
|
|||
const showDefaultDescription =
|
||||
isFreeTier || (isPremiumTier && !selectedTeamId && selectedTeamId !== null);
|
||||
|
||||
// If there aren't any policies of if there are loading errors, we don't show the update interval info banner.
|
||||
// We also want to check selectTeamId for the null case so that we don't render the element prematurely.
|
||||
const showInfoBanner =
|
||||
(selectedTeamId && !isTeamPoliciesError && !!teamPolicies?.length) ||
|
||||
(!selectedTeamId &&
|
||||
|
|
@ -327,7 +325,6 @@ const ManagePolicyPage = (managePoliciesPageProps: {
|
|||
!isGlobalPoliciesError &&
|
||||
!!globalPolicies?.length);
|
||||
|
||||
// If there aren't any policies of if there are loading errors, we don't show the inherited policies button.
|
||||
const showInheritedPoliciesButton =
|
||||
!!selectedTeamId && !!globalPolicies?.length && !isGlobalPoliciesError;
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,8 @@ const PolicyPage = ({
|
|||
// to the selected targets automatically
|
||||
useQuery<IHostResponse, Error, IHost>(
|
||||
"hostFromURL",
|
||||
() => hostAPI.load(parseInt(URLQuerySearch.host_ids as string, 10)),
|
||||
() =>
|
||||
hostAPI.loadHostDetails(parseInt(URLQuerySearch.host_ids as string, 10)),
|
||||
{
|
||||
enabled: !!URLQuerySearch.host_ids,
|
||||
select: (data: IHostResponse) => data.host,
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ const QueryPage = ({
|
|||
// to the selected targets automatically
|
||||
useQuery<IHostResponse, Error, IHost>(
|
||||
"hostFromURL",
|
||||
() => hostAPI.load(parseInt(URLQuerySearch.host_ids as string, 10)),
|
||||
() =>
|
||||
hostAPI.loadHostDetails(parseInt(URLQuerySearch.host_ids as string, 10)),
|
||||
{
|
||||
enabled: !!URLQuerySearch.host_ids && !queryParamHostsAdded,
|
||||
select: (data: IHostResponse) => data.host,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export interface ISortOption {
|
|||
direction: string;
|
||||
}
|
||||
|
||||
export interface IHostLoadOptions {
|
||||
export interface ILoadHostsOptions {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
selectedLabels?: string[];
|
||||
|
|
@ -20,6 +20,8 @@ export interface IHostLoadOptions {
|
|||
softwareId?: number;
|
||||
}
|
||||
|
||||
export type ILoadHostDetailsExtension = "device_mapping" | "macadmins";
|
||||
|
||||
export default {
|
||||
destroy: (host: IHost) => {
|
||||
const { HOSTS } = endpoints;
|
||||
|
|
@ -48,19 +50,7 @@ export default {
|
|||
},
|
||||
});
|
||||
},
|
||||
refetch: (host: IHost) => {
|
||||
const { HOSTS } = endpoints;
|
||||
const path = `${HOSTS}/${host.id}/refetch`;
|
||||
|
||||
return sendRequest("POST", path);
|
||||
},
|
||||
load: (hostID: number) => {
|
||||
const { HOSTS } = endpoints;
|
||||
const path = `${HOSTS}/${hostID}`;
|
||||
|
||||
return sendRequest("GET", path);
|
||||
},
|
||||
loadAll: (options: IHostLoadOptions | undefined) => {
|
||||
loadHosts: (options: ILoadHostsOptions | undefined) => {
|
||||
const { HOSTS, LABEL_HOSTS } = endpoints;
|
||||
const page = options?.page || 0;
|
||||
const perPage = options?.perPage || 100;
|
||||
|
|
@ -131,6 +121,27 @@ export default {
|
|||
|
||||
return sendRequest("GET", path);
|
||||
},
|
||||
loadHostDetails: (hostID: number) => {
|
||||
const { HOSTS } = endpoints;
|
||||
const path = `${HOSTS}/${hostID}`;
|
||||
|
||||
return sendRequest("GET", path);
|
||||
},
|
||||
loadHostDetailsExtension: (
|
||||
hostID: number,
|
||||
extension: ILoadHostDetailsExtension
|
||||
) => {
|
||||
const { HOSTS } = endpoints;
|
||||
const path = `${HOSTS}/${hostID}/${extension}`;
|
||||
|
||||
return sendRequest("GET", path);
|
||||
},
|
||||
refetch: (host: IHost) => {
|
||||
const { HOSTS } = endpoints;
|
||||
const path = `${HOSTS}/${host.id}/refetch`;
|
||||
|
||||
return sendRequest("POST", path);
|
||||
},
|
||||
search: (searchText: string) => {
|
||||
const { HOSTS } = endpoints;
|
||||
const path = `${HOSTS}?query=${searchText}`;
|
||||
|
|
|
|||
Loading…
Reference in a new issue