fleet/frontend/pages/labels/EditLabelPage/EditLabelPage.tsx
Ian Littman 8e4e89f4e9
API + auth + UI changes for team labels (#37208)
Covers #36760, #36758.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] 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.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [ ] QA'd all new/changed functionality manually
2025-12-29 21:28:45 -06:00

160 lines
4.4 KiB
TypeScript

import React, { useContext } from "react";
import { useQuery } from "react-query";
import { RouteComponentProps } from "react-router";
import { AxiosError } from "axios";
import PATHS from "router/paths";
import labelsAPI, {
IGetHostsInLabelResponse,
IGetLabelResponse,
} from "services/entities/labels";
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
import { ILabel } from "interfaces/label";
import { IHost } from "interfaces/host";
import { NotificationContext } from "context/notification";
import { AppContext } from "context/app";
import MainContent from "components/MainContent";
import Spinner from "components/Spinner";
import DataError from "components/DataError";
import DynamicLabelForm from "../components/DynamicLabelForm";
import ManualLabelForm from "../components/ManualLabelForm";
import { IDynamicLabelFormData } from "../components/DynamicLabelForm/DynamicLabelForm";
import { IManualLabelFormData } from "../components/ManualLabelForm/ManualLabelForm";
import { hasEditPermission } from "../ManageLabelsPage/LabelsTable/LabelsTableConfig";
const baseClass = "edit-label-page";
interface IEditLabelPageRouteParams {
label_id: string;
}
type IEditLabelPageProps = RouteComponentProps<
never,
IEditLabelPageRouteParams
>;
const EditLabelPage = ({ routeParams, router }: IEditLabelPageProps) => {
const { renderFlash } = useContext(NotificationContext);
const { currentUser } = useContext(AppContext);
const labelId = parseInt(routeParams.label_id, 10);
const {
data: label,
isLoading: isLoadingLabel,
isError: isErrorLabel,
} = useQuery<IGetLabelResponse, AxiosError, ILabel>(
["label", labelId, currentUser],
() => labelsAPI.getLabel(labelId),
{
...DEFAULT_USE_QUERY_OPTIONS,
select: (data) => data.label,
onSuccess: (data) => {
// can't edit host_vitals labels yet
if (data.label_membership_type === "host_vitals") {
renderFlash(
"error",
"Host vitals labels are not editable. Delete the label and re-add it to make changes."
);
router.replace(PATHS.MANAGE_LABELS);
}
if (currentUser && !hasEditPermission(currentUser, data)) {
renderFlash(
"error",
"You do not have permission to edit this label."
);
router.replace(PATHS.MANAGE_LABELS);
}
},
}
);
const {
data: targetedHosts,
isLoading: isLoadingHosts,
isError: isErrorHosts,
} = useQuery<IGetHostsInLabelResponse, AxiosError, IHost[]>(
["hosts"],
() => {
return labelsAPI.getHostsInLabel(labelId);
},
{
...DEFAULT_USE_QUERY_OPTIONS,
select: (data) => data.hosts,
enabled: label?.label_membership_type === "manual",
}
);
const onCancelEdit = () => {
router.goBack();
};
const onUpdateLabel = async (
formData: IDynamicLabelFormData | IManualLabelFormData
) => {
try {
await labelsAPI.update(labelId, formData);
renderFlash("success", "Label updated successfully.");
} catch {
renderFlash("error", "Couldn't edit label. Please try again.");
}
};
const renderContent = () => {
if (isLoadingLabel || isLoadingHosts) {
return <Spinner />;
}
if (isErrorLabel || isErrorHosts) {
return <DataError />;
}
if (!label) return null;
if (label.label_type === "builtin") {
return (
<DataError
description="Built in labels cannot be edited"
excludeIssueLink
/>
);
}
return label.label_membership_type === "dynamic" ? (
<DynamicLabelForm
defaultName={label.name}
defaultDescription={label.description}
defaultQuery={label.query}
defaultPlatform={label.platform}
teamName={label.team_name || null}
isEditing
onSave={onUpdateLabel}
onCancel={onCancelEdit}
/>
) : (
<ManualLabelForm
key={targetedHosts?.toString()}
defaultName={label.name}
defaultDescription={label.description}
defaultTargetedHosts={targetedHosts}
teamName={label.team_name || null}
onSave={onUpdateLabel}
onCancel={onCancelEdit}
/>
);
};
return (
<>
<MainContent className={baseClass}>
<h1 className="page-header">Edit label</h1>
{renderContent()}
</MainContent>
</>
);
};
export default EditLabelPage;