mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
refactor: auth policies page
- remove mobx local store - filter table policies from meta.policies - clean up
This commit is contained in:
1 parent
c05d165b9e
commit
8a9b46250a
3 files changed
+88
-124
No files matched your search
@@ -1,5 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import { PostgresTable, PostgresPolicy } from '@supabase/postgres-meta'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useStore } from 'hooks'
|
||||
|
||||
import PolicyTableRowHeader from './PolicyTableRowHeader'
|
||||
import PolicyRow from './PolicyRow'
|
||||
@@ -13,40 +15,44 @@ interface Props {
|
||||
onSelectDeletePolicy: (policy: PostgresPolicy) => void
|
||||
}
|
||||
|
||||
const PolicyTableRow: FC<Props> = ({
|
||||
table,
|
||||
onSelectToggleRLS = () => {},
|
||||
onSelectCreatePolicy = () => {},
|
||||
onSelectEditPolicy = () => {},
|
||||
onSelectDeletePolicy = () => {},
|
||||
}) => {
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<PolicyTableRowHeader
|
||||
table={table}
|
||||
onSelectToggleRLS={onSelectToggleRLS}
|
||||
onSelectCreatePolicy={onSelectCreatePolicy}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{table.policies.length === 0 && (
|
||||
<div className="p-4 px-6">
|
||||
<p className="text-scale-900 text-sm">No policies created yet</p>
|
||||
</div>
|
||||
)}
|
||||
const PolicyTableRow: FC<Props> = observer(
|
||||
({
|
||||
table,
|
||||
onSelectToggleRLS = () => {},
|
||||
onSelectCreatePolicy = () => {},
|
||||
onSelectEditPolicy = () => {},
|
||||
onSelectDeletePolicy = () => {},
|
||||
}) => {
|
||||
const { meta } = useStore()
|
||||
const policies = meta.policies.list((x: PostgresPolicy) => x.table === table.name)
|
||||
return (
|
||||
<Panel
|
||||
title={
|
||||
<PolicyTableRowHeader
|
||||
table={table}
|
||||
onSelectToggleRLS={onSelectToggleRLS}
|
||||
onSelectCreatePolicy={onSelectCreatePolicy}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{policies.length === 0 && (
|
||||
<div className="p-4 px-6">
|
||||
<p className="text-scale-900 text-sm">No policies created yet</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* @ts-ignore */}
|
||||
{table.policies.map((policy) => (
|
||||
<PolicyRow
|
||||
key={policy.id}
|
||||
policy={policy}
|
||||
onSelectEditPolicy={onSelectEditPolicy}
|
||||
onSelectDeletePolicy={onSelectDeletePolicy}
|
||||
/>
|
||||
))}
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
{/* @ts-ignore */}
|
||||
{policies.map((policy) => (
|
||||
<PolicyRow
|
||||
key={policy.id}
|
||||
policy={policy}
|
||||
onSelectEditPolicy={onSelectEditPolicy}
|
||||
onSelectDeletePolicy={onSelectDeletePolicy}
|
||||
/>
|
||||
))}
|
||||
</Panel>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default PolicyTableRow
|
||||
@@ -25,6 +25,7 @@ const AuthLayout: FC<Props> = ({ title, children }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (ui.selectedProject) {
|
||||
meta.policies.load()
|
||||
meta.tables.load()
|
||||
meta.roles.load()
|
||||
}
|
||||
|
||||
@@ -1,82 +1,50 @@
|
||||
import React, { useState, PropsWithChildren, FC } from 'react'
|
||||
import { isEmpty } from 'lodash'
|
||||
import React, { createContext, useContext, useState, useEffect, PropsWithChildren } from 'react'
|
||||
import { Button, IconSearch, Input } from '@supabase/ui'
|
||||
import { observer, useLocalObservable } from 'mobx-react-lite'
|
||||
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useStore } from 'hooks'
|
||||
import { AuthLayout } from 'components/layouts'
|
||||
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
|
||||
import NoTableState from 'components/ui/States/NoTableState'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { PolicyEditorModal, PolicyTableRow } from 'components/interfaces/Authentication/Policies'
|
||||
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
import { PostgresRole } from '@supabase/postgres-meta'
|
||||
|
||||
const PageContext = createContext(null)
|
||||
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
|
||||
import NoTableState from 'components/ui/States/NoTableState'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
|
||||
const AuthPoliciesLayout = ({ children }: PropsWithChildren<{}>) => {
|
||||
const PageState: any = useLocalObservable(() => ({
|
||||
meta: null,
|
||||
project: null,
|
||||
policiesFilter: '',
|
||||
selectedTableId: null,
|
||||
tables: [],
|
||||
tablesLoading: true,
|
||||
get filteredTables() {
|
||||
if (!PageState.policiesFilter)
|
||||
return PageState.tables.slice().sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
else {
|
||||
let filter = PageState.policiesFilter.toLowerCase()
|
||||
let stringSearch = (s: string) => s.toLowerCase().indexOf(filter) != -1
|
||||
return PageState.tables
|
||||
.slice()
|
||||
.filter((x: any) => {
|
||||
let searchTableName = stringSearch(x.name)
|
||||
let searchPolicyName = x.policies.some((p: any) => stringSearch(p.name))
|
||||
return searchTableName || searchPolicyName
|
||||
})
|
||||
.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
}
|
||||
},
|
||||
get selectedTable() {
|
||||
if (!PageState.selectedTableId) return null
|
||||
for (let i = 0; i < PageState.tables.length; i++) {
|
||||
const element: any = PageState.tables[i]
|
||||
if (element.id == PageState.selectedTableId) return element
|
||||
}
|
||||
},
|
||||
onTableUpdated(table: any) {
|
||||
for (let i = 0; i < PageState.tables.length; i++) {
|
||||
let el: any = PageState.tables[i]
|
||||
if (el.id == table.id) {
|
||||
PageState.tables[i] = { ...el, ...table }
|
||||
}
|
||||
}
|
||||
},
|
||||
}))
|
||||
return <div className="p-4">{children}</div>
|
||||
}
|
||||
|
||||
const { meta, ui } = useStore()
|
||||
PageState.meta = meta as any
|
||||
PageState.project = ui.selectedProject as any
|
||||
|
||||
return (
|
||||
<PageContext.Provider value={PageState}>
|
||||
<div className="p-4">{children}</div>
|
||||
</PageContext.Provider>
|
||||
)
|
||||
const onFilterTables = (
|
||||
tables: {
|
||||
name: string
|
||||
policies: {
|
||||
name: string
|
||||
}[]
|
||||
}[],
|
||||
keywords?: string
|
||||
) => {
|
||||
if (!keywords) return tables.slice().sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
else {
|
||||
let filter = keywords.toLowerCase()
|
||||
let stringSearch = (s: string) => s.toLowerCase().indexOf(filter) != -1
|
||||
return tables
|
||||
.slice()
|
||||
.filter((x: any) => {
|
||||
let searchTableName = stringSearch(x.name)
|
||||
let searchPolicyName = x.policies.some((p: any) => stringSearch(p.name))
|
||||
return searchTableName || searchPolicyName
|
||||
})
|
||||
.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
}
|
||||
}
|
||||
|
||||
const AuthPoliciesPage: NextPageWithLayout = () => {
|
||||
const PageState: any = useContext(PageContext)
|
||||
|
||||
const { meta } = useStore()
|
||||
const tables = meta.tables.list((table: any) => table.schema === 'public')
|
||||
|
||||
useEffect(() => {
|
||||
PageState.tablesLoading = false
|
||||
PageState.tables = tables.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
}, [])
|
||||
const [policiesFilter, setPoliciesFilter] = useState<string | undefined>(undefined)
|
||||
const publicTables = meta.tables.list((table: { schema: string }) => table.schema === 'public')
|
||||
const filteredTables = onFilterTables(publicTables, policiesFilter)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -87,8 +55,8 @@ const AuthPoliciesPage: NextPageWithLayout = () => {
|
||||
size="small"
|
||||
placeholder="Filter tables"
|
||||
className="block w-full text-sm placeholder-gray-400"
|
||||
value={PageState.policiesFilter}
|
||||
onChange={(e) => (PageState.policiesFilter = e.target.value)}
|
||||
value={policiesFilter}
|
||||
onChange={(e) => setPoliciesFilter(e.target.value)}
|
||||
icon={<IconSearch size="tiny" />}
|
||||
/>
|
||||
</div>
|
||||
@@ -103,7 +71,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<AuthPoliciesTables />
|
||||
<AuthPoliciesTables hasPublicTables={publicTables.length > 0} tables={filteredTables} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@@ -117,10 +85,12 @@ AuthPoliciesPage.getLayout = (page) => (
|
||||
|
||||
export default observer(AuthPoliciesPage)
|
||||
|
||||
const AuthPoliciesTables = observer(() => {
|
||||
interface AuthPoliciesTablesProps {
|
||||
hasPublicTables: boolean
|
||||
tables: any[]
|
||||
}
|
||||
const AuthPoliciesTables: FC<AuthPoliciesTablesProps> = observer(({ tables, hasPublicTables }) => {
|
||||
const { ui, meta } = useStore()
|
||||
const PageState: any = useContext(PageContext)
|
||||
|
||||
const roles = meta.roles.list((role: PostgresRole) => !meta.roles.systemRoles.includes(role.name))
|
||||
|
||||
const [selectedSchemaAndTable, setSelectedSchemaAndTable] = useState<any>({})
|
||||
@@ -157,18 +127,10 @@ const AuthPoliciesTables = observer(() => {
|
||||
|
||||
const onSavePolicySuccess = async () => {
|
||||
ui.setNotification({ category: 'success', message: 'Policy successfully saved!' })
|
||||
await refreshTables()
|
||||
closePolicyEditorModal()
|
||||
}
|
||||
|
||||
// Methods that involve some API
|
||||
|
||||
const refreshTables = async () => {
|
||||
await meta.tables.load()
|
||||
const res: any = meta.tables.list((table: any) => table.schema === 'public')
|
||||
if (!res.error) PageState.tables.replace(res)
|
||||
}
|
||||
|
||||
const onToggleRLS = async () => {
|
||||
const payload = {
|
||||
id: selectedTableToToggleRLS.id,
|
||||
@@ -176,21 +138,17 @@ const AuthPoliciesTables = observer(() => {
|
||||
}
|
||||
|
||||
const res: any = await meta.tables.update(payload.id, payload)
|
||||
// const url = `${API_URL}/database/${router.query.ref}/tables?id=${payload.id}`
|
||||
// const res = await patch(url, payload)
|
||||
if (res.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to toggle RLS: ${res.error.message}`,
|
||||
})
|
||||
} else {
|
||||
PageState.onTableUpdated(res)
|
||||
}
|
||||
closeConfirmModal()
|
||||
}
|
||||
|
||||
const onCreatePolicy = async (payload: any) => {
|
||||
const res = await PageState.meta.policies.create(payload)
|
||||
const res = await meta.policies.create(payload)
|
||||
if (res.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
@@ -202,7 +160,7 @@ const AuthPoliciesTables = observer(() => {
|
||||
}
|
||||
|
||||
const onUpdatePolicy = async (payload: any) => {
|
||||
const res = await PageState.meta.policies.update(payload.id, payload)
|
||||
const res = await meta.policies.update(payload.id, payload)
|
||||
if (res.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
@@ -214,8 +172,8 @@ const AuthPoliciesTables = observer(() => {
|
||||
}
|
||||
|
||||
const onDeletePolicy = async () => {
|
||||
const res = await PageState.meta.policies.del(selectedPolicyToDelete.id)
|
||||
if (res.error) {
|
||||
const res = await meta.policies.del(selectedPolicyToDelete.id)
|
||||
if (typeof res !== 'boolean' && res.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Error deleting policy: ${res.error.message}`,
|
||||
@@ -223,14 +181,13 @@ const AuthPoliciesTables = observer(() => {
|
||||
} else {
|
||||
ui.setNotification({ category: 'success', message: 'Successfully deleted policy!' })
|
||||
}
|
||||
await refreshTables()
|
||||
closeConfirmModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{PageState.filteredTables.length > 0 ? (
|
||||
PageState.filteredTables.map((table: any) => (
|
||||
{tables.length > 0 ? (
|
||||
tables.map((table: any) => (
|
||||
<section key={table.id}>
|
||||
<PolicyTableRow
|
||||
table={table}
|
||||
@@ -245,7 +202,7 @@ const AuthPoliciesTables = observer(() => {
|
||||
/>
|
||||
</section>
|
||||
))
|
||||
) : PageState.tables.length > 0 ? (
|
||||
) : hasPublicTables ? (
|
||||
<NoSearchResults />
|
||||
) : (
|
||||
<NoTableState message="A public schema table is required before you can create a row-level security policy" />
|
||||
|
||||
Reference in new issue
Block a user