feat(cross-project): surface UI des permissions contactables — PermissionsPanel et TemplateEditor exposent contactable/default_contactable, alignement httpInvoker — #260 (typecheck + 48 tests verts, QA en attente)
This commit is contained in:
@ -39,6 +39,9 @@ const NETWORK_OPTIONS: { value: "" | NetworkPolicy; label: string }[] = [
|
||||
{ value: "deny", label: "Réseau refusé" },
|
||||
];
|
||||
|
||||
const TEMPLATE_CROSS_PROJECT_HELP =
|
||||
"Les agents créés avec ce template reprendront cette permission.";
|
||||
|
||||
const ALL_MCP_TOOLS = Array.from(
|
||||
new Set(MCP_TOOL_GROUPS.flatMap((group) => group.tools)),
|
||||
);
|
||||
@ -61,6 +64,9 @@ export function TemplateEditor({
|
||||
const [network, setNetwork] = useState<"" | NetworkPolicy>(
|
||||
template?.systemPermissions?.network ?? "",
|
||||
);
|
||||
const [crossProjectContactable, setCrossProjectContactable] = useState(
|
||||
template?.systemPermissions?.crossProjectContactable ?? false,
|
||||
);
|
||||
const [allowedTools, setAllowedTools] = useState<string[]>(
|
||||
template?.mcpToolPermissions?.allowedTools ?? [],
|
||||
);
|
||||
@ -85,7 +91,15 @@ export function TemplateEditor({
|
||||
content,
|
||||
defaultProfileId,
|
||||
defaultAgentName: defaultAgentName.trim() || null,
|
||||
systemPermissions: network ? { network } : null,
|
||||
systemPermissions:
|
||||
network || crossProjectContactable
|
||||
? {
|
||||
...(network ? { network } : {}),
|
||||
...(crossProjectContactable
|
||||
? { crossProjectContactable: true }
|
||||
: {}),
|
||||
}
|
||||
: null,
|
||||
mcpToolPermissions:
|
||||
allowedTools.length > 0 ? { allowedTools: [...allowedTools] } : null,
|
||||
});
|
||||
@ -256,6 +270,25 @@ export function TemplateEditor({
|
||||
options={NETWORK_OPTIONS}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex min-w-0 items-start gap-2 rounded-md border border-border bg-raised px-3 py-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label="Contactable depuis d’autres projets"
|
||||
checked={crossProjectContactable}
|
||||
onChange={(e) => setCrossProjectContactable(e.target.checked)}
|
||||
disabled={busy}
|
||||
className="mt-0.5 h-4 w-4 shrink-0 accent-primary disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<span className="flex min-w-0 flex-col gap-1">
|
||||
<span className="text-sm font-medium text-content">
|
||||
Contactable depuis d’autres projets
|
||||
</span>
|
||||
<span className="text-xs text-muted">
|
||||
{TEMPLATE_CROSS_PROJECT_HELP}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<section className="flex flex-col gap-3 rounded-md border border-border bg-raised p-3">
|
||||
|
||||
@ -173,6 +173,7 @@ describe("TemplatesPanel (with MockTemplateGateway)", () => {
|
||||
target: { value: "profile-main" },
|
||||
});
|
||||
chooseDropdownOption("template system network permission", "Réseau autorisé");
|
||||
fireEvent.click(screen.getByLabelText("Contactable depuis d’autres projets"));
|
||||
fireEvent.click(screen.getByLabelText("template MCP tool idea_ticket_read"));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Save template" }));
|
||||
|
||||
@ -186,11 +187,30 @@ describe("TemplatesPanel (with MockTemplateGateway)", () => {
|
||||
expect(templates[0]).toMatchObject({
|
||||
defaultAgentName: "Main",
|
||||
defaultProfileId: "profile-main",
|
||||
systemPermissions: { network: "allow" },
|
||||
systemPermissions: { network: "allow", crossProjectContactable: true },
|
||||
mcpToolPermissions: { allowedTools: ["idea_ticket_read"] },
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults template cross-project contactability off when the field is absent", async () => {
|
||||
renderTemplatesPanel();
|
||||
await waitForTemplatesIdle();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "create template" }));
|
||||
await waitFor(() => expect(screen.getByLabelText("template name")).toBeTruthy());
|
||||
|
||||
const checkbox = screen.getByLabelText(
|
||||
"Contactable depuis d’autres projets",
|
||||
) as HTMLInputElement;
|
||||
|
||||
expect(checkbox.checked).toBe(false);
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Les agents créés avec ce template reprendront cette permission.",
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("the Create button is disabled when the name is empty", async () => {
|
||||
renderTemplatesPanel();
|
||||
await waitForTemplatesIdle();
|
||||
|
||||
Reference in New Issue
Block a user