diff --git a/changes/41753-policy-details-page b/changes/41753-policy-details-page new file mode 100644 index 00000000000..4cc329316b7 --- /dev/null +++ b/changes/41753-policy-details-page @@ -0,0 +1,3 @@ +- Fleet UI: Added new policy details page with read-only view of policy information +- Fleet UI: Updated edit policy page to redirect users with read-only access to policy details page. +- Fleet UI: Added dedicated `/policies/:id/live` route for running policies diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx index 05a9ee54e6b..e7723d6c78f 100644 --- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx +++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx @@ -132,7 +132,7 @@ const generateTableHeaders = ( } path={getPathWithQueryParams(PATHS.POLICY_DETAILS(id), { - team_id, + fleet_id: team_id, })} /> ); diff --git a/frontend/pages/policies/PolicyPage/index.ts b/frontend/pages/policies/PolicyPage/index.ts deleted file mode 100644 index f1da6f2c9b9..00000000000 --- a/frontend/pages/policies/PolicyPage/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./PolicyPage"; diff --git a/frontend/pages/policies/PolicyDetailsPage/PolicyDetailsPage.tsx b/frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx similarity index 86% rename from frontend/pages/policies/PolicyDetailsPage/PolicyDetailsPage.tsx rename to frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx index 89955b19120..7c5e56f5661 100644 --- a/frontend/pages/policies/PolicyDetailsPage/PolicyDetailsPage.tsx +++ b/frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx @@ -11,6 +11,7 @@ import { ILabelPolicy } from "interfaces/label"; import { API_ALL_TEAMS_ID, APP_CONTEXT_ALL_TEAMS_ID } from "interfaces/team"; import { PLATFORM_DISPLAY_NAMES, Platform } from "interfaces/platform"; import globalPoliciesAPI from "services/entities/global_policies"; +import teamPoliciesAPI from "services/entities/team_policies"; import teamsAPI, { ILoadTeamResponse } from "services/entities/teams"; import { addGravatarUrlToResource } from "utilities/helpers"; import { DOCUMENT_TITLE_SUFFIX } from "utilities/constants"; @@ -27,7 +28,7 @@ import Spinner from "components/Spinner"; import TooltipWrapper from "components/TooltipWrapper"; import Avatar from "components/Avatar"; import ShowQueryModal from "components/modals/ShowQueryModal"; -import PolicyAutomations from "pages/policies/PolicyPage/components/PolicyAutomations"; +import PolicyAutomations from "pages/policies/edit/components/PolicyAutomations"; interface IPolicyDetailsPageProps { router: InjectedRouter; @@ -35,7 +36,7 @@ interface IPolicyDetailsPageProps { location: { pathname: string; search: string; - query: { team_id?: string }; + query: { fleet_id?: string }; }; } @@ -108,34 +109,41 @@ const PolicyDetailsPage = ({ IStoredPolicyResponse, Error, IPolicy - >(["policy", policyId], () => globalPoliciesAPI.load(policyId as number), { - enabled: isRouteOk && !!policyId, - refetchOnWindowFocus: false, - retry: false, - select: (data: IStoredPolicyResponse) => data.policy, - onSuccess: (returnedPolicy) => { - setLastEditedQueryId(returnedPolicy.id); - setLastEditedQueryName(returnedPolicy.name); - setLastEditedQueryDescription(returnedPolicy.description); - setLastEditedQueryBody(returnedPolicy.query); - setLastEditedQueryResolution(returnedPolicy.resolution); - setLastEditedQueryCritical(returnedPolicy.critical); - setLastEditedQueryPlatform(returnedPolicy.platform); - setLastEditedQueryLabelsIncludeAny( - returnedPolicy.labels_include_any || [] - ); - setLastEditedQueryLabelsExcludeAny( - returnedPolicy.labels_exclude_any || [] - ); - const deNulledTeamId = returnedPolicy.team_id ?? undefined; - setPolicyTeamId( - deNulledTeamId === API_ALL_TEAMS_ID - ? APP_CONTEXT_ALL_TEAMS_ID - : deNulledTeamId - ); - }, - onError: (error) => handlePageError(error), - }); + >( + ["policy", policyId, teamIdForApi], + () => + teamIdForApi && teamIdForApi > 0 + ? teamPoliciesAPI.load(teamIdForApi, policyId as number) + : globalPoliciesAPI.load(policyId as number), + { + enabled: isRouteOk && !!policyId, + refetchOnWindowFocus: false, + retry: false, + select: (data: IStoredPolicyResponse) => data.policy, + onSuccess: (returnedPolicy) => { + setLastEditedQueryId(returnedPolicy.id); + setLastEditedQueryName(returnedPolicy.name); + setLastEditedQueryDescription(returnedPolicy.description); + setLastEditedQueryBody(returnedPolicy.query); + setLastEditedQueryResolution(returnedPolicy.resolution); + setLastEditedQueryCritical(returnedPolicy.critical); + setLastEditedQueryPlatform(returnedPolicy.platform); + setLastEditedQueryLabelsIncludeAny( + returnedPolicy.labels_include_any || [] + ); + setLastEditedQueryLabelsExcludeAny( + returnedPolicy.labels_exclude_any || [] + ); + const deNulledTeamId = returnedPolicy.team_id ?? undefined; + setPolicyTeamId( + deNulledTeamId === API_ALL_TEAMS_ID + ? APP_CONTEXT_ALL_TEAMS_ID + : deNulledTeamId + ); + }, + onError: (error) => handlePageError(error), + } + ); const { data: teamData } = useQuery( ["team", teamIdForApi], @@ -187,7 +195,7 @@ const PolicyDetailsPage = ({ const disabledLiveQuery = config?.server_settings.live_query_disabled; const backToPoliciesPath = getPathWithQueryParams(PATHS.MANAGE_POLICIES, { - team_id: teamIdForApi, + fleet_id: teamIdForApi, }); const renderAuthor = (): JSX.Element | null => { @@ -329,12 +337,9 @@ const PolicyDetailsPage = ({ onClick={() => { policyId && router.push( - `${getPathWithQueryParams( - PATHS.EDIT_POLICY(policyId), - { - team_id: teamIdForApi, - } - )}#targets` + getPathWithQueryParams(PATHS.LIVE_POLICY(policyId), { + fleet_id: teamIdForApi, + }) ); }} disabled={!!disabledLiveQuery} @@ -348,7 +353,7 @@ const PolicyDetailsPage = ({ policyId && router.push( getPathWithQueryParams(PATHS.EDIT_POLICY(policyId), { - team_id: teamIdForApi, + fleet_id: teamIdForApi, }) ); }} diff --git a/frontend/pages/policies/PolicyDetailsPage/_styles.scss b/frontend/pages/policies/details/PolicyDetailsPage/_styles.scss similarity index 100% rename from frontend/pages/policies/PolicyDetailsPage/_styles.scss rename to frontend/pages/policies/details/PolicyDetailsPage/_styles.scss diff --git a/frontend/pages/policies/PolicyDetailsPage/index.ts b/frontend/pages/policies/details/PolicyDetailsPage/index.ts similarity index 100% rename from frontend/pages/policies/PolicyDetailsPage/index.ts rename to frontend/pages/policies/details/PolicyDetailsPage/index.ts diff --git a/frontend/pages/policies/PolicyPage/PolicyPage.tsx b/frontend/pages/policies/edit/EditPolicyPage.tsx similarity index 79% rename from frontend/pages/policies/PolicyPage/PolicyPage.tsx rename to frontend/pages/policies/edit/EditPolicyPage.tsx index 16b1f1db5a0..9c6e74455fc 100644 --- a/frontend/pages/policies/PolicyPage/PolicyPage.tsx +++ b/frontend/pages/policies/edit/EditPolicyPage.tsx @@ -6,36 +6,27 @@ import { useErrorHandler } from "react-error-boundary"; import { AppContext } from "context/app"; import { PolicyContext } from "context/policy"; import useTeamIdParam from "hooks/useTeamIdParam"; -import { IHost, IHostResponse } from "interfaces/host"; -import { ILabel } from "interfaces/label"; import { IPolicyFormData, IPolicy, IStoredPolicyResponse, } from "interfaces/policy"; -import { ITarget } from "interfaces/target"; -import { - API_ALL_TEAMS_ID, - APP_CONTEXT_ALL_TEAMS_ID, - ITeam, -} from "interfaces/team"; +import { API_ALL_TEAMS_ID, APP_CONTEXT_ALL_TEAMS_ID } from "interfaces/team"; import globalPoliciesAPI from "services/entities/global_policies"; import teamPoliciesAPI from "services/entities/team_policies"; import teamsAPI, { ILoadTeamResponse } from "services/entities/teams"; -import hostAPI from "services/entities/hosts"; import statusAPI from "services/entities/status"; -import { DOCUMENT_TITLE_SUFFIX, LIVE_POLICY_STEPS } from "utilities/constants"; +import PATHS from "router/paths"; +import { DOCUMENT_TITLE_SUFFIX } from "utilities/constants"; import { getPathWithQueryParams } from "utilities/url"; import SidePanelPage from "components/SidePanelPage"; import QuerySidePanel from "components/side_panels/QuerySidePanel"; -import QueryEditor from "pages/policies/PolicyPage/screens/QueryEditor"; -import SelectTargets from "components/LiveQuery/SelectTargets"; +import QueryEditor from "pages/policies/edit/screens/QueryEditor"; import MainContent from "components/MainContent"; import SidePanelContent from "components/SidePanelContent"; import Spinner from "components/Spinner/Spinner"; import CustomLink from "components/CustomLink"; -import RunQuery from "pages/policies/PolicyPage/screens/RunQuery"; import { DEFAULT_POLICY } from "pages/policies/constants"; interface IPolicyPageProps { @@ -44,12 +35,11 @@ interface IPolicyPageProps { location: { pathname: string; search: string; - query: { host_ids: string; fleet_id: string }; - hash?: string; + query: { fleet_id: string }; }; } -const baseClass = "policy-page"; +const baseClass = "edit-policy-page"; const PolicyPage = ({ router, @@ -144,14 +134,6 @@ const PolicyPage = ({ }; }, []); - const [step, setStep] = useState( - location.hash === "#targets" ? LIVE_POLICY_STEPS[2] : LIVE_POLICY_STEPS[1] - ); - const [selectedTargets, setSelectedTargets] = useState([]); - const [targetedHosts, setTargetedHosts] = useState([]); - const [targetedLabels, setTargetedLabels] = useState([]); - const [targetedTeams, setTargetedTeams] = useState([]); - const [targetsTotalCount, setTargetsTotalCount] = useState(0); const [isLiveQueryRunnable, setIsLiveQueryRunnable] = useState(true); const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [showOpenSchemaActionText, setShowOpenSchemaActionText] = useState( @@ -203,23 +185,6 @@ const PolicyPage = ({ } ); - useQuery( - "hostFromURL", - () => - hostAPI.loadHostDetails(parseInt(location.query.host_ids as string, 10)), // TODO(sarah): What should happen if this doesn't parse (e.g. the string is "foo")? Also, note that "1,2,3" parses as 1. - { - enabled: isRouteOk && !!location.query.host_ids, - retry: false, - select: (data: IHostResponse) => data.host, - onSuccess: (host) => { - const targets = selectedTargets; - host.target_type = "hosts"; - targets.push(host); - setSelectedTargets([...targets]); - }, - } - ); - /** Pesky bug affecting team level users: - Navigating to policies/:id immediately defaults the user to the first team they're on with the most permissions, in the URL bar because of useTeamIdParam @@ -338,7 +303,7 @@ const PolicyPage = ({ }; const renderScreen = () => { - const step1Opts = { + const queryEditorOpts = { router, baseClass, policyIdForEdit: policyId, @@ -352,51 +317,22 @@ const PolicyPage = ({ storedPolicyError, createPolicy, onOsqueryTableSelect, - goToSelectTargets: () => setStep(LIVE_POLICY_STEPS[2]), + goToSelectTargets: () => + router.push( + getPathWithQueryParams(PATHS.LIVE_POLICY(policyId), { + fleet_id: teamIdForApi, + }) + ), onOpenSchemaSidebar, renderLiveQueryWarning, teamIdForApi, currentAutomatedPolicies, }; - const step2Opts = { - baseClass, - selectedTargets, - targetedHosts, - targetedLabels, - targetedTeams, - targetsTotalCount, - goToQueryEditor: () => setStep(LIVE_POLICY_STEPS[1]), - goToRunQuery: () => setStep(LIVE_POLICY_STEPS[3]), - setSelectedTargets, - setTargetedHosts, - setTargetedLabels, - setTargetedTeams, - setTargetsTotalCount, - isLivePolicy: true, - }; - - const step3Opts = { - selectedTargets, - storedPolicy, - setSelectedTargets, - goToQueryEditor: () => setStep(LIVE_POLICY_STEPS[1]), - targetsTotalCount, - }; - - switch (step) { - case LIVE_POLICY_STEPS[2]: - return ; - case LIVE_POLICY_STEPS[3]: - return ; - default: - return ; - } + return ; }; - const isFirstStep = step === LIVE_POLICY_STEPS[1]; const showSidebar = - isFirstStep && isSidebarOpen && (isGlobalAdmin || isGlobalMaintainer || isAnyTeamMaintainerOrTeamAdmin); diff --git a/frontend/pages/policies/edit/_styles.scss b/frontend/pages/policies/edit/_styles.scss new file mode 100644 index 00000000000..d193b6ab3f1 --- /dev/null +++ b/frontend/pages/policies/edit/_styles.scss @@ -0,0 +1,39 @@ +.edit-policy-page { + @include vertical-page-layout; + + &__form { + @include vertical-form-layout; + } + + &__warning { + padding: $pad-medium; + font-size: $x-small; + color: $core-fleet-black; + background-color: #fff0b9; + border: 1px solid #f2c94c; + border-radius: $border-radius; + + p { + margin: 0; + line-height: 20px; + } + } + + &__observer-query-view { + width: 90%; + max-width: 1060px; + margin: 0 auto; + color: $core-fleet-black; + + h1 { + font-size: $medium; + } + p { + font-size: $x-small; + } + } + + .ace_content { + min-height: 500px !important; + } +} diff --git a/frontend/pages/policies/PolicyPage/components/PolicyAutomations/PolicyAutomations.tsx b/frontend/pages/policies/edit/components/PolicyAutomations/PolicyAutomations.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyAutomations/PolicyAutomations.tsx rename to frontend/pages/policies/edit/components/PolicyAutomations/PolicyAutomations.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyAutomations/_styles.scss b/frontend/pages/policies/edit/components/PolicyAutomations/_styles.scss similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyAutomations/_styles.scss rename to frontend/pages/policies/edit/components/PolicyAutomations/_styles.scss diff --git a/frontend/pages/policies/PolicyPage/components/PolicyAutomations/index.ts b/frontend/pages/policies/edit/components/PolicyAutomations/index.ts similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyAutomations/index.ts rename to frontend/pages/policies/edit/components/PolicyAutomations/index.ts diff --git a/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTable.tsx b/frontend/pages/policies/edit/components/PolicyErrorsTable/PolicyErrorsTable.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTable.tsx rename to frontend/pages/policies/edit/components/PolicyErrorsTable/PolicyErrorsTable.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTableConfig.tsx b/frontend/pages/policies/edit/components/PolicyErrorsTable/PolicyErrorsTableConfig.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTableConfig.tsx rename to frontend/pages/policies/edit/components/PolicyErrorsTable/PolicyErrorsTableConfig.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/_styles.scss b/frontend/pages/policies/edit/components/PolicyErrorsTable/_styles.scss similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/_styles.scss rename to frontend/pages/policies/edit/components/PolicyErrorsTable/_styles.scss diff --git a/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/index.ts b/frontend/pages/policies/edit/components/PolicyErrorsTable/index.ts similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/index.ts rename to frontend/pages/policies/edit/components/PolicyErrorsTable/index.ts diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tests.tsx b/frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tests.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tests.tsx rename to frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tests.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx b/frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tsx similarity index 99% rename from frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx rename to frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tsx index c3e334119bc..4cf202a43e9 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx +++ b/frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tsx @@ -271,7 +271,19 @@ const PolicyForm = ({ }) ); } - }, [policyIdForEdit, isTeamMaintainerOrTeamAdmin, isStoredPolicyLoading]); + }, [ + policyIdForEdit, + isEditMode, + isStoredPolicyLoading, + isTeamObserver, + isGlobalObserver, + isTeamTechnician, + isGlobalTechnician, + isOnGlobalTeam, + storedPolicy?.team_id, + router, + teamIdForApi, + ]); useEffect(() => { setSelectedTargetType( diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss b/frontend/pages/policies/edit/components/PolicyForm/_styles.scss similarity index 98% rename from frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss rename to frontend/pages/policies/edit/components/PolicyForm/_styles.scss index 7f29d1a73f6..92a76d8d414 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss +++ b/frontend/pages/policies/edit/components/PolicyForm/_styles.scss @@ -7,7 +7,7 @@ position: relative; font-size: $x-small; - .policy-page__warning { + .edit-policy-page__warning { margin: 0; margin-bottom: $pad-large; } diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/index.ts b/frontend/pages/policies/edit/components/PolicyForm/index.ts similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyForm/index.ts rename to frontend/pages/policies/edit/components/PolicyForm/index.ts diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx b/frontend/pages/policies/edit/components/PolicyResults/PolicyResults.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx rename to frontend/pages/policies/edit/components/PolicyResults/PolicyResults.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResults/_styles.scss b/frontend/pages/policies/edit/components/PolicyResults/_styles.scss similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyResults/_styles.scss rename to frontend/pages/policies/edit/components/PolicyResults/_styles.scss diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResults/helpers.tsx b/frontend/pages/policies/edit/components/PolicyResults/helpers.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyResults/helpers.tsx rename to frontend/pages/policies/edit/components/PolicyResults/helpers.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResults/index.ts b/frontend/pages/policies/edit/components/PolicyResults/index.ts similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyResults/index.ts rename to frontend/pages/policies/edit/components/PolicyResults/index.ts diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTable.tsx b/frontend/pages/policies/edit/components/PolicyResultsTable/PolicyResultsTable.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTable.tsx rename to frontend/pages/policies/edit/components/PolicyResultsTable/PolicyResultsTable.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTableConfig.tsx b/frontend/pages/policies/edit/components/PolicyResultsTable/PolicyResultsTableConfig.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTableConfig.tsx rename to frontend/pages/policies/edit/components/PolicyResultsTable/PolicyResultsTableConfig.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/_styles.scss b/frontend/pages/policies/edit/components/PolicyResultsTable/_styles.scss similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyResultsTable/_styles.scss rename to frontend/pages/policies/edit/components/PolicyResultsTable/_styles.scss diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/index.ts b/frontend/pages/policies/edit/components/PolicyResultsTable/index.ts similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyResultsTable/index.ts rename to frontend/pages/policies/edit/components/PolicyResultsTable/index.ts diff --git a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tests.tsx b/frontend/pages/policies/edit/components/SaveNewPolicyModal/SaveNewPolicyModal.tests.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tests.tsx rename to frontend/pages/policies/edit/components/SaveNewPolicyModal/SaveNewPolicyModal.tests.tsx diff --git a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx b/frontend/pages/policies/edit/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx rename to frontend/pages/policies/edit/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx diff --git a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/_styles.scss b/frontend/pages/policies/edit/components/SaveNewPolicyModal/_styles.scss similarity index 100% rename from frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/_styles.scss rename to frontend/pages/policies/edit/components/SaveNewPolicyModal/_styles.scss diff --git a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/index.ts b/frontend/pages/policies/edit/components/SaveNewPolicyModal/index.ts similarity index 100% rename from frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/index.ts rename to frontend/pages/policies/edit/components/SaveNewPolicyModal/index.ts diff --git a/frontend/pages/policies/edit/index.ts b/frontend/pages/policies/edit/index.ts new file mode 100644 index 00000000000..b1dc948c5b2 --- /dev/null +++ b/frontend/pages/policies/edit/index.ts @@ -0,0 +1 @@ +export { default } from "./EditPolicyPage"; diff --git a/frontend/pages/policies/PolicyPage/screens/QueryEditor.tsx b/frontend/pages/policies/edit/screens/QueryEditor.tsx similarity index 98% rename from frontend/pages/policies/PolicyPage/screens/QueryEditor.tsx rename to frontend/pages/policies/edit/screens/QueryEditor.tsx index 7fce523513c..100c3dcfa4d 100644 --- a/frontend/pages/policies/PolicyPage/screens/QueryEditor.tsx +++ b/frontend/pages/policies/edit/screens/QueryEditor.tsx @@ -14,7 +14,7 @@ import { getPathWithQueryParams } from "utilities/url"; import { IPolicyFormData, IPolicy } from "interfaces/policy"; import BackButton from "components/BackButton"; -import PolicyForm from "pages/policies/PolicyPage/components/PolicyForm"; +import PolicyForm from "pages/policies/edit/components/PolicyForm"; import { APP_CONTEXT_ALL_TEAMS_ID } from "interfaces/team"; interface IQueryEditorProps { @@ -261,7 +261,7 @@ const QueryEditor = ({ const backPath = policyIdForEdit ? getPathWithQueryParams(PATHS.POLICY_DETAILS(policyIdForEdit), { - team_id: teamIdForApi, + fleet_id: teamIdForApi, }) : backToPoliciesPath(); diff --git a/frontend/pages/policies/live/LivePolicyPage/LivePolicyPage.tsx b/frontend/pages/policies/live/LivePolicyPage/LivePolicyPage.tsx new file mode 100644 index 00000000000..12531cfa8e1 --- /dev/null +++ b/frontend/pages/policies/live/LivePolicyPage/LivePolicyPage.tsx @@ -0,0 +1,204 @@ +import React, { useState, useEffect, useContext, useCallback } from "react"; +import { useQuery } from "react-query"; +import { useErrorHandler } from "react-error-boundary"; +import { InjectedRouter, Params } from "react-router/lib/Router"; +import PATHS from "router/paths"; +import useTeamIdParam from "hooks/useTeamIdParam"; + +import { AppContext } from "context/app"; +import { PolicyContext } from "context/policy"; +import { LIVE_QUERY_STEPS, DOCUMENT_TITLE_SUFFIX } from "utilities/constants"; +import { getPathWithQueryParams } from "utilities/url"; +import globalPoliciesAPI from "services/entities/global_policies"; +import teamPoliciesAPI from "services/entities/team_policies"; +import hostAPI from "services/entities/hosts"; +import { IHost, IHostResponse } from "interfaces/host"; +import { ILabel } from "interfaces/label"; +import { ITeam } from "interfaces/team"; +import { IPolicy, IStoredPolicyResponse } from "interfaces/policy"; +import { ITarget } from "interfaces/target"; + +import MainContent from "components/MainContent"; +import SelectTargets from "components/LiveQuery/SelectTargets"; + +import RunQuery from "pages/policies/live/screens/RunQuery"; + +interface ILivePolicyPageProps { + router: InjectedRouter; + params: Params; + location: { + pathname: string; + query: { host_ids?: string; fleet_id?: string }; + search: string; + }; +} + +const baseClass = "live-policy-page"; + +const LivePolicyPage = ({ + router, + params: { id: paramsPolicyId }, + location, +}: ILivePolicyPageProps): JSX.Element => { + const policyId = paramsPolicyId ? parseInt(paramsPolicyId, 10) : null; + const handlePageError = useErrorHandler(); + + const { currentTeamId } = useTeamIdParam({ + location, + router, + includeAllTeams: true, + includeNoTeam: true, + }); + + const { config } = useContext(AppContext); + const { + setLastEditedQueryId, + setLastEditedQueryName, + setLastEditedQueryDescription, + setLastEditedQueryBody, + setLastEditedQueryResolution, + setLastEditedQueryCritical, + setLastEditedQueryPlatform, + setLastEditedQueryLabelsIncludeAny, + setLastEditedQueryLabelsExcludeAny, + } = useContext(PolicyContext); + + const [queryParamHostsAdded, setQueryParamHostsAdded] = useState(false); + const [step, setStep] = useState(LIVE_QUERY_STEPS[1]); + const [selectedTargets, setSelectedTargets] = useState([]); + const [targetedHosts, setTargetedHosts] = useState([]); + const [targetedLabels, setTargetedLabels] = useState([]); + const [targetedTeams, setTargetedTeams] = useState([]); + const [targetsTotalCount, setTargetsTotalCount] = useState(0); + + const disabledLiveQuery = config?.server_settings.live_query_disabled; + const teamIdForApi = currentTeamId === -1 ? undefined : currentTeamId; + + // Reroute users out of live flow when live queries are globally disabled + // Reroute users out of live flow when live queries are globally disabled + useEffect(() => { + if (disabledLiveQuery) { + const path = policyId + ? PATHS.POLICY_DETAILS(policyId) + : PATHS.MANAGE_POLICIES; + + router.push(getPathWithQueryParams(path, { fleet_id: teamIdForApi })); + } + }, [disabledLiveQuery, policyId, router, teamIdForApi]); + + const { data: storedPolicy } = useQuery< + IStoredPolicyResponse, + Error, + IPolicy + >( + ["policy", policyId, teamIdForApi], + () => + teamIdForApi && teamIdForApi > 0 + ? teamPoliciesAPI.load(teamIdForApi, policyId as number) + : globalPoliciesAPI.load(policyId as number), + { + enabled: !!policyId, + refetchOnWindowFocus: false, + select: (data: IStoredPolicyResponse) => data.policy, + onSuccess: (returnedPolicy) => { + setLastEditedQueryId(returnedPolicy.id); + setLastEditedQueryName(returnedPolicy.name); + setLastEditedQueryDescription(returnedPolicy.description); + setLastEditedQueryBody(returnedPolicy.query); + setLastEditedQueryResolution(returnedPolicy.resolution); + setLastEditedQueryCritical(returnedPolicy.critical); + setLastEditedQueryPlatform(returnedPolicy.platform); + setLastEditedQueryLabelsIncludeAny( + returnedPolicy.labels_include_any || [] + ); + setLastEditedQueryLabelsExcludeAny( + returnedPolicy.labels_exclude_any || [] + ); + }, + onError: (error) => handlePageError(error), + } + ); + + const hostIdFromURL = location.query.host_ids + ? parseInt(location.query.host_ids as string, 10) + : null; + + useQuery( + ["hostFromURL", hostIdFromURL, teamIdForApi], + () => hostAPI.loadHostDetails(hostIdFromURL as number), + { + enabled: !!hostIdFromURL && !queryParamHostsAdded, + select: (data: IHostResponse) => data.host, + onSuccess: (host) => { + setTargetedHosts((prevHosts) => + prevHosts.filter((h) => h.id !== host.id).concat(host) + ); + const targets = selectedTargets; + host.target_type = "hosts"; + targets.push(host); + setSelectedTargets([...targets]); + if (!queryParamHostsAdded) { + setQueryParamHostsAdded(true); + } + router.replace(location.pathname); + }, + } + ); + + // Updates title that shows up on browser tabs + useEffect(() => { + if (storedPolicy?.name) { + document.title = `Run ${storedPolicy.name} | Policies | ${DOCUMENT_TITLE_SUFFIX}`; + } else { + document.title = `Policies | ${DOCUMENT_TITLE_SUFFIX}`; + } + }, [location.pathname, storedPolicy?.name]); + + const goToQueryEditor = useCallback(() => { + const path = policyId ? PATHS.EDIT_POLICY(policyId) : PATHS.NEW_POLICY; + + router.push(getPathWithQueryParams(path, { fleet_id: teamIdForApi })); + }, [policyId, router, teamIdForApi]); + + const renderScreen = () => { + const step1Props = { + baseClass, + selectedTargets, + targetedHosts, + targetedLabels, + targetedTeams, + targetsTotalCount, + goToQueryEditor, + goToRunQuery: () => setStep(LIVE_QUERY_STEPS[2]), + setSelectedTargets, + setTargetedHosts, + setTargetedLabels, + setTargetedTeams, + setTargetsTotalCount, + isLivePolicy: true, + }; + + const step2Props = { + selectedTargets, + storedPolicy, + setSelectedTargets, + goToQueryEditor, + targetsTotalCount, + }; + + switch (step) { + case LIVE_QUERY_STEPS[2]: + return ; + default: + return ; + } + }; + + return ( + +
{renderScreen()}
+
+ ); +}; + +export default LivePolicyPage; diff --git a/frontend/pages/policies/PolicyPage/_styles.scss b/frontend/pages/policies/live/LivePolicyPage/_styles.scss similarity index 73% rename from frontend/pages/policies/PolicyPage/_styles.scss rename to frontend/pages/policies/live/LivePolicyPage/_styles.scss index e4f6727743c..964d37bca0a 100644 --- a/frontend/pages/policies/PolicyPage/_styles.scss +++ b/frontend/pages/policies/live/LivePolicyPage/_styles.scss @@ -1,8 +1,6 @@ -.policy-page { - @include vertical-page-layout; - - &__form { - @include vertical-form-layout; +.live-policy-page { + &_wrapper { + width: 100%; } &__results { @@ -12,34 +10,6 @@ min-height: 400px; } - &__warning { - padding: $pad-medium; - font-size: $x-small; - color: $core-fleet-black; - background-color: #fff0b9; - border: 1px solid #f2c94c; - border-radius: $border-radius; - - p { - margin: 0; - line-height: 20px; - } - } - - &__observer-query-view { - width: 90%; - max-width: 1060px; - margin: 0 auto; - color: $core-fleet-black; - - h1 { - font-size: $medium; - } - p { - font-size: $x-small; - } - } - .ace_content { min-height: 500px !important; } diff --git a/frontend/pages/policies/live/LivePolicyPage/index.ts b/frontend/pages/policies/live/LivePolicyPage/index.ts new file mode 100644 index 00000000000..971f6899788 --- /dev/null +++ b/frontend/pages/policies/live/LivePolicyPage/index.ts @@ -0,0 +1 @@ +export { default } from "./LivePolicyPage"; diff --git a/frontend/pages/policies/PolicyPage/screens/RunQuery.tsx b/frontend/pages/policies/live/screens/RunQuery.tsx similarity index 98% rename from frontend/pages/policies/PolicyPage/screens/RunQuery.tsx rename to frontend/pages/policies/live/screens/RunQuery.tsx index 5a9a9a99626..2c4c9dc5fcd 100644 --- a/frontend/pages/policies/PolicyPage/screens/RunQuery.tsx +++ b/frontend/pages/policies/live/screens/RunQuery.tsx @@ -15,7 +15,7 @@ import { ICampaign, ICampaignState } from "interfaces/campaign"; import { IPolicy } from "interfaces/policy"; import { ITarget } from "interfaces/target"; -import PolicyResults from "../components/PolicyResults"; +import PolicyResults from "pages/policies/edit/components/PolicyResults"; interface IRunQueryProps { storedPolicy: IPolicy | undefined; diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index 9c18ff0e038..d340535395f 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -42,10 +42,11 @@ import ManagePacksPage from "pages/packs/ManagePacksPage"; import ManagePoliciesPage from "pages/policies/ManagePoliciesPage"; import NoAccessPage from "pages/NoAccessPage"; import PackComposerPage from "pages/packs/PackComposerPage"; -import PolicyDetailsPage from "pages/policies/PolicyDetailsPage"; -import PolicyPage from "pages/policies/PolicyPage"; +import PolicyDetailsPage from "pages/policies/details/PolicyDetailsPage"; +import EditPolicyPage from "pages/policies/edit"; import QueryDetailsPage from "pages/queries/details/QueryDetailsPage"; import LiveQueryPage from "pages/queries/live/LiveQueryPage"; +import LivePolicyPage from "pages/policies/live/LivePolicyPage"; import EditQueryPage from "pages/queries/edit/EditQueryPage"; import RegistrationPage from "pages/RegistrationPage"; import ResetPasswordPage from "pages/ResetPasswordPage"; @@ -410,11 +411,15 @@ const routes = ( - + + + + - + + {/* deprecated URL */} diff --git a/frontend/router/paths.ts b/frontend/router/paths.ts index 637be790da8..db74d50fd1c 100644 --- a/frontend/router/paths.ts +++ b/frontend/router/paths.ts @@ -128,6 +128,8 @@ export default { `${URL_PREFIX}/policies/${policyId}`, EDIT_POLICY: (policyId: number): string => `${URL_PREFIX}/policies/${policyId}/edit`, + LIVE_POLICY: (policyId: number | null): string => + `${URL_PREFIX}/policies/${policyId || "new"}/live`, FORGOT_PASSWORD: `${URL_PREFIX}/login/forgot`, MFA: `${URL_PREFIX}/login/mfa`, NO_ACCESS: `${URL_PREFIX}/login/denied`,