Sandbox UI: Homepage, CTA to add personal device (#6775)

This commit is contained in:
RachelElysia 2022-07-25 16:42:03 -04:00 committed by GitHub
parent 8d3de2a70a
commit 08485b3a5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 61 deletions

View file

@ -0,0 +1 @@
* Dashboard updates for app, Fleet Sandbox, and Fleet Preview

View file

@ -3,10 +3,15 @@ import { useQuery } from "react-query";
import { AppContext } from "context/app";
import { find } from "lodash";
import {
IEnrollSecret,
IEnrollSecretsResponse,
} from "interfaces/enroll_secret";
import { IHostSummary, IHostSummaryPlatforms } from "interfaces/host_summary";
import { ILabelSummary } from "interfaces/label";
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 teamsAPI, { ILoadTeamsResponse } from "services/entities/teams";
import sortUtils from "utilities/sort";
@ -26,6 +31,7 @@ import WelcomeHost from "./cards/WelcomeHost";
import MDM from "./cards/MDM";
import Munki from "./cards/Munki";
import OperatingSystems from "./cards/OperatingSystems";
import AddHostsModal from "../../components/AddHostsModal";
import ExternalURLIcon from "../../../assets/images/icon-external-url-12x12@2x.png";
const baseClass = "homepage";
@ -34,9 +40,13 @@ const Homepage = (): JSX.Element => {
const {
config,
currentTeam,
isGlobalAdmin,
isGlobalMaintainer,
isTeamAdmin,
isTeamMaintainer,
isPremiumTier,
isFreeTier,
isPreviewMode,
isSandboxMode,
isOnGlobalTeam,
setCurrentTeam,
} = useContext(AppContext);
@ -54,25 +64,30 @@ const Homepage = (): JSX.Element => {
const [showSoftwareUI, setShowSoftwareUI] = useState<boolean>(false);
const [showMunkiUI, setShowMunkiUI] = useState<boolean>(false);
const [showMDMUI, setShowMDMUI] = useState<boolean>(false);
const [showAddHostsModal, setShowAddHostsModal] = useState<boolean>(false);
const [showOperatingSystemsUI, setShowOperatingSystemsUI] = useState<boolean>(
false
);
const [showHostsUI, setShowHostsUI] = useState<boolean>(false); // Hides UI on first load only
const { data: teams } = useQuery<ILoadTeamsResponse, Error, ITeam[]>(
["teams"],
() => teamsAPI.loadAll(),
{
enabled: !!isPremiumTier,
select: (data: ILoadTeamsResponse) =>
data.teams.sort((a, b) => sortUtils.caseInsensitiveAsc(a.name, b.name)),
onSuccess: (responseTeams) => {
if (!currentTeam && !isOnGlobalTeam && responseTeams.length) {
setCurrentTeam(responseTeams[0]);
}
},
}
);
const canEnrollHosts =
isGlobalAdmin || isGlobalMaintainer || isTeamAdmin || isTeamMaintainer;
const canEnrollGlobalHosts = isGlobalAdmin || isGlobalMaintainer;
const { data: teams, isLoading: isLoadingTeams } = useQuery<
ILoadTeamsResponse,
Error,
ITeam[]
>(["teams"], () => teamsAPI.loadAll(), {
enabled: !!isPremiumTier,
select: (data: ILoadTeamsResponse) =>
data.teams.sort((a, b) => sortUtils.caseInsensitiveAsc(a.name, b.name)),
onSuccess: (responseTeams) => {
if (!currentTeam && !isOnGlobalTeam && responseTeams.length) {
setCurrentTeam(responseTeams[0]);
}
},
});
const {
data: hostSummaryData,
@ -108,11 +123,28 @@ const Homepage = (): JSX.Element => {
}
);
const {
isLoading: isGlobalSecretsLoading,
data: globalSecrets,
refetch: refetchGlobalSecrets,
} = useQuery<IEnrollSecretsResponse, Error, IEnrollSecret[]>(
["global secrets"],
() => enrollSecretsAPI.getGlobalEnrollSecrets(),
{
enabled: !!canEnrollGlobalHosts,
select: (data: IEnrollSecretsResponse) => data.secrets,
}
);
const handleTeamSelect = (teamId: number) => {
const selectedTeam = find(teams, ["id", teamId]);
setCurrentTeam(selectedTeam);
};
const toggleAddHostsModal = () => {
setShowAddHostsModal(!showAddHostsModal);
};
const HostsSummaryCard = useInfoCard({
title: "Hosts",
action: {
@ -156,11 +188,20 @@ const Homepage = (): JSX.Element => {
const WelcomeHostCard = useInfoCard({
title: "Welcome to Fleet",
children: <WelcomeHost />,
showTitle: true,
children: (
<WelcomeHost
totalsHostsCount={
(hostSummaryData && hostSummaryData.totals_hosts_count) || 0
}
toggleAddHostsModal={toggleAddHostsModal}
/>
),
});
const LearnFleetCard = useInfoCard({
title: "Learn how to use Fleet",
showTitle: true,
children: <LearnFleet />,
});
@ -252,16 +293,14 @@ const Homepage = (): JSX.Element => {
const allLayout = () => (
<div className={`${baseClass}__section`}>
{isPreviewMode && (
{hostSummaryData && hostSummaryData?.totals_hosts_count < 2 && (
<>
{WelcomeHostCard}
{LearnFleetCard}
</>
)}
{SoftwareCard}
{!isPreviewMode && !currentTeam && isOnGlobalTeam && (
<>{ActivityFeedCard}</>
)}
{!currentTeam && isOnGlobalTeam && <>{ActivityFeedCard}</>}
</div>
);
@ -289,6 +328,19 @@ const Homepage = (): JSX.Element => {
}
};
const renderAddHostsModal = () => {
const enrollSecret = globalSecrets?.[0].secret;
return (
<AddHostsModal
currentTeam={currentTeam}
enrollSecret={enrollSecret}
isLoading={isLoadingTeams || isGlobalSecretsLoading}
isSandboxMode={!!isSandboxMode}
onCancel={toggleAddHostsModal}
/>
);
};
return (
<div className={baseClass}>
<div className={`${baseClass}__wrapper body-wrap`}>
@ -336,6 +388,7 @@ const Homepage = (): JSX.Element => {
</>
</div>
{renderCards()}
{showAddHostsModal && renderAddHostsModal()}
</div>
</div>
);

View file

@ -145,11 +145,10 @@ const ActivityFeed = ({
return (
<div className={`${baseClass}__no-activities`}>
<p>
<b>This is the start of your Fleet activities.</b>
<b>Fleet has not recorded any activity.</b>
</p>
<p>
Did you recently edit your queries, update your packs, or run a live
query? Try again in a few seconds as the system catches up.
Try editing a query, updating your policies, or running a live query.
</p>
</div>
);

View file

@ -24,12 +24,20 @@ interface IHostResponse {
host: IHost;
}
interface IWelcomeHostCardProps {
totalsHostsCount: number;
toggleAddHostsModal: (showAddHostsModal: boolean) => void;
}
const baseClass = "welcome-host";
const HOST_ID = 1;
const policyPass = "pass";
const policyFail = "fail";
const WelcomeHost = (): JSX.Element => {
const WelcomeHost = ({
totalsHostsCount,
toggleAddHostsModal,
}: IWelcomeHostCardProps): JSX.Element => {
const { renderFlash } = useContext(NotificationContext);
const [refetchStartTime, setRefetchStartTime] = useState<number | null>(null);
const [currentPolicyShown, setCurrentPolicyShown] = useState<IHostPolicy>();
@ -129,7 +137,6 @@ const WelcomeHost = (): JSX.Element => {
return (
<div className={baseClass}>
<div className={`${baseClass}__loading`}>
<p>Adding your device to Fleet</p>
<Spinner />
</div>
</div>
@ -137,6 +144,26 @@ const WelcomeHost = (): JSX.Element => {
}
if (loadingHostError) {
return (
<div className={baseClass}>
<div className={`${baseClass}__empty-hosts`}>
<p>Add your personal device to assess the security of your device.</p>
<p>
In Fleet, laptops, workstations, and servers are referred to as
&quot;hosts.&quot;
</p>
<Button
onClick={toggleAddHostsModal}
className={`${baseClass}__add-host button button--brand`}
>
<span>Add hosts</span>
</Button>
</div>
</div>
);
}
if (totalsHostsCount === 1 && host && host.status === "offline") {
return (
<div className={baseClass}>
<div className={`${baseClass}__error`}>
@ -194,7 +221,7 @@ const WelcomeHost = (): JSX.Element => {
);
}
if (host) {
if (totalsHostsCount === 1 && host && host.status === "online") {
return (
<div className={baseClass}>
<div className={`${baseClass}__intro`}>
@ -204,53 +231,48 @@ const WelcomeHost = (): JSX.Element => {
{host.hostname}
<img alt="" src={LinkArrow} />
</Link>
<p>
Your device is successully connected to this local preview of
Fleet.
</p>
<p>Your host is successfully connected to Fleet.</p>
</div>
</div>
<div className={`${baseClass}__blurb`}>
<p>
Fleet already ran the following checks to assess the security of
Fleet already ran the following policies to assess the security of
your device:{" "}
</p>
</div>
<div className={`${baseClass}__policies`}>
{host.policies?.slice(0, 10).map((p) => {
{host.policies?.slice(0, 3).map((p) => {
if (p.response) {
return (
<div className="policy-block">
<div className="info">
<img
alt={p.response}
src={p.response === policyPass ? IconPassed : IconError}
/>
{p.name}
</div>
<Button
variant="text-icon"
onClick={() => handlePolicyModal(p.id)}
>
<Button
variant="text-icon"
onClick={() => handlePolicyModal(p.id)}
>
<div className="policy-block">
<div className="info">
<img
alt={p.response}
src={p.response === policyPass ? IconPassed : IconError}
/>
{p.name}
</div>
<img alt="" src={IconChevron} />
</Button>
</div>
</div>
</Button>
);
}
return null;
})}
{host.policies?.length > 10 && (
{host.policies?.length > 3 && (
<Link to={PATHS.HOST_DETAILS(host)} className="external-link">
Go to Host details to see all checks
Go to Host details to see all policies
<img alt="" src={LinkArrow} />
</Link>
)}
</div>
<div className={`${baseClass}__blurb`}>
<p>
Resolved a failing check? Refetch your device information to verify.
</p>
<p>Resolved a failing policy? Refetch your host vitals to verify.</p>
</div>
<div className={`${baseClass}__refetch`}>
<Button

View file

@ -57,14 +57,28 @@
margin-top: $pad-large;
margin-bottom: $pad-large;
> * {
margin: 4px 0;
}
button {
height: auto;
width: 100%;
img {
transform: scale(0.58) rotate(-90deg);
position: relative;
top: 1px;
}
}
.policy-block {
width: 100%;
padding: $pad-small 12px;
border: 1px solid $ui-fleet-black-25;
border-radius: 6px;
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 0;
.info {
font-size: $small;
@ -79,15 +93,11 @@
top: -1px;
}
}
button {
height: auto;
img {
transform: scale(0.58) rotate(-90deg);
position: relative;
top: 1px;
}
}
}
}
&__loading {
.loading-spinner {
margin: $pad-medium auto;
}
}
&__refetch {