mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 02:15:57 +00:00
fix(ui): show group names in permission scope dropdown
Replace raw scope strings (e.g. group:nta7-goclaw:-5101523...) with human-readable group names by fetching delivery targets from channel_contacts via heartbeat.targets API. Scope labels are also resolved in the permissions list display.
This commit is contained in:
1 parent
3e4d614aa1
commit
1a2d5789d3
1 file changed
+46
-17
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useState, useEffect, useMemo, useCallback } from "react";
|
||||
import { Plus, Trash2, Loader2, Shield, FolderOpen, RefreshCw } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -9,6 +9,9 @@ import {
|
||||
import { Combobox, type ComboboxOption } from "@/components/ui/combobox";
|
||||
import { useConfigPermissions, type ConfigPermission } from "../hooks/use-config-permissions";
|
||||
import { UserPickerCombobox } from "@/components/shared/user-picker-combobox";
|
||||
import { useWs } from "@/hooks/use-ws";
|
||||
import { Methods } from "@/api/protocol";
|
||||
import type { DeliveryTarget } from "../hooks/use-agent-heartbeat";
|
||||
|
||||
const CONFIG_TYPES = [
|
||||
{ value: "file_writer", label: "File Writer", descKey: "permissions.types.file_writer_desc" },
|
||||
@@ -18,18 +21,26 @@ const CONFIG_TYPES = [
|
||||
{ value: "*", label: "All (*)", descKey: "permissions.types.all_desc" },
|
||||
];
|
||||
|
||||
function getScopeOptions(configType: string, existingScopes: string[]): ComboboxOption[] {
|
||||
function getScopeOptions(configType: string, targets: DeliveryTarget[]): ComboboxOption[] {
|
||||
// Only groups — topic-level permissions are not supported by the backend scope check
|
||||
const groupOptions = targets
|
||||
.filter((t) => t.kind === "group")
|
||||
.map((t) => ({
|
||||
value: `group:${t.channel}:${t.chatId}`,
|
||||
label: t.title ? `${t.title} (${t.chatId})` : t.chatId,
|
||||
}));
|
||||
|
||||
if (configType === "file_writer") {
|
||||
const dynamic = existingScopes.map((s) => ({ value: s, label: s }));
|
||||
return [
|
||||
{ value: "group:*", label: "All Groups" },
|
||||
...dynamic,
|
||||
...groupOptions,
|
||||
{ value: "*", label: "Global (*)" },
|
||||
];
|
||||
}
|
||||
return [
|
||||
{ value: "agent", label: "Agent (DM)" },
|
||||
{ value: "group:*", label: "All Groups" },
|
||||
...groupOptions,
|
||||
{ value: "*", label: "Global (*)" },
|
||||
];
|
||||
}
|
||||
@@ -40,6 +51,7 @@ interface AgentPermissionsTabProps {
|
||||
|
||||
export function AgentPermissionsTab({ agentId }: AgentPermissionsTabProps) {
|
||||
const { t } = useTranslation("agents");
|
||||
const ws = useWs();
|
||||
const { permissions, loading, load, grant, revoke } = useConfigPermissions(agentId);
|
||||
|
||||
const [userId, setUserId] = useState("");
|
||||
@@ -47,22 +59,39 @@ export function AgentPermissionsTab({ agentId }: AgentPermissionsTabProps) {
|
||||
const [scope, setScope] = useState("group:*");
|
||||
const [permission, setPermission] = useState("allow");
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [targets, setTargets] = useState<DeliveryTarget[]>([]);
|
||||
|
||||
// Collect existing file_writer scopes for dynamic scope options
|
||||
const existingFileWriterScopes = useMemo(() =>
|
||||
[...new Set(
|
||||
permissions
|
||||
.filter((p) => p.configType === "file_writer")
|
||||
.map((p) => p.scope)
|
||||
)],
|
||||
[permissions],
|
||||
);
|
||||
// Fetch delivery targets (groups/topics) from channel_contacts
|
||||
const loadTargets = useCallback(async () => {
|
||||
if (!agentId || !ws.isConnected) return;
|
||||
try {
|
||||
const res = await ws.call<{ targets: DeliveryTarget[] }>(
|
||||
Methods.HEARTBEAT_TARGETS, { agentId },
|
||||
);
|
||||
setTargets(res.targets ?? []);
|
||||
} catch { /* ignore — targets are optional enhancement */ }
|
||||
}, [ws, agentId]);
|
||||
|
||||
useEffect(() => { loadTargets(); }, [loadTargets]);
|
||||
|
||||
const scopeOptions = useMemo(
|
||||
() => getScopeOptions(configType, existingFileWriterScopes),
|
||||
[configType, existingFileWriterScopes],
|
||||
() => getScopeOptions(configType, targets),
|
||||
[configType, targets],
|
||||
);
|
||||
|
||||
// Build scope → display label lookup from targets
|
||||
const scopeLabels = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
map.set("group:*", t("permissions.scopes.group_all"));
|
||||
map.set("*", t("permissions.scopes.global"));
|
||||
map.set("agent", t("permissions.scopes.agent"));
|
||||
for (const tgt of targets) {
|
||||
const key = `group:${tgt.channel}:${tgt.chatId}`;
|
||||
map.set(key, tgt.title ? `${tgt.title} (${tgt.chatId})` : tgt.chatId);
|
||||
}
|
||||
return map;
|
||||
}, [targets, t]);
|
||||
|
||||
// Reset scope when configType changes
|
||||
useEffect(() => {
|
||||
if (configType === "file_writer") {
|
||||
@@ -196,7 +225,7 @@ export function AgentPermissionsTab({ agentId }: AgentPermissionsTabProps) {
|
||||
<div key={scopeKey}>
|
||||
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-muted/40">
|
||||
<FolderOpen className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<span className="text-xs font-medium text-muted-foreground">{scopeKey}</span>
|
||||
<span className="text-xs font-medium text-muted-foreground">{scopeLabels.get(scopeKey) ?? scopeKey}</span>
|
||||
</div>
|
||||
{writers.map((p) => {
|
||||
const displayName = p.metadata?.displayName || p.userId;
|
||||
@@ -251,7 +280,7 @@ export function AgentPermissionsTab({ agentId }: AgentPermissionsTabProps) {
|
||||
</Badge>
|
||||
<span className="font-medium truncate">{p.userId}</span>
|
||||
<span className="text-[11px] text-muted-foreground shrink-0">{p.configType}</span>
|
||||
<span className="text-[11px] text-muted-foreground shrink-0">@ {p.scope}</span>
|
||||
<span className="text-[11px] text-muted-foreground shrink-0">@ {scopeLabels.get(p.scope) ?? p.scope}</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
Reference in new issue
Block a user