import React from "react"; import { Button } from "@/components/ui/button.tsx"; import { Label } from "@/components/ui/label.tsx"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select.tsx"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table.tsx"; import { Input } from "@/components/ui/input.tsx"; import { Badge } from "@/components/ui/badge.tsx"; import { AlertCircle, Plus, Trash2, Users, Shield, Clock, UserCircle, } from "lucide-react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; import { useConfirmation } from "@/hooks/use-confirmation.ts"; import { getRoles, getUserList, shareHost, getHostAccess, revokeHostAccess, type Role, type AccessRecord, } from "@/ui/main-axios.ts"; interface HostSharingTabProps { hostId: number | undefined; isNewHost: boolean; } interface User { id: string; username: string; is_admin: boolean; } const PERMISSION_LEVELS = [ { value: "view", labelKey: "rbac.view" }, { value: "use", labelKey: "rbac.use" }, { value: "manage", labelKey: "rbac.manage" }, ]; export function HostSharingTab({ hostId, isNewHost, }: HostSharingTabProps): React.ReactElement { const { t } = useTranslation(); const { confirmWithToast } = useConfirmation(); const [shareType, setShareType] = React.useState<"user" | "role">("user"); const [selectedUserId, setSelectedUserId] = React.useState(""); const [selectedRoleId, setSelectedRoleId] = React.useState( null, ); const [permissionLevel, setPermissionLevel] = React.useState("use"); const [expiresInHours, setExpiresInHours] = React.useState(""); const [roles, setRoles] = React.useState([]); const [users, setUsers] = React.useState([]); const [accessList, setAccessList] = React.useState([]); const [loading, setLoading] = React.useState(false); // Load roles const loadRoles = React.useCallback(async () => { try { const response = await getRoles(); setRoles(response.roles || []); } catch (error) { console.error("Failed to load roles:", error); setRoles([]); } }, []); // Load users const loadUsers = React.useCallback(async () => { try { const response = await getUserList(); // Map UserInfo to User format const mappedUsers = (response.users || []).map((user) => ({ id: user.id, username: user.username, is_admin: user.is_admin, })); setUsers(mappedUsers); } catch (error) { console.error("Failed to load users:", error); setUsers([]); } }, []); // Load access list const loadAccessList = React.useCallback(async () => { if (!hostId) return; setLoading(true); try { const response = await getHostAccess(hostId); setAccessList(response.accessList || []); } catch (error) { console.error("Failed to load access list:", error); setAccessList([]); } finally { setLoading(false); } }, [hostId]); React.useEffect(() => { loadRoles(); loadUsers(); if (!isNewHost) { loadAccessList(); } }, [loadRoles, loadUsers, loadAccessList, isNewHost]); // Share host const handleShare = async () => { if (!hostId) { toast.error(t("rbac.saveHostFirst")); return; } if (shareType === "user" && !selectedUserId) { toast.error(t("rbac.selectUser")); return; } if (shareType === "role" && !selectedRoleId) { toast.error(t("rbac.selectRole")); return; } try { await shareHost(hostId, { targetType: shareType, targetUserId: shareType === "user" ? selectedUserId : undefined, targetRoleId: shareType === "role" ? selectedRoleId : undefined, permissionLevel, durationHours: expiresInHours ? parseInt(expiresInHours, 10) : undefined, }); toast.success(t("rbac.sharedSuccessfully")); setSelectedUserId(""); setSelectedRoleId(null); setExpiresInHours(""); loadAccessList(); } catch (error) { toast.error(t("rbac.failedToShare")); } }; // Revoke access const handleRevoke = async (accessId: number) => { if (!hostId) return; const confirmed = await confirmWithToast({ title: t("rbac.confirmRevokeAccess"), description: t("rbac.confirmRevokeAccessDescription"), confirmText: t("common.revoke"), cancelText: t("common.cancel"), }); if (!confirmed) return; try { await revokeHostAccess(hostId, accessId); toast.success(t("rbac.accessRevokedSuccessfully")); loadAccessList(); } catch (error) { toast.error(t("rbac.failedToRevokeAccess")); } }; // Format date const formatDate = (dateString: string | null) => { if (!dateString) return "-"; return new Date(dateString).toLocaleString(); }; // Check if expired const isExpired = (expiresAt: string | null) => { if (!expiresAt) return false; return new Date(expiresAt) < new Date(); }; if (isNewHost) { return ( {t("rbac.saveHostFirst")} {t("rbac.saveHostFirstDescription")} ); } return (
{/* Share Form */}

{t("rbac.shareHost")}

{/* Share Type Selection */} setShareType(v as "user" | "role")}> {t("rbac.shareWithUser")} {t("rbac.shareWithRole")}
{/* Permission Level */}
{/* Expiration */}
setExpiresInHours(e.target.value)} placeholder={t("rbac.neverExpires")} min="1" />
{/* Access List */}

{t("rbac.accessList")}

{t("rbac.type")} {t("rbac.target")} {t("rbac.permissionLevel")} {t("rbac.grantedBy")} {t("rbac.expires")} {t("rbac.accessCount")} {t("common.actions")} {loading ? ( {t("common.loading")} ) : accessList.length === 0 ? ( {t("rbac.noAccessRecords")} ) : ( accessList.map((access) => ( {access.targetType === "user" ? ( {t("rbac.user")} ) : ( {t("rbac.role")} )} {access.targetType === "user" ? access.username : t(access.roleDisplayName || access.roleName || "")} {access.permissionLevel} {access.grantedByUsername} {access.expiresAt ? (
{formatDate(access.expiresAt)} {isExpired(access.expiresAt) && ( ({t("rbac.expired")}) )}
) : ( t("rbac.never") )}
{access.accessCount}
)) )}
); }