fleet/frontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/ScriptLibrary.tsx
Scott Gress 2686907dba
Update API calls in front-end to use new, non-deprecated URLs and params (#41515)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #41391

# Details

This PR updates front-end API calls to use new URLs and API params, so
that the front end doesn't cause deprecation warnings to appear on the
server.

# 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, should not be user-visible

## Testing

- [X] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually
The biggest risk here is not that we missed a spot that still causes a
deprecation warning, but that we might inadvertently make a change that
breaks the front end, for instance by sending `fleet_id` to a function
that drops it silently and thus sends no ID to the server. Fortunately
we use TypeScript in virtually every place affected by these changes, so
the code would not compile if there were mismatches between the API
expectation and what we're sending. Still, spot checking as many places
as possible both for deprecation-warning leaks and loss of functionality
is important.

## Summary by CodeRabbit

* **Refactor**
* Updated API nomenclature across the application to use "fleets"
instead of "teams" and "reports" instead of "queries" in endpoint paths
and request/response payloads.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-03-12 22:26:48 -05:00

228 lines
6.5 KiB
TypeScript

import React, { useCallback, useContext, useRef, useState } from "react";
import { AxiosError } from "axios";
import { useQuery } from "react-query";
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
import PATHS from "router/paths";
import { AppContext } from "context/app";
import { IScript } from "interfaces/script";
import scriptAPI, {
IListScriptsQueryKey,
IScriptsResponse,
} from "services/entities/scripts";
import DataError from "components/DataError";
import InfoBanner from "components/InfoBanner";
import Spinner from "components/Spinner";
import Pagination from "components/Pagination";
import SectionHeader from "components/SectionHeader";
import Card from "components/Card";
import UploadList from "../../../../../components/UploadList";
import DeleteScriptModal from "../../components/DeleteScriptModal";
import EditScriptModal from "../../components/EditScriptModal";
import ScriptUploadModal from "../../components/ScriptUploadModal";
import ScriptListHeading from "../../components/ScriptListHeading";
import ScriptListItem from "../../components/ScriptListItem";
import ScriptUploader from "../../components/ScriptUploader";
import { IScriptsCommonProps } from "../../ScriptsNavItems";
const baseClass = "script-library";
const SCRIPTS_PER_PAGE = 10;
const DEFAULT_PAGE = 0;
export type IScriptLibraryProps = IScriptsCommonProps;
const ScriptLibrary = ({ router, teamId, location }: IScriptLibraryProps) => {
const currentPage = location.query.page
? parseInt(location.query.page, 10)
: DEFAULT_PAGE;
const { isPremiumTier, isGlobalTechnician, isTeamTechnician } = useContext(
AppContext
);
const isTechnician = isGlobalTechnician || isTeamTechnician;
const [showDeleteScriptModal, setShowDeleteScriptModal] = useState(false);
const [showEditScriptModal, setShowEditScriptModal] = useState(false);
const [showAddScriptModal, setShowAddScriptModal] = useState(false);
const selectedScript = useRef<IScript | null>(null);
const {
data: { scripts, meta } = {},
isLoading,
isError,
refetch: refetchScripts,
} = useQuery<
IScriptsResponse,
AxiosError,
IScriptsResponse,
IListScriptsQueryKey[]
>(
[
{
scope: "scripts",
fleet_id: teamId,
page: currentPage,
per_page: SCRIPTS_PER_PAGE,
},
],
({ queryKey: [{ fleet_id, page, per_page }] }) =>
scriptAPI.getScripts({ fleet_id, page, per_page }),
{
...DEFAULT_USE_QUERY_OPTIONS,
staleTime: 3000,
}
);
// pagination controls
const path = PATHS.CONTROLS_SCRIPTS_LIBRARY;
const queryString = isPremiumTier ? `?fleet_id=${teamId}&` : "?";
const onPrevPage = useCallback(() => {
router.push(path.concat(`${queryString}page=${currentPage - 1}`));
}, [router, path, currentPage, queryString]);
const onNextPage = useCallback(() => {
router.push(path.concat(`${queryString}page=${currentPage + 1}`));
}, [router, path, currentPage, queryString]);
const { config } = useContext(AppContext);
if (!config) return null;
const onClickScript = (script: IScript) => {
selectedScript.current = script;
setShowEditScriptModal(true);
};
const onEditScript = (script: IScript) => {
selectedScript.current = script;
setShowEditScriptModal(true);
};
const onExitEditScript = () => {
selectedScript.current = null;
setShowEditScriptModal(false);
};
const onClickDelete = (script: IScript) => {
selectedScript.current = script;
setShowDeleteScriptModal(true);
};
const onCancelDelete = () => {
setShowDeleteScriptModal(false);
selectedScript.current = null;
};
const onDeleteScript = () => {
selectedScript.current = null;
setShowDeleteScriptModal(false);
refetchScripts();
};
const renderScriptsList = () => {
if (isLoading) {
return <Spinner />;
}
if (isError) {
return <DataError />;
}
if (currentPage === 0 && !scripts?.length) {
return null;
}
const headingComponent = () => (
<ScriptListHeading
onClickAddScript={
isTechnician ? undefined : () => setShowAddScriptModal(true)
}
/>
);
return (
<>
<UploadList
keyAttribute="id"
listItems={scripts || []}
HeadingComponent={headingComponent}
ListItemComponent={({ listItem }) => (
<ScriptListItem
script={listItem}
onDelete={onClickDelete}
onClickScript={onClickScript}
onEdit={onEditScript}
isTechnician={isTechnician}
/>
)}
/>
<Pagination
disablePrev={isLoading || !meta?.has_previous_results}
disableNext={isLoading || !meta?.has_next_results}
hidePagination={
!isLoading && !meta?.has_previous_results && !meta?.has_next_results
}
onPrevPage={onPrevPage}
onNextPage={onNextPage}
/>
</>
);
};
const renderScriptsDisabledBanner = () => (
<InfoBanner color="yellow">
<div>
<b>Running scripts is disabled in organization settings.</b> You can
still manage your library of macOS and Windows scripts below.
</div>
</InfoBanner>
);
return (
<div className={baseClass}>
<SectionHeader title="Library" alignLeftHeaderVertically />
{config.server_settings.scripts_disabled && renderScriptsDisabledBanner()}
{renderScriptsList()}
{!isLoading &&
currentPage === 0 &&
!scripts?.length &&
(isTechnician ? (
<Card className="empty-scripts">No scripts uploaded.</Card>
) : (
<ScriptUploader onButtonClick={() => setShowAddScriptModal(true)} />
))}
{showDeleteScriptModal && selectedScript.current && (
<DeleteScriptModal
scriptName={selectedScript.current?.name}
scriptId={selectedScript.current?.id}
onCancel={onCancelDelete}
afterDelete={onDeleteScript}
/>
)}
{showEditScriptModal && selectedScript.current && (
<EditScriptModal
scriptId={selectedScript.current.id}
scriptName={selectedScript.current.name}
onExit={onExitEditScript}
/>
)}
{showAddScriptModal && (
<ScriptUploadModal
currentTeamId={teamId}
onExit={() => setShowAddScriptModal(false)}
onSubmit={() => {
setShowAddScriptModal(false);
refetchScripts();
}}
/>
)}
</div>
);
};
export default ScriptLibrary;