mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 11:55:05 +03:00
Add abac for db replications
This commit is contained in:
1 parent
a036513bbc
commit
e45f7d02f7
8 files changed
+109
-88
No files matched your search
@@ -1,6 +1,5 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Badge, IconLoader, Toggle } from '@supabase/ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
@@ -82,31 +81,12 @@ const ExtensionCard: FC<Props> = ({ extension }) => {
|
||||
{loading ? (
|
||||
<IconLoader className="animate-spin" size={16} />
|
||||
) : (
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
<Tooltip.Trigger>
|
||||
<Toggle
|
||||
size="tiny"
|
||||
checked={isOn}
|
||||
disabled={!canUpdateExtentions}
|
||||
onChange={() => (isOn ? disableExtension() : enableExtension())}
|
||||
/>
|
||||
</Tooltip.Trigger>
|
||||
{!canUpdateExtentions && (
|
||||
<Tooltip.Content side="bottom">
|
||||
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
||||
<div
|
||||
className={[
|
||||
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
|
||||
'border-scale-200 border',
|
||||
].join(' ')}
|
||||
>
|
||||
<span className="text-scale-1200 text-xs">
|
||||
You need additional permissions to update database extensions
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
)}
|
||||
</Tooltip.Root>
|
||||
<Toggle
|
||||
size="tiny"
|
||||
checked={isOn}
|
||||
disabled={!canUpdateExtentions}
|
||||
onChange={() => (isOn ? disableExtension() : enableExtension())}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { partition, isNull } from 'lodash'
|
||||
import { Input, IconSearch } from '@supabase/ui'
|
||||
import { Input, IconSearch, IconAlertCircle } from '@supabase/ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { useStore, useFlag } from 'hooks'
|
||||
import { useStore, useFlag, checkPermissions } from 'hooks'
|
||||
import ExtensionCard from './ExtensionCard'
|
||||
import { HIDDEN_EXTENSIONS } from './Extensions.constants'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
|
||||
interface Props {}
|
||||
|
||||
@@ -31,10 +33,15 @@ const Extensions: FC<Props> = ({}) => {
|
||||
(ext: any) => !isNull(ext.installed_version)
|
||||
)
|
||||
|
||||
const canUpdateExtentions = checkPermissions(
|
||||
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
||||
'extensions'
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex items-center justify-between">
|
||||
<Input
|
||||
size="small"
|
||||
placeholder={'Filter'}
|
||||
@@ -42,6 +49,14 @@ const Extensions: FC<Props> = ({}) => {
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
icon={<IconSearch size="tiny" />}
|
||||
/>
|
||||
{!canUpdateExtentions && (
|
||||
<div className="w-[500px]">
|
||||
<InformationBox
|
||||
icon={<IconAlertCircle className="text-scale-1100" strokeWidth={2} />}
|
||||
title="You need additional permissions to update database extensions"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Button, Input, Toggle, IconSearch } from '@supabase/ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { Button, Input, Toggle, IconSearch, IconAlertCircle } from '@supabase/ui'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
|
||||
interface Props {
|
||||
onSelectPublication: (id: number) => void
|
||||
@@ -15,6 +17,17 @@ const PublicationsList: FC<Props> = ({ onSelectPublication = () => {} }) => {
|
||||
const { ui, meta } = useStore()
|
||||
const [filterString, setFilterString] = useState<string>('')
|
||||
|
||||
const canUpdatePublications = checkPermissions(
|
||||
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
||||
'publications'
|
||||
)
|
||||
|
||||
const publicationEvents = [
|
||||
{ event: 'Insert', key: 'publish_insert' },
|
||||
{ event: 'Update', key: 'publish_update' },
|
||||
{ event: 'Delete', key: 'publish_delete' },
|
||||
{ event: 'Truncate', key: 'publish_truncate' },
|
||||
]
|
||||
const publications =
|
||||
filterString.length === 0
|
||||
? meta.publications.list()
|
||||
@@ -51,17 +64,22 @@ const PublicationsList: FC<Props> = ({ onSelectPublication = () => {} }) => {
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div>
|
||||
<Input
|
||||
size="small"
|
||||
icon={<IconSearch size="tiny" />}
|
||||
placeholder={'Filter'}
|
||||
value={filterString}
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
<Input
|
||||
size="small"
|
||||
icon={<IconSearch size="tiny" />}
|
||||
placeholder={'Filter'}
|
||||
value={filterString}
|
||||
onChange={(e) => setFilterString(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{!canUpdatePublications && (
|
||||
<div className="w-[500px]">
|
||||
<InformationBox
|
||||
icon={<IconAlertCircle className="text-scale-1100" strokeWidth={2} />}
|
||||
title="You need additional permissions to update database replications"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className=""></div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{publications.length === 0 ? (
|
||||
@@ -98,42 +116,18 @@ const PublicationsList: FC<Props> = ({ onSelectPublication = () => {} }) => {
|
||||
<Table.td className="hidden lg:table-cell" style={{ width: '25%' }}>
|
||||
{x.id}
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<div className="flex justify-center">
|
||||
<Toggle
|
||||
size="tiny"
|
||||
checked={x.publish_insert}
|
||||
onChange={() => toggleListenEvent(x, 'insert', x.publish_insert)}
|
||||
/>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<div className="flex justify-center">
|
||||
<Toggle
|
||||
size="tiny"
|
||||
checked={x.publish_update}
|
||||
onChange={() => toggleListenEvent(x, 'update', x.publish_update)}
|
||||
/>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<div className="flex justify-center">
|
||||
<Toggle
|
||||
size="tiny"
|
||||
checked={x.publish_delete}
|
||||
onChange={() => toggleListenEvent(x, 'delete', x.publish_delete)}
|
||||
/>
|
||||
</div>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<div className="flex justify-center">
|
||||
<Toggle
|
||||
size="tiny"
|
||||
checked={x.publish_truncate}
|
||||
onChange={() => toggleListenEvent(x, 'truncate', x.publish_truncate)}
|
||||
/>
|
||||
</div>
|
||||
</Table.td>
|
||||
{publicationEvents.map((event) => (
|
||||
<Table.td key={event.key}>
|
||||
<div className="flex justify-center">
|
||||
<Toggle
|
||||
size="tiny"
|
||||
checked={x[event.key]}
|
||||
disabled={!canUpdatePublications}
|
||||
onChange={() => toggleListenEvent(x, 'insert', x[event.key])}
|
||||
/>
|
||||
</div>
|
||||
</Table.td>
|
||||
))}
|
||||
<Table.td className="px-4 py-3 pr-2">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
|
||||
@@ -2,8 +2,9 @@ import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Badge, Toggle } from '@supabase/ui'
|
||||
import { PostgresPublication, PostgresTable } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
|
||||
interface Props {
|
||||
@@ -20,6 +21,11 @@ const PublicationsTableItem: FC<Props> = ({ table, selectedPublication }) => {
|
||||
selectedPublication.tables?.find((x: any) => x.id == table.id) != undefined
|
||||
)
|
||||
|
||||
const canUpdatePublications = checkPermissions(
|
||||
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
||||
'publications'
|
||||
)
|
||||
|
||||
const toggleReplicationForTable = async (
|
||||
table: PostgresTable,
|
||||
publication: PostgresPublication
|
||||
@@ -72,7 +78,7 @@ const PublicationsTableItem: FC<Props> = ({ table, selectedPublication }) => {
|
||||
<Toggle
|
||||
size="tiny"
|
||||
align="right"
|
||||
disabled={loading}
|
||||
disabled={!canUpdatePublications || loading}
|
||||
className="m-0 ml-2 mt-1 -mb-1 p-0"
|
||||
checked={checked}
|
||||
onChange={() => toggleReplicationForTable(table, selectedPublication)}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Button, Input, IconChevronLeft, IconSearch } from '@supabase/ui'
|
||||
import { Button, Input, IconChevronLeft, IconSearch, IconAlertCircle } from '@supabase/ui'
|
||||
import { PostgresPublication } from '@supabase/postgres-meta'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import PublicationsTableItem from './PublicationsTableItem'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
|
||||
interface Props {
|
||||
selectedPublication: PostgresPublication
|
||||
@@ -17,6 +19,11 @@ const PublicationsTables: FC<Props> = ({ selectedPublication, onSelectBack }) =>
|
||||
const { meta } = useStore()
|
||||
const [filterString, setFilterString] = useState<string>('')
|
||||
|
||||
const canUpdatePublications = checkPermissions(
|
||||
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
||||
'publications'
|
||||
)
|
||||
|
||||
const tables =
|
||||
filterString.length === 0
|
||||
? meta.tables.list((table: any) => !meta.excludedSchemas.includes(table.schema))
|
||||
@@ -73,7 +80,14 @@ const PublicationsTables: FC<Props> = ({ selectedPublication, onSelectBack }) =>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className=""></div>
|
||||
{!canUpdatePublications && (
|
||||
<div className="w-[500px]">
|
||||
<InformationBox
|
||||
icon={<IconAlertCircle className="text-scale-1100" strokeWidth={2} />}
|
||||
title="You need additional permissions to update database replications"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{tables.length === 0 ? (
|
||||
|
||||
@@ -31,12 +31,17 @@ const ProjectLayout = ({
|
||||
hideHeader = false,
|
||||
hideIconBar = false,
|
||||
}: PropsWithChildren<Props>) => {
|
||||
const { ui } = useStore()
|
||||
const ongoingIncident = useFlag('ongoingIncident')
|
||||
|
||||
const projectName = ui.selectedProject?.name
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{title ? `${title} | Supabase` : 'Supabase'}</title>
|
||||
<title>
|
||||
{title ? `${title} | Supabase` : projectName ? `${projectName} | Supabase` : 'Supabase'}
|
||||
</title>
|
||||
<meta name="description" content="Supabase Studio" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
@@ -13,11 +13,7 @@ const DatabaseExtensions: NextPageWithLayout = () => {
|
||||
return <NoPermission isFullPage resourceText="view database extensions" />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Extensions />
|
||||
</div>
|
||||
)
|
||||
return <Extensions />
|
||||
}
|
||||
|
||||
DatabaseExtensions.getLayout = (page) => <DatabaseLayout title="Database">{page}</DatabaseLayout>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { DatabaseLayout } from 'components/layouts'
|
||||
import { PublicationsList, PublicationsTables } from 'components/interfaces/Database'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
|
||||
// [Joshen] Technically, best that we have these as separate URLs
|
||||
// makes it easier to manage state, but foresee that this page might
|
||||
@@ -14,9 +16,18 @@ const DatabaseReplication: NextPageWithLayout = () => {
|
||||
const { meta } = useStore()
|
||||
const publications = meta.publications.list()
|
||||
|
||||
const canViewPublications = checkPermissions(
|
||||
PermissionAction.TENANT_SQL_ADMIN_READ,
|
||||
'publications'
|
||||
)
|
||||
|
||||
const [selectedPublicationId, setSelectedPublicationId] = useState<number>()
|
||||
const selectedPublication = publications.find((pub) => pub.id === selectedPublicationId)
|
||||
|
||||
if (!canViewPublications) {
|
||||
return <NoPermission isFullPage resourceText="view database publications" />
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
{selectedPublicationId === undefined ? (
|
||||
|
||||
Reference in new issue
Block a user