- Policy will target hosts on selected platforms that{" "}
- have any of these labels:
-
- }
- suppressTitle
- />
- )}
- {isExistingPolicy && storedPolicy && (
-
- )}
- {isExistingPolicy && isPremiumTier && renderCriticalPolicy()}
{renderLiveQueryWarning()}
- {hasSavePermissions && (
-
(
-
- Select the platforms this
-
- policy will be checked on
-
- to save or run the policy.
- >
- }
- tooltipClass={`${baseClass}__button-wrap--tooltip`}
- position="top"
- disableTooltip={!isExistingPolicy || isAnyPlatformSelected}
- underline={false}
- >
-
-
-
-
- )}
- />
+ {isEditMode && onCancel && (
+
)}
+ (
+
+ Select the platforms this
+
+ policy will be checked on
+
+ to save or run the policy.
+ >
+ }
+ tooltipClass={`${baseClass}__button-wrap--tooltip`}
+ position="top"
+ disableTooltip={!isEditMode || isAnyPlatformSelected}
+ underline={false}
+ >
+
+
+
+
+ )}
+ />
;
}
- const isInheritedPolicy = isExistingPolicy && storedPolicy?.team_id === null;
-
- const noEditPermissions =
- isTeamObserver ||
- isGlobalObserver ||
- isTeamTechnician ||
- isGlobalTechnician ||
- (!isOnGlobalTeam && isInheritedPolicy); // Team user viewing inherited policy
-
- // Render non-editable form only
- if (noEditPermissions) {
- return renderNonEditableForm;
- }
-
- // Render default editable form
- return renderEditablePolicyForm();
+ return renderPolicyForm();
};
export default PolicyForm;
diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss b/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss
index 8f505877b4..7f29d1a73f 100644
--- a/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss
+++ b/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss
@@ -17,6 +17,24 @@
}
}
+ &__page-header {
+ display: flex;
+ flex-direction: column;
+ gap: $pad-small;
+ }
+
+ &__page-title {
+ font-size: $large;
+ font-weight: $bold;
+ margin: 0;
+ }
+
+ &__page-subtitle {
+ font-size: $x-small;
+ color: $ui-fleet-black-75;
+ margin: 0;
+ }
+
&__title-bar {
display: flex;
justify-content: space-between;
@@ -34,10 +52,8 @@
}
}
- .input-field,
- .input-field__text-area {
- min-height: auto;
- white-space: normal;
+ .input-field__textarea {
+ min-width: 100%;
}
/* Hide scrollbar for Chrome, Safari and Opera */
@@ -56,7 +72,7 @@
height: 18px;
}
- &__policy-name,
+ &__policy-name-fleet-name,
&__description,
&__resolution {
.button--text-icon {
@@ -65,90 +81,31 @@
}
}
- &__policy-name-wrapper,
- &__policy-description-wrapper,
- &__policy-resolution-wrapper {
+ &__policy-name-fleet-name {
display: flex;
- align-items: flex-start;
+ flex-direction: column;
gap: $pad-small;
- width: fit-content;
-
- &:not(&--editing) {
- &:hover {
- cursor: pointer;
- * {
- color: $core-fleet-green;
- cursor: pointer;
- }
- }
- }
- &--disabled-by-gitops-mode {
- @include disabled;
- }
- }
-
- &__policy-name-wrapper {
- line-height: $line-height-large;
-
- .no-value {
- min-width: 112px;
- }
- }
-
- &__edit-icon {
- opacity: 1;
- transition: opacity 0.2s;
- margin-left: 0;
- /** Designed to be aligned with first line of text, not center of multiple
- lines of text, without this, the icon will vertically center on multiline */
- align-self: initial;
- height: 42px; // 24px font-size * 1.75 line height
- align-items: center; // Centers the icon in the height area
-
- &--hide {
- opacity: 0;
- }
- }
-
- &__policy-description-wrapper,
- &__policy-resolution-wrapper {
- .no-value {
- min-width: 106px;
- }
- .policy-form__edit-icon {
- height: 21px; // 14px font-size * 1.5 line height
- }
- }
-
- &__policy-name,
- &__policy-description,
- &__policy-resolution {
- width: 100%;
- margin: 0;
- padding: 0;
- border: 0;
- resize: none;
- white-space: normal;
- background-color: transparent;
- overflow: hidden;
- font-size: $x-small;
- }
-
- &__policy-name {
- font-size: $large;
-
- &.input-field--error {
- border: 1px solid $core-vibrant-red;
- }
}
&__autofill-label {
display: flex;
justify-content: space-between;
align-items: center;
+
+ .autofill-tooltip-wrapper {
+ display: flex; // Required for vertical centering
+ }
+
+ .autofill-button-tooltip {
+ font-weight: $regular;
+ }
}
&__button-wrap {
+ &--tooltip {
+ display: flex;
+ }
+
.policy-form__run {
min-width: 64px;
}
diff --git a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tests.tsx b/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tests.tsx
index 0129961ca5..2d270a91ca 100644
--- a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tests.tsx
+++ b/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tests.tsx
@@ -125,7 +125,9 @@ describe("SaveNewPolicyModal", () => {
const saveButton = screen.getByRole("button", { name: "Save" });
expect(saveButton).toBeDisabled();
- const funButton = screen.getByLabelText("Fun");
+ const funButton = await screen.findByRole("checkbox", {
+ name: "Fun",
+ });
expect(funButton).not.toBeChecked();
await userEvent.click(funButton);
expect(saveButton).toBeEnabled();
@@ -149,7 +151,9 @@ describe("SaveNewPolicyModal", () => {
// Set a label.
await userEvent.click(screen.getByLabelText("Custom"));
- await userEvent.click(screen.getByLabelText("Fun"));
+ await userEvent.click(
+ await screen.findByRole("checkbox", { name: "Fun" })
+ );
await userEvent.click(screen.getByRole("button", { name: "Save" }));
expect(onCreatePolicy.mock.calls[0][0].labels_include_any).toEqual([
@@ -175,7 +179,9 @@ describe("SaveNewPolicyModal", () => {
// Set a label.
await userEvent.click(screen.getByLabelText("Custom"));
- await userEvent.click(screen.getByLabelText("Fun"));
+ await userEvent.click(
+ await screen.findByRole("checkbox", { name: "Fun" })
+ );
// Click "Include any" to open the dropdown.
const includeAnyOption = screen.getByRole("option", {
diff --git a/frontend/pages/policies/PolicyPage/screens/QueryEditor.tsx b/frontend/pages/policies/PolicyPage/screens/QueryEditor.tsx
index e366b4616f..7fce523513 100644
--- a/frontend/pages/policies/PolicyPage/screens/QueryEditor.tsx
+++ b/frontend/pages/policies/PolicyPage/screens/QueryEditor.tsx
@@ -259,12 +259,22 @@ const QueryEditor = ({
);
};
+ const backPath = policyIdForEdit
+ ? getPathWithQueryParams(PATHS.POLICY_DETAILS(policyIdForEdit), {
+ team_id: teamIdForApi,
+ })
+ : backToPoliciesPath();
+
+ const backText = policyIdForEdit ? "Back to policy" : "Back to policies";
+
return (
-
+
setPolicyAutofillData(null)}
currentAutomatedPolicies={currentAutomatedPolicies || []}
+ onCancel={
+ policyIdForEdit
+ ? () =>
+ router.push(
+ getPathWithQueryParams(
+ PATHS.POLICY_DETAILS(policyIdForEdit),
+ { fleet_id: teamIdForApi }
+ )
+ )
+ : undefined
+ }
/>
);
diff --git a/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tests.tsx b/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tests.tsx
index 4ba05513f7..fc65129848 100644
--- a/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tests.tsx
+++ b/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tests.tsx
@@ -429,8 +429,10 @@ describe("EditQueryForm - component", () => {
expect(screen.getByLabelText("All hosts")).toBeInTheDocument();
expect(screen.getByLabelText("Custom")).toBeInTheDocument();
expect(screen.getByLabelText("Custom")).toBeChecked();
- expect(screen.getByLabelText("Fun")).toBeChecked();
- expect(screen.getByLabelText("Fresh")).not.toBeChecked();
+ expect(screen.getByRole("checkbox", { name: "Fun" })).toBeChecked();
+ expect(
+ screen.getByRole("checkbox", { name: "Fresh" })
+ ).not.toBeChecked();
expect(screen.getByRole("button", { name: "Save" })).toBeEnabled();
});
});
@@ -449,9 +451,11 @@ describe("EditQueryForm - component", () => {
expect(screen.getByLabelText("All hosts")).toBeInTheDocument();
expect(screen.getByLabelText("Custom")).toBeInTheDocument();
expect(screen.getByLabelText("Custom")).toBeChecked();
- funButton = screen.getByLabelText("Fun");
+ funButton = screen.getByRole("checkbox", { name: "Fun" });
expect(funButton).toBeChecked();
- expect(screen.getByLabelText("Fresh")).not.toBeChecked();
+ expect(
+ screen.getByRole("checkbox", { name: "Fresh" })
+ ).not.toBeChecked();
saveButton = screen.getByRole("button", { name: "Save" });
expect(saveButton).toBeEnabled();
});
diff --git a/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx b/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx
index 7f949a4575..5bfe7dde91 100644
--- a/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx
+++ b/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx
@@ -2,7 +2,6 @@ import React, {
useState,
useContext,
useEffect,
- KeyboardEvent,
useCallback,
useMemo,
} from "react";
@@ -11,7 +10,6 @@ import { Location } from "history";
import { useQuery } from "react-query";
import { size } from "lodash";
-import classnames from "classnames";
import { useDebouncedCallback } from "use-debounce";
import { Ace } from "ace-builds";
@@ -65,7 +63,8 @@ import Slider from "components/forms/fields/Slider";
import TooltipWrapper from "components/TooltipWrapper";
import Spinner from "components/Spinner";
import Icon from "components/Icon/Icon";
-import AutoSizeInputField from "components/forms/fields/AutoSizeInputField";
+// @ts-ignore
+import InputField from "components/forms/fields/InputField";
import LogDestinationIndicator from "components/LogDestinationIndicator";
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
import TargetLabelSelector from "components/TargetLabelSelector";
@@ -192,6 +191,7 @@ const EditQueryForm = ({
config,
isPremiumTier,
isFreeTier,
+ currentTeam,
} = useContext(AppContext);
const isExistingQuery = !!queryIdForEdit;
@@ -207,8 +207,6 @@ const EditQueryForm = ({
const [showQueryEditor, setShowQueryEditor] = useState(
isObserverPlus || isAnyTeamObserverPlus || false
);
- const [isEditingName, setIsEditingName] = useState(false);
- const [isEditingDescription, setIsEditingDescription] = useState(false);
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const [queryWasChanged, setQueryWasChanged] = useState(false);
const [selectedTargetType, setSelectedTargetType] = useState("");
@@ -327,14 +325,6 @@ const EditQueryForm = ({
setLastEditedQueryBody(sqlString);
};
- const onInputKeypress = (event: KeyboardEvent) => {
- if (event.key.toLowerCase() === "enter" && !event.shiftKey) {
- event.preventDefault();
- event.currentTarget.blur();
- setIsEditingName(false);
- setIsEditingDescription(false);
- }
- };
const frequencyOptions = useMemo(
() =>
getCustomDropdownOptions(
@@ -413,30 +403,6 @@ const EditQueryForm = ({
}
};
- const renderAuthor = (): JSX.Element | null => {
- return storedQuery ? (
-
-
-
- {storedQuery.author_name === currentUser?.name
- ? "You"
- : storedQuery.author_name}
-
- >
- }
- />
- ) : null;
- };
-
const renderLabelComponent = (): JSX.Element | null => {
if (!showOpenSchemaActionText) {
return null;
@@ -460,70 +426,20 @@ const EditQueryForm = ({
return platformCompatibility.render();
};
- const editName = () => {
- if (!isEditingName) {
- setIsEditingName(true);
- }
- };
-
- const queryNameWrapperClass = `${baseClass}__query-name-wrapper`;
- const queryNameWrapperClasses = classnames(queryNameWrapperClass, {
- [`${baseClass}--editing`]: isEditingName,
- });
-
- const queryDescriptionWrapperClass = `${baseClass}__query-description-wrapper`;
- const queryDescriptionWrapperClasses = classnames(
- queryDescriptionWrapperClass,
- {
- [`${baseClass}--editing`]: isEditingDescription,
- }
- );
-
const renderName = () => {
if (isExistingQuery) {
return (
- {
- const classes = classnames(queryNameWrapperClasses, {
- [`${queryNameWrapperClass}--disabled-by-gitops-mode`]: disableChildren,
- });
- return (
- setIsEditingName(true)}
- onBlur={() => setIsEditingName(false)}
- onClick={editName}
- >
-
{
- setLastEditedQueryName(lastEditedQueryName.trim());
- }}
- onKeyPress={onInputKeypress}
- isFocused={isEditingName}
- disableTabability={disableChildren}
- />
-
-
- );
+ setLastEditedQueryName(value)}
+ onBlur={() => {
+ setLastEditedQueryName(lastEditedQueryName.trim());
}}
+ disabled={gitOpsModeEnabled}
/>
);
}
@@ -531,53 +447,18 @@ const EditQueryForm = ({
return New report
;
};
- const editDescription = () => {
- if (!isEditingDescription) {
- setIsEditingDescription(true);
- }
- };
-
const renderDescription = () => {
if (isExistingQuery) {
return (
- {
- const classes = classnames(queryDescriptionWrapperClasses, {
- [`${queryDescriptionWrapperClass}--disabled-by-gitops-mode`]: disableChildren,
- });
- return (
- setIsEditingDescription(true)}
- onBlur={() => setIsEditingDescription(false)}
- onClick={editDescription}
- >
-
-
-
- );
- }}
+ setLastEditedQueryDescription(value)}
+ disabled={gitOpsModeEnabled}
/>
);
}
@@ -616,12 +497,9 @@ const EditQueryForm = ({
// Observers and observer+ of existing query
const renderNonEditableForm = (
+ )}
+
+ {renderPlatformCompatibility()}
+ {isExistingQuery && (
+ <>
)}
-
+ >
)}
{renderLiveQueryWarning()}
@@ -935,6 +825,7 @@ const EditQueryForm = ({
>
)}
{
const saveButton = screen.getByRole("button", { name: "Save" });
expect(saveButton).toBeDisabled();
- const funButton = screen.getByLabelText("Fun");
+ const funButton = await screen.findByRole("checkbox", {
+ name: "Fun",
+ });
expect(funButton).not.toBeChecked();
await userEvent.click(funButton);
expect(saveButton).toBeEnabled();
@@ -280,7 +282,9 @@ describe("SaveNewQueryModal", () => {
// Set a label.
await userEvent.click(screen.getByLabelText("Custom"));
- await userEvent.click(screen.getByLabelText("Fun"));
+ await userEvent.click(
+ await screen.findByRole("checkbox", { name: "Fun" })
+ );
await userEvent.click(screen.getByRole("button", { name: "Save" }));
expect(saveQuery.mock.calls[0][0].labels_include_any).toEqual(["Fun"]);
diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx
index 98c01ed538..9c18ff0e03 100644
--- a/frontend/router/index.tsx
+++ b/frontend/router/index.tsx
@@ -42,6 +42,7 @@ 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 QueryDetailsPage from "pages/queries/details/QueryDetailsPage";
import LiveQueryPage from "pages/queries/live/LiveQueryPage";
@@ -411,7 +412,10 @@ const routes = (
-
+
+
+
+
{/* deprecated URL */}
diff --git a/frontend/router/paths.ts b/frontend/router/paths.ts
index 4bd6cf0ad5..637be790da 100644
--- a/frontend/router/paths.ts
+++ b/frontend/router/paths.ts
@@ -124,8 +124,10 @@ export default {
`${URL_PREFIX}/reports/${queryId || "new"}/live`,
REPORT_DETAILS: (queryId: number): string =>
`${URL_PREFIX}/reports/${queryId}`,
- EDIT_POLICY: (policyId: number): string =>
+ POLICY_DETAILS: (policyId: number): string =>
`${URL_PREFIX}/policies/${policyId}`,
+ EDIT_POLICY: (policyId: number): string =>
+ `${URL_PREFIX}/policies/${policyId}/edit`,
FORGOT_PASSWORD: `${URL_PREFIX}/login/forgot`,
MFA: `${URL_PREFIX}/login/mfa`,
NO_ACCESS: `${URL_PREFIX}/login/denied`,