mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 23:29:08 +03:00
Show tables outside of public schema on database tables page
This commit is contained in:
1 parent
91183b712c
commit
b24c26bfb7
4 files changed
+222
-187
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { FC, ReactNode, useState } from 'react'
|
||||
import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import {
|
||||
Input,
|
||||
@@ -10,66 +10,40 @@ import {
|
||||
IconTrash,
|
||||
} from '@supabase/ui'
|
||||
|
||||
import Table from '../../../to-be-cleaned/Table'
|
||||
import { useStore } from 'hooks'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
import { PostgresTable } from '@supabase/postgres-meta'
|
||||
|
||||
const Header: FC<{
|
||||
filterString: string
|
||||
filterPlaceholder: string
|
||||
leftComponents: ReactNode
|
||||
rightComponents: ReactNode
|
||||
setFilterString: (value: string) => void
|
||||
}> = ({
|
||||
filterString,
|
||||
setFilterString,
|
||||
filterPlaceholder = 'Filter',
|
||||
leftComponents,
|
||||
rightComponents,
|
||||
}) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div>{leftComponents}</div>
|
||||
<div>
|
||||
<Input
|
||||
size="small"
|
||||
placeholder={filterPlaceholder}
|
||||
value={filterString}
|
||||
onChange={(e: any) => setFilterString(e.target.value)}
|
||||
icon={<IconSearch size="tiny" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">{rightComponents}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const ColumnList: FC<{
|
||||
selectedTable: any
|
||||
interface Props {
|
||||
selectedTable: PostgresTable
|
||||
onSelectBack: () => void
|
||||
onAddColumn: () => void
|
||||
onEditColumn: (column: any) => void
|
||||
onDeleteColumn: (column: any) => void
|
||||
}> = ({
|
||||
}
|
||||
|
||||
const ColumnList: FC<Props> = ({
|
||||
selectedTable,
|
||||
onSelectBack = () => {},
|
||||
onAddColumn = () => {},
|
||||
onEditColumn = () => {},
|
||||
onDeleteColumn = () => {},
|
||||
}) => {
|
||||
const { meta } = useStore()
|
||||
const [filterString, setFilterString] = useState<string>('')
|
||||
const columns =
|
||||
filterString.length === 0
|
||||
? selectedTable.columns
|
||||
: selectedTable.columns.filter((column: any) => column.name.includes(filterString))
|
||||
|
||||
const isLocked = meta.excludedSchemas.includes(selectedTable?.schema ?? '')
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<Header
|
||||
filterPlaceholder="Filter columns"
|
||||
filterString={filterString}
|
||||
setFilterString={setFilterString}
|
||||
leftComponents={
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center mr-4">
|
||||
<Button
|
||||
type="outline"
|
||||
@@ -80,13 +54,24 @@ const ColumnList: FC<{
|
||||
/>
|
||||
<code>{selectedTable.name}</code>
|
||||
</div>
|
||||
}
|
||||
rightComponents={
|
||||
<Button icon={<IconPlus />} onClick={() => onAddColumn()}>
|
||||
New
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<div>
|
||||
<Input
|
||||
size="small"
|
||||
placeholder="Filter columns"
|
||||
value={filterString}
|
||||
onChange={(e: any) => setFilterString(e.target.value)}
|
||||
icon={<IconSearch size="tiny" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isLocked && (
|
||||
<div>
|
||||
<Button icon={<IconPlus />} onClick={() => onAddColumn()}>
|
||||
New column
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{columns.length === 0 ? (
|
||||
<NoSearchResults />
|
||||
@@ -111,8 +96,12 @@ const ColumnList: FC<{
|
||||
<Table.td>
|
||||
<p>{x.name}</p>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<p>{x.comment}</p>
|
||||
<Table.td className="break-all whitespace-normal">
|
||||
{x.comment !== null ? (
|
||||
<p title={x.comment}>{x.comment}</p>
|
||||
) : (
|
||||
<p className="text-scale-800">No description</p>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<code className="text-sm">{x.data_type}</code>
|
||||
@@ -127,12 +116,14 @@ const ColumnList: FC<{
|
||||
icon={<IconEdit3 />}
|
||||
style={{ padding: 5 }}
|
||||
type="text"
|
||||
disabled={isLocked}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => onDeleteColumn(x)}
|
||||
icon={<IconTrash />}
|
||||
style={{ padding: 5 }}
|
||||
type="text"
|
||||
disabled={isLocked}
|
||||
/>
|
||||
</div>
|
||||
</Table.td>
|
||||
|
||||
@@ -1,155 +1,195 @@
|
||||
import { FC, ReactNode, useState } from 'react'
|
||||
import { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import {
|
||||
Button,
|
||||
IconPlus,
|
||||
Input,
|
||||
Listbox,
|
||||
IconSearch,
|
||||
IconTrash,
|
||||
IconEdit3,
|
||||
IconColumns,
|
||||
IconLock,
|
||||
} from '@supabase/ui'
|
||||
import { partition } from 'lodash'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
|
||||
import { PostgresTable, PostgresSchema } from '@supabase/postgres-meta'
|
||||
|
||||
const Header: FC<{
|
||||
filterString: string
|
||||
filterPlaceholder: string
|
||||
leftComponents: ReactNode
|
||||
rightComponents: ReactNode
|
||||
setFilterString: (value: string) => void
|
||||
}> = ({
|
||||
filterString,
|
||||
setFilterString,
|
||||
filterPlaceholder = 'Filter',
|
||||
leftComponents,
|
||||
rightComponents,
|
||||
}) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div>{leftComponents}</div>
|
||||
<div>
|
||||
<Input
|
||||
size="small"
|
||||
placeholder={filterPlaceholder}
|
||||
value={filterString}
|
||||
onChange={(e: any) => setFilterString(e.target.value)}
|
||||
icon={<IconSearch size="tiny" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">{rightComponents}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const TableList: FC<{
|
||||
interface Props {
|
||||
selectedSchema: string
|
||||
onSelectSchema: (schema: string) => void
|
||||
onAddTable: () => void
|
||||
onEditTable: (table: any) => void
|
||||
onDeleteTable: (table: any) => void
|
||||
onOpenTable: (table: any) => void
|
||||
}> = observer(
|
||||
({
|
||||
onAddTable = () => {},
|
||||
onEditTable = () => {},
|
||||
onDeleteTable = () => {},
|
||||
onOpenTable = () => {},
|
||||
}) => {
|
||||
const { meta } = useStore()
|
||||
const [filterString, setFilterString] = useState<string>('')
|
||||
const tables =
|
||||
filterString.length === 0
|
||||
? meta.tables.list((table: any) => table.schema === 'public')
|
||||
: meta.tables.list(
|
||||
(table: any) => table.schema === 'public' && table.name.includes(filterString)
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{/* @ts-ignore */}
|
||||
<Header
|
||||
filterString={filterString}
|
||||
setFilterString={setFilterString}
|
||||
rightComponents={
|
||||
<Button icon={<IconPlus />} onClick={() => onAddTable()}>
|
||||
New
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{tables.length === 0 ? (
|
||||
<NoSearchResults />
|
||||
) : (
|
||||
<div className="my-4 w-full">
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="name">Name</Table.th>,
|
||||
<Table.th key="schema">Schema</Table.th>,
|
||||
<Table.th key="description" className="hidden lg:table-cell">
|
||||
Description
|
||||
</Table.th>,
|
||||
<Table.th key="rows" className="hidden xl:table-cell">
|
||||
Rows (Estimated)
|
||||
</Table.th>,
|
||||
<Table.th key="size" className="hidden xl:table-cell">
|
||||
Size (Estimated)
|
||||
</Table.th>,
|
||||
<Table.th key="buttons"></Table.th>,
|
||||
]}
|
||||
body={tables.map((x: any, i: any) => (
|
||||
<Table.tr key={x.id} hoverable>
|
||||
<Table.td>
|
||||
<p>{x.name}</p>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<p>{x.schema}</p>
|
||||
</Table.td>
|
||||
<Table.td className=" hidden max-w-sm truncate lg:table-cell">
|
||||
<p>{x.comment}</p>
|
||||
</Table.td>
|
||||
<Table.td className=" hidden xl:table-cell">
|
||||
<code className="text-sm">{x.live_rows_estimate ?? x.live_row_count}</code>
|
||||
</Table.td>
|
||||
<Table.td className=" hidden xl:table-cell">
|
||||
<code className="text-sm">{x.size}</code>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
iconRight={<IconColumns />}
|
||||
type="default"
|
||||
className="whitespace-nowrap hover:border-gray-500"
|
||||
style={{ paddingTop: 3, paddingBottom: 3 }}
|
||||
onClick={() => {
|
||||
onOpenTable(x)
|
||||
}}
|
||||
>
|
||||
{x.columns.length} columns
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => onEditTable(x)}
|
||||
icon={<IconEdit3 />}
|
||||
style={{ padding: 5 }}
|
||||
type="text"
|
||||
/>
|
||||
<Button
|
||||
onClick={() => onDeleteTable(x)}
|
||||
icon={<IconTrash />}
|
||||
style={{ padding: 5 }}
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
const TableList: FC<Props> = ({
|
||||
selectedSchema,
|
||||
onSelectSchema = () => {},
|
||||
onAddTable = () => {},
|
||||
onEditTable = () => {},
|
||||
onDeleteTable = () => {},
|
||||
onOpenTable = () => {},
|
||||
}) => {
|
||||
const { meta } = useStore()
|
||||
|
||||
const [filterString, setFilterString] = useState<string>('')
|
||||
|
||||
const schemas: PostgresSchema[] = meta.schemas.list()
|
||||
const [protectedSchemas, openSchemas] = partition(schemas, (schema) =>
|
||||
meta.excludedSchemas.includes(schema?.name ?? '')
|
||||
)
|
||||
|
||||
const allTables: PostgresTable[] = meta.tables.list(
|
||||
(table: PostgresTable) => table.schema === selectedSchema
|
||||
)
|
||||
const tables =
|
||||
filterString.length === 0
|
||||
? allTables
|
||||
: // @ts-ignore
|
||||
allTables.filter((table: PostgresTable) => table.name.includes(filterString))
|
||||
|
||||
// @ts-ignore
|
||||
const schema = schemas.find((schema) => schema.name === selectedSchema)
|
||||
const isLocked = protectedSchemas.some((s) => s.id === schema?.id)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="w-[230px]">
|
||||
<Listbox
|
||||
size="small"
|
||||
value={selectedSchema}
|
||||
onChange={onSelectSchema}
|
||||
icon={isLocked && <IconLock size={16} strokeWidth={2} />}
|
||||
>
|
||||
<Listbox.Option disabled key="normal-schemas" value="normal-schemas" label="Schemas">
|
||||
<p className="text-sm">Schemas</p>
|
||||
</Listbox.Option>
|
||||
{/* @ts-ignore */}
|
||||
{openSchemas.map((schema) => (
|
||||
<Listbox.Option
|
||||
key={schema.id}
|
||||
value={schema.name}
|
||||
label={schema.name}
|
||||
addOnBefore={() => <span className="text-scale-900">schema</span>}
|
||||
>
|
||||
<span className="text-scale-1200 text-sm">{schema.name}</span>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
<Listbox.Option
|
||||
disabled
|
||||
key="protected-schemas"
|
||||
value="protected-schemas"
|
||||
label="Protected schemas"
|
||||
>
|
||||
<p className="text-sm">Protected schemas</p>
|
||||
</Listbox.Option>
|
||||
{protectedSchemas.map((schema) => (
|
||||
<Listbox.Option
|
||||
key={schema.id}
|
||||
value={schema.name}
|
||||
label={schema.name}
|
||||
addOnBefore={() => <span className="text-scale-900">schema</span>}
|
||||
>
|
||||
<span className="text-scale-1200 text-sm">{schema.name}</span>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
size="small"
|
||||
placeholder="Filter tables"
|
||||
value={filterString}
|
||||
onChange={(e: any) => setFilterString(e.target.value)}
|
||||
icon={<IconSearch size="tiny" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isLocked && (
|
||||
<div>
|
||||
<Button icon={<IconPlus />} onClick={() => onAddTable()}>
|
||||
New table
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
</div>
|
||||
{tables.length === 0 ? (
|
||||
<NoSearchResults />
|
||||
) : (
|
||||
<div className="my-4 w-full">
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="name">Name</Table.th>,
|
||||
<Table.th key="description" className="hidden lg:table-cell">
|
||||
Description
|
||||
</Table.th>,
|
||||
<Table.th key="rows" className="hidden xl:table-cell">
|
||||
Rows (Estimated)
|
||||
</Table.th>,
|
||||
<Table.th key="size" className="hidden xl:table-cell">
|
||||
Size (Estimated)
|
||||
</Table.th>,
|
||||
<Table.th key="buttons"></Table.th>,
|
||||
]}
|
||||
body={tables.map((x: any, i: any) => (
|
||||
<Table.tr key={x.id}>
|
||||
<Table.td>
|
||||
<p title={x.name}>{x.name}</p>
|
||||
</Table.td>
|
||||
<Table.td className="hidden max-w-sm truncate lg:table-cell break-all whitespace-normal">
|
||||
{x.comment !== null ? (
|
||||
<p title={x.comment}>{x.comment}</p>
|
||||
) : (
|
||||
<p className="text-scale-800">No description</p>
|
||||
)}
|
||||
</Table.td>
|
||||
<Table.td className="hidden xl:table-cell">
|
||||
<code className="text-sm">{x.live_rows_estimate ?? x.live_row_count}</code>
|
||||
</Table.td>
|
||||
<Table.td className="hidden xl:table-cell">
|
||||
<code className="text-sm">{x.size}</code>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
type="default"
|
||||
iconRight={<IconColumns />}
|
||||
className="whitespace-nowrap hover:border-gray-500"
|
||||
style={{ paddingTop: 3, paddingBottom: 3 }}
|
||||
onClick={() => onOpenTable(x)}
|
||||
>
|
||||
{x.columns.length} columns
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<IconEdit3 />}
|
||||
style={{ padding: 5 }}
|
||||
disabled={isLocked}
|
||||
onClick={() => onEditTable(x)}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<IconTrash />}
|
||||
style={{ padding: 5 }}
|
||||
disabled={isLocked}
|
||||
onClick={() => onDeleteTable(x)}
|
||||
/>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableList
|
||||
export default observer(TableList)
|
||||
@@ -104,8 +104,8 @@ const TableEditorMenu: FC<Props> = ({
|
||||
<div className="my-6 mx-4 flex flex-grow flex-col space-y-6">
|
||||
{/* Schema selection dropdown */}
|
||||
<div className="px-3">
|
||||
{meta.schemas.isLoading ? (
|
||||
<div className="flex h-[30px] items-center space-x-3 rounded border border-gray-500 px-3">
|
||||
{!meta.schemas.isInitialized ? (
|
||||
<div className="flex h-[26px] items-center space-x-3 rounded border border-gray-500 px-3">
|
||||
<IconLoader className="animate-spin" size={12} />
|
||||
<span className="text-scale-900 text-xs">Loading schemas...</span>
|
||||
</div>
|
||||
@@ -121,7 +121,7 @@ const TableEditorMenu: FC<Props> = ({
|
||||
}}
|
||||
>
|
||||
<Listbox.Option disabled key="normal-schemas" value="normal-schemas" label="Schemas">
|
||||
<p className="text-sm">Schemas</p>
|
||||
<p className="text-xs text-scale-1100">Schemas</p>
|
||||
</Listbox.Option>
|
||||
{/* @ts-ignore */}
|
||||
{openSchemas.map((schema) => (
|
||||
@@ -129,9 +129,9 @@ const TableEditorMenu: FC<Props> = ({
|
||||
key={schema.id}
|
||||
value={schema.name}
|
||||
label={schema.name}
|
||||
addOnBefore={() => <span className="text-scale-900">schema</span>}
|
||||
addOnBefore={() => <span className="text-scale-900 text-xs">schema</span>}
|
||||
>
|
||||
<span className="text-scale-1200 text-sm">{schema.name}</span>
|
||||
<span className="text-scale-1200 text-xs">{schema.name}</span>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
<Listbox.Option
|
||||
@@ -140,16 +140,16 @@ const TableEditorMenu: FC<Props> = ({
|
||||
value="protected-schemas"
|
||||
label="Protected schemas"
|
||||
>
|
||||
<p className="text-sm">Protected schemas</p>
|
||||
<p className="text-xs text-scale-1100">Protected schemas</p>
|
||||
</Listbox.Option>
|
||||
{protectedSchemas.map((schema) => (
|
||||
<Listbox.Option
|
||||
key={schema.id}
|
||||
value={schema.name}
|
||||
label={schema.name}
|
||||
addOnBefore={() => <span className="text-scale-900">schema</span>}
|
||||
addOnBefore={() => <span className="text-scale-900 text-xs">schema</span>}
|
||||
>
|
||||
<span className="text-scale-1200 text-sm">{schema.name}</span>
|
||||
<span className="text-scale-1200 text-xs">{schema.name}</span>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
|
||||
@@ -16,8 +16,10 @@ const DatabaseTables: NextPageWithLayout = () => {
|
||||
const { meta, ui } = useStore()
|
||||
|
||||
const [isDeleting, setIsDeleting] = useState<boolean>(false)
|
||||
const [selectedSchema, setSelectedSchema] = useState('public')
|
||||
const [selectedTable, setSelectedTable] = useState<any>()
|
||||
const [sidePanelKey, setSidePanelKey] = useState<'column' | 'table'>()
|
||||
|
||||
const [selectedColumnToEdit, setSelectedColumnToEdit] = useState<PostgresColumn>()
|
||||
const [selectedTableToEdit, setSelectedTableToEdit] = useState<PostgresTable>()
|
||||
|
||||
@@ -121,6 +123,8 @@ const DatabaseTables: NextPageWithLayout = () => {
|
||||
<div className="p-4">
|
||||
{isUndefined(selectedTable) ? (
|
||||
<TableList
|
||||
selectedSchema={selectedSchema}
|
||||
onSelectSchema={setSelectedSchema}
|
||||
onAddTable={onAddTable}
|
||||
onEditTable={onEditTable}
|
||||
onDeleteTable={onDeleteTable}
|
||||
|
||||
Reference in new issue
Block a user