Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/docs/content/guides/functions/websockets.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Here are some basic examples of setting up WebSocket servers using Deno and Node
Deno.serve((req) => {
const upgrade = req.headers.get('upgrade') || ''

if (upgrade.toLowerCase() != 'WebSocket') {
if (upgrade.toLowerCase() != 'websocket') {
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
import Table from 'components/to-be-cleaned/Table'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { Toggle } from 'ui'
import { Switch, TableCell, TableRow } from 'ui'

export interface PublicationSkeletonProps {
index?: number
}

const PublicationSkeleton = ({ index }: PublicationSkeletonProps) => {
export const PublicationSkeleton = ({ index }: PublicationSkeletonProps) => {
return (
<Table.tr className="border-t">
<Table.td className="px-4 py-3" style={{ width: '25%' }}>
<TableRow>
<TableCell style={{ width: '35%' }}>
<ShimmeringLoader className="h-4 w-24 my-0.5 p-0" delayIndex={index} />
</Table.td>
<Table.td className="hidden lg:table-cell" style={{ width: '25%' }}>
</TableCell>
<TableCell className="hidden lg:table-cell" style={{ width: '15%' }}>
<ShimmeringLoader className="h-4 w-14 my-0.5 p-0" delayIndex={index} />
</Table.td>
</TableCell>
{Array.from({ length: 4 }).map((_, i) => (
<Table.td key={i}>
<Toggle size="tiny" checked={false} disabled={true} />
</Table.td>
<TableCell key={i}>
<Switch size="small" checked={false} disabled={true} />
</TableCell>
))}
<Table.td className="px-4 py-3 pr-2">
<TableCell className="px-4 py-3 pr-2">
<div className="flex justify-end">
<ShimmeringLoader className="h-6 w-12 p-0" delayIndex={index} />
</div>
</Table.td>
</Table.tr>
</TableCell>
</TableRow>
)
}

export default PublicationSkeleton
Original file line number Diff line number Diff line change
@@ -1,34 +1,52 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { noop } from 'lodash'
import { AlertCircle, Search } from 'lucide-react'
import { AlertCircle, Info, Search } from 'lucide-react'
import Link from 'next/link'
import { useState } from 'react'
import { toast } from 'sonner'
import { Button, Input, Toggle } from 'ui'

import Table from 'components/to-be-cleaned/Table'
import { useParams } from 'common'
import AlertError from 'components/ui/AlertError'
import InformationBox from 'components/ui/InformationBox'
import NoSearchResults from 'components/ui/NoSearchResults'
import { useDatabasePublicationsQuery } from 'data/database-publications/database-publications-query'
import { useDatabasePublicationUpdateMutation } from 'data/database-publications/database-publications-update-mutation'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import {
Button,
Card,
Switch,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import PublicationSkeleton from './PublicationSkeleton'
import { PublicationSkeleton } from './PublicationSkeleton'

interface PublicationEvent {
event: string
key: string
}

interface PublicationsListProps {
onSelectPublication: (id: number) => void
}

export const PublicationsList = ({ onSelectPublication = noop }: PublicationsListProps) => {
export const PublicationsList = () => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const [filterString, setFilterString] = useState<string>('')

const { data, isLoading } = useDatabasePublicationsQuery({
const {
data = [],
error,
isLoading,
isSuccess,
isError,
} = useDatabasePublicationsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
Expand All @@ -48,10 +66,11 @@ export const PublicationsList = ({ onSelectPublication = noop }: PublicationsLis
{ event: 'Delete', key: 'publish_delete' },
{ event: 'Truncate', key: 'publish_truncate' },
]
const publications =
const publications = (
filterString.length === 0
? data ?? []
: (data ?? []).filter((publication) => publication.name.includes(filterString))
? data
: data.filter((publication) => publication.name.includes(filterString))
).sort((a, b) => a.id - b.id)

const [toggleListenEventValue, setToggleListenEventValue] = useState<{
publication: any
Expand Down Expand Up @@ -79,8 +98,9 @@ export const PublicationsList = ({ onSelectPublication = noop }: PublicationsLis
<div className="flex items-center">
<Input
size="tiny"
icon={<Search size="14" />}
placeholder={'Filter'}
icon={<Search size={12} />}
className="w-48 pl-8"
placeholder="Search for a publication"
value={filterString}
onChange={(e) => setFilterString(e.target.value)}
/>
Expand All @@ -97,60 +117,87 @@ export const PublicationsList = ({ onSelectPublication = noop }: PublicationsLis
</div>

<div className="w-full overflow-hidden overflow-x-auto">
<Table
head={[
<Table.th key="header.name">Name</Table.th>,
<Table.th key="header.id">System ID</Table.th>,
<Table.th key="header.insert">Insert</Table.th>,
<Table.th key="header.update">Update</Table.th>,
<Table.th key="header.delete">Delete</Table.th>,
<Table.th key="header.truncate">Truncate</Table.th>,
<Table.th key="header.source" className="text-right">
Source
</Table.th>,
]}
body={
isLoading
? Array.from({ length: 5 }).map((_, i) => <PublicationSkeleton key={i} index={i} />)
: publications.map((x) => (
<Table.tr className="border-t" key={x.name}>
<Table.td className="px-4 py-3">{x.name}</Table.td>
<Table.td>{x.id}</Table.td>
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>System ID</TableHead>
<TableHead>Insert</TableHead>
<TableHead>Update</TableHead>
<TableHead>Delete</TableHead>
<TableHead>Truncate</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
{isLoading &&
Array.from({ length: 2 }).map((_, i) => <PublicationSkeleton key={i} index={i} />)}

{isError && (
<TableRow>
<TableCell colSpan={7}>
<AlertError error={error} subject="Failed to retrieve publications" />
</TableCell>
</TableRow>
)}

{isSuccess &&
publications.map((x) => (
<TableRow key={x.name}>
<TableCell className="flex items-center gap-x-2 items-center">
{x.name}
{/* [Joshen] Making this tooltip very specific for these 2 publications */}
{['supabase_realtime', 'supabase_realtime_messages_publication'].includes(
x.name
) && (
<Tooltip>
<TooltipTrigger>
<Info size={14} className="text-foreground-light" />
</TooltipTrigger>
<TooltipContent side="bottom">
{x.name === 'supabase_realtime'
? 'This publication is managed by Supabase and handles Postgres changes'
: x.name === 'supabase_realtime_messages_publication'
? 'This publication is managed by Supabase and handles broadcasts from the database'
: undefined}
</TooltipContent>
</Tooltip>
)}
</TableCell>
<TableCell>{x.id}</TableCell>
{publicationEvents.map((event) => (
<Table.td key={event.key}>
<Toggle
size="tiny"
<TableCell key={event.key}>
<Switch
size="small"
checked={(x as any)[event.key]}
disabled={!canUpdatePublications}
onChange={() => {
onClick={() => {
setToggleListenEventValue({
publication: x,
event,
currentStatus: (x as any)[event.key],
})
}}
/>
</Table.td>
</TableCell>
))}
<Table.td className="px-4 py-3 pr-2">
<div className="flex justify-end gap-2">
<Button
type="default"
style={{ paddingTop: 3, paddingBottom: 3 }}
onClick={() => onSelectPublication(x.id)}
>
{x.tables == null
? 'All tables'
: `${x.tables.length} ${
x.tables.length > 1 || x.tables.length == 0 ? 'tables' : 'table'
}`}
<TableCell>
<div className="flex justify-end">
<Button asChild type="default" style={{ paddingTop: 3, paddingBottom: 3 }}>
<Link href={`/project/${ref}/database/publications/${x.id}`}>
{x.tables === null
? 'All tables'
: `${x.tables.length} ${x.tables.length === 1 ? 'table' : 'tables'}`}
</Link>
</Button>
</div>
</Table.td>
</Table.tr>
))
}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</div>

{!isLoading && publications.length === 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useState } from 'react'
import { toast } from 'sonner'

import Table from 'components/to-be-cleaned/Table'
import { useDatabasePublicationUpdateMutation } from 'data/database-publications/database-publications-update-mutation'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Badge, Toggle } from 'ui'
import { useProtectedSchemas } from 'hooks/useProtectedSchemas'
import { Badge, Switch, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui'

interface PublicationsTableItemProps {
table: PostgresTable
Expand All @@ -16,8 +16,11 @@ interface PublicationsTableItemProps {

const PublicationsTableItem = ({ table, selectedPublication }: PublicationsTableItemProps) => {
const { data: project } = useSelectedProjectQuery()
const { data: protectedSchemas } = useProtectedSchemas()
const enabledForAllTables = selectedPublication.tables == null

const isProtected = protectedSchemas.map((x) => x.name).includes(table.schema)

const [checked, setChecked] = useState(
selectedPublication.tables?.find((x: any) => x.id == table.id) != undefined
)
Expand Down Expand Up @@ -70,32 +73,39 @@ const PublicationsTableItem = ({ table, selectedPublication }: PublicationsTable
}

return (
<Table.tr key={table.id}>
<Table.td className="whitespace-nowrap">{table.name}</Table.td>
<Table.td className="whitespace-nowrap">{table.schema}</Table.td>
<Table.td className="hidden max-w-sm truncate whitespace-nowrap lg:table-cell">
<TableRow key={table.id}>
<TableCell className="py-3 whitespace-nowrap">{table.name}</TableCell>
<TableCell className="py-3 whitespace-nowrap">{table.schema}</TableCell>
<TableCell className="py-3 hidden max-w-sm truncate whitespace-nowrap lg:table-cell">
{table.comment}
</Table.td>
<Table.td className="px-4 py-3 pr-2">
</TableCell>
<TableCell className="py-3">
<div className="flex justify-end gap-2">
{enabledForAllTables ? (
<Badge>
<span>Enabled</span>
<span className="hidden lg:inline-block">&nbsp;for all tables</span>
</Badge>
) : (
<Toggle
size="tiny"
align="right"
disabled={!canUpdatePublications || isLoading}
className="m-0 ml-2 mt-1 -mb-1 p-0"
checked={checked}
onChange={() => toggleReplicationForTable(table, selectedPublication)}
/>
<Tooltip>
<TooltipTrigger>
<Switch
size="small"
disabled={!canUpdatePublications || isLoading || isProtected}
checked={checked}
onClick={() => toggleReplicationForTable(table, selectedPublication)}
/>
</TooltipTrigger>
{isProtected && (
<TooltipContent side="bottom" className="w-64 text-center">
This table belongs to a protected schema, and its publication cannot be toggled
</TooltipContent>
)}
</Tooltip>
)}
</div>
</Table.td>
</Table.tr>
</TableCell>
</TableRow>
)
}

Expand Down
Loading
Loading