mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Homepage UI: Add host total count and host status count (#2894)
This commit is contained in:
parent
de1084e920
commit
7db762f8e2
1
changes/issue-1995-homepage-various-host-counts
Normal file
1
changes/issue-1995-homepage-various-host-counts
Normal file
@ -0,0 +1 @@
|
||||
* Homepage UI now renders total count, online count, offline count, and new host count for all dashboards
|
@ -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 => {
|
||||
|
@ -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;
|
||||
|
@ -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 && (
|
||||
|
52
frontend/pages/Homepage/cards/HostsStatus/HostsStatus.tsx
Normal file
52
frontend/pages/Homepage/cards/HostsStatus/HostsStatus.tsx
Normal 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;
|
42
frontend/pages/Homepage/cards/HostsStatus/_styles.scss
Normal file
42
frontend/pages/Homepage/cards/HostsStatus/_styles.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
1
frontend/pages/Homepage/cards/HostsStatus/index.ts
Normal file
1
frontend/pages/Homepage/cards/HostsStatus/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from "./HostsStatus";
|
@ -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],
|
||||
() => {
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
11
frontend/services/entities/host_summary.ts
Normal file
11
frontend/services/entities/host_summary.ts
Normal 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));
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue
Block a user