+ {/* Add share form */}
+
+
{t("shares.grantAccess")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Share list */}
+ {loading && shares.length === 0 ? (
+
{t("shares.loadingShares")}
+ ) : shares.length === 0 ? (
+
+
+
{t("shares.noShares")}
+
+ {t("shares.noSharesDesc")}
+
+
+ ) : (
+
+
+ {t("shares.user")}
+ {t("shares.role")}
+
+
+ {shares.map((share) => {
+ const contact = resolve(share.user_id);
+ const contactByGrant = share.granted_by ? resolve(share.granted_by) : null;
+ return (
+
+
+
+ {contact?.display_name ?? share.user_id}
+
+ {contact?.display_name && (
+
+ {share.user_id}
+
+ )}
+ {contact?.username && (
+
+ @{contact.username}
+
+ )}
+ {share.granted_by && (
+
+ by {contactByGrant?.display_name ?? share.granted_by}
+
+ )}
+
+
{share.role}
+
+
+ );
+ })}
+
+ )}
+
+
setRevokeTarget(null)}
+ title={t("shares.revokeTitle")}
+ description={t("shares.revokeDesc", { userId: revokeTarget })}
+ confirmLabel={t("shares.revoke")}
+ variant="destructive"
+ onConfirm={async () => {
+ if (revokeTarget) {
+ try {
+ await revokeShare(revokeTarget);
+ } catch {
+ // ignore
+ }
+ setRevokeTarget(null);
+ }
+ }}
+ />
+
+ );
+}
diff --git a/ui/web/src/pages/agents/hooks/use-agent-shares.ts b/ui/web/src/pages/agents/hooks/use-agent-shares.ts
new file mode 100644
index 00000000..1a4d2ffa
--- /dev/null
+++ b/ui/web/src/pages/agents/hooks/use-agent-shares.ts
@@ -0,0 +1,48 @@
+import { useState, useEffect, useCallback } from "react";
+import { useHttp } from "@/hooks/use-ws";
+import type { AgentShareData } from "@/types/agent";
+
+export function useAgentShares(agentId: string) {
+ const http = useHttp();
+ const [shares, setShares] = useState