Homepage UI: Add host total count and host status count (#2894)

This commit is contained in:
RachelElysia 2021-11-12 09:27:05 -05:00 committed by GitHub
parent de1084e920
commit 7db762f8e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 208 additions and 51 deletions

View File

@ -0,0 +1 @@
* Homepage UI now renders total count, online count, offline count, and new host count for all dashboards

View File

@ -15,6 +15,10 @@ export default {
FORGOT_PASSWORD: "/v1/fleet/forgot_password",
GLOBAL_POLICIES: "/v1/fleet/global/policies",
GLOBAL_SCHEDULE: "/v1/fleet/global/schedule",
HOST_SUMMARY: (teamId: number | undefined): string => {
const teamString = teamId ? `?team_id=${teamId}` : "";
return `/v1/fleet/host_summary${teamString}`;
},
HOSTS: "/v1/fleet/hosts",
HOSTS_COUNT: "/v1/fleet/hosts/count",
HOSTS_DELETE: "/v1/fleet/hosts/delete",
@ -45,11 +49,11 @@ export default {
STATUS_LIVE_QUERY: "/v1/fleet/status/live_query",
STATUS_RESULT_STORE: "/v1/fleet/status/result_store",
TARGETS: "/v1/fleet/targets",
TEAM_POLICIES: (id: number): string => {
return `/v1/fleet/teams/${id}/policies`;
TEAM_POLICIES: (teamId: number): string => {
return `/v1/fleet/teams/${teamId}/policies`;
},
TEAM_SCHEDULE: (id: number): string => {
return `/v1/fleet/teams/${id}/schedule`;
TEAM_SCHEDULE: (teamId: number): string => {
return `/v1/fleet/teams/${teamId}/schedule`;
},
TEAMS: "/v1/fleet/teams",
TEAMS_MEMBERS: (teamId: number): string => {

View File

@ -7,7 +7,14 @@ export default PropTypes.shape({
new_count: PropTypes.number,
});
export interface IHostSummaryPlatforms {
platform: string;
hosts_count: number;
}
export interface IHostSummary {
totals_hosts_count: number;
platforms: IHostSummaryPlatforms[] | null;
online_count: number;
offline_count: number;
mia_count: number;

View File

@ -5,13 +5,16 @@ import { Link } from "react-router";
import { AppContext } from "context/app";
import { find } from "lodash";
import hostSummaryAPI from "services/entities/host_summary";
import teamsAPI from "services/entities/teams";
import { ITeam } from "interfaces/team";
import { IHostSummary, IHostSummaryPlatforms } from "interfaces/host_summary";
import { ISoftware } from "interfaces/software";
import { ITeam } from "interfaces/team";
import TeamsDropdown from "components/TeamsDropdown";
import Button from "components/buttons/Button";
import InfoCard from "./components/InfoCard";
import HostsStatus from "./cards/HostsStatus";
import HostsSummary from "./cards/HostsSummary";
import ActivityFeed from "./cards/ActivityFeed";
import Software from "./cards/Software";
@ -44,6 +47,12 @@ const Homepage = (): JSX.Element => {
const [isSoftwareModalOpen, setIsSoftwareModalOpen] = useState<boolean>(
false
);
const [totalCount, setTotalCount] = useState<string | undefined>();
const [macCount, setMacCount] = useState<string>("0");
const [windowsCount, setWindowsCount] = useState<string>("0");
const [onlineCount, setOnlineCount] = useState<string | undefined>();
const [offlineCount, setOfflineCount] = useState<string | undefined>();
const [newCount, setNewCount] = useState<string | undefined>();
const { data: teams, isLoading: isLoadingTeams } = useQuery<
ITeamsResponse,
@ -59,6 +68,30 @@ const Homepage = (): JSX.Element => {
setCurrentTeam(selectedTeam);
};
useQuery<IHostSummary, Error, IHostSummary>(
["host summary", currentTeam],
() => {
return hostSummaryAPI.getSummary(currentTeam?.id);
},
{
select: (data: IHostSummary) => data,
onSuccess: (data: any) => {
setTotalCount(data.totals_hosts_count.toLocaleString("en-US"));
setOnlineCount(data.online_count.toLocaleString("en-US"));
setOfflineCount(data.offline_count.toLocaleString("en-US"));
setNewCount(data.new_count.toLocaleString("en-US"));
const macHosts = data.platforms?.find(
(platform: IHostSummaryPlatforms) => platform.platform === "darwin"
) || { platform: "darwin", hosts_count: 0 };
setMacCount(macHosts.hosts_count.toLocaleString("en-US"));
const windowsHosts = data.platforms?.find(
(platform: IHostSummaryPlatforms) => platform.platform === "windows"
) || { platform: "windows", hosts_count: 0 };
setWindowsCount(windowsHosts.hosts_count.toLocaleString("en-US"));
},
}
);
return (
<div className={baseClass}>
<div className={`${baseClass}__header-wrap`}>
@ -88,8 +121,22 @@ const Homepage = (): JSX.Element => {
MANAGE_HOSTS + TAGGED_TEMPLATES.hostsByTeamRoute(currentTeam?.id),
text: "View all hosts",
}}
total_host_count={totalCount}
>
<HostsSummary currentTeamId={currentTeam?.id} />
<HostsSummary
currentTeamId={currentTeam?.id}
macCount={macCount}
windowsCount={windowsCount}
/>
</InfoCard>
</div>
<div className={`${baseClass}__section one-column`}>
<InfoCard title="">
<HostsStatus
onlineCount={onlineCount}
offlineCount={offlineCount}
newCount={newCount}
/>
</InfoCard>
</div>
{isPreviewMode && (

View File

@ -0,0 +1,52 @@
import React from "react";
const baseClass = "hosts-status";
interface IHostSummaryProps {
onlineCount: string | undefined;
offlineCount: string | undefined;
newCount: string | undefined;
}
const HostsStatus = ({
onlineCount,
offlineCount,
newCount,
}: IHostSummaryProps): JSX.Element => {
return (
<div className={baseClass}>
<div className={`${baseClass}__tile online-tile`}>
<div>
<div
className={`${baseClass}__tile-count ${baseClass}__tile-count--online`}
>
{onlineCount}
</div>
<div className={`${baseClass}__tile-description`}>Online hosts</div>
</div>
</div>
<div className={`${baseClass}__tile offline-tile`}>
<div>
<div
className={`${baseClass}__tile-count ${baseClass}__tile-count--offline`}
>
{offlineCount}
</div>
<div className={`${baseClass}__tile-description`}>Offline hosts</div>
</div>
</div>
<div className={`${baseClass}__tile new-tile`}>
<div>
<div
className={`${baseClass}__tile-count ${baseClass}__tile-count--new`}
>
{newCount}
</div>
<div className={`${baseClass}__tile-description`}>New hosts</div>
</div>
</div>
</div>
);
};
export default HostsStatus;

View File

@ -0,0 +1,42 @@
.hosts-status {
width: 100%;
display: flex;
justify-content: space-around;
font-size: $x-small;
&__tile {
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
&:first-of-type {
justify-content: flex-end;
}
&:last-of-type {
justify-content: flex-start;
}
}
&__tile-count {
font-size: $large;
&:before {
border-radius: 100%;
content: " ";
display: inline-block;
margin-right: $pad-small;
height: 8px;
width: 8px;
margin-bottom: 5px;
}
&--online:before {
background-color: $ui-success;
}
&--offline:before {
background-color: $ui-offline;
}
}
}

View File

@ -0,0 +1 @@
export { default } from "./HostsStatus";

View File

@ -11,8 +11,10 @@ import MacIcon from "../../../../../assets/images/icon-mac-48x48@2x.png";
const baseClass = "hosts-summary";
interface IHostsSummaryProps {
interface IHostSummaryProps {
currentTeamId: number | undefined;
macCount: string | undefined;
windowsCount: string | undefined;
}
interface ILabelsResponse {
@ -23,9 +25,11 @@ interface IHostCountResponse {
count: number;
}
const HostsSummary = ({ currentTeamId }: IHostsSummaryProps): JSX.Element => {
const [macCount, setMacCount] = useState<string | undefined>();
const [windowsCount, setWindowsCount] = useState<string | undefined>();
const HostsSummary = ({
currentTeamId,
macCount,
windowsCount,
}: IHostSummaryProps): JSX.Element => {
const [linuxCount, setLinuxCount] = useState<string | undefined>();
const getLabel = (labelString: string, labels: ILabel[]) => {
@ -41,43 +45,6 @@ const HostsSummary = ({ currentTeamId }: IHostsSummaryProps): JSX.Element => {
}
);
useQuery<IHostCountResponse, Error, number>(
["mac host count", currentTeamId],
() => {
const macOsLabel = getLabel("macOS", labels || []);
return (
hostCountAPI.load({
selectedLabels: [`labels/${macOsLabel[0].id}`],
teamId: currentTeamId,
}) || { count: 0 }
);
},
{
select: (data: IHostCountResponse) => data.count,
enabled: !!labels,
onSuccess: (data: number) => setMacCount(data.toLocaleString("en-US")),
}
);
useQuery<IHostCountResponse, Error, number>(
["windows host count", currentTeamId],
() => {
const windowsLabel = getLabel("MS Windows", labels || []);
return (
hostCountAPI.load({
selectedLabels: [`labels/${windowsLabel[0].id}`],
teamId: currentTeamId,
}) || { count: 0 }
);
},
{
select: (data: IHostCountResponse) => data.count,
enabled: !!labels,
onSuccess: (data: number) =>
setWindowsCount(data.toLocaleString("en-US")),
}
);
useQuery<IHostCountResponse, Error, number>(
["linux host count", currentTeamId],
() => {

View File

@ -18,11 +18,17 @@ interface IInfoCardProps {
text: string;
onClick?: () => void;
};
total_host_count?: string;
}
const baseClass = "homepage-info-card";
const InfoCard = ({ title, children, action }: IInfoCardProps) => {
const InfoCard = ({
title,
children,
action,
total_host_count,
}: IInfoCardProps) => {
const renderAction = () => {
if (action) {
if (action.type === "button") {
@ -53,8 +59,11 @@ const InfoCard = ({ title, children, action }: IInfoCardProps) => {
return (
<div className={baseClass}>
<div className={`${baseClass}__section-title`}>
<h2>{title}</h2>
<div className={`${baseClass}__section-title-cta`}>
<div className={`${baseClass}__section-title`}>
<h2>{title}</h2>
{total_host_count && <span>{total_host_count}</span>}
</div>
{renderAction()}
</div>
{children}

View File

@ -7,7 +7,7 @@
box-shadow: 0 2px 0 0 $ui-fleet-blue-15;
box-sizing: border-box;
&__section-title {
&__section-title-cta {
display: flex;
justify-content: space-between;
@ -16,6 +16,22 @@
}
}
&__section-title {
display: flex;
flex-direction: row;
align-items: center;
span {
background-color: $core-vibrant-blue;
color: $core-white;
font-size: $xx-small;
font-weight: $bold;
padding: 2px 4px;
border-radius: 4px;
margin-left: $pad-small;
}
}
&__action-button {
display: flex;
align-items: center;

View File

@ -0,0 +1,11 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import sendRequest from "services";
import endpoints from "fleet/endpoints";
export default {
getSummary: (teamId: number | undefined) => {
const { HOST_SUMMARY } = endpoints;
return sendRequest("GET", HOST_SUMMARY(teamId));
},
};