Fleet UI: Munki title on homepage, hide entire card when no munki_versions installed on team/global (#7646)

This commit is contained in:
RachelElysia 2022-09-08 16:12:12 -04:00 committed by GitHub
parent 7c0b47e568
commit 78ca68d13d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 170 additions and 167 deletions

View file

@ -100,7 +100,6 @@ describe(
cy.findByText(/fleet test/i).should("exist");
cy.getAttached(".hosts-summary").should("exist");
cy.getAttached(".hosts-status").should("exist");
cy.getAttached(".home-munki").should("exist");
cy.getAttached(".home-mdm").should("exist");
// "get" because we expect it not to exist
cy.get(".home-software").should("not.exist");

View file

@ -92,7 +92,6 @@ describe(
cy.findByText(/fleet test/i).should("exist");
cy.getAttached(".hosts-summary").should("exist");
cy.getAttached(".hosts-status").should("exist");
cy.getAttached(".home-munki").should("exist");
cy.getAttached(".home-mdm").should("exist");
// "get" because we expect it not to exist
cy.get(".home-software").should("not.exist");

View file

@ -87,7 +87,6 @@ describe("Free tier - Observer user", () => {
cy.findByText(/fleet test/i).should("exist");
cy.getAttached(".hosts-summary").should("exist");
cy.getAttached(".hosts-status").should("exist");
cy.getAttached(".home-munki").should("exist");
cy.getAttached(".home-mdm").should("exist");
// "get" because we expect it not to exist
cy.get(".home-software").should("not.exist");

View file

@ -84,7 +84,6 @@ describe("Premium tier - Maintainer user", () => {
cy.findByText(/all teams/i).should("exist");
cy.getAttached(".hosts-summary").should("exist");
cy.getAttached(".hosts-status").should("exist");
cy.getAttached(".home-munki").should("exist");
cy.getAttached(".home-mdm").should("exist");
// "get" because we expect it not to exist
cy.get(".home-software").should("not.exist");

View file

@ -85,7 +85,6 @@ describe("Premium tier - Observer user", () => {
cy.findByText(/all teams/i).should("exist");
cy.getAttached(".hosts-summary").should("exist");
cy.getAttached(".hosts-status").should("exist");
cy.getAttached(".home-munki").should("exist");
cy.getAttached(".home-mdm").should("exist");
// "get" because we expect it not to exist
cy.get(".home-software").should("not.exist");

View file

@ -90,7 +90,6 @@ describe("Premium tier - Team Admin user", () => {
cy.findByText(/apples/i).should("exist");
cy.getAttached(".hosts-summary").should("exist");
cy.getAttached(".hosts-status").should("exist");
cy.getAttached(".home-munki").should("exist");
cy.getAttached(".home-mdm").should("exist");
// "get" because we expect it not to exist
cy.get(".home-software").should("not.exist");

View file

@ -85,7 +85,6 @@ describe("Premium tier - Team observer/maintainer user", () => {
cy.findByText(/apples/i).should("exist");
cy.getAttached(".hosts-summary").should("exist");
cy.getAttached(".hosts-status").should("exist");
cy.getAttached(".home-munki").should("exist");
cy.getAttached(".home-mdm").should("exist");
// "get" because we expect it not to exist
cy.get(".home-software").should("not.exist");

View file

@ -1,4 +1,4 @@
export interface IDataTableMDMFormat {
export interface IDataTableMdmFormat {
status: string;
hosts: number;
}
@ -14,13 +14,13 @@ export interface IMunkiIssuesAggregate {
type: "error" | "warning";
hosts_count: number;
}
export interface IMDMAggregateStatus {
export interface IMdmAggregateStatus {
enrolled_manual_hosts_count: number;
enrolled_automated_hosts_count: number;
unenrolled_hosts_count: number;
}
export interface IMDMSolution {
export interface IMdmSolution {
id: number;
name: string | null;
server_url: string;
@ -32,7 +32,7 @@ export interface IMacadminAggregate {
counts_updated_at: string;
munki_versions: IMunkiVersionsAggregate[];
munki_issues: IMunkiIssuesAggregate[];
mobile_device_management_enrollment_status: IMDMAggregateStatus;
mobile_device_management_solution: IMDMSolution[] | null;
mobile_device_management_enrollment_status: IMdmAggregateStatus;
mobile_device_management_solution: IMdmSolution[] | null;
};
}

View file

@ -9,10 +9,18 @@ import {
} from "interfaces/enroll_secret";
import { IHostSummary, IHostSummaryPlatforms } from "interfaces/host_summary";
import { ILabelSummary } from "interfaces/label";
import {
IDataTableMdmFormat,
IMdmSolution,
IMacadminAggregate,
IMunkiIssuesAggregate,
IMunkiVersionsAggregate,
} from "interfaces/macadmins";
import { IOsqueryPlatform } from "interfaces/platform";
import { ITeam } from "interfaces/team";
import enrollSecretsAPI from "services/entities/enroll_secret";
import hostSummaryAPI from "services/entities/host_summary";
import macadminsAPI from "services/entities/macadmins";
import teamsAPI, { ILoadTeamsResponse } from "services/entities/teams";
import sortUtils from "utilities/sort";
import { PLATFORM_DROPDOWN_OPTIONS } from "utilities/constants";
@ -22,6 +30,7 @@ import Spinner from "components/Spinner";
// @ts-ignore
import Dropdown from "components/forms/fields/Dropdown";
import MainContent from "components/MainContent";
import LastUpdatedText from "components/LastUpdatedText";
import useInfoCard from "./components/InfoCard";
import HostsStatus from "./cards/HostsStatus";
import HostsSummary from "./cards/HostsSummary";
@ -29,7 +38,7 @@ import ActivityFeed from "./cards/ActivityFeed";
import Software from "./cards/Software";
import LearnFleet from "./cards/LearnFleet";
import WelcomeHost from "./cards/WelcomeHost";
import MDM from "./cards/MDM";
import Mdm from "./cards/MDM";
import Munki from "./cards/Munki";
import OperatingSystems from "./cards/OperatingSystems";
import AddHostsModal from "../../components/AddHostsModal";
@ -62,10 +71,28 @@ const Homepage = (): JSX.Element => {
const [showActivityFeedTitle, setShowActivityFeedTitle] = useState(false);
const [showSoftwareUI, setShowSoftwareUI] = useState(false);
const [showMunkiUI, setShowMunkiUI] = useState(false);
const [showMunkiCard, setShowMunkiCard] = useState(true);
const [showMDMUI, setShowMDMUI] = useState(false);
const [showAddHostsModal, setShowAddHostsModal] = useState(false);
const [showOperatingSystemsUI, setShowOperatingSystemsUI] = useState(false);
const [showHostsUI, setShowHostsUI] = useState(false); // Hides UI on first load only
const [formattedMdmData, setFormattedMdmData] = useState<
IDataTableMdmFormat[]
>([]);
const [mdmSolutions, setMdmSolutions] = useState<IMdmSolution[] | null>([]);
const [munkiIssuesData, setMunkiIssuesData] = useState<
IMunkiIssuesAggregate[]
>([]);
const [munkiVersionsData, setMunkiVersionsData] = useState<
IMunkiVersionsAggregate[]
>([]);
const [mdmTitleDetail, setMdmTitleDetail] = useState<
JSX.Element | string | null
>();
const [munkiTitleDetail, setMunkiTitleDetail] = useState<
JSX.Element | string | null
>();
const canEnrollHosts =
isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer;
@ -147,6 +174,68 @@ const Homepage = (): JSX.Element => {
}
);
const { isFetching: isMacAdminsFetching, error: errorMacAdmins } = useQuery<
IMacadminAggregate,
Error
>(
["macAdmins", currentTeam?.id],
() => macadminsAPI.loadAll(currentTeam?.id),
{
keepPreviousData: true,
enabled: selectedPlatform === "darwin",
onSuccess: (data) => {
const {
counts_updated_at: macadmins_counts_updated_at,
mobile_device_management_enrollment_status,
mobile_device_management_solution,
} = data.macadmins;
const {
enrolled_manual_hosts_count,
enrolled_automated_hosts_count,
unenrolled_hosts_count,
} = mobile_device_management_enrollment_status;
const {
counts_updated_at: munki_counts_updated_at,
munki_versions,
munki_issues,
} = data.macadmins;
setMdmTitleDetail(
<LastUpdatedText
lastUpdatedAt={macadmins_counts_updated_at}
whatToRetrieve={"MDM enrollment"}
/>
);
setFormattedMdmData([
{
status: "Enrolled (manual)",
hosts: enrolled_manual_hosts_count,
},
{
status: "Enrolled (automatic)",
hosts: enrolled_automated_hosts_count,
},
{ status: "Unenrolled", hosts: unenrolled_hosts_count },
]);
setMdmSolutions(mobile_device_management_solution);
setMunkiVersionsData(munki_versions);
setMunkiIssuesData(munki_issues);
setShowMunkiCard(!!munki_versions);
setMunkiTitleDetail(
<LastUpdatedText
lastUpdatedAt={munki_counts_updated_at}
whatToRetrieve={"Munki"}
/>
);
},
onError: () => {
setShowMDMUI(true);
setShowMunkiUI(true);
},
}
);
const handleTeamSelect = (teamId: number) => {
const selectedTeam = find(teams, ["id", teamId]);
setCurrentTeam(selectedTeam);
@ -242,8 +331,9 @@ const Homepage = (): JSX.Element => {
});
const MunkiCard = useInfoCard({
title: "Munki versions",
showTitle: showMunkiUI,
title: "Munki",
titleDetail: munkiTitleDetail,
showTitle: !isMacAdminsFetching,
description: (
<p>
Munki is a tool for managing software on macOS devices.{" "}
@ -259,16 +349,18 @@ const Homepage = (): JSX.Element => {
),
children: (
<Munki
setShowMunkiUI={setShowMunkiUI}
showMunkiUI={showMunkiUI}
currentTeamId={currentTeam?.id}
errorMacAdmins={errorMacAdmins}
isMacAdminsFetching={isMacAdminsFetching}
munkiIssuesData={munkiIssuesData}
munkiVersionsData={munkiVersionsData}
/>
),
});
const MDMCard = useInfoCard({
title: "Mobile device management (MDM)",
showTitle: showMDMUI,
titleDetail: mdmTitleDetail,
showTitle: !isMacAdminsFetching,
description: (
<p>
MDM is used to manage configuration on macOS devices.{" "}
@ -283,10 +375,11 @@ const Homepage = (): JSX.Element => {
</p>
),
children: (
<MDM
setShowMDMUI={setShowMDMUI}
showMDMUI={showMDMUI}
currentTeamId={currentTeam?.id}
<Mdm
isMacAdminsFetching={isMacAdminsFetching}
errorMacAdmins={errorMacAdmins}
formattedMdmData={formattedMdmData}
mdmSolutions={mdmSolutions}
/>
),
});
@ -326,7 +419,9 @@ const Homepage = (): JSX.Element => {
<>
<div className={`${baseClass}__section`}>{OperatingSystemsCard}</div>
<div className={`${baseClass}__section`}>{MDMCard}</div>
<div className={`${baseClass}__section`}>{MunkiCard}</div>
{showMunkiCard && (
<div className={`${baseClass}__section`}>{MunkiCard}</div>
)}
</>
);

View file

@ -1,30 +1,23 @@
import React, { useState } from "react";
import { useQuery } from "react-query";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import macadminsAPI from "services/entities/macadmins";
import {
IMacadminAggregate,
IDataTableMDMFormat,
IMDMSolution,
} from "interfaces/macadmins";
import { IDataTableMdmFormat, IMdmSolution } from "interfaces/macadmins";
import TabsWrapper from "components/TabsWrapper";
import TableContainer from "components/TableContainer";
import Spinner from "components/Spinner";
import TableDataError from "components/DataError";
import LastUpdatedText from "components/LastUpdatedText";
import {
generateSolutionsTableHeaders,
generateSolutionsDataSet,
} from "./MDMSolutionsTableConfig";
import generateEnrollmentTableHeaders from "./MDMEnrollmentTableConfig";
interface IMDMCardProps {
showMDMUI: boolean;
currentTeamId: number | undefined;
setShowMDMUI: (showMDMTitle: boolean) => void;
setTitleDetail?: (content: JSX.Element | string | null) => void;
interface IMdmCardProps {
errorMacAdmins: Error | null;
isMacAdminsFetching: boolean;
formattedMdmData: IDataTableMdmFormat[];
mdmSolutions: IMdmSolution[] | null;
}
const DEFAULT_SORT_DIRECTION = "desc";
@ -34,7 +27,7 @@ const ENROLLMENT_DEFAULT_SORT_HEADER = "status";
const PAGE_SIZE = 8;
const baseClass = "home-mdm";
const EmptyMDMEnrollment = (): JSX.Element => (
const EmptyMdmEnrollment = (): JSX.Element => (
<div className={`${baseClass}__empty-mdm`}>
<h1>Unable to detect MDM enrollment</h1>
<p>
@ -51,7 +44,7 @@ const EmptyMDMEnrollment = (): JSX.Element => (
</div>
);
const EmptyMDMSolutions = (): JSX.Element => (
const EmptyMdmSolutions = (): JSX.Element => (
<div className={`${baseClass}__empty-mdm`}>
<h1>No MDM solutions detected</h1>
<p>
@ -61,60 +54,13 @@ const EmptyMDMSolutions = (): JSX.Element => (
</div>
);
const MDM = ({
showMDMUI,
currentTeamId,
setShowMDMUI,
setTitleDetail,
}: IMDMCardProps): JSX.Element => {
const Mdm = ({
isMacAdminsFetching,
errorMacAdmins,
formattedMdmData,
mdmSolutions,
}: IMdmCardProps): JSX.Element => {
const [navTabIndex, setNavTabIndex] = useState(0);
const [formattedMDMData, setFormattedMDMData] = useState<
IDataTableMDMFormat[]
>([]);
const [solutions, setSolutions] = useState<IMDMSolution[] | null>([]);
const { isFetching: isMDMFetching, error: errorMDM } = useQuery<
IMacadminAggregate,
Error
>(["MDM", currentTeamId], () => macadminsAPI.loadAll(currentTeamId), {
keepPreviousData: true,
onSuccess: (data) => {
const {
counts_updated_at,
mobile_device_management_enrollment_status,
mobile_device_management_solution,
} = data.macadmins;
const {
enrolled_manual_hosts_count,
enrolled_automated_hosts_count,
unenrolled_hosts_count,
} = mobile_device_management_enrollment_status;
setShowMDMUI(true);
setTitleDetail &&
setTitleDetail(
<LastUpdatedText
lastUpdatedAt={counts_updated_at}
whatToRetrieve={"MDM enrollment"}
/>
);
setFormattedMDMData([
{
status: "Enrolled (manual)",
hosts: enrolled_manual_hosts_count,
},
{
status: "Enrolled (automatic)",
hosts: enrolled_automated_hosts_count,
},
{ status: "Unenrolled", hosts: unenrolled_hosts_count },
]);
setSolutions(mobile_device_management_solution);
},
onError: () => {
setShowMDMUI(true);
},
});
const onTabChange = (index: number) => {
setNavTabIndex(index);
@ -122,14 +68,14 @@ const MDM = ({
const solutionsTableHeaders = generateSolutionsTableHeaders();
const enrollmentTableHeaders = generateEnrollmentTableHeaders();
const solutionsDataSet = generateSolutionsDataSet(solutions);
const solutionsDataSet = generateSolutionsDataSet(mdmSolutions);
// Renders opaque information as host information is loading
const opacity = showMDMUI ? { opacity: 1 } : { opacity: 0 };
const opacity = isMacAdminsFetching ? { opacity: 0 } : { opacity: 1 };
return (
<div className={baseClass}>
{!showMDMUI && (
{isMacAdminsFetching && (
<div className="spinner">
<Spinner />
</div>
@ -142,18 +88,18 @@ const MDM = ({
<Tab>Enrollment</Tab>
</TabList>
<TabPanel>
{errorMDM ? (
{errorMacAdmins ? (
<TableDataError card />
) : (
<TableContainer
columns={solutionsTableHeaders}
data={solutionsDataSet}
isLoading={isMDMFetching}
isLoading={isMacAdminsFetching}
defaultSortHeader={SOLUTIONS_DEFAULT_SORT_HEADER}
defaultSortDirection={DEFAULT_SORT_DIRECTION}
hideActionButton
resultsTitle={"MDM"}
emptyComponent={EmptyMDMSolutions}
emptyComponent={EmptyMdmSolutions}
showMarkAllPages={false}
isAllPagesSelected={false}
isClientSidePagination
@ -164,18 +110,18 @@ const MDM = ({
)}
</TabPanel>
<TabPanel>
{errorMDM ? (
{errorMacAdmins ? (
<TableDataError card />
) : (
<TableContainer
columns={enrollmentTableHeaders}
data={formattedMDMData}
isLoading={isMDMFetching}
data={formattedMdmData}
isLoading={isMacAdminsFetching}
defaultSortHeader={ENROLLMENT_DEFAULT_SORT_HEADER}
defaultSortDirection={ENROLLMENT_DEFAULT_SORT_DIRECTION}
hideActionButton
resultsTitle={"MDM"}
emptyComponent={EmptyMDMEnrollment}
emptyComponent={EmptyMdmEnrollment}
showMarkAllPages={false}
isAllPagesSelected={false}
disableCount
@ -192,4 +138,4 @@ const MDM = ({
);
};
export default MDM;
export default Mdm;

View file

@ -1,7 +1,7 @@
import React from "react";
import { Link } from "react-router";
import { IDataTableMDMFormat } from "interfaces/macadmins";
import { IDataTableMdmFormat } from "interfaces/macadmins";
import PATHS from "router/paths";
import TextCell from "components/TableContainer/DataTable/TextCell";
@ -15,7 +15,7 @@ interface ICellProps {
value: string;
};
row: {
original: IDataTableMDMFormat;
original: IDataTableMdmFormat;
};
}

View file

@ -1,7 +1,7 @@
import React from "react";
import { Link } from "react-router";
import { IMDMSolution } from "interfaces/macadmins";
import { IMdmSolution } from "interfaces/macadmins";
import PATHS from "router/paths";
import { greyCell } from "utilities/helpers";
@ -16,7 +16,7 @@ interface ICellProps {
value: string;
};
row: {
original: IMDMSolution;
original: IMdmSolution;
};
}
@ -98,7 +98,7 @@ export const generateSolutionsTableHeaders = (): IDataColumn[] => {
return solutionsTableHeaders;
};
const enhanceSolutionsData = (solutions: IMDMSolution[]): IMDMSolution[] => {
const enhanceSolutionsData = (solutions: IMdmSolution[]): IMdmSolution[] => {
return Object.values(solutions).map((solution) => {
return {
id: solution.id,
@ -110,8 +110,8 @@ const enhanceSolutionsData = (solutions: IMDMSolution[]): IMDMSolution[] => {
};
export const generateSolutionsDataSet = (
solutions: IMDMSolution[] | null
): IMDMSolution[] => {
solutions: IMdmSolution[] | null
): IMdmSolution[] => {
if (!solutions) {
return [];
}

View file

@ -1,10 +1,7 @@
import React, { useState } from "react";
import { useQuery } from "react-query";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import macadminsAPI from "services/entities/macadmins";
import {
IMacadminAggregate,
IMunkiIssuesAggregate,
IMunkiVersionsAggregate,
} from "interfaces/macadmins";
@ -13,15 +10,14 @@ import TabsWrapper from "components/TabsWrapper";
import TableContainer from "components/TableContainer";
import Spinner from "components/Spinner";
import TableDataError from "components/DataError";
import LastUpdatedText from "components/LastUpdatedText";
import munkiVersionsTableHeaders from "./MunkiVersionsTableConfig";
import munkiIssuesTableHeaders from "./MunkiIssuesTableConfig";
interface IMunkiCardProps {
showMunkiUI: boolean;
currentTeamId: number | undefined;
setShowMunkiUI: (showMunkiTitle: boolean) => void;
setTitleDetail?: (content: JSX.Element | string | null) => void;
errorMacAdmins: Error | null;
isMacAdminsFetching: boolean;
munkiIssuesData: IMunkiIssuesAggregate[];
munkiVersionsData: IMunkiVersionsAggregate[];
}
const DEFAULT_SORT_DIRECTION = "desc";
@ -57,58 +53,23 @@ const EmptyMunkiVersions = (): JSX.Element => (
);
const Munki = ({
showMunkiUI,
currentTeamId,
setShowMunkiUI,
setTitleDetail,
errorMacAdmins,
isMacAdminsFetching,
munkiIssuesData,
munkiVersionsData,
}: IMunkiCardProps): JSX.Element => {
const [navTabIndex, setNavTabIndex] = useState<number>(0);
const [pageIndex, setPageIndex] = useState<number>(0);
const [munkiIssuesData, setMunkiIssuesData] = useState<
IMunkiIssuesAggregate[]
>([]);
const [munkiVersionsData, setMunkiVersionsData] = useState<
IMunkiVersionsAggregate[]
>([]);
const { isFetching: isMunkiFetching, error: errorMunki } = useQuery<
IMacadminAggregate,
Error
>(["munki", currentTeamId], () => macadminsAPI.loadAll(currentTeamId), {
keepPreviousData: true,
onSuccess: (data) => {
const {
counts_updated_at,
munki_versions,
munki_issues,
} = data.macadmins;
setMunkiVersionsData(munki_versions);
setMunkiIssuesData(munki_issues);
setShowMunkiUI(true);
setTitleDetail &&
setTitleDetail(
<LastUpdatedText
lastUpdatedAt={counts_updated_at}
whatToRetrieve={"Munki versions"}
/>
);
},
onError: () => {
setShowMunkiUI(true);
},
});
const onTabChange = (index: number) => {
setNavTabIndex(index);
};
// Renders opaque information as host information is loading
const opacity = showMunkiUI ? { opacity: 1 } : { opacity: 0 };
const opacity = isMacAdminsFetching ? { opacity: 0 } : { opacity: 1 };
return (
<div className={baseClass}>
{!showMunkiUI && (
{isMacAdminsFetching && (
<div className="spinner">
<Spinner />
</div>
@ -121,13 +82,13 @@ const Munki = ({
<Tab>Versions</Tab>
</TabList>
<TabPanel>
{errorMunki ? (
{errorMacAdmins ? (
<TableDataError card />
) : (
<TableContainer
columns={munkiIssuesTableHeaders}
data={munkiIssuesData || []}
isLoading={isMunkiFetching}
isLoading={isMacAdminsFetching}
defaultSortHeader={DEFAULT_SORT_HEADER}
defaultSortDirection={DEFAULT_SORT_DIRECTION}
hideActionButton
@ -144,13 +105,13 @@ const Munki = ({
)}
</TabPanel>
<TabPanel>
{errorMunki ? (
{errorMacAdmins ? (
<TableDataError card />
) : (
<TableContainer
columns={munkiVersionsTableHeaders}
data={munkiVersionsData || []}
isLoading={isMunkiFetching}
isLoading={isMacAdminsFetching}
defaultSortHeader={DEFAULT_SORT_HEADER}
defaultSortDirection={DEFAULT_SORT_DIRECTION}
hideActionButton

View file

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { Link } from "react-router";
import Button from "components/buttons/Button";
@ -6,6 +6,7 @@ import LinkArrow from "../../../../../assets/images/icon-arrow-right-vibrant-blu
interface IInfoCardProps {
title: string;
titleDetail?: JSX.Element | string | null;
description?: JSX.Element | string;
children: React.ReactChild | React.ReactChild[];
action?:
@ -27,6 +28,7 @@ const baseClass = "homepage-info-card";
const useInfoCard = ({
title,
titleDetail: defaultTitleDetail,
description: defaultDescription,
children,
action,
@ -35,12 +37,18 @@ const useInfoCard = ({
}: IInfoCardProps): JSX.Element => {
const [actionLink, setActionURL] = useState<string | null>(null);
const [titleDetail, setTitleDetail] = useState<JSX.Element | string | null>(
null
defaultTitleDetail || null
);
const [description, setDescription] = useState<JSX.Element | string | null>(
defaultDescription || null
);
useEffect(() => {
if (defaultTitleDetail) {
setTitleDetail(defaultTitleDetail);
}
}, [defaultTitleDetail]);
const renderAction = () => {
if (action) {
if (action.type === "button") {

View file

@ -34,7 +34,7 @@ import {
} from "interfaces/enroll_secret";
import { IHost } from "interfaces/host";
import { ILabel } from "interfaces/label";
import { IMDMSolution, IMunkiIssuesAggregate } from "interfaces/macadmins";
import { IMdmSolution, IMunkiIssuesAggregate } from "interfaces/macadmins";
import {
formatOperatingSystemDisplayName,
IOperatingSystemVersion,
@ -225,7 +225,7 @@ const ManageHostsPage = ({
const [
mdmSolutionDetails,
setMDMSolutionDetails,
] = useState<IMDMSolution | null>(null);
] = useState<IMdmSolution | null>(null);
const [
munkiIssueDetails,
setMunkiIssueDetails,