feat(permissions): expose network permission state (#103)
This commit is contained in:
@ -14,7 +14,7 @@
|
||||
* the existing `createAgent` path with the selected profile.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Button, Input, Panel, Spinner, cn } from "@/shared";
|
||||
import { TerminalView } from "@/features/terminals/TerminalView";
|
||||
@ -25,6 +25,7 @@ import { useAgents } from "./useAgents";
|
||||
import { correlateModelServerStatus } from "./modelServerLaunch";
|
||||
import { AgentLimitBadge } from "./AgentLimitBadge";
|
||||
import { ModelServerLaunchBadge } from "./ModelServerLaunchBadge";
|
||||
import type { ResolvedAgentSystemPermissions } from "@/domain";
|
||||
|
||||
export interface AgentsPanelProps {
|
||||
/** The project whose agents to manage. */
|
||||
@ -41,6 +42,7 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
const drift = useDrift(projectId);
|
||||
const gateways = useGateways();
|
||||
const templateGw = gateways.template ?? null;
|
||||
const permissionGw = gateways.permission ?? null;
|
||||
|
||||
// Create form state
|
||||
const [newName, setNewName] = useState("");
|
||||
@ -130,6 +132,45 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
const [agentSessions, setAgentSessions] = useState<Record<string, string>>(
|
||||
{},
|
||||
);
|
||||
const [networkByAgent, setNetworkByAgent] = useState<
|
||||
Record<string, ResolvedAgentSystemPermissions>
|
||||
>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (!permissionGw || vm.agents.length === 0) {
|
||||
setNetworkByAgent({});
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
void Promise.all(
|
||||
vm.agents.map(async (candidate) => {
|
||||
try {
|
||||
return [
|
||||
candidate.id,
|
||||
await permissionGw.resolveAgentSystemPermissions(
|
||||
projectId,
|
||||
candidate.id,
|
||||
),
|
||||
] as const;
|
||||
} catch {
|
||||
return [candidate.id, null] as const;
|
||||
}
|
||||
}),
|
||||
).then((pairs) => {
|
||||
if (cancelled) return;
|
||||
setNetworkByAgent(
|
||||
Object.fromEntries(
|
||||
pairs.filter(
|
||||
(pair): pair is readonly [string, ResolvedAgentSystemPermissions] =>
|
||||
pair[1] !== null,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [permissionGw, projectId, vm.agents]);
|
||||
|
||||
/**
|
||||
* Pending profile change awaiting confirmation: the target agent + the chosen
|
||||
@ -342,6 +383,7 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
vm.modelServerStatusByServer,
|
||||
);
|
||||
const launchFailure = vm.launchFailureByAgent[a.id];
|
||||
const network = networkByAgent[a.id] ?? null;
|
||||
return (
|
||||
<li
|
||||
key={a.id}
|
||||
@ -379,6 +421,7 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
{delegationSource}
|
||||
</span>
|
||||
)}
|
||||
{network && <NetworkPermissionBadge state={network} />}
|
||||
</span>
|
||||
<span className="text-xs text-muted">{profileName}</span>
|
||||
{live && (
|
||||
@ -513,6 +556,7 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
onSessionId={(sid) =>
|
||||
setAgentSessions((prev) => ({ ...prev, [activeAgentId]: sid }))
|
||||
}
|
||||
systemPermissions={networkByAgent[activeAgentId] ?? null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -667,3 +711,40 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
function NetworkPermissionBadge({
|
||||
state,
|
||||
}: {
|
||||
state: ResolvedAgentSystemPermissions;
|
||||
}) {
|
||||
const locked = state.runtimeLock.state === "locked";
|
||||
const label = locked
|
||||
? "Verrouillé"
|
||||
: state.effective === "allow"
|
||||
? "Autorisé"
|
||||
: state.effective === "deny"
|
||||
? "Interdit"
|
||||
: "Demande";
|
||||
return (
|
||||
<span
|
||||
aria-label={`network ${label}`}
|
||||
title={
|
||||
locked
|
||||
? (state.runtimeLock.reason ?? "Réseau verrouillé par le runtime")
|
||||
: `État effectif : ${label}`
|
||||
}
|
||||
className={cn(
|
||||
"rounded-full px-2 py-0.5 text-xs font-medium",
|
||||
locked
|
||||
? "bg-warning/15 text-warning"
|
||||
: state.effective === "allow"
|
||||
? "bg-success/15 text-success"
|
||||
: state.effective === "deny"
|
||||
? "bg-danger/15 text-danger"
|
||||
: "bg-primary/15 text-primary",
|
||||
)}
|
||||
>
|
||||
Réseau: {label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user