Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changes/41753-policy-details-page
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const generateTableHeaders = (
</>
}
path={getPathWithQueryParams(PATHS.POLICY_DETAILS(id), {
team_id,
fleet_id: team_id,
})}
/>
);
Expand Down
1 change: 0 additions & 1 deletion frontend/pages/policies/PolicyPage/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -27,15 +28,15 @@ 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;
params: Params;
location: {
pathname: string;
search: string;
query: { team_id?: string };
query: { fleet_id?: string };
};
}

Expand Down Expand Up @@ -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<ILoadTeamResponse>(
["team", teamIdForApi],
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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}
Expand All @@ -348,7 +353,7 @@ const PolicyDetailsPage = ({
policyId &&
router.push(
getPathWithQueryParams(PATHS.EDIT_POLICY(policyId), {
team_id: teamIdForApi,
fleet_id: teamIdForApi,
})
);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand Down Expand Up @@ -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<ITarget[]>([]);
const [targetedHosts, setTargetedHosts] = useState<IHost[]>([]);
const [targetedLabels, setTargetedLabels] = useState<ILabel[]>([]);
const [targetedTeams, setTargetedTeams] = useState<ITeam[]>([]);
const [targetsTotalCount, setTargetsTotalCount] = useState(0);
const [isLiveQueryRunnable, setIsLiveQueryRunnable] = useState(true);
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
const [showOpenSchemaActionText, setShowOpenSchemaActionText] = useState(
Expand Down Expand Up @@ -203,23 +185,6 @@ const PolicyPage = ({
}
);

useQuery<IHostResponse, Error, IHost>(
"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
Expand Down Expand Up @@ -338,7 +303,7 @@ const PolicyPage = ({
};

const renderScreen = () => {
const step1Opts = {
const queryEditorOpts = {
router,
baseClass,
policyIdForEdit: policyId,
Expand All @@ -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 <SelectTargets {...step2Opts} />;
case LIVE_POLICY_STEPS[3]:
return <RunQuery {...step3Opts} />;
default:
return <QueryEditor {...step1Opts} />;
}
return <QueryEditor {...queryEditorOpts} />;
};

const isFirstStep = step === LIVE_POLICY_STEPS[1];
const showSidebar =
isFirstStep &&
isSidebarOpen &&
(isGlobalAdmin || isGlobalMaintainer || isAnyTeamMaintainerOrTeamAdmin);

Expand Down
39 changes: 39 additions & 0 deletions frontend/pages/policies/edit/_styles.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,19 @@ const PolicyForm = ({
})
);
}
}, [policyIdForEdit, isTeamMaintainerOrTeamAdmin, isStoredPolicyLoading]);
}, [
policyIdForEdit,
isEditMode,
isStoredPolicyLoading,
isTeamObserver,
isGlobalObserver,
isTeamTechnician,
isGlobalTechnician,
isOnGlobalTeam,
storedPolicy?.team_id,
router,
teamIdForApi,
]);

useEffect(() => {
setSelectedTargetType(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
position: relative;
font-size: $x-small;

.policy-page__warning {
.edit-policy-page__warning {
margin: 0;
margin-bottom: $pad-large;
}
Expand Down
1 change: 1 addition & 0 deletions frontend/pages/policies/edit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./EditPolicyPage";
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -261,7 +261,7 @@ const QueryEditor = ({

const backPath = policyIdForEdit
? getPathWithQueryParams(PATHS.POLICY_DETAILS(policyIdForEdit), {
team_id: teamIdForApi,
fleet_id: teamIdForApi,
})
: backToPoliciesPath();

Expand Down
Loading
Loading