import { useMemo, useState, useEffect } from 'react'; import RefreshIcon from '@/assets/icons/refresh.svg?react'; import { Button, DataGrid, type ConvertedValue, type DataGridColumn, type DataGridRowType, EmptyState, SearchInput, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components'; import { useFullMetadata } from '../hooks/useFullMetadata'; import type { ExportDatabaseResponse, ExportDatabaseJsonData } from '@insforge/shared-schemas'; import { isSystemTable } from '../constants'; import { DataUpdatePayload, DataUpdateResourceType, ServerEvents, SocketMessage, useSocket, } from '@/lib/contexts/SocketContext'; interface PolicyRow extends DataGridRowType { id: string; tableName: string; policyName: string; cmd: string; roles: string; qual: string | null; withCheck: string | null; [key: string]: ConvertedValue | { [key: string]: string }[]; } function parsePoliciesFromMetadata(metadata: ExportDatabaseResponse | undefined): PolicyRow[] { if (!metadata || metadata.format !== 'json' || typeof metadata.data === 'string') { return []; } const data = metadata.data as ExportDatabaseJsonData; const policies: PolicyRow[] = []; Object.entries(data.tables).forEach(([tableName, tableData]) => { if (isSystemTable(tableName)) { return; } tableData.policies.forEach((policy) => { policies.push({ id: `${tableName}_${policy.policyname}`, tableName, policyName: policy.policyname, cmd: policy.cmd, roles: Array.isArray(policy.roles) ? policy.roles.join(', ') : String(policy.roles), qual: policy.qual, withCheck: policy.withCheck, }); }); }); return policies; } export default function PoliciesPage() { const [searchQuery, setSearchQuery] = useState(''); const [isRefreshing, setIsRefreshing] = useState(false); const { data: metadata, isLoading, error, refetch } = useFullMetadata(true); const { socket, isConnected } = useSocket(); const allPolicies = useMemo(() => parsePoliciesFromMetadata(metadata), [metadata]); useEffect(() => { if (!socket || !isConnected) { return; } const handleDataUpdate = (message: SocketMessage) => { if (message.payload?.resource === DataUpdateResourceType.DATABASE) { void refetch(); } }; socket.on(ServerEvents.DATA_UPDATE, handleDataUpdate); return () => { socket.off(ServerEvents.DATA_UPDATE, handleDataUpdate); }; }, [socket, isConnected, refetch]); const filteredPolicies = useMemo(() => { if (!searchQuery.trim()) { return allPolicies; } const query = searchQuery.toLowerCase(); return allPolicies.filter( (policy) => policy.policyName.toLowerCase().includes(query) || policy.tableName.toLowerCase().includes(query) ); }, [allPolicies, searchQuery]); const handleRefresh = async () => { setIsRefreshing(true); try { setSearchQuery(''); await refetch(); } finally { setIsRefreshing(false); } }; const columns: DataGridColumn[] = useMemo( () => [ { key: 'tableName', name: 'Table', width: 'minmax(180px, 1.5fr)', resizable: true, sortable: true, }, { key: 'policyName', name: 'Name', width: 'minmax(200px, 2fr)', resizable: true, sortable: true, }, { key: 'cmd', name: 'Command', width: 'minmax(100px, 1fr)', resizable: true, sortable: true, renderCell: ({ row }) => { const cmd = row.cmd; const cmdLabel = cmd === '*' ? 'ALL' : cmd; return ( {cmdLabel} ); }, }, { key: 'roles', name: 'Roles', width: 'minmax(150px, 1.5fr)', resizable: true, }, { key: 'qual', name: 'Using', width: 'minmax(200px, 2fr)', resizable: true, renderCell: ({ row }) => { return {row.qual || '-'}; }, }, { key: 'withCheck', name: 'With Check', width: 'minmax(200px, 2fr)', resizable: true, renderCell: ({ row }) => { return {row.withCheck || '-'}; }, }, ], [] ); if (error) { return (
); } return (

RLS Policies

{/* Separator */}
{/* Refresh button */}

{isRefreshing ? 'Refreshing...' : 'Refresh'}

{isLoading ? (
) : (
{searchQuery ? 'No policies match your search criteria' : 'No policies found'}
} />
)}
); }