mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
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
156 lines
4.1 KiB
TypeScript
156 lines
4.1 KiB
TypeScript
import React, { useState } from "react";
|
|
import { useDebouncedCallback } from "use-debounce";
|
|
import { IAceEditor } from "react-ace/lib/types";
|
|
|
|
import { validateQuery } from "components/forms/validators/validate_query";
|
|
import SQLEditor from "components/SQLEditor";
|
|
import Button from "components/buttons/Button";
|
|
import Icon from "components/Icon";
|
|
|
|
import LabelForm from "../LabelForm";
|
|
import { ILabelFormData } from "../LabelForm/LabelForm";
|
|
import PlatformField from "../PlatformField";
|
|
|
|
const baseClass = "dynamic-label-form";
|
|
|
|
export interface IDynamicLabelFormData {
|
|
name: string;
|
|
description: string;
|
|
query: string;
|
|
platform: string;
|
|
}
|
|
|
|
interface IDynamicLabelFormProps {
|
|
defaultName?: string;
|
|
defaultDescription?: string;
|
|
defaultQuery?: string;
|
|
defaultPlatform?: string;
|
|
showOpenSidebarButton?: boolean;
|
|
isEditing?: boolean;
|
|
onOpenSidebar?: () => void;
|
|
onOsqueryTableSelect?: (tableName: string) => void;
|
|
teamName: string | null;
|
|
onSave: (formData: IDynamicLabelFormData) => void;
|
|
onCancel: () => void;
|
|
}
|
|
|
|
const DynamicLabelForm = ({
|
|
defaultName = "",
|
|
defaultDescription = "",
|
|
defaultQuery = "",
|
|
defaultPlatform = "",
|
|
isEditing = false,
|
|
showOpenSidebarButton = false,
|
|
onOpenSidebar,
|
|
onOsqueryTableSelect,
|
|
teamName,
|
|
onSave,
|
|
onCancel,
|
|
}: IDynamicLabelFormProps) => {
|
|
const [query, setQuery] = useState(defaultQuery);
|
|
const [platform, setPlatform] = useState(defaultPlatform);
|
|
const [queryError, setQueryError] = useState<string | null>(null);
|
|
|
|
const debounceValidateSQL = useDebouncedCallback((queryString: string) => {
|
|
const { error } = validateQuery(queryString);
|
|
if (query === "" || error === "") {
|
|
setQueryError(null);
|
|
} else {
|
|
setQueryError(error);
|
|
}
|
|
}, 500);
|
|
|
|
const onQueryChange = (newQuery: string) => {
|
|
setQuery(newQuery);
|
|
debounceValidateSQL(newQuery);
|
|
};
|
|
|
|
const onSaveForm = (
|
|
labelFormData: ILabelFormData,
|
|
labelFormDataValid: boolean
|
|
) => {
|
|
const { error } = validateQuery(query);
|
|
if (error) {
|
|
setQueryError(error);
|
|
} else if (labelFormDataValid) {
|
|
// values from LabelForm component must be valid too
|
|
onSave({ ...labelFormData, query, platform });
|
|
}
|
|
};
|
|
|
|
const renderLabelComponent = (): JSX.Element | null => {
|
|
if (!showOpenSidebarButton) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Button variant="inverse" onClick={onOpenSidebar}>
|
|
Schema
|
|
<Icon name="info" size="small" />
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
const onLoad = (editor: IAceEditor) => {
|
|
editor.setOptions({
|
|
enableLinking: true,
|
|
enableMultiselect: false, // Disables command + click creating multiple cursors
|
|
});
|
|
|
|
// @ts-expect-error
|
|
// the string "linkClick" is not officially in the lib but we need it
|
|
editor.on("linkClick", (data) => {
|
|
const { type, value } = data.token;
|
|
|
|
if (type === "osquery-token" && onOsqueryTableSelect) {
|
|
return onOsqueryTableSelect(value);
|
|
}
|
|
|
|
return false;
|
|
});
|
|
};
|
|
|
|
const onChangePlatform = (value: string) => {
|
|
setPlatform(value);
|
|
};
|
|
|
|
return (
|
|
<div className={baseClass}>
|
|
<LabelForm
|
|
defaultName={defaultName}
|
|
defaultDescription={defaultDescription}
|
|
teamName={teamName}
|
|
onSave={onSaveForm}
|
|
onCancel={onCancel}
|
|
immutableFields={
|
|
teamName
|
|
? ["teams", "queries", "platforms"]
|
|
: ["queries", "platforms"]
|
|
}
|
|
additionalFields={
|
|
<>
|
|
<SQLEditor
|
|
error={queryError}
|
|
name="query"
|
|
onChange={onQueryChange}
|
|
value={query}
|
|
label="Query"
|
|
labelActionComponent={renderLabelComponent()}
|
|
readOnly={isEditing}
|
|
onLoad={onLoad}
|
|
wrapperClassName={`${baseClass}__text-editor-wrapper form-field`}
|
|
wrapEnabled
|
|
/>
|
|
<PlatformField
|
|
platform={platform}
|
|
isEditing={isEditing}
|
|
onChange={onChangePlatform}
|
|
/>
|
|
</>
|
|
}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DynamicLabelForm;
|