2024-12-03 14:09:53 +00:00
|
|
|
|
import React, {
|
|
|
|
|
|
useCallback,
|
|
|
|
|
|
useContext,
|
|
|
|
|
|
useRef,
|
|
|
|
|
|
useState,
|
|
|
|
|
|
useEffect,
|
|
|
|
|
|
} from "react";
|
2024-11-06 17:48:11 +00:00
|
|
|
|
import { format } from "date-fns";
|
2024-11-08 14:22:49 +00:00
|
|
|
|
import {
|
|
|
|
|
|
useQuery,
|
|
|
|
|
|
RefetchOptions,
|
|
|
|
|
|
RefetchQueryFilters,
|
|
|
|
|
|
QueryObserverResult,
|
|
|
|
|
|
} from "react-query";
|
2024-11-06 17:48:11 +00:00
|
|
|
|
import FileSaver from "file-saver";
|
|
|
|
|
|
|
|
|
|
|
|
import { AppContext } from "context/app";
|
|
|
|
|
|
import { NotificationContext } from "context/notification";
|
2024-11-08 14:22:49 +00:00
|
|
|
|
import scriptAPI, { IHostScriptsResponse } from "services/entities/scripts";
|
2024-11-06 17:48:11 +00:00
|
|
|
|
import { IHostScript } from "interfaces/script";
|
2024-11-08 14:22:49 +00:00
|
|
|
|
import { IApiError, getErrorReason } from "interfaces/errors";
|
2024-11-06 17:48:11 +00:00
|
|
|
|
|
|
|
|
|
|
import Modal from "components/Modal";
|
2024-12-03 14:09:53 +00:00
|
|
|
|
import ModalFooter from "components/ModalFooter";
|
2024-11-06 17:48:11 +00:00
|
|
|
|
import Button from "components/buttons/Button";
|
|
|
|
|
|
import Spinner from "components/Spinner";
|
|
|
|
|
|
import Icon from "components/Icon";
|
|
|
|
|
|
import Textarea from "components/Textarea";
|
|
|
|
|
|
import CustomLink from "components/CustomLink";
|
|
|
|
|
|
import DataError from "components/DataError";
|
|
|
|
|
|
import paths from "router/paths";
|
|
|
|
|
|
import ActionsDropdown from "components/ActionsDropdown";
|
|
|
|
|
|
import { generateActionDropdownOptions } from "pages/hosts/details/HostDetailsPage/modals/RunScriptModal/ScriptsTableConfig";
|
UI - GitOps Mode: Core abstractions, first batch of applications (#26401)
## For #26229 – Part 1

- This PR contains the core abstractions, routes, API updates, and types
for GitOps mode in the UI. Since this work will touch essentially every
part of the Fleet UI, it is ripe for merge conflicts. To mitigate such
conflicts, I'll be merging this work in a number of iterative PRs. ~To
effectively gate any of this work from showing until it is all merged to
`main`, [this commit](feedbb2d4c25ec2e304e1f18d409cee62f6752ed) hides
the settings section that allows enabling/disabling this setting,
effectively feature flagging the entire thing. In the last of these
iterative PRs, that commit will be reverted to engage the entire
feature. For testing purposes, reviewers can `git revert
feedbb2d4c25ec2e304e1f18d409cee62f6752ed` locally~ The new settings
section for this feature is feature flagged until all PRs are merged -
to show the setting section while testing, run `ALLOW_GITOPS_MODE=true
NODE_ENV=development yarn run webpack --progress --watch` in place of
`make generate-dev`
- Changes file will be added and feature flag removed in the last PR
- [x] Settings page with routing, form, API integration (hidden until
last PR)
- [x] Activities
- [x] Navbar indicator
- Apply GOM conditional UI to:
- [x] Manage enroll secret modal: .5
- Controls >
- [x] Scripts:
- Setup experience >
- [x] Install software > Select software modal
- [x] OS Settings >
- [x] Custom settings
- [x] Disk encryption
- [x] OS Updates
2/18/25, added to this PR:
- [x] Controls > Setup experience > Run script
- [x] Software >
- [x] Manage automations modal
- [x] Add software >
- [x] App Store (VPP)
- [x] Custom package
- [x] Queries
- [x] Manage
- [x] Automations modal
- [x] New
- [x] Edit
- [x] Policies
- [x] Manage
- [x] New
- [x] Edit
- Manage automations
- [x] Calendar events
- [x] Manual QA for all new/changed functionality
---------
Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2025-02-20 16:41:07 +00:00
|
|
|
|
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
|
2025-02-27 15:53:34 +00:00
|
|
|
|
import { getPathWithQueryParams } from "utilities/url";
|
2025-04-28 23:32:41 +00:00
|
|
|
|
import { IPaginatedListScript } from "pages/hosts/ManageHostsPage/components/RunScriptBatchPaginatedList/RunScriptBatchPaginatedList";
|
2024-11-06 17:48:11 +00:00
|
|
|
|
|
|
|
|
|
|
const baseClass = "script-details-modal";
|
|
|
|
|
|
|
|
|
|
|
|
type PartialOrFullHostScript =
|
|
|
|
|
|
| Pick<IHostScript, "script_id" | "name"> // Use on Scripts page does not include last_execution
|
|
|
|
|
|
| IHostScript;
|
|
|
|
|
|
|
|
|
|
|
|
interface IScriptDetailsModalProps {
|
|
|
|
|
|
onCancel: () => void;
|
2025-04-28 23:32:41 +00:00
|
|
|
|
onDelete?: () => void;
|
2024-11-06 17:48:11 +00:00
|
|
|
|
runScriptHelpText?: boolean;
|
|
|
|
|
|
showHostScriptActions?: boolean;
|
|
|
|
|
|
setRunScriptRequested?: (value: boolean) => void;
|
|
|
|
|
|
hostId?: number | null;
|
|
|
|
|
|
hostTeamId?: number | null;
|
2024-11-08 14:22:49 +00:00
|
|
|
|
refetchHostScripts?: <TPageData>(
|
|
|
|
|
|
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
|
|
|
|
|
|
) => Promise<QueryObserverResult<IHostScriptsResponse, IApiError>>;
|
UI: Batch script run detail page (#32333)
## For #31226
New features:
- Dynamic header for each possible state of a batch script run: Started,
Scheduled, and Finished (corresponds to tabs at
`/controls/scripts/progress`
- Unique tabs for each possible status of hosts targeted by a batch
script run: Ran, Errored, Pending, Incompatible, Canceled.
- Within each tab, sortable, paginated host results with output preview
and execution time.
- View script/run details, cancel a batch, view manage hosts page
filtered for the script batch run and a status.
- Global script batch runs activities and and Scripts progress rows now
navigate to this details page.
Cleanups and improvements:
- Expand tab count badge options using “alert”/“pending” variants across
hosts, policies, and query results.
- Misc cleanups and improvements

- [x] Changes file added for user-visible changes in `changes/`,
- [x] Updated automated tests - new tests tracked for follow-up work
- [x] QA'd all new/changed functionality manually
---------
Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2025-08-29 15:37:05 +00:00
|
|
|
|
selectedScriptId?: number;
|
2025-04-28 23:32:41 +00:00
|
|
|
|
selectedScriptDetails?: PartialOrFullHostScript | IPaginatedListScript;
|
2024-11-06 17:48:11 +00:00
|
|
|
|
selectedScriptContent?: string;
|
|
|
|
|
|
isLoadingScriptContent?: boolean;
|
|
|
|
|
|
isScriptContentError?: Error | null;
|
|
|
|
|
|
isHidden?: boolean;
|
|
|
|
|
|
onClickRunDetails?: (scriptExecutionId: string) => void;
|
2025-02-27 15:53:34 +00:00
|
|
|
|
teamIdForApi?: number;
|
2025-04-28 23:32:41 +00:00
|
|
|
|
suppressSecondaryActions?: boolean;
|
|
|
|
|
|
customPrimaryButtons?: React.ReactNode;
|
2024-11-06 17:48:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const ScriptDetailsModal = ({
|
|
|
|
|
|
onCancel,
|
|
|
|
|
|
onDelete,
|
|
|
|
|
|
runScriptHelpText = false,
|
|
|
|
|
|
showHostScriptActions = false,
|
|
|
|
|
|
setRunScriptRequested,
|
|
|
|
|
|
hostId,
|
|
|
|
|
|
hostTeamId,
|
|
|
|
|
|
refetchHostScripts,
|
UI: Batch script run detail page (#32333)
## For #31226
New features:
- Dynamic header for each possible state of a batch script run: Started,
Scheduled, and Finished (corresponds to tabs at
`/controls/scripts/progress`
- Unique tabs for each possible status of hosts targeted by a batch
script run: Ran, Errored, Pending, Incompatible, Canceled.
- Within each tab, sortable, paginated host results with output preview
and execution time.
- View script/run details, cancel a batch, view manage hosts page
filtered for the script batch run and a status.
- Global script batch runs activities and and Scripts progress rows now
navigate to this details page.
Cleanups and improvements:
- Expand tab count badge options using “alert”/“pending” variants across
hosts, policies, and query results.
- Misc cleanups and improvements

- [x] Changes file added for user-visible changes in `changes/`,
- [x] Updated automated tests - new tests tracked for follow-up work
- [x] QA'd all new/changed functionality manually
---------
Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2025-08-29 15:37:05 +00:00
|
|
|
|
selectedScriptId,
|
2024-11-06 17:48:11 +00:00
|
|
|
|
selectedScriptDetails,
|
|
|
|
|
|
selectedScriptContent,
|
|
|
|
|
|
isLoadingScriptContent,
|
|
|
|
|
|
isScriptContentError,
|
|
|
|
|
|
isHidden = false,
|
|
|
|
|
|
onClickRunDetails,
|
2025-02-27 15:53:34 +00:00
|
|
|
|
teamIdForApi,
|
2025-04-28 23:32:41 +00:00
|
|
|
|
suppressSecondaryActions = false,
|
|
|
|
|
|
customPrimaryButtons,
|
2024-11-06 17:48:11 +00:00
|
|
|
|
}: IScriptDetailsModalProps) => {
|
2024-12-03 14:09:53 +00:00
|
|
|
|
// For scrollable modal
|
|
|
|
|
|
const [isTopScrolling, setIsTopScrolling] = useState(false);
|
|
|
|
|
|
const topDivRef = useRef<HTMLDivElement>(null);
|
|
|
|
|
|
const checkScroll = () => {
|
|
|
|
|
|
if (topDivRef.current) {
|
|
|
|
|
|
const isScrolling =
|
|
|
|
|
|
topDivRef.current.scrollHeight > topDivRef.current.clientHeight;
|
|
|
|
|
|
setIsTopScrolling(isScrolling);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2024-11-06 17:48:11 +00:00
|
|
|
|
const { currentUser } = useContext(AppContext);
|
|
|
|
|
|
const { renderFlash } = useContext(NotificationContext);
|
|
|
|
|
|
|
2025-04-28 23:32:41 +00:00
|
|
|
|
// handle multiple possibilities for `selectedScriptDetails`
|
|
|
|
|
|
let scriptId: number | null = null;
|
UI: Batch script run detail page (#32333)
## For #31226
New features:
- Dynamic header for each possible state of a batch script run: Started,
Scheduled, and Finished (corresponds to tabs at
`/controls/scripts/progress`
- Unique tabs for each possible status of hosts targeted by a batch
script run: Ran, Errored, Pending, Incompatible, Canceled.
- Within each tab, sortable, paginated host results with output preview
and execution time.
- View script/run details, cancel a batch, view manage hosts page
filtered for the script batch run and a status.
- Global script batch runs activities and and Scripts progress rows now
navigate to this details page.
Cleanups and improvements:
- Expand tab count badge options using “alert”/“pending” variants across
hosts, policies, and query results.
- Misc cleanups and improvements

- [x] Changes file added for user-visible changes in `changes/`,
- [x] Updated automated tests - new tests tracked for follow-up work
- [x] QA'd all new/changed functionality manually
---------
Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2025-08-29 15:37:05 +00:00
|
|
|
|
if (selectedScriptId) {
|
|
|
|
|
|
scriptId = selectedScriptId;
|
|
|
|
|
|
} else if (selectedScriptDetails) {
|
2025-04-28 23:32:41 +00:00
|
|
|
|
if ("script_id" in selectedScriptDetails) {
|
|
|
|
|
|
scriptId = selectedScriptDetails.script_id;
|
|
|
|
|
|
} else if ("id" in selectedScriptDetails) {
|
|
|
|
|
|
scriptId = selectedScriptDetails.id;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-06 17:48:11 +00:00
|
|
|
|
const {
|
|
|
|
|
|
data: scriptContent,
|
|
|
|
|
|
error: isSelectedScriptContentError,
|
|
|
|
|
|
isLoading: isLoadingSelectedScriptContent,
|
|
|
|
|
|
} = useQuery<any, Error>(
|
2025-04-28 23:32:41 +00:00
|
|
|
|
["scriptContent", scriptId],
|
2024-11-06 17:48:11 +00:00
|
|
|
|
() =>
|
2025-04-28 23:32:41 +00:00
|
|
|
|
scriptId
|
2024-11-08 14:22:49 +00:00
|
|
|
|
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
2025-04-28 23:32:41 +00:00
|
|
|
|
scriptAPI.downloadScript(scriptId)
|
2024-11-06 17:48:11 +00:00
|
|
|
|
: Promise.resolve(null),
|
|
|
|
|
|
{
|
|
|
|
|
|
refetchOnWindowFocus: false,
|
2025-04-28 23:32:41 +00:00
|
|
|
|
enabled: !selectedScriptContent && !!scriptId,
|
2024-11-06 17:48:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
);
|
2024-12-03 14:09:53 +00:00
|
|
|
|
|
|
|
|
|
|
// For scrollable modal
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
checkScroll();
|
|
|
|
|
|
window.addEventListener("resize", checkScroll);
|
|
|
|
|
|
return () => window.removeEventListener("resize", checkScroll);
|
|
|
|
|
|
}, [scriptContent]); // Re-run when data changes
|
|
|
|
|
|
|
2024-11-06 17:48:11 +00:00
|
|
|
|
const getScriptContent = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const content = selectedScriptContent || scriptContent;
|
|
|
|
|
|
const formatDate = format(new Date(), "yyyy-MM-dd");
|
|
|
|
|
|
const filename = `${formatDate} ${
|
|
|
|
|
|
selectedScriptDetails?.name || "Script details"
|
|
|
|
|
|
}`;
|
|
|
|
|
|
const file = new File([content], filename);
|
|
|
|
|
|
FileSaver.saveAs(file);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
renderFlash("error", "Couldn’t Download. Please try again.");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onClickDownload = () => {
|
|
|
|
|
|
if (selectedScriptContent) {
|
|
|
|
|
|
const formatDate = format(new Date(), "yyyy-MM-dd");
|
|
|
|
|
|
const filename = `${formatDate} ${selectedScriptDetails?.name}`;
|
|
|
|
|
|
const file = new File([selectedScriptContent], filename);
|
|
|
|
|
|
FileSaver.saveAs(file);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
getScriptContent();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onSelectMoreActions = useCallback(
|
|
|
|
|
|
async (action: string, script: IHostScript) => {
|
2024-11-08 14:22:49 +00:00
|
|
|
|
if (hostId && !!setRunScriptRequested && !!refetchHostScripts) {
|
2024-11-06 17:48:11 +00:00
|
|
|
|
switch (action) {
|
|
|
|
|
|
case "showRunDetails": {
|
2024-11-08 14:22:49 +00:00
|
|
|
|
if (script.last_execution?.execution_id) {
|
|
|
|
|
|
onClickRunDetails &&
|
|
|
|
|
|
onClickRunDetails(script.last_execution?.execution_id);
|
|
|
|
|
|
}
|
2024-11-06 17:48:11 +00:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
case "run": {
|
|
|
|
|
|
try {
|
|
|
|
|
|
setRunScriptRequested && setRunScriptRequested(true);
|
|
|
|
|
|
await scriptAPI.runScript({
|
|
|
|
|
|
host_id: hostId,
|
|
|
|
|
|
script_id: script.script_id,
|
|
|
|
|
|
});
|
|
|
|
|
|
renderFlash(
|
|
|
|
|
|
"success",
|
|
|
|
|
|
"Script is running or will run when the host comes online."
|
|
|
|
|
|
);
|
|
|
|
|
|
refetchHostScripts();
|
2024-11-08 14:22:49 +00:00
|
|
|
|
|
2025-04-28 23:32:41 +00:00
|
|
|
|
onCancel(); // Running a script returns to previous state
|
2024-11-06 17:48:11 +00:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
renderFlash("error", getErrorReason(e));
|
|
|
|
|
|
setRunScriptRequested(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
default: // do nothing
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
[
|
|
|
|
|
|
hostId,
|
|
|
|
|
|
onClickRunDetails,
|
|
|
|
|
|
setRunScriptRequested,
|
|
|
|
|
|
refetchHostScripts,
|
|
|
|
|
|
renderFlash,
|
2024-11-08 14:22:49 +00:00
|
|
|
|
onCancel,
|
2024-11-06 17:48:11 +00:00
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2024-12-03 14:09:53 +00:00
|
|
|
|
const shouldShowFooter =
|
|
|
|
|
|
!isLoadingScriptContent && selectedScriptDetails !== undefined;
|
2024-11-06 17:48:11 +00:00
|
|
|
|
|
|
|
|
|
|
const renderFooter = () => {
|
|
|
|
|
|
if (!shouldShowFooter) {
|
2024-12-03 14:09:53 +00:00
|
|
|
|
return null;
|
2024-11-06 17:48:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2024-12-03 14:09:53 +00:00
|
|
|
|
<ModalFooter
|
|
|
|
|
|
isTopScrolling={isTopScrolling}
|
|
|
|
|
|
secondaryButtons={
|
2025-04-28 23:32:41 +00:00
|
|
|
|
suppressSecondaryActions ? undefined : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
className={`${baseClass}__action-button`}
|
|
|
|
|
|
variant="icon"
|
|
|
|
|
|
onClick={() => onClickDownload()}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icon name="download" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<GitOpsModeTooltipWrapper
|
|
|
|
|
|
position="bottom"
|
|
|
|
|
|
renderChildren={(disableChildren) => (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
disabled={disableChildren}
|
|
|
|
|
|
className={`${baseClass}__action-button`}
|
|
|
|
|
|
variant="icon"
|
|
|
|
|
|
onClick={onDelete}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icon name="trash" color="ui-fleet-black-75" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)
|
2024-12-03 14:09:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
primaryButtons={
|
2025-04-28 23:32:41 +00:00
|
|
|
|
customPrimaryButtons || (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{showHostScriptActions && selectedScriptDetails && (
|
|
|
|
|
|
<div className={`${baseClass}__manage-automations-wrapper`}>
|
|
|
|
|
|
<ActionsDropdown
|
|
|
|
|
|
className={`${baseClass}__manage-automations-dropdown`}
|
|
|
|
|
|
onChange={(value) =>
|
|
|
|
|
|
onSelectMoreActions(
|
|
|
|
|
|
value,
|
|
|
|
|
|
selectedScriptDetails as IHostScript
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
placeholder="More actions"
|
|
|
|
|
|
isSearchable={false}
|
|
|
|
|
|
options={generateActionDropdownOptions(
|
|
|
|
|
|
currentUser,
|
|
|
|
|
|
hostTeamId || null,
|
2024-12-03 14:09:53 +00:00
|
|
|
|
selectedScriptDetails as IHostScript
|
2025-04-28 23:32:41 +00:00
|
|
|
|
)}
|
|
|
|
|
|
menuPlacement="top"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<Button onClick={onCancel}>Done</Button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)
|
2024-12-03 14:09:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
/>
|
2024-11-06 17:48:11 +00:00
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const renderContent = () => {
|
|
|
|
|
|
if (isLoadingScriptContent || isLoadingSelectedScriptContent) {
|
|
|
|
|
|
return <Spinner />;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isScriptContentError || isSelectedScriptContentError) {
|
|
|
|
|
|
return <DataError description="Close this modal and try again." />;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2024-12-03 14:09:53 +00:00
|
|
|
|
<div
|
|
|
|
|
|
className={`${baseClass}__script-content modal-scrollable-content`}
|
|
|
|
|
|
ref={topDivRef}
|
|
|
|
|
|
>
|
2025-04-28 23:32:41 +00:00
|
|
|
|
<Textarea label="Script content:" variant="code">
|
2024-11-06 17:48:11 +00:00
|
|
|
|
{scriptContent}
|
|
|
|
|
|
</Textarea>
|
|
|
|
|
|
{runScriptHelpText && (
|
|
|
|
|
|
<div className="form-field__help-text">
|
|
|
|
|
|
To run this script on a host, go to the{" "}
|
2025-02-27 15:53:34 +00:00
|
|
|
|
<CustomLink
|
|
|
|
|
|
text="Hosts"
|
|
|
|
|
|
url={getPathWithQueryParams(paths.MANAGE_HOSTS, {
|
|
|
|
|
|
team_id: teamIdForApi,
|
|
|
|
|
|
})}
|
|
|
|
|
|
/>{" "}
|
|
|
|
|
|
page and select a host.
|
2024-11-06 17:48:11 +00:00
|
|
|
|
<br />
|
|
|
|
|
|
To run the script across multiple hosts, add a policy automation on
|
2025-02-27 15:53:34 +00:00
|
|
|
|
the{" "}
|
|
|
|
|
|
<CustomLink
|
|
|
|
|
|
text="Policies"
|
|
|
|
|
|
url={getPathWithQueryParams(paths.MANAGE_POLICIES, {
|
|
|
|
|
|
team_id: teamIdForApi,
|
|
|
|
|
|
})}
|
|
|
|
|
|
/>{" "}
|
|
|
|
|
|
page.
|
2024-11-06 17:48:11 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
className={baseClass}
|
|
|
|
|
|
title={selectedScriptDetails?.name || "Script details"}
|
|
|
|
|
|
width="large"
|
|
|
|
|
|
onExit={onCancel}
|
|
|
|
|
|
isHidden={isHidden}
|
|
|
|
|
|
>
|
2024-12-03 14:09:53 +00:00
|
|
|
|
<>
|
|
|
|
|
|
{renderContent()}
|
|
|
|
|
|
{shouldShowFooter ? renderFooter() : undefined}
|
|
|
|
|
|
</>
|
2024-11-06 17:48:11 +00:00
|
|
|
|
</Modal>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default ScriptDetailsModal;
|