diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent.tsx index 6d8233515..0ef65d98b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/AgentPermissionsContent.tsx @@ -2,7 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useAtomValue } from "jotai"; -import { AlertTriangle, Check, Plus, ShieldCheck, Trash2, X } from "lucide-react"; +import { AlertTriangle, Info, ShieldCheck, Trash2 } from "lucide-react"; import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom"; @@ -20,8 +20,18 @@ import { } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Skeleton } from "@/components/ui/skeleton"; import { Select, SelectContent, @@ -67,20 +77,29 @@ function permissionRulesQueryKey(searchSpaceId: number) { function ScopeBadge({ rule }: { rule: AgentPermissionRule }) { if (rule.thread_id !== null) { return ( - + Thread #{rule.thread_id} ); } if (rule.user_id !== null) { return ( - + User-specific ); } return ( - + Search space ); @@ -170,8 +189,8 @@ export function AgentPermissionsContent() { permission: formData.permission.trim(), pattern: formData.pattern.trim() || "*", }); - setShowForm(false); setFormData(EMPTY_FORM); + setShowForm(false); } catch (err) { if (err instanceof AppError && err.message) { // already toasted by onError @@ -190,13 +209,15 @@ export function AgentPermissionsContent() { if (!featureEnabled) { return ( - - + + Permission middleware is disabled - Flip{" "} - SURFSENSE_ENABLE_PERMISSION on - the backend to manage allow/deny/ask rules from this panel. +

+ Flip{" "} + SURFSENSE_ENABLE_PERMISSION on + the backend to manage allow/deny/ask rules from this panel. +

); @@ -208,28 +229,8 @@ export function AgentPermissionsContent() { ); } - if (isLoading) { - return ( -
- -
- ); - } - - if (isError) { - return ( - - - Failed to load rules - - {error instanceof Error ? error.message : "Unknown error."} - - - ); - } - return ( -
+

@@ -237,27 +238,36 @@ export function AgentPermissionsContent() { patterns and are evaluated at the most specific scope first.

- {!showForm && ( - - )} +
- {showForm && ( -
-
-

New permission rule

+ { + setShowForm(open); + if (!open) setFormData(EMPTY_FORM); + }} + > + + + New permission rule + + Tell the agent whether matching tool calls should be allowed, denied, or paused for + approval. + + -
+
+
- -
- - -
+ + + + + + +
+ + {isLoading && ( +
+ {["skeleton-a", "skeleton-b", "skeleton-c"].map((key) => ( + + + + + + + + ))}
)} - {sortedRules.length === 0 && !showForm && ( + {isError && ( + + + Failed to load rules + + {error instanceof Error ? error.message : "Unknown error."} + + + )} + + {!isLoading && !isError && sortedRules.length === 0 && !showForm && (

No rules yet

@@ -343,8 +379,8 @@ export function AgentPermissionsContent() {
)} - {sortedRules.length > 0 && ( -
+ {!isLoading && !isError && sortedRules.length > 0 && ( +
{sortedRules.map((rule) => { const badge = ACTION_BADGE[rule.action]; const isUpdating = @@ -352,14 +388,14 @@ export function AgentPermissionsContent() { const isDeleting = deleteMutation.isPending && deleteMutation.variables === rule.id; return ( -
-
+
- + {rule.permission} {rule.pattern !== "*" && ( @@ -374,7 +410,7 @@ export function AgentPermissionsContent() {

-
+
setFormData((p) => ({ ...p, name: e.target.value }))} - placeholder="e.g. Fix grammar" - /> +
+
+ + setFormData((p) => ({ ...p, name: e.target.value }))} + placeholder="e.g. Fix grammar" + /> +
+ +
+ +