feat(permissions): expose network permission state (#103)
This commit is contained in:
@ -55,6 +55,22 @@ describe("TerminalView (with MockTerminalGateway)", () => {
|
||||
expect(screen.getByTestId("terminal-view")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows a non-blocking network banner when system permissions are locked", () => {
|
||||
renderView(new MockTerminalGateway(), "/home/me/proj", {
|
||||
systemPermissions: {
|
||||
wanted: "allow",
|
||||
effective: "deny",
|
||||
runtimeLock: { state: "locked", reason: "Runtime network locked." },
|
||||
control: { mode: "readOnly", reason: "Runtime network locked." },
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("terminal-network-banner").textContent).toContain(
|
||||
"Réseau verrouillé par le runtime.",
|
||||
);
|
||||
expect(screen.getByTestId("terminal-view")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("opens a terminal through the gateway with the given cwd", async () => {
|
||||
const gw = new MockTerminalGateway();
|
||||
const openSpy = vi.spyOn(gw, "openTerminal");
|
||||
|
||||
@ -39,6 +39,7 @@ import { FitAddon } from "@xterm/addon-fit";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
|
||||
import { useGateways } from "@/app/di";
|
||||
import type { ResolvedAgentSystemPermissions } from "@/domain";
|
||||
import type {
|
||||
OpenTerminalOptions,
|
||||
ReattachResult,
|
||||
@ -113,6 +114,8 @@ interface TerminalViewProps {
|
||||
* it never remounts/reopens the terminal.
|
||||
*/
|
||||
refitSignal?: number;
|
||||
/** Optional resolved system permissions for this agent/cell. */
|
||||
systemPermissions?: ResolvedAgentSystemPermissions | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,6 +147,7 @@ export function TerminalView({
|
||||
portal,
|
||||
onReady,
|
||||
refitSignal,
|
||||
systemPermissions,
|
||||
}: TerminalViewProps) {
|
||||
const { terminal } = useGateways();
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -414,6 +418,15 @@ export function TerminalView({
|
||||
refitRef.current?.();
|
||||
}, [refitSignal]);
|
||||
|
||||
const showNetworkBanner =
|
||||
systemPermissions != null &&
|
||||
(systemPermissions.runtimeLock.state === "locked" ||
|
||||
systemPermissions.effective === "deny");
|
||||
const networkReason =
|
||||
systemPermissions?.runtimeLock.reason ??
|
||||
systemPermissions?.control.reason ??
|
||||
"Le réseau est interdit pour cette cellule.";
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="terminal-view"
|
||||
@ -427,6 +440,34 @@ export function TerminalView({
|
||||
{/* xterm mounts into this inner node; the error banner is a sibling so
|
||||
React never fights xterm over the same subtree. */}
|
||||
<div ref={containerRef} style={{ width: "100%", height: "100%" }} />
|
||||
{showNetworkBanner && (
|
||||
<div
|
||||
role="status"
|
||||
data-testid="terminal-network-banner"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
padding: "0.5rem 0.75rem",
|
||||
border: "1px solid rgba(245, 158, 11, 0.45)",
|
||||
borderRadius: 6,
|
||||
background: "rgba(24, 24, 27, 0.94)",
|
||||
color: "var(--color-warning, #f59e0b)",
|
||||
fontSize: 12,
|
||||
fontFamily:
|
||||
'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
{systemPermissions.runtimeLock.state === "locked"
|
||||
? "Réseau verrouillé par le runtime."
|
||||
: "Réseau interdit pour cet agent."}{" "}
|
||||
{networkReason}
|
||||
</div>
|
||||
)}
|
||||
{openError && (
|
||||
<div
|
||||
role="alert"
|
||||
|
||||
Reference in New Issue
Block a user