mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Fleet UI: Disable all live query/policy buttons when globally disabled (#14821)
This commit is contained in:
parent
ae6c387059
commit
9ad0962de2
1
changes/14267-disable-run-button
Normal file
1
changes/14267-disable-run-button
Normal file
@ -0,0 +1 @@
|
||||
- If live queries are disabled globally, all UI buttons to run a live query or policy are disabled with a tooltip explanation
|
@ -55,6 +55,7 @@ const PolicyPage = ({
|
||||
isGlobalAdmin,
|
||||
isGlobalMaintainer,
|
||||
isAnyTeamMaintainerOrTeamAdmin,
|
||||
config,
|
||||
} = useContext(AppContext);
|
||||
const {
|
||||
lastEditedQueryBody,
|
||||
@ -225,7 +226,7 @@ const PolicyPage = ({
|
||||
};
|
||||
|
||||
const renderLiveQueryWarning = (): JSX.Element | null => {
|
||||
if (isLiveQueryRunnable) {
|
||||
if (isLiveQueryRunnable || config?.server_settings.live_query_disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import { createCustomRenderer } from "test/test-utils";
|
||||
|
||||
import createMockPolicy from "__mocks__/policyMock";
|
||||
import createMockUser from "__mocks__/userMock";
|
||||
import createMockConfig from "__mocks__/configMock";
|
||||
|
||||
import PolicyForm from "./PolicyForm";
|
||||
|
||||
@ -38,6 +39,7 @@ describe("PolicyForm - component", () => {
|
||||
isOnGlobalTeam: true,
|
||||
isPremiumTier: true,
|
||||
isSandboxMode: false,
|
||||
config: createMockConfig(),
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -93,6 +95,7 @@ describe("PolicyForm - component", () => {
|
||||
isOnGlobalTeam: true,
|
||||
isPremiumTier: true,
|
||||
isSandboxMode: false,
|
||||
config: createMockConfig(),
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -122,9 +125,76 @@ describe("PolicyForm - component", () => {
|
||||
|
||||
await user.hover(screen.getByRole("button", { name: "Save" }));
|
||||
|
||||
expect(
|
||||
container.querySelector("#policy-form__button-wrap--tooltip")
|
||||
).toHaveTextContent(/to save or run the policy/i);
|
||||
expect(container.querySelector("#save-policy-button")).toHaveTextContent(
|
||||
/to save or run the policy/i
|
||||
);
|
||||
});
|
||||
|
||||
it("disables run button with tooltip for globally disabled queries", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
policy: {
|
||||
policyTeamId: undefined,
|
||||
lastEditedQueryId: mockPolicy.id,
|
||||
lastEditedQueryName: mockPolicy.name,
|
||||
lastEditedQueryDescription: mockPolicy.description,
|
||||
lastEditedQueryBody: mockPolicy.query,
|
||||
lastEditedQueryResolution: mockPolicy.resolution,
|
||||
lastEditedQueryCritical: mockPolicy.critical,
|
||||
lastEditedQueryPlatform: undefined, // missing policy platforms
|
||||
defaultPolicy: false,
|
||||
setLastEditedQueryName: jest.fn(),
|
||||
setLastEditedQueryDescription: jest.fn(),
|
||||
setLastEditedQueryBody: jest.fn(),
|
||||
setLastEditedQueryResolution: jest.fn(),
|
||||
setLastEditedQueryCritical: jest.fn(),
|
||||
setLastEditedQueryPlatform: jest.fn(),
|
||||
},
|
||||
app: {
|
||||
currentUser: createMockUser(),
|
||||
isGlobalObserver: false,
|
||||
isGlobalAdmin: true,
|
||||
isGlobalMaintainer: false,
|
||||
isOnGlobalTeam: true,
|
||||
isPremiumTier: true,
|
||||
isSandboxMode: false,
|
||||
config: createMockConfig({
|
||||
server_settings: {
|
||||
...createMockConfig().server_settings,
|
||||
live_query_disabled: true, // Live query disabled
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { container, user } = render(
|
||||
<PolicyForm
|
||||
policyIdForEdit={mockPolicy.id}
|
||||
showOpenSchemaActionText={false}
|
||||
storedPolicy={createMockPolicy()}
|
||||
isStoredPolicyLoading={false}
|
||||
isTeamAdmin={false}
|
||||
isTeamMaintainer={false}
|
||||
isTeamObserver={false}
|
||||
isUpdatingPolicy={false}
|
||||
onCreatePolicy={jest.fn()}
|
||||
onOsqueryTableSelect={jest.fn()}
|
||||
goToSelectTargets={jest.fn()}
|
||||
onUpdate={jest.fn()}
|
||||
onOpenSchemaSidebar={jest.fn()}
|
||||
renderLiveQueryWarning={jest.fn()}
|
||||
backendValidators={{}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Run" })).toBeDisabled();
|
||||
|
||||
await user.hover(screen.getByRole("button", { name: "Run" }));
|
||||
|
||||
expect(container.querySelector("#run-policy-button")).toHaveTextContent(
|
||||
/live queries are disabled/i
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: Consider testing save button is disabled for a sql error
|
||||
|
@ -6,6 +6,7 @@ import ReactTooltip from "react-tooltip";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { size } from "lodash";
|
||||
import classnames from "classnames";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
|
||||
import { addGravatarUrlToResource } from "utilities/helpers";
|
||||
import { AppContext } from "context/app";
|
||||
@ -117,6 +118,7 @@ const PolicyForm = ({
|
||||
isOnGlobalTeam,
|
||||
isPremiumTier,
|
||||
isSandboxMode,
|
||||
config,
|
||||
} = useContext(AppContext);
|
||||
|
||||
const debounceSQL = useDebouncedCallback((sql: string) => {
|
||||
@ -152,6 +154,8 @@ const PolicyForm = ({
|
||||
!policyIdForEdit &&
|
||||
DEFAULT_POLICIES.find((p) => p.name === lastEditedQueryName);
|
||||
|
||||
const disabledLiveQuery = config?.server_settings.live_query_disabled;
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewTemplatePolicy) {
|
||||
setCompatiblePlatforms(lastEditedQueryBody);
|
||||
@ -550,7 +554,7 @@ const PolicyForm = ({
|
||||
<span
|
||||
className={`${baseClass}__button-wrap--tooltip`}
|
||||
data-tip
|
||||
data-for={`${baseClass}__button-wrap--tooltip`}
|
||||
data-for="save-policy-button"
|
||||
data-tip-disable={!isEditMode || isAnyPlatformSelected}
|
||||
>
|
||||
<Button
|
||||
@ -567,8 +571,8 @@ const PolicyForm = ({
|
||||
className={`${baseClass}__button-wrap--tooltip`}
|
||||
place="bottom"
|
||||
effect="solid"
|
||||
id={`${baseClass}__button-wrap--tooltip`}
|
||||
backgroundColor="#3e4771"
|
||||
id="save-policy-button"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
>
|
||||
Select the platform(s) this
|
||||
<br />
|
||||
@ -581,14 +585,18 @@ const PolicyForm = ({
|
||||
<span
|
||||
className={`${baseClass}__button-wrap--tooltip`}
|
||||
data-tip
|
||||
data-for={`${baseClass}__button-wrap--tooltip`}
|
||||
data-tip-disable={!isEditMode || isAnyPlatformSelected}
|
||||
data-for="run-policy-button"
|
||||
data-tip-disable={
|
||||
(!isEditMode || isAnyPlatformSelected) && !disabledLiveQuery
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="blue-green"
|
||||
onClick={goToSelectTargets}
|
||||
disabled={isEditMode && !isAnyPlatformSelected}
|
||||
disabled={
|
||||
(isEditMode && !isAnyPlatformSelected) || disabledLiveQuery
|
||||
}
|
||||
>
|
||||
Run
|
||||
</Button>
|
||||
@ -597,14 +605,19 @@ const PolicyForm = ({
|
||||
className={`${baseClass}__button-wrap--tooltip`}
|
||||
place="bottom"
|
||||
effect="solid"
|
||||
id={`${baseClass}__button-wrap--tooltip`}
|
||||
backgroundColor="#3e4771"
|
||||
id="run-policy-button"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
data-html
|
||||
>
|
||||
Select the platform(s) this
|
||||
<br />
|
||||
policy will be checked on
|
||||
<br />
|
||||
to save or run the policy.
|
||||
{disabledLiveQuery ? (
|
||||
<>Live queries are disabled in organization settings</>
|
||||
) : (
|
||||
<>
|
||||
Select the platform(s) this <br />
|
||||
policy will be checked on <br />
|
||||
to save or run the policy.
|
||||
</>
|
||||
)}
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -2,6 +2,8 @@ import React, { useContext, useState, useEffect } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { InjectedRouter, Params } from "react-router/lib/Router";
|
||||
import { useErrorHandler } from "react-error-boundary";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
|
||||
import PATHS from "router/paths";
|
||||
import { AppContext } from "context/app";
|
||||
@ -171,6 +173,7 @@ const QueryDetailsPage = ({
|
||||
const isApiError = storedQueryError || queryReportError;
|
||||
const isClipped =
|
||||
(queryReport?.results?.length ?? 0) >= QUERY_REPORT_RESULTS_LIMIT;
|
||||
const disabledLiveQuery = config?.server_settings.live_query_disabled;
|
||||
|
||||
const renderHeader = () => {
|
||||
const canEditQuery =
|
||||
@ -216,15 +219,33 @@ const QueryDetailsPage = ({
|
||||
<div
|
||||
className={`${baseClass}__button-wrap ${baseClass}__button-wrap--new-query`}
|
||||
>
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="blue-green"
|
||||
onClick={() => {
|
||||
queryId && router.push(PATHS.LIVE_QUERY(queryId));
|
||||
}}
|
||||
<div
|
||||
data-tip
|
||||
data-for="live-query-button"
|
||||
// Tooltip shows when live queries are globally disabled
|
||||
data-tip-disable={!disabledLiveQuery}
|
||||
>
|
||||
Live query
|
||||
</Button>
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="blue-green"
|
||||
onClick={() => {
|
||||
queryId && router.push(PATHS.LIVE_QUERY(queryId));
|
||||
}}
|
||||
disabled={disabledLiveQuery}
|
||||
>
|
||||
Live query
|
||||
</Button>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
className="live-query-button-tooltip"
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id="live-query-button"
|
||||
data-html
|
||||
>
|
||||
Live queries are disabled in organization settings
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -68,6 +68,7 @@ const EditQueryPage = ({
|
||||
isAnyTeamMaintainerOrTeamAdmin,
|
||||
isObserverPlus,
|
||||
isAnyTeamObserverPlus,
|
||||
config,
|
||||
} = useContext(AppContext);
|
||||
const {
|
||||
selectedOsqueryTable,
|
||||
@ -293,7 +294,7 @@ const EditQueryPage = ({
|
||||
};
|
||||
|
||||
const renderLiveQueryWarning = (): JSX.Element | null => {
|
||||
if (isLiveQueryRunnable) {
|
||||
if (isLiveQueryRunnable || config?.server_settings.live_query_disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import { createCustomRenderer } from "test/test-utils";
|
||||
|
||||
import createMockQuery from "__mocks__/queryMock";
|
||||
import createMockUser from "__mocks__/userMock";
|
||||
import createMockConfig from "__mocks__/configMock";
|
||||
|
||||
import EditQueryForm from "./EditQueryForm";
|
||||
|
||||
@ -51,6 +52,7 @@ describe("EditQueryForm - component", () => {
|
||||
isOnGlobalTeam: true,
|
||||
isPremiumTier: true,
|
||||
isSandboxMode: false,
|
||||
config: createMockConfig(),
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -80,6 +82,76 @@ describe("EditQueryForm - component", () => {
|
||||
expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("disables live query button for globally disabled live queries", async () => {
|
||||
const render = createCustomRenderer({
|
||||
context: {
|
||||
query: {
|
||||
lastEditedQueryId: mockQuery.id,
|
||||
lastEditedQueryName: "", // missing query name
|
||||
lastEditedQueryDescription: mockQuery.description,
|
||||
lastEditedQueryBody: mockQuery.query,
|
||||
lastEditedQueryObserverCanRun: mockQuery.observer_can_run,
|
||||
lastEditedQueryFrequency: mockQuery.interval,
|
||||
lastEditedQueryPlatforms: mockQuery.platform,
|
||||
lastEditedQueryMinOsqueryVersion: mockQuery.min_osquery_version,
|
||||
lastEditedQueryLoggingType: mockQuery.logging,
|
||||
setLastEditedQueryName: jest.fn(),
|
||||
setLastEditedQueryDescription: jest.fn(),
|
||||
setLastEditedQueryBody: jest.fn(),
|
||||
setLastEditedQueryObserverCanRun: jest.fn(),
|
||||
setLastEditedQueryFrequency: jest.fn(),
|
||||
setLastEditedQueryPlatforms: jest.fn(),
|
||||
setLastEditedQueryMinOsqueryVersion: jest.fn(),
|
||||
setLastEditedQueryLoggingType: jest.fn(),
|
||||
},
|
||||
app: {
|
||||
currentUser: createMockUser(),
|
||||
isGlobalObserver: false,
|
||||
isGlobalAdmin: true,
|
||||
isGlobalMaintainer: false,
|
||||
isOnGlobalTeam: true,
|
||||
isPremiumTier: true,
|
||||
isSandboxMode: false,
|
||||
config: createMockConfig({
|
||||
server_settings: {
|
||||
...createMockConfig().server_settings,
|
||||
live_query_disabled: true, // Live query disabled
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { container, user } = render(
|
||||
<EditQueryForm
|
||||
router={mockRouter}
|
||||
queryIdForEdit={1}
|
||||
apiTeamIdForQuery={1}
|
||||
teamNameForQuery={"Apples"}
|
||||
showOpenSchemaActionText
|
||||
storedQuery={createMockQuery({ name: "Mock query" })}
|
||||
isStoredQueryLoading={false}
|
||||
isQuerySaving={false}
|
||||
isQueryUpdating={false}
|
||||
onSubmitNewQuery={jest.fn()}
|
||||
onOsqueryTableSelect={jest.fn()}
|
||||
onUpdate={jest.fn()}
|
||||
onOpenSchemaSidebar={jest.fn()}
|
||||
renderLiveQueryWarning={jest.fn()}
|
||||
backendValidators={{}}
|
||||
showConfirmSaveChangesModal={false}
|
||||
setShowConfirmSaveChangesModal={jest.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Live query" })).toBeDisabled();
|
||||
|
||||
await user.hover(screen.getByRole("button", { name: "Live query" }));
|
||||
|
||||
expect(container.querySelector("#live-query-button")).toHaveTextContent(
|
||||
/live queries are disabled/i
|
||||
);
|
||||
});
|
||||
// TODO: Consider testing save button is disabled for a sql error
|
||||
// Trickiness is in modifying react-ace using react-testing library
|
||||
});
|
||||
|
@ -161,10 +161,12 @@ const EditQueryForm = ({
|
||||
isGlobalMaintainer,
|
||||
isObserverPlus,
|
||||
isAnyTeamObserverPlus,
|
||||
config,
|
||||
} = useContext(AppContext);
|
||||
const { renderFlash } = useContext(NotificationContext);
|
||||
|
||||
const savedQueryMode = !!queryIdForEdit;
|
||||
const disabledLiveQuery = config?.server_settings.live_query_disabled;
|
||||
const [errors, setErrors] = useState<{ [key: string]: any }>({}); // string | null | undefined or boolean | undefined
|
||||
// NOTE: SaveQueryModal is only being used to create a new query in this component.
|
||||
// It's easy to confuse with other names like promptSaveQuery, promptSaveAsNewQuery, etc.,
|
||||
@ -589,18 +591,36 @@ const EditQueryForm = ({
|
||||
<div
|
||||
className={`${baseClass}__button-wrap ${baseClass}__button-wrap--new-query`}
|
||||
>
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="blue-green"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
PATHS.LIVE_QUERY(queryIdForEdit) +
|
||||
TAGGED_TEMPLATES.queryByHostRoute(hostId)
|
||||
);
|
||||
}}
|
||||
<div
|
||||
data-tip
|
||||
data-for="live-query-button"
|
||||
// Tooltip shows when live queries are globally disabled
|
||||
data-tip-disable={!disabledLiveQuery}
|
||||
>
|
||||
Live query
|
||||
</Button>
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="blue-green"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
PATHS.LIVE_QUERY(queryIdForEdit) +
|
||||
TAGGED_TEMPLATES.queryByHostRoute(hostId)
|
||||
);
|
||||
}}
|
||||
disabled={disabledLiveQuery}
|
||||
>
|
||||
Live query
|
||||
</Button>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
className={`live-query-button-tooltip`}
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id="live-query-button"
|
||||
data-html
|
||||
>
|
||||
Live queries are disabled in organization settings
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
@ -808,18 +828,36 @@ const EditQueryForm = ({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="blue-green"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
PATHS.LIVE_QUERY(queryIdForEdit) +
|
||||
TAGGED_TEMPLATES.queryByHostRoute(hostId)
|
||||
);
|
||||
}}
|
||||
<div
|
||||
data-tip
|
||||
data-for="live-query-button"
|
||||
// Tooltip shows when live queries are globally disabled
|
||||
data-tip-disable={!disabledLiveQuery}
|
||||
>
|
||||
Live query
|
||||
</Button>
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="blue-green"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
PATHS.LIVE_QUERY(queryIdForEdit) +
|
||||
TAGGED_TEMPLATES.queryByHostRoute(hostId)
|
||||
);
|
||||
}}
|
||||
disabled={disabledLiveQuery}
|
||||
>
|
||||
Live query
|
||||
</Button>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
className={`live-query-button-tooltip`}
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id="live-query-button"
|
||||
data-html
|
||||
>
|
||||
Live queries are disabled in organization settings
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
</form>
|
||||
{showSaveQueryModal && (
|
||||
|
@ -59,6 +59,7 @@ const RunQueryPage = ({
|
||||
isAnyTeamMaintainerOrTeamAdmin,
|
||||
isObserverPlus,
|
||||
isAnyTeamObserverPlus,
|
||||
config,
|
||||
} = useContext(AppContext);
|
||||
const {
|
||||
selectedQueryTargets,
|
||||
@ -90,6 +91,15 @@ const RunQueryPage = ({
|
||||
const [targetsTotalCount, setTargetsTotalCount] = useState(0);
|
||||
const [isLiveQueryRunnable, setIsLiveQueryRunnable] = useState(true);
|
||||
|
||||
const disabledLiveQuery = config?.server_settings.live_query_disabled;
|
||||
|
||||
// Reroute users out of live flow when live queries are globally disabled
|
||||
if (disabledLiveQuery) {
|
||||
queryId
|
||||
? router.push(PATHS.QUERY(queryId))
|
||||
: router.push(PATHS.NEW_QUERY());
|
||||
}
|
||||
|
||||
// disabled on page load so we can control the number of renders
|
||||
// else it will re-populate the context on occasion
|
||||
const { data: storedQuery } = useQuery<
|
||||
@ -155,11 +165,6 @@ const RunQueryPage = ({
|
||||
});
|
||||
}, [targetedLabels, targetedHosts, targetedTeams]);
|
||||
|
||||
console.log(
|
||||
"LiveQueryPage.tsx: selectedQueryTargetsByType",
|
||||
selectedQueryTargetsByType
|
||||
);
|
||||
|
||||
// Updates title that shows up on browser tabs
|
||||
useEffect(() => {
|
||||
// e.g., Run live query | Discover TLS certificates | Fleet for osquery
|
||||
|
Loading…
Reference in New Issue
Block a user