Files
Stirling-PDF/frontend/editor/src/proprietary/components/policies/PolicyToolConfig.tsx
T
2026-06-11 23:37:43 +01:00

160 lines
6.4 KiB
TypeScript

import { Suspense } from "react";
import { useTranslation } from "react-i18next";
import { Loader } from "@mantine/core";
import { ToggleSwitch } from "@shared/components/ToggleSwitch";
import { Card } from "@shared/components/Card";
import LocalIcon from "@app/components/shared/LocalIcon";
import { Tooltip as AppTooltip } from "@app/components/shared/Tooltip";
import { PolicyRedactConfig } from "@app/components/policies/PolicyRedactConfig";
import { PolicyWatermarkConfig } from "@app/components/policies/PolicyWatermarkConfig";
import type { ToolRegistry } from "@app/data/toolsTaxonomy";
import type { ToolId } from "@app/types/toolId";
/**
* Plain-language, non-technical descriptions shown by each tool's info button.
* Stored as [i18n key, English default] pairs so they can be resolved with `t`
* at render (the map lives at module scope, outside the component).
*/
const TOOL_PLAIN_INFO: Record<string, readonly [key: string, en: string]> = {
redact: [
"policies.toolConfig.info.redact",
"Automatically finds and blacks out sensitive details — like Social Security and card numbers — so they can't be read in the document.",
],
sanitize: [
"policies.toolConfig.info.sanitize",
"Removes hidden JavaScript from the file, so nothing can run automatically when someone opens it.",
],
watermark: [
"policies.toolConfig.info.watermark",
"Stamps a visible mark (e.g. “Confidential”) across every page.",
],
};
/** One tool in a policy's fixed chain: whether it runs + its configured params. */
export interface PolicyToolState {
/** Frontend tool-registry id (also the registry key + the thing we map to an endpoint). */
operation: string;
/** Whether this tool runs as part of the policy (the per-tool on/off). */
enabled: boolean;
/** Tool-specific parameters (the shape its endpoint accepts). */
parameters: Record<string, unknown>;
}
interface PolicyToolConfigProps {
/** The policy's fixed tool chain — locked (no add/remove), only configurable. */
tools: PolicyToolState[];
toolRegistry: Partial<ToolRegistry>;
onChange: (tools: PolicyToolState[]) => void;
/** Read-only when the policy is managed / the user can't configure. */
editable?: boolean;
}
/**
* Locked, configure-only tool panel for a policy. The chain is fixed (you can't
* add or remove tools); each tool is a section that renders its OWN settings form
* from the tool registry (`automationSettings`) — the same forms the automation
* builder uses — so the config is generated from the tools in the workflow, not
* hardcoded per policy. The parameters produced here are exactly what the backend
* engine POSTs to each tool's endpoint.
*/
export function PolicyToolConfig({
tools,
toolRegistry,
onChange,
editable = true,
}: PolicyToolConfigProps) {
const { t } = useTranslation();
const patchTool = (index: number, patch: Partial<PolicyToolState>) =>
onChange(tools.map((t, i) => (i === index ? { ...t, ...patch } : t)));
return (
<div className="pol-tool-config">
{tools.map((tool, index) => {
const entry = toolRegistry[tool.operation as ToolId];
const Settings = entry?.automationSettings ?? null;
const toolName = entry?.name ?? tool.operation;
const plainInfo = TOOL_PLAIN_INFO[tool.operation];
return (
<Card key={tool.operation} padding="none">
<div className="pol-tool-head">
<span className="pol-tool-icon">{entry?.icon}</span>
<span className="pol-tool-name">{toolName}</span>
{plainInfo && (
<AppTooltip
content={t(plainInfo[0], plainInfo[1])}
sidebarTooltip
pinOnClick
>
<button
type="button"
className="pol-info-btn"
aria-label={t(
"policies.toolConfig.infoAriaLabel",
"What does {{tool}} do?",
{ tool: toolName },
)}
>
<LocalIcon
icon="info-outline-rounded"
width="1rem"
height="1rem"
style={{ color: "var(--icon-files-color)" }}
/>
</button>
</AppTooltip>
)}
<ToggleSwitch
size="sm"
checked={tool.enabled}
disabled={!editable}
onChange={(checked) => patchTool(index, { enabled: checked })}
aria-label={t("policies.toolConfig.enableAriaLabel", "Enable {{tool}}", {
tool: toolName,
})}
/>
</div>
{tool.enabled &&
(tool.operation === "redact" ? (
<div className="pol-tool-body">
<PolicyRedactConfig
parameters={tool.parameters}
onChange={(parameters) => patchTool(index, { parameters })}
disabled={!editable}
/>
</div>
) : tool.operation === "sanitize" ? (
// Sanitize is config-less: it only removes JavaScript (params
// are fixed in the policy preset), so no settings are shown.
<></>
) : tool.operation === "watermark" ? (
// Watermark settings with flatten hidden + forced on (see
// PolicyWatermarkConfig).
<div className="pol-tool-body">
<PolicyWatermarkConfig
parameters={tool.parameters}
onChange={(parameters) => patchTool(index, { parameters })}
disabled={!editable}
/>
</div>
) : Settings ? (
<div className="pol-tool-body">
<Suspense fallback={<Loader size="sm" />}>
<Settings
parameters={tool.parameters}
onParameterChange={(key: string, value: unknown) =>
patchTool(index, {
parameters: { ...tool.parameters, [key]: value },
})
}
disabled={!editable}
/>
</Suspense>
</div>
) : null)}
</Card>
);
})}
</div>
);
}