fleet/frontend/pages/hosts/details/HostDetailsPage/modals/SelectQueryModal/SelectQueryModal.tsx

253 lines
6.6 KiB
TypeScript
Raw Normal View History

import React, { useState, useCallback, useContext } from "react";
import { useQuery } from "react-query";
import { filter, includes } from "lodash";
import { InjectedRouter } from "react-router";
import PATHS from "router/paths";
import permissions from "utilities/permissions";
import { AppContext } from "context/app";
import { QueryContext } from "context/query";
import queryAPI from "services/entities/queries";
// @ts-ignore
import InputFieldWithIcon from "components/forms/fields/InputFieldWithIcon";
import Button from "components/buttons/Button";
import Modal from "components/Modal";
import DataError from "components/DataError";
import {
IListQueriesResponse,
IQueryKeyQueriesLoadAll,
ISchedulableQuery,
} from "interfaces/schedulable_query";
import { API_ALL_TEAMS_ID } from "interfaces/team";
import { DEFAULT_TARGETS_BY_TYPE } from "interfaces/target";
import { getPathWithQueryParams } from "utilities/url";
export interface ISelectQueryModalProps {
onCancel: () => void;
isOnlyObserver?: boolean;
hostId: number;
hostTeamId: number | null;
router: InjectedRouter; // v3
currentTeamId: number | undefined;
}
const baseClass = "select-query-modal";
const SelectQueryModal = ({
onCancel,
isOnlyObserver,
hostId,
hostTeamId,
router,
currentTeamId,
}: ISelectQueryModalProps): JSX.Element => {
const { setSelectedQueryTargetsByType } = useContext(QueryContext);
const { data: queries, error: queriesErr } = useQuery<
IListQueriesResponse,
Error,
ISchedulableQuery[],
IQueryKeyQueriesLoadAll[]
>(
[
{
scope: "queries",
teamId: hostTeamId || API_ALL_TEAMS_ID,
mergeInherited: hostTeamId !== API_ALL_TEAMS_ID,
},
],
({ queryKey }) => queryAPI.loadAll(queryKey[0]),
{
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
retry: false,
select: (data: IListQueriesResponse) => data.queries,
}
);
const onQueryHostCustom = () => {
setSelectedQueryTargetsByType(DEFAULT_TARGETS_BY_TYPE);
router.push(
Update urls to use "fleets" and "reports" instead of "teams" and "queries" (#41084) <!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** For #41030 # Details This PR updates front-end routes and redirects the old routes to the new ones. While I typically have shied away from renaming vars and constants in this phase of the renaming work, I chose to rename the path constants here because they're a lot less useful when they have names that don't correspond to the paths they're representing. I did the renames using VSCode's "Rename Symbol" feature which automatically finds and fixes any references. I then asked Claude to verify the changes and it didn't find any dangling references (also the code would fail to compile unless all the new names collided with old ones). # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. n/a ## Testing - [ ] Added/updated automated tests no relevant tests exist - [X] QA'd all new/changed functionality manually ## Reports (formerly Queries) **New routes:** - [x] /reports/manage — Reports list page - [x] /reports/new — New report editor - [x] /reports/new/live — New report live query - [x] /reports/:id — Report details - [x] /reports/:id/edit — Edit report - [x] /reports/:id/live — Live report run **Redirects from old routes:** - [x] /queries → /reports - [x] /queries/manage → /reports/manage - [x] /queries/new → /reports/new - [x] /queries/new/live → /reports/new/live - [x] /queries/:id → /reports/:id - [x] /queries/:id/edit → /reports/:id/edit - [x] /queries/:id/live → /reports/:id/live ## Host Reports (formerly Host Queries) **New routes:** - [x] /hosts/:host_id/reports/:query_id — Host report results **Redirects from old routes:** - [ ] ~/hosts/:host_id/schedule → /hosts/:host_id/reports~ <- this is not a real URL; removed current broken redirect - [x] /hosts/:host_id/queries/:query_id → /hosts/:host_id/reports/:query_id ## Fleets (formerly Teams) **New routes:** - [x] /settings/fleets — Fleets list page - [x] /settings/fleets/users?fleet_id=:id — Fleet users - [x] /settings/fleets/options?fleet_id=:id — Fleet agent options - [x] /settings/fleets/settings?fleet_id=:id — Fleet settings **Redirects from old routes:** - [x] /settings/teams → /settings/fleets - [x] /settings/teams/users → /settings/fleets/users - [x] /settings/teams/options → /settings/fleets/options - [x] /settings/teams/settings → /settings/fleets/settings - [x] /settings/teams/:team_id → /settings/fleets - [x] /settings/teams/:team_id/users → /settings/fleets - [x] /settings/teams/:team_id/options → /settings/fleets **Navigation & Links** - [x] Top nav "Reports" link goes to /reports/manage - [x] User menu team switcher navigates to /settings/fleets/users?fleet_id=:id - [x] Admin sidebar "Fleets" tab goes to /settings/fleets - [x] "Create a fleet" links (user form, transfer host modal) go to /settings/fleets - [x] "Back to fleets" button on fleet details goes to /settings/fleets - [x] Fleet table name links go to /settings/fleets/users?fleet_id=:id - [x] Host details "Add query" button goes to /reports/new - [x] Select query modal links go to /reports/new and /reports/:id/edit - [x] Query report "full report" link goes to /reports/:id - [x] Browser tab titles show correct names for report pages **Query params preserved through redirects** - [x] /queries/:id?fleet_id=1 → /reports/:id?fleet_id=1 - [x] /settings/teams/users?fleet_id=1 → /settings/fleets/users?fleet_id=1 For unreleased bug fixes in a release candidate, one of: - [X] Confirmed that the fix is not expected to adversely impact load test results
2026-03-06 14:24:50 +00:00
getPathWithQueryParams(PATHS.NEW_REPORT, {
host_id: hostId,
fleet_id: currentTeamId,
})
);
};
const onQueryHostSaved = (selectedQuery: ISchedulableQuery) => {
setSelectedQueryTargetsByType(DEFAULT_TARGETS_BY_TYPE);
router.push(
Update urls to use "fleets" and "reports" instead of "teams" and "queries" (#41084) <!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** For #41030 # Details This PR updates front-end routes and redirects the old routes to the new ones. While I typically have shied away from renaming vars and constants in this phase of the renaming work, I chose to rename the path constants here because they're a lot less useful when they have names that don't correspond to the paths they're representing. I did the renames using VSCode's "Rename Symbol" feature which automatically finds and fixes any references. I then asked Claude to verify the changes and it didn't find any dangling references (also the code would fail to compile unless all the new names collided with old ones). # Checklist for submitter If some of the following don't apply, delete the relevant line. - [ ] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. n/a ## Testing - [ ] Added/updated automated tests no relevant tests exist - [X] QA'd all new/changed functionality manually ## Reports (formerly Queries) **New routes:** - [x] /reports/manage — Reports list page - [x] /reports/new — New report editor - [x] /reports/new/live — New report live query - [x] /reports/:id — Report details - [x] /reports/:id/edit — Edit report - [x] /reports/:id/live — Live report run **Redirects from old routes:** - [x] /queries → /reports - [x] /queries/manage → /reports/manage - [x] /queries/new → /reports/new - [x] /queries/new/live → /reports/new/live - [x] /queries/:id → /reports/:id - [x] /queries/:id/edit → /reports/:id/edit - [x] /queries/:id/live → /reports/:id/live ## Host Reports (formerly Host Queries) **New routes:** - [x] /hosts/:host_id/reports/:query_id — Host report results **Redirects from old routes:** - [ ] ~/hosts/:host_id/schedule → /hosts/:host_id/reports~ <- this is not a real URL; removed current broken redirect - [x] /hosts/:host_id/queries/:query_id → /hosts/:host_id/reports/:query_id ## Fleets (formerly Teams) **New routes:** - [x] /settings/fleets — Fleets list page - [x] /settings/fleets/users?fleet_id=:id — Fleet users - [x] /settings/fleets/options?fleet_id=:id — Fleet agent options - [x] /settings/fleets/settings?fleet_id=:id — Fleet settings **Redirects from old routes:** - [x] /settings/teams → /settings/fleets - [x] /settings/teams/users → /settings/fleets/users - [x] /settings/teams/options → /settings/fleets/options - [x] /settings/teams/settings → /settings/fleets/settings - [x] /settings/teams/:team_id → /settings/fleets - [x] /settings/teams/:team_id/users → /settings/fleets - [x] /settings/teams/:team_id/options → /settings/fleets **Navigation & Links** - [x] Top nav "Reports" link goes to /reports/manage - [x] User menu team switcher navigates to /settings/fleets/users?fleet_id=:id - [x] Admin sidebar "Fleets" tab goes to /settings/fleets - [x] "Create a fleet" links (user form, transfer host modal) go to /settings/fleets - [x] "Back to fleets" button on fleet details goes to /settings/fleets - [x] Fleet table name links go to /settings/fleets/users?fleet_id=:id - [x] Host details "Add query" button goes to /reports/new - [x] Select query modal links go to /reports/new and /reports/:id/edit - [x] Query report "full report" link goes to /reports/:id - [x] Browser tab titles show correct names for report pages **Query params preserved through redirects** - [x] /queries/:id?fleet_id=1 → /reports/:id?fleet_id=1 - [x] /settings/teams/users?fleet_id=1 → /settings/fleets/users?fleet_id=1 For unreleased bug fixes in a release candidate, one of: - [X] Confirmed that the fix is not expected to adversely impact load test results
2026-03-06 14:24:50 +00:00
getPathWithQueryParams(PATHS.EDIT_REPORT(selectedQuery.id), {
host_id: hostId,
fleet_id: currentTeamId,
})
);
};
let queriesAvailableToRun = queries;
const { currentUser, isObserverPlus } = useContext(AppContext);
/* Context team id might be different that host's team id
Observer plus must be checked against host's team id */
const isHostsTeamObserverPlus = currentUser
? permissions.isObserverPlus(currentUser, hostTeamId)
: false;
const [queriesFilter, setQueriesFilter] = useState("");
if (isOnlyObserver && !isObserverPlus && !isHostsTeamObserverPlus) {
queriesAvailableToRun =
queries?.filter((query) => query.observer_can_run === true) || [];
}
const getQueries = () => {
if (!queriesFilter) {
return queriesAvailableToRun;
}
const lowerQueryFilter = queriesFilter.toLowerCase();
return filter(queriesAvailableToRun, (query) => {
if (!query.name) {
return false;
}
const lowerQueryName = query.name.toLowerCase();
return includes(lowerQueryName, lowerQueryFilter);
});
};
const onFilterQueries = useCallback(
(filterString: string): void => {
setQueriesFilter(filterString);
},
[setQueriesFilter]
);
const queriesFiltered = getQueries();
const queriesCount = queriesFiltered?.length || 0;
const renderDescription = (): JSX.Element => {
return (
<div className={`${baseClass}__description`}>
Choose a report to run on this host
{(!isOnlyObserver || isObserverPlus || isHostsTeamObserverPlus) && (
<>
{" "}
or{" "}
<Button variant="text-link" onClick={onQueryHostCustom}>
create your own report
</Button>
</>
)}
.
</div>
);
};
const renderQueries = (): JSX.Element => {
if (queriesErr) {
return <DataError />;
}
if (!queriesFilter && queriesCount === 0) {
return (
<div className={`${baseClass}__no-queries`}>
<span className="info__header">You have no saved reports.</span>
<span className="info__data">
Expecting to see reports? Try again in a few seconds as the system
catches up.
</span>
</div>
);
}
if (queriesCount > 0) {
const queryList =
queriesFiltered?.map((query) => {
return (
<Button
key={query.id}
variant="unstyled-modal-query"
className={`${baseClass}__modal-query-button`}
onClick={() => onQueryHostSaved(query)}
>
<>
<span className="info__header">{query.name}</span>
{query.description && (
<span className="info__data">{query.description}</span>
)}
</>
</Button>
);
}) || [];
return (
<>
<InputFieldWithIcon
name="query-filter"
onChange={onFilterQueries}
placeholder="Filter reports"
value={queriesFilter}
autofocus
iconSvg="search"
/>
<div className={`${baseClass}__query-selection`}>{queryList}</div>
</>
);
}
if (queriesFilter && queriesCount === 0) {
return (
<>
<div className={`${baseClass}__filter-queries`}>
<InputFieldWithIcon
name="query-filter"
onChange={onFilterQueries}
placeholder="Filter reports"
value={queriesFilter}
autofocus
iconSvg="search"
/>
</div>
<div className={`${baseClass}__no-queries`}>
<span className="info__header">
No reports match the current search criteria.
</span>
<span className="info__data">
Expecting to see reports? Try again in a few seconds as the system
catches up.
</span>
</div>
</>
);
}
return <></>;
};
return (
<Modal
title="Select a report"
onExit={onCancel}
onEnter={onCancel}
className={baseClass}
width="large"
>
{renderDescription()}
{renderQueries()}
</Modal>
);
};
export default SelectQueryModal;