mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 16:39:01 +00:00
Host details page: Surface disk encryption information (#8437)
This commit is contained in:
parent
e1120f06e0
commit
6921e43bb7
10 changed files with 142 additions and 56 deletions
2
changes/issue-7440-8226-disk-encryption-etc-host-details
Normal file
2
changes/issue-7440-8226-disk-encryption-etc-host-details
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
- Host details page includes information about the host's disk encryption
|
||||
- Information surfaced to device user includes all summary/about information surfaced in host details page
|
||||
|
|
@ -193,4 +193,5 @@ export interface IHost {
|
|||
query_results?: unknown[];
|
||||
geolocation?: IGeoLocation;
|
||||
batteries?: IBattery[];
|
||||
disk_encryption_enabled?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,12 @@ import { pick } from "lodash";
|
|||
|
||||
import { NotificationContext } from "context/notification";
|
||||
import deviceUserAPI from "services/entities/device_user";
|
||||
import { IHost, IDeviceMappingResponse } from "interfaces/host";
|
||||
import hostAPI from "services/entities/hosts";
|
||||
import {
|
||||
IHost,
|
||||
IDeviceMappingResponse,
|
||||
IMacadminsResponse,
|
||||
} from "interfaces/host";
|
||||
import { ISoftware } from "interfaces/software";
|
||||
import { IHostPolicy } from "interfaces/policy";
|
||||
import DeviceUserError from "components/DeviceUserError";
|
||||
|
|
@ -17,7 +22,11 @@ import OrgLogoIcon from "components/icons/OrgLogoIcon";
|
|||
import Spinner from "components/Spinner";
|
||||
import Button from "components/buttons/Button";
|
||||
import TabsWrapper from "components/TabsWrapper";
|
||||
import { normalizeEmptyValues, wrapFleetHelper } from "utilities/helpers";
|
||||
import {
|
||||
normalizeEmptyValues,
|
||||
wrapFleetHelper,
|
||||
humanHostDiskEncryptionEnabled,
|
||||
} from "utilities/helpers";
|
||||
|
||||
import HostSummaryCard from "../cards/HostSummary";
|
||||
import AboutCard from "../cards/About";
|
||||
|
|
@ -47,6 +56,13 @@ interface IHostResponse {
|
|||
host: IHost;
|
||||
org_logo_url: string;
|
||||
license: ILicense;
|
||||
disk_encryption_enabled?: boolean;
|
||||
platform?: string;
|
||||
}
|
||||
|
||||
interface IHostDiskEncryptionProps {
|
||||
enabled?: boolean;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
const DeviceUserPage = ({
|
||||
|
|
@ -60,6 +76,10 @@ const DeviceUserPage = ({
|
|||
const [refetchStartTime, setRefetchStartTime] = useState<number | null>(null);
|
||||
const [showRefetchSpinner, setShowRefetchSpinner] = useState(false);
|
||||
const [hostSoftware, setHostSoftware] = useState<ISoftware[]>([]);
|
||||
const [
|
||||
hostDiskEncryption,
|
||||
setHostDiskEncryption,
|
||||
] = useState<IHostDiskEncryptionProps>({});
|
||||
const [host, setHost] = useState<IHost | null>();
|
||||
const [orgLogoURL, setOrgLogoURL] = useState("");
|
||||
const [selectedPolicy, setSelectedPolicy] = useState<IHostPolicy | null>(
|
||||
|
|
@ -81,10 +101,22 @@ const DeviceUserPage = ({
|
|||
}
|
||||
);
|
||||
|
||||
const { data: macadmins, refetch: refetchMacadmins } = useQuery(
|
||||
["macadmins", deviceAuthToken],
|
||||
() => deviceUserAPI.loadHostDetailsExtension(deviceAuthToken, "macadmins"),
|
||||
{
|
||||
enabled: !!deviceAuthToken,
|
||||
refetchOnMount: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
select: (data: IMacadminsResponse) => data.macadmins,
|
||||
}
|
||||
);
|
||||
|
||||
const refetchExtensions = () => {
|
||||
deviceMapping !== null && refetchDeviceMapping();
|
||||
};
|
||||
|
||||
const {
|
||||
isLoading: isLoadingHost,
|
||||
error: loadingDeviceUserError,
|
||||
|
|
@ -104,6 +136,13 @@ const DeviceUserPage = ({
|
|||
setIsPremiumTier(returnedHost.license.tier === "premium");
|
||||
setHostSoftware(returnedHost.host.software ?? []);
|
||||
setHost(returnedHost.host);
|
||||
setHostDiskEncryption({
|
||||
enabled: returnedHost.host.disk_encryption_enabled,
|
||||
tooltip: humanHostDiskEncryptionEnabled(
|
||||
returnedHost.host.platform,
|
||||
returnedHost.host.disk_encryption_enabled
|
||||
),
|
||||
});
|
||||
setOrgLogoURL(returnedHost.org_logo_url);
|
||||
if (returnedHost?.host.refetch_requested) {
|
||||
// If the API reports that a Fleet refetch request is pending, we want to check back for fresh
|
||||
|
|
@ -165,6 +204,7 @@ const DeviceUserPage = ({
|
|||
"detail_updated_at",
|
||||
"percent_disk_space_available",
|
||||
"gigs_disk_space_available",
|
||||
"team_name",
|
||||
])
|
||||
);
|
||||
|
||||
|
|
@ -177,6 +217,7 @@ const DeviceUserPage = ({
|
|||
"hardware_serial",
|
||||
"primary_ip",
|
||||
"public_ip",
|
||||
"geolocation",
|
||||
"batteries",
|
||||
"detail_updated_at",
|
||||
])
|
||||
|
|
@ -241,6 +282,7 @@ const DeviceUserPage = ({
|
|||
<HostSummaryCard
|
||||
statusClassName={statusClassName}
|
||||
titleData={titleData}
|
||||
diskEncryption={hostDiskEncryption}
|
||||
showRefetchSpinner={showRefetchSpinner}
|
||||
onRefetchHost={onRefetchHost}
|
||||
renderActionButtons={renderActionButtons}
|
||||
|
|
@ -267,6 +309,7 @@ const DeviceUserPage = ({
|
|||
<AboutCard
|
||||
aboutData={aboutData}
|
||||
deviceMapping={deviceMapping}
|
||||
macadmins={macadmins}
|
||||
wrapFleetHelper={wrapFleetHelper}
|
||||
deviceUser
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -73,6 +73,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Position tooltip over parent div
|
||||
.component__tooltip-wrapper {
|
||||
position: absolute;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ import TabsWrapper from "components/TabsWrapper";
|
|||
import MainContent from "components/MainContent";
|
||||
import BackLink from "components/BackLink";
|
||||
|
||||
import { normalizeEmptyValues, wrapFleetHelper } from "utilities/helpers";
|
||||
import {
|
||||
normalizeEmptyValues,
|
||||
humanHostDiskEncryptionEnabled,
|
||||
wrapFleetHelper,
|
||||
} from "utilities/helpers";
|
||||
|
||||
import HostSummaryCard from "../cards/HostSummary";
|
||||
import AboutCard from "../cards/About";
|
||||
|
|
@ -52,7 +56,7 @@ import SelectQueryModal from "./modals/SelectQueryModal";
|
|||
import TransferHostModal from "./modals/TransferHostModal";
|
||||
import PolicyDetailsModal from "../cards/Policies/HostPoliciesTable/PolicyDetailsModal";
|
||||
import DeleteHostModal from "./modals/DeleteHostModal";
|
||||
import RenderOSPolicyModal from "./modals/OSPolicyModal";
|
||||
import OSPolicyModal from "./modals/OSPolicyModal";
|
||||
|
||||
import parseOsVersion from "./modals/OSPolicyModal/helpers";
|
||||
import DeleteIcon from "../../../../../assets/images/icon-action-delete-14x14@2x.png";
|
||||
|
|
@ -81,6 +85,11 @@ interface ISearchQueryData {
|
|||
pageIndex: number;
|
||||
}
|
||||
|
||||
interface IHostDiskEncryptionProps {
|
||||
enabled?: boolean;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
const TAGGED_TEMPLATES = {
|
||||
queryByHostRoute: (hostId: number | undefined | null) => {
|
||||
return `${hostId ? `?host_ids=${hostId}` : ""}`;
|
||||
|
|
@ -139,6 +148,10 @@ const HostDetailsPage = ({
|
|||
const [packsState, setPacksState] = useState<IPackStats[]>();
|
||||
const [scheduleState, setScheduleState] = useState<IQueryStats[]>();
|
||||
const [hostSoftware, setHostSoftware] = useState<ISoftware[]>([]);
|
||||
const [
|
||||
hostDiskEncryption,
|
||||
setHostDiskEncryption,
|
||||
] = useState<IHostDiskEncryptionProps>({});
|
||||
const [usersState, setUsersState] = useState<{ username: string }[]>([]);
|
||||
const [usersSearchString, setUsersSearchString] = useState("");
|
||||
|
||||
|
|
@ -261,6 +274,13 @@ const HostDetailsPage = ({
|
|||
}
|
||||
setHostSoftware(returnedHost.software || []);
|
||||
setUsersState(returnedHost.users || []);
|
||||
setHostDiskEncryption({
|
||||
enabled: returnedHost.disk_encryption_enabled,
|
||||
tooltip: humanHostDiskEncryptionEnabled(
|
||||
returnedHost.platform,
|
||||
returnedHost.disk_encryption_enabled
|
||||
),
|
||||
});
|
||||
if (returnedHost.pack_stats) {
|
||||
const packStatsByType = returnedHost.pack_stats.reduce(
|
||||
(
|
||||
|
|
@ -543,6 +563,7 @@ const HostDetailsPage = ({
|
|||
<HostSummaryCard
|
||||
statusClassName={statusClassName}
|
||||
titleData={titleData}
|
||||
diskEncryption={hostDiskEncryption}
|
||||
isPremiumTier={isPremiumTier}
|
||||
isOnlyObserver={isOnlyObserver}
|
||||
toggleOSPolicyModal={toggleOSPolicyModal}
|
||||
|
|
@ -656,10 +677,11 @@ const HostDetailsPage = ({
|
|||
/>
|
||||
)}
|
||||
{showOSPolicyModal && (
|
||||
<RenderOSPolicyModal
|
||||
<OSPolicyModal
|
||||
onCancel={() => setShowOSPolicyModal(false)}
|
||||
onCreateNewPolicy={onCreateNewPolicy}
|
||||
titleData={titleData}
|
||||
osVersion={host?.os_version}
|
||||
detailsUpdatedAt={host?.detail_updated_at}
|
||||
osPolicy={osPolicyQuery}
|
||||
osPolicyLabel={osPolicyLabel}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -61,6 +61,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Position tooltip over parent div
|
||||
.component__tooltip-wrapper {
|
||||
position: absolute;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import CopyIcon from "../../../../../../../assets/images/icon-copy-clipboard-fle
|
|||
interface IRenderOSPolicyModal {
|
||||
onCreateNewPolicy: (team: ITeam) => void;
|
||||
onCancel: () => void;
|
||||
titleData?: any;
|
||||
osVersion?: string;
|
||||
detailsUpdatedAt?: string;
|
||||
osPolicy: string;
|
||||
osPolicyLabel: string;
|
||||
}
|
||||
|
|
@ -26,7 +27,8 @@ const baseClass = "os-policy-modal";
|
|||
const RenderOSPolicyModal = ({
|
||||
onCancel,
|
||||
onCreateNewPolicy,
|
||||
titleData,
|
||||
osVersion,
|
||||
detailsUpdatedAt,
|
||||
osPolicy,
|
||||
osPolicyLabel,
|
||||
}: IRenderOSPolicyModal): JSX.Element => {
|
||||
|
|
@ -73,11 +75,9 @@ const RenderOSPolicyModal = ({
|
|||
<Modal title="Operating system" onExit={onCancel} className={baseClass}>
|
||||
<>
|
||||
<p>
|
||||
<span className={`${baseClass}__os-modal-title`}>
|
||||
{titleData.os_version}{" "}
|
||||
</span>
|
||||
<span className={`${baseClass}__os-modal-title`}>{osVersion} </span>
|
||||
<span className={`${baseClass}__os-modal-updated`}>
|
||||
Reported {humanHostDetailUpdated(titleData.detail_updated_at)}
|
||||
Reported {humanHostDetailUpdated(detailsUpdatedAt)}
|
||||
</span>
|
||||
</p>
|
||||
<span className={`${baseClass}__os-modal-example-title`}>
|
||||
|
|
|
|||
|
|
@ -152,38 +152,6 @@ const About = ({
|
|||
);
|
||||
};
|
||||
|
||||
if (deviceUser) {
|
||||
return (
|
||||
<div className="section about">
|
||||
<p className="section__header">About</p>
|
||||
<div className="info-grid">
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Last restarted</span>
|
||||
<span className="info-grid__data">
|
||||
{humanHostLastRestart(
|
||||
aboutData.detail_updated_at,
|
||||
aboutData.uptime
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Hardware model</span>
|
||||
<span className="info-grid__data">{aboutData.hardware_model}</span>
|
||||
</div>
|
||||
<div className="info-grid__block">
|
||||
<span className="info-grid__header">Added to Fleet</span>
|
||||
<span className="info-grid__data">
|
||||
{wrapFleetHelper(humanHostEnrolled, aboutData.last_enrolled_at)}
|
||||
</span>
|
||||
</div>
|
||||
{renderSerialAndIPs()}
|
||||
{renderDeviceUser()}
|
||||
{renderBattery()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="section about">
|
||||
<p className="section__header">About</p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import DiskSpaceGraph from "components/DiskSpaceGraph";
|
||||
|
|
@ -13,9 +14,14 @@ import IssueIcon from "../../../../../../assets/images/icon-issue-fleet-black-50
|
|||
|
||||
const baseClass = "host-summary";
|
||||
|
||||
interface IHostDiskEncryptionProps {
|
||||
enabled?: boolean;
|
||||
tooltip?: string;
|
||||
}
|
||||
interface IHostSummaryProps {
|
||||
statusClassName: string;
|
||||
titleData: any; // TODO: create interfaces for this and use consistently across host pages and related helpers
|
||||
diskEncryption?: IHostDiskEncryptionProps;
|
||||
isPremiumTier?: boolean;
|
||||
isOnlyObserver?: boolean;
|
||||
toggleOSPolicyModal?: () => void;
|
||||
|
|
@ -30,6 +36,7 @@ interface IHostSummaryProps {
|
|||
const HostSummary = ({
|
||||
statusClassName,
|
||||
titleData,
|
||||
diskEncryption,
|
||||
isPremiumTier,
|
||||
isOnlyObserver,
|
||||
toggleOSPolicyModal,
|
||||
|
|
@ -131,13 +138,9 @@ const HostSummary = ({
|
|||
</span>
|
||||
</div>
|
||||
{titleData.issues?.total_issues_count > 0 &&
|
||||
deviceUser &&
|
||||
isPremiumTier &&
|
||||
renderIssues()}
|
||||
{titleData.issues?.total_issues_count > 0 &&
|
||||
!deviceUser &&
|
||||
renderIssues()}
|
||||
{!deviceUser && isPremiumTier && renderHostTeam()}
|
||||
{isPremiumTier && renderHostTeam()}
|
||||
<div className="info-flex__item info-flex__item--title">
|
||||
<span className="info-flex__header">Disk space</span>
|
||||
<DiskSpaceGraph
|
||||
|
|
@ -147,6 +150,17 @@ const HostSummary = ({
|
|||
id={"disk-space-tooltip"}
|
||||
/>
|
||||
</div>
|
||||
{typeof diskEncryption?.enabled === "boolean" &&
|
||||
diskEncryption?.tooltip ? (
|
||||
<div className="info-flex__item info-flex__item--title">
|
||||
<span className="info-flex__header">Disk encryption</span>
|
||||
<TooltipWrapper tipContent={diskEncryption.tooltip}>
|
||||
{diskEncryption.enabled ? "On" : "Off"}
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<div className="info-flex__item info-flex__item--title">
|
||||
<span className="info-flex__header">Memory</span>
|
||||
<span className="info-flex__data">
|
||||
|
|
@ -173,12 +187,10 @@ const HostSummary = ({
|
|||
)}
|
||||
</span>
|
||||
</div>
|
||||
{!deviceUser && (
|
||||
<div className="info-flex__item info-flex__item--title">
|
||||
<span className="info-flex__header">Osquery</span>
|
||||
<span className="info-flex__data">{titleData.osquery_version}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="info-flex__item info-flex__item--title">
|
||||
<span className="info-flex__header">Osquery</span>
|
||||
<span className="info-flex__data">{titleData.osquery_version}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -617,6 +617,31 @@ export const humanHostDetailUpdated = (detailUpdated?: string): string => {
|
|||
}
|
||||
};
|
||||
|
||||
const DISK_ENCRYPTION_MESSAGES = {
|
||||
darwin: {
|
||||
enabled:
|
||||
"The disk is encrypted. The user must enter their<br/> password when they start their computer.",
|
||||
disabled:
|
||||
"The disk might be encrypted, but FileVault is off. The<br/> disk can be accessed without entering a password.",
|
||||
},
|
||||
windows: {
|
||||
enabled:
|
||||
"The disk is encrypted. If recently turned on,<br/> encryption could take awhile.",
|
||||
disabled: "The disk is unencrypted.",
|
||||
},
|
||||
};
|
||||
|
||||
export const humanHostDiskEncryptionEnabled = (
|
||||
platform?: string,
|
||||
isDiskEncrypted = false
|
||||
): string => {
|
||||
if (platform !== "windows" && platform !== "darwin") {
|
||||
return "Disk encryption is enabled.";
|
||||
}
|
||||
const encryptionStatus = isDiskEncrypted ? "enabled" : "disabled";
|
||||
return DISK_ENCRYPTION_MESSAGES[platform][encryptionStatus];
|
||||
};
|
||||
|
||||
export const hostTeamName = (teamName: string | null): string => {
|
||||
if (!teamName) {
|
||||
return "No team";
|
||||
|
|
@ -805,6 +830,7 @@ export default {
|
|||
humanHostEnrolled,
|
||||
humanHostMemory,
|
||||
humanHostDetailUpdated,
|
||||
humanHostDiskEncryptionEnabled,
|
||||
hostTeamName,
|
||||
humanQueryLastRun,
|
||||
inMilliseconds,
|
||||
|
|
|
|||
Loading…
Reference in a new issue