mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 11:25:06 +03:00
## Context Previous PR was [here](https://github.com/supabase/supabase/pull/45143) but it got stale with lots of conflicts so figured it'll be easier redo it off the latest master Moves policies page from Auth to Database under an Access Control section along with Roles. This moves all existing files, applies redirects, and updates urls to point to the new route <img width="274" height="412" alt="image" src="https://github.com/user-attachments/assets/7952c185-64ae-4355-ba36-45397efe1787" /> <img width="453" height="471" alt="image" src="https://github.com/user-attachments/assets/04b3dcb3-48a5-4049-9893-d01109fb46a9" /> ## To test - [ ] Verify that policies now live under Database correctly <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a quick navigation shortcut to open **Database > Policies (RLS)**. * **Bug Fixes** * Updated Policies and RLS-related links across the product to open the **Database policies** area (menus, command palette, context actions, alerts, and link-outs). * Added a permanent redirect from the old **auth policies** URL to the new **database policies** URL. * **Documentation** * Updated RLS Dashboard and security checklist instructions to reference **Database > Policies**. * **Tests** * Adjusted automated tests to validate the new Policies route. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
160 lines
4.8 KiB
TypeScript
160 lines
4.8 KiB
TypeScript
'use client'
|
|
|
|
import { useParams } from 'common'
|
|
import { Auth } from 'icons'
|
|
import { Loader2 } from 'lucide-react'
|
|
import { useMemo } from 'react'
|
|
|
|
import {
|
|
EmptyState,
|
|
ResultsList,
|
|
SkeletonResults,
|
|
type SearchResult,
|
|
} from './ContextSearchResults.shared'
|
|
import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
|
|
interface PolicySearchResultsProps {
|
|
query: string
|
|
}
|
|
|
|
export function PolicySearchResults({ query }: PolicySearchResultsProps) {
|
|
const { ref: projectRef } = useParams()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
|
|
const trimmedQuery = query.trim()
|
|
|
|
const {
|
|
data: policies,
|
|
isLoading: isLoadingPolicies,
|
|
isError: isErrorPolicies,
|
|
} = useDatabasePoliciesQuery(
|
|
{
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
},
|
|
{
|
|
enabled: !!project?.ref,
|
|
}
|
|
)
|
|
|
|
const policyResults: SearchResult[] = useMemo(() => {
|
|
if (!policies) return []
|
|
|
|
const filtered = trimmedQuery
|
|
? policies.filter((policy) => {
|
|
const searchLower = trimmedQuery.toLowerCase()
|
|
const policyName = policy.name?.toLowerCase() || ''
|
|
const tableName = policy.table?.toLowerCase() || ''
|
|
const schemaName = policy.schema?.toLowerCase() || ''
|
|
const fullTableName = `${schemaName}.${tableName}`
|
|
const command = policy.command?.toLowerCase() || ''
|
|
|
|
return (
|
|
policyName.includes(searchLower) ||
|
|
tableName.includes(searchLower) ||
|
|
schemaName.includes(searchLower) ||
|
|
fullTableName.includes(searchLower) ||
|
|
command.includes(searchLower)
|
|
)
|
|
})
|
|
: policies
|
|
|
|
// Limit results for performance
|
|
return filtered.slice(0, 20).map((policy) => {
|
|
const displayName = policy.name || 'Untitled Policy'
|
|
const tableDisplay =
|
|
policy.schema && policy.schema !== 'public'
|
|
? `${policy.schema}.${policy.table}`
|
|
: policy.table || 'unknown table'
|
|
const commandDisplay = policy.command || 'ALL'
|
|
const description = `${commandDisplay} on ${tableDisplay}`
|
|
|
|
return {
|
|
id: String(policy.id),
|
|
name: displayName,
|
|
description,
|
|
}
|
|
})
|
|
}, [policies, trimmedQuery])
|
|
|
|
const totalPolicies = policies?.length ?? 0
|
|
|
|
const renderFooter = () => (
|
|
<div className="absolute bottom-0 left-0 right-0 flex items-center justify-between min-h-9 h-9 px-4 border-t bg-surface-200 text-xs text-foreground-light z-10">
|
|
<div className="flex items-center gap-x-2">
|
|
{isLoadingPolicies ? (
|
|
<span className="flex items-center gap-2">
|
|
<Loader2 size={14} className="animate-spin" /> Loading...
|
|
</span>
|
|
) : (
|
|
<span>
|
|
Total: {totalPolicies.toLocaleString()} polic{totalPolicies !== 1 ? 'ies' : 'y'}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
|
|
if (isLoadingPolicies) {
|
|
return (
|
|
<div className="relative h-full flex flex-col">
|
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
<SkeletonResults />
|
|
</div>
|
|
{renderFooter()}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (isErrorPolicies) {
|
|
return (
|
|
<div className="relative h-full flex flex-col">
|
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
<div className="h-full flex flex-col items-center justify-center py-12 px-4 gap-4 text-center text-foreground-lighter">
|
|
<Auth className="h-6 w-6" strokeWidth={1.5} />
|
|
<p className="text-sm">Failed to load policies</p>
|
|
</div>
|
|
</div>
|
|
{renderFooter()}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (policyResults.length === 0) {
|
|
return (
|
|
<div className="relative h-full flex flex-col">
|
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
<EmptyState icon={Auth} label="RLS Policies" query={query} />
|
|
</div>
|
|
{renderFooter()}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="relative h-full flex flex-col">
|
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
<ResultsList
|
|
results={policyResults}
|
|
icon={Auth}
|
|
getRoute={(result) => {
|
|
const policy = policies?.find((p) => String(p.id) === result.id)
|
|
if (!policy || !projectRef)
|
|
return `/project/${projectRef}/database/policies` as `/${string}`
|
|
|
|
const params = new URLSearchParams()
|
|
params.set('edit', String(policy.id))
|
|
if (policy.schema) {
|
|
params.set('schema', policy.schema)
|
|
}
|
|
return `/project/${projectRef}/database/policies?${params.toString()}` as `/${string}`
|
|
}}
|
|
className="pb-9"
|
|
/>
|
|
</div>
|
|
{renderFooter()}
|
|
</div>
|
|
)
|
|
}
|