fleet/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx

547 lines
17 KiB
TypeScript
Raw Normal View History

import React, { useCallback, useContext, useEffect, useState } from "react";
import { useQuery } from "react-query";
import { useDispatch } from "react-redux";
import { InjectedRouter } from "react-router/lib/Router";
import { noop } from "lodash";
2021-08-30 23:02:53 +00:00
import { AppContext } from "context/app";
import { PolicyContext } from "context/policy";
import { TableContext } from "context/table";
import { inMilliseconds, secondsToHms } from "fleet/helpers";
import { IPolicyStats, ILoadAllPoliciesResponse } from "interfaces/policy";
import { IWebhookFailingPolicies } from "interfaces/webhook";
import { IConfig, IConfigNested } from "interfaces/config";
// @ts-ignore
import { getConfig } from "redux/nodes/app/actions";
2021-08-30 23:02:53 +00:00
// @ts-ignore
import { renderFlash } from "redux/nodes/notifications/actions";
import PATHS from "router/paths";
2021-12-28 18:07:18 +00:00
import configAPI from "services/entities/config";
import globalPoliciesAPI from "services/entities/global_policies";
import teamPoliciesAPI from "services/entities/team_policies";
import usersAPI, { IGetMeResponse } from "services/entities/users";
import { DEFAULT_POLICY } from "utilities/constants";
2021-08-30 23:02:53 +00:00
import Button from "components/buttons/Button";
import RevealButton from "components/buttons/RevealButton";
2021-08-30 23:02:53 +00:00
import InfoBanner from "components/InfoBanner/InfoBanner";
import Spinner from "components/Spinner";
import TeamsDropdown from "components/TeamsDropdown";
import TableDataError from "components/TableDataError";
2021-08-30 23:02:53 +00:00
import PoliciesListWrapper from "./components/PoliciesListWrapper";
2021-12-28 18:07:18 +00:00
import ManageAutomationsModal from "./components/ManageAutomationsModal";
import AddPolicyModal from "./components/AddPolicyModal";
2021-08-30 23:02:53 +00:00
import RemovePoliciesModal from "./components/RemovePoliciesModal";
interface IManagePoliciesPageProps {
router: InjectedRouter; // v3
location: {
action: string;
hash: string;
key: string;
pathname: string;
query: { team_id?: string };
search: string;
};
}
2021-08-30 23:02:53 +00:00
const baseClass = "manage-policies-page";
const DOCS_LINK =
"https://fleetdm.com/docs/deploying/configuration#osquery-policy-update-interval";
2021-08-30 23:02:53 +00:00
const ManagePolicyPage = ({
router,
location,
}: IManagePoliciesPageProps): JSX.Element => {
2021-08-30 23:02:53 +00:00
const dispatch = useDispatch();
const {
availableTeams,
config,
isGlobalAdmin,
isGlobalMaintainer,
isOnGlobalTeam,
isFreeTier,
isPremiumTier,
isTeamAdmin,
isTeamMaintainer,
currentTeam,
setAvailableTeams,
setCurrentUser,
setCurrentTeam,
setConfig,
} = useContext(AppContext);
2021-08-30 23:02:53 +00:00
const teamId = location?.query?.team_id
? parseInt(location?.query?.team_id, 10)
: 0;
const {
setLastEditedQueryName,
setLastEditedQueryDescription,
setLastEditedQueryBody,
setLastEditedQueryResolution,
setLastEditedQueryPlatform,
} = useContext(PolicyContext);
const { setResetSelectedRows } = useContext(TableContext);
const [selectedPolicyIds, setSelectedPolicyIds] = useState<number[]>([]);
2021-12-28 18:07:18 +00:00
const [showManageAutomationsModal, setShowManageAutomationsModal] = useState(
false
);
const [showPreviewPayloadModal, setShowPreviewPayloadModal] = useState(false);
const [showAddPolicyModal, setShowAddPolicyModal] = useState(false);
2021-08-30 23:02:53 +00:00
const [showRemovePoliciesModal, setShowRemovePoliciesModal] = useState(false);
const [showInheritedPolicies, setShowInheritedPolicies] = useState(false);
2021-12-28 18:07:18 +00:00
const [currentAutomatedPolicies, setCurrentAutomatedPolicies] = useState<
number[]
>();
2021-08-30 23:02:53 +00:00
useQuery(["me"], () => usersAPI.me(), {
onSuccess: ({ user, available_teams }: IGetMeResponse) => {
setCurrentUser(user);
setAvailableTeams(available_teams);
},
});
const {
data: globalPolicies,
error: globalPoliciesError,
isLoading: isLoadingGlobalPolicies,
isStale: isStaleGlobalPolicies,
refetch: refetchGlobalPolicies,
} = useQuery<ILoadAllPoliciesResponse, Error, IPolicyStats[]>(
["globalPolicies"],
() => {
return globalPoliciesAPI.loadAll();
},
{
enabled: !!availableTeams,
select: (data) => data.policies,
onSuccess: () => setLastEditedQueryPlatform(""),
staleTime: 3000,
2021-08-30 23:02:53 +00:00
}
);
const {
data: teamPolicies,
error: teamPoliciesError,
isLoading: isLoadingTeamPolicies,
refetch: refetchTeamPolicies,
} = useQuery<ILoadAllPoliciesResponse, Error, IPolicyStats[]>(
["teamPolicies", teamId],
() => teamPoliciesAPI.loadAll(teamId),
{
enabled: !!availableTeams && isPremiumTier && !!teamId,
select: (data) => data.policies,
}
);
const canAddOrRemovePolicy =
isGlobalAdmin || isGlobalMaintainer || isTeamMaintainer || isTeamAdmin;
const {
data: failingPoliciesWebhook,
isLoading: isLoadingFailingPoliciesWebhook,
refetch: refetchFailingPoliciesWebhook,
} = useQuery<IConfigNested, Error, IWebhookFailingPolicies>(
["config"],
() => configAPI.loadAll(),
{
enabled: canAddOrRemovePolicy,
select: (data: IConfigNested) =>
data.webhook_settings.failing_policies_webhook,
onSuccess: (data) => {
setCurrentAutomatedPolicies(data.policy_ids);
},
}
);
const refetchPolicies = (id?: number) => {
refetchGlobalPolicies();
if (id) {
refetchTeamPolicies();
}
};
const findAvailableTeam = (id: number) => {
return availableTeams?.find((t) => t.id === id);
};
const handleTeamSelect = (id: number) => {
const { MANAGE_POLICIES } = PATHS;
const selectedTeam = findAvailableTeam(id);
const path = selectedTeam?.id
? `${MANAGE_POLICIES}?team_id=${selectedTeam.id}`
: MANAGE_POLICIES;
router.replace(path);
setShowInheritedPolicies(false);
setSelectedPolicyIds([]);
setCurrentTeam(selectedTeam);
isStaleGlobalPolicies && refetchGlobalPolicies();
};
2021-08-30 23:02:53 +00:00
2021-12-28 18:07:18 +00:00
const toggleManageAutomationsModal = () =>
setShowManageAutomationsModal(!showManageAutomationsModal);
const togglePreviewPayloadModal = useCallback(() => {
setShowPreviewPayloadModal(!showPreviewPayloadModal);
}, [setShowPreviewPayloadModal, showPreviewPayloadModal]);
const toggleAddPolicyModal = () => setShowAddPolicyModal(!showAddPolicyModal);
const toggleRemovePoliciesModal = () =>
2021-08-30 23:02:53 +00:00
setShowRemovePoliciesModal(!showRemovePoliciesModal);
const toggleShowInheritedPolicies = () =>
setShowInheritedPolicies(!showInheritedPolicies);
2021-12-28 18:07:18 +00:00
const onManageAutomationsClick = () => {
toggleManageAutomationsModal();
};
const onCreateWebhookSubmit = async ({
destination_url,
policy_ids,
enable_failing_policies_webhook,
}: IWebhookFailingPolicies) => {
try {
const request = configAPI.update({
webhook_settings: {
failing_policies_webhook: {
destination_url,
policy_ids,
enable_failing_policies_webhook,
},
},
});
await request.then(() => {
dispatch(
renderFlash("success", "Successfully updated policy automations.")
);
});
} catch {
dispatch(
renderFlash(
"error",
"Could not update policy automations. Please try again."
)
);
} finally {
toggleManageAutomationsModal();
refetchFailingPoliciesWebhook();
// Config must be updated in both Redux and AppContext
dispatch(getConfig())
.then((configState: IConfig) => {
setConfig(configState);
})
.catch(() => false);
2021-12-28 18:07:18 +00:00
}
};
const onAddPolicyClick = () => {
setLastEditedQueryName("");
setLastEditedQueryDescription("");
setLastEditedQueryBody(DEFAULT_POLICY.query);
setLastEditedQueryResolution("");
toggleAddPolicyModal();
};
const onRemovePoliciesClick = (selectedTableIds: number[]): void => {
toggleRemovePoliciesModal();
setSelectedPolicyIds(selectedTableIds);
};
const onRemovePoliciesSubmit = async () => {
const id = currentTeam?.id;
try {
const request = id
? teamPoliciesAPI.destroy(id, selectedPolicyIds)
: globalPoliciesAPI.destroy(selectedPolicyIds);
2021-08-30 23:02:53 +00:00
await request.then(() => {
2021-08-30 23:02:53 +00:00
dispatch(
renderFlash(
"success",
`Successfully removed ${
selectedPolicyIds?.length === 1 ? "policy" : "policies"
2021-08-30 23:02:53 +00:00
}.`
)
);
setResetSelectedRows(true);
refetchPolicies(id);
2021-08-30 23:02:53 +00:00
});
} catch {
dispatch(
renderFlash(
"error",
`Unable to remove ${
selectedPolicyIds?.length === 1 ? "policy" : "policies"
}. Please try again.`
)
);
} finally {
toggleRemovePoliciesModal();
}
};
2021-08-30 23:02:53 +00:00
const inheritedPoliciesButtonText = (
showPolicies: boolean,
count: number
) => {
return `${showPolicies ? "Hide" : "Show"} ${count} inherited ${
count > 1 ? "policies" : "policy"
}`;
};
const policyUpdateInterval =
secondsToHms(inMilliseconds(config?.osquery_policy || 0) / 1000) ||
"osquery policy update interval";
const showTeamDescription = isPremiumTier && !!teamId;
2021-10-02 00:35:13 +00:00
const showInfoBanner =
(teamId && !teamPoliciesError && !!teamPolicies?.length) ||
(!teamId && !globalPoliciesError && !!globalPolicies?.length);
2021-10-02 00:35:13 +00:00
const showInheritedPoliciesButton =
!!teamId &&
!isLoadingTeamPolicies &&
!teamPoliciesError &&
!isLoadingGlobalPolicies &&
!globalPoliciesError &&
!!globalPolicies?.length;
// If team_id from URL query params is not valid, we instead use a default team
// either the current team (if any) or all teams (for global users) or
// the first available team (for non-global users)
const getValidatedTeamId = () => {
if (findAvailableTeam(teamId)) {
return teamId;
}
if (!teamId && currentTeam) {
return currentTeam.id;
}
if (!teamId && !currentTeam && !isOnGlobalTeam && availableTeams) {
return availableTeams[0]?.id;
}
return 0;
};
2021-10-02 00:35:13 +00:00
// If team_id or currentTeam doesn't match validated id, switch to validated id
useEffect(() => {
if (availableTeams) {
const validatedId = getValidatedTeamId();
if (validatedId !== currentTeam?.id || validatedId !== teamId) {
handleTeamSelect(validatedId);
}
}
}, [availableTeams]);
return !availableTeams ? (
<Spinner />
) : (
2021-08-30 23:02:53 +00:00
<div className={baseClass}>
<div className={`${baseClass}__wrapper body-wrap`}>
<div className={`${baseClass}__header-wrap`}>
<div className={`${baseClass}__header`}>
<div className={`${baseClass}__text`}>
<div className={`${baseClass}__title`}>
{isFreeTier && <h1>Policies</h1>}
{isPremiumTier &&
(availableTeams.length > 1 || isOnGlobalTeam) && (
<TeamsDropdown
currentUserTeams={availableTeams || []}
selectedTeamId={teamId}
onChange={(newSelectedValue: number) =>
handleTeamSelect(newSelectedValue)
}
/>
)}
{isPremiumTier &&
!isOnGlobalTeam &&
availableTeams.length === 1 && (
<h1>{availableTeams[0].name}</h1>
)}
</div>
2021-08-30 23:02:53 +00:00
</div>
</div>
2021-12-28 18:07:18 +00:00
<div className={`${baseClass} button-wrap`}>
2022-02-14 22:09:55 +00:00
{canAddOrRemovePolicy &&
teamId === 0 &&
!isLoadingFailingPoliciesWebhook &&
!isLoadingGlobalPolicies && (
<Button
onClick={() => onManageAutomationsClick()}
className={`${baseClass}__manage-automations button`}
variant="inverse"
>
<span>Manage automations</span>
</Button>
)}
{canAddOrRemovePolicy && (
2021-12-28 18:07:18 +00:00
<div className={`${baseClass}__action-button-container`}>
<Button
variant="brand"
className={`${baseClass}__select-policy-button`}
onClick={onAddPolicyClick}
>
Add a policy
</Button>
</div>
)}
</div>
2021-08-30 23:02:53 +00:00
</div>
<div className={`${baseClass}__description`}>
{showTeamDescription ? (
<p>
Add additional policies for <b>all hosts assigned to this team</b>
.
</p>
) : (
<p>
Add policies for <b>all of your hosts</b> to see which pass your
organizations standards.
</p>
)}
</div>
{!!policyUpdateInterval && showInfoBanner && (
2021-10-02 00:35:13 +00:00
<InfoBanner className={`${baseClass}__sandbox-info`}>
<p>
Your policies are checked every{" "}
<b>{policyUpdateInterval.trim()}</b>.{" "}
2021-10-02 00:35:13 +00:00
{isGlobalAdmin && (
<span>
Check out the Fleet documentation on{" "}
<a href={DOCS_LINK} target="_blank" rel="noreferrer">
<b>how to edit this frequency</b>
</a>
.
</span>
)}
</p>
</InfoBanner>
)}
<div>
{!!teamId && teamPoliciesError && <TableDataError />}
{!!teamId &&
!teamPoliciesError &&
(isLoadingTeamPolicies && isLoadingFailingPoliciesWebhook ? (
<Spinner />
) : (
<PoliciesListWrapper
policiesList={teamPolicies || []}
2021-12-28 18:07:18 +00:00
isLoading={
isLoadingTeamPolicies && isLoadingFailingPoliciesWebhook
}
onRemovePoliciesClick={onRemovePoliciesClick}
canAddOrRemovePolicy={canAddOrRemovePolicy}
currentTeam={currentTeam}
2021-12-28 18:07:18 +00:00
currentAutomatedPolicies={currentAutomatedPolicies}
/>
))}
{!teamId && globalPoliciesError && <TableDataError />}
{!teamId &&
!globalPoliciesError &&
(isLoadingGlobalPolicies ? (
<Spinner />
) : (
<PoliciesListWrapper
policiesList={globalPolicies || []}
2021-12-28 18:07:18 +00:00
isLoading={
isLoadingGlobalPolicies && isLoadingFailingPoliciesWebhook
}
onRemovePoliciesClick={onRemovePoliciesClick}
canAddOrRemovePolicy={canAddOrRemovePolicy}
currentTeam={currentTeam}
2021-12-28 18:07:18 +00:00
currentAutomatedPolicies={currentAutomatedPolicies}
/>
))}
2021-08-30 23:02:53 +00:00
</div>
2022-02-14 22:09:55 +00:00
{showInheritedPoliciesButton && globalPolicies && (
<RevealButton
isShowing={showInheritedPolicies}
baseClass={baseClass}
hideText={inheritedPoliciesButtonText(
showInheritedPolicies,
globalPolicies.length
)}
showText={inheritedPoliciesButtonText(
showInheritedPolicies,
globalPolicies.length
)}
caretPosition={"before"}
tooltipHtml={
'"All teams" policies are checked <br/> for this teams hosts.'
}
onClick={toggleShowInheritedPolicies}
/>
2021-10-02 00:35:13 +00:00
)}
{showInheritedPoliciesButton && showInheritedPolicies && (
<div className={`${baseClass}__inherited-policies-table`}>
{globalPoliciesError && <TableDataError />}
{!globalPoliciesError &&
(isLoadingGlobalPolicies ? (
<Spinner />
) : (
<PoliciesListWrapper
isLoading={
isLoadingGlobalPolicies && isLoadingFailingPoliciesWebhook
}
policiesList={globalPolicies || []}
onRemovePoliciesClick={noop}
resultsTitle="policies"
canAddOrRemovePolicy={canAddOrRemovePolicy}
tableType="inheritedPolicies"
currentTeam={currentTeam}
currentAutomatedPolicies={currentAutomatedPolicies}
/>
))}
2021-10-02 00:35:13 +00:00
</div>
)}
2021-12-28 18:07:18 +00:00
{showManageAutomationsModal && (
<ManageAutomationsModal
onCancel={toggleManageAutomationsModal}
onCreateWebhookSubmit={onCreateWebhookSubmit}
togglePreviewPayloadModal={togglePreviewPayloadModal}
showPreviewPayloadModal={showPreviewPayloadModal}
availablePolicies={globalPolicies || []}
2021-12-28 18:07:18 +00:00
currentAutomatedPolicies={currentAutomatedPolicies || []}
currentDestinationUrl={
(failingPoliciesWebhook &&
failingPoliciesWebhook.destination_url) ||
""
}
enableFailingPoliciesWebhook={
(failingPoliciesWebhook &&
failingPoliciesWebhook.enable_failing_policies_webhook) ||
false
}
2021-12-28 18:07:18 +00:00
/>
)}
{showAddPolicyModal && (
<AddPolicyModal
onCancel={toggleAddPolicyModal}
router={router}
teamId={teamId}
teamName={currentTeam?.name}
/>
)}
2021-08-30 23:02:53 +00:00
{showRemovePoliciesModal && (
<RemovePoliciesModal
onCancel={toggleRemovePoliciesModal}
onSubmit={onRemovePoliciesSubmit}
/>
)}
</div>
</div>
);
};
export default ManagePolicyPage;