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/database/replication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Postgres comes with built-in support for replication via publications and replic

<Admonition type="tip">

If you want to set up a read replica, see [Read Replicas](/docs/guides/platform/read-replicas) instead. If you want to sync your data in real time to a client such as a browser or mobile app, see [Realtime](/docs/guides/realtime) instead.
If you want to set up a read replica, see [Read Replicas](/docs/guides/platform/read-replicas) instead. If you want to sync your data in real time to a client such as a browser or mobile app, see [Realtime](/docs/guides/realtime) instead. For configuring replication to an ETL destination, use the [Dashboard](/dashboard/project/_/database/replication).

</Admonition>

Expand Down
32 changes: 28 additions & 4 deletions apps/studio/components/interfaces/Account/AuditLogs.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import dayjs from 'dayjs'
import { ArrowDown, ArrowUp, RefreshCw } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'

import { useDebounce } from '@uidotdev/usehooks'
import { LogDetailsPanel } from 'components/interfaces/AuditLogs'
import Table from 'components/to-be-cleaned/Table'
import AlertError from 'components/ui/AlertError'
Expand All @@ -11,13 +12,17 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import type { AuditLog } from 'data/organizations/organization-audit-logs-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProfileAuditLogsQuery } from 'data/profile/profile-audit-logs-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useProjectsInfiniteQuery } from 'data/projects/projects-infinite-query'
import { Button } from 'ui'
import { TimestampInfo } from 'ui-patterns'
import { LogsDatePicker } from '../Settings/Logs/Logs.DatePickers'

const AuditLogs = () => {
const currentTime = dayjs().utc().set('millisecond', 0)

const [search, setSearch] = useState('')
const debouncedSearch = useDebounce(search, 500)

const [dateSortDesc, setDateSortDesc] = useState(true)
const [dateRange, setDateRange] = useState({
from: currentTime.subtract(1, 'day').toISOString(),
Expand All @@ -29,8 +34,20 @@ const AuditLogs = () => {
projects: [], // project_ref[]
})

const { data: projectsData } = useProjectsQuery()
const projects = projectsData?.projects ?? []
const {
data: projectsData,
isLoading: isLoadingProjects,
isFetching,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
} = useProjectsInfiniteQuery(
{ search: search.length === 0 ? search : debouncedSearch },
{ keepPreviousData: true }
)
const projects =
useMemo(() => projectsData?.pages.flatMap((page) => page.projects), [projectsData?.pages]) || []

const { data: organizations } = useOrganizationsQuery()
const { data, error, isLoading, isSuccess, isError, isRefetching, refetch } =
useProfileAuditLogsQuery(
Expand Down Expand Up @@ -88,6 +105,13 @@ const AuditLogs = () => {
valueKey="ref"
activeOptions={filters.projects}
onSaveFilters={(values) => setFilters({ ...filters, projects: values })}
search={search}
setSearch={setSearch}
hasNextPage={hasNextPage}
isLoading={isLoadingProjects}
isFetching={isFetching}
isFetchingNextPage={isFetchingNextPage}
fetchNextPage={fetchNextPage}
/>
<LogsDatePicker
hideWarnings
Expand Down
42 changes: 16 additions & 26 deletions apps/studio/components/interfaces/App/RouteValidationWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { toast } from 'sonner'

import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useIsMFAEnabled, useParams } from 'common'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useProjectDetailQuery } from 'data/projects/project-detail-query'
import { useDashboardHistory } from 'hooks/misc/useDashboardHistory'
import useLatest from 'hooks/misc/useLatest'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'

// Ideally these could all be within a _middleware when we use Next 12
const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
export const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
const router = useRouter()
const { ref, slug, id } = useParams()
const { data: organization } = useSelectedOrganizationQuery()
Expand All @@ -21,12 +21,16 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
const isUserMFAEnabled = useIsMFAEnabled()

const { setLastVisitedSnippet, setLastVisitedTable } = useDashboardHistory()
const [__, setLastVisitedOrganization] = useLocalStorageQuery(
const [lastVisitedOrganization, setLastVisitedOrganization] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
''
)

const DEFAULT_HOME = IS_PLATFORM ? '/organizations' : '/project/default'
const DEFAULT_HOME = IS_PLATFORM
? !!lastVisitedOrganization
? `/org/${lastVisitedOrganization}`
: '/organizations'
: '/project/default'

/**
* Array of urls/routes that should be ignored
Expand All @@ -49,6 +53,8 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
return excemptUrls.includes(router?.pathname)
}

const { isError: isErrorProject } = useProjectDetailQuery({ ref })

const { data: organizations, isSuccess: orgsInitialized } = useOrganizationsQuery({
enabled: isLoggedIn,
})
Expand All @@ -71,31 +77,17 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
}
}, [orgsInitialized])

const { data, isSuccess: projectsInitialized } = useProjectsQuery({
enabled: isLoggedIn,
})
const projects = data?.projects ?? []
const projectsRef = useLatest(projects)

useEffect(() => {
// check if current route is excempted from route validation check
if (isExceptUrl() || !isLoggedIn) return

if (projectsInitialized && ref) {
// Check validity of project that the user is trying to access
const projects = projectsRef.current ?? []
const isValidProject = projects.some((project) => project.ref === ref)
const isValidBranch = IS_PLATFORM
? projects.some((project) => project.preview_branch_refs.includes(ref))
: true

if (!isValidProject && !isValidBranch) {
toast.error('This project does not exist')
router.push(DEFAULT_HOME)
return
}
// A successful request to project details will validate access to both project and branches
if (!!ref && isErrorProject) {
toast.error('This project does not exist')
router.push(DEFAULT_HOME)
return
}
}, [projectsInitialized])
}, [isErrorProject])

useEffect(() => {
if (ref !== undefined && id !== undefined) {
Expand Down Expand Up @@ -125,5 +117,3 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {

return <>{children}</>
}

export default RouteValidationWrapper
2 changes: 1 addition & 1 deletion apps/studio/components/interfaces/App/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as AppBannerWrapper } from './AppBannerWrapper'
export { default as RouteValidationWrapper } from './RouteValidationWrapper'
export { RouteValidationWrapper } from './RouteValidationWrapper'
141 changes: 136 additions & 5 deletions apps/studio/components/interfaces/Auth/Overview/OverviewLearnMore.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,145 @@
import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold'
import { Card } from 'ui'
import { Card, CardContent, CardHeader, CardTitle, Button, AiIconAnimation, Image } from 'ui'
import Link from 'next/link'
import { BookOpen } from 'lucide-react'
import { Logs } from 'icons'
import { BASE_PATH } from 'lib/constants'
import { useParams } from 'common'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'

export const OverviewLearnMore = () => {
const { ref } = useParams()
const aiSnap = useAiAssistantStateSnapshot()

const LearnMoreCards = [
{
label: 'Docs',
title: 'Authentication docs',
description: 'Read more on authentication and benefits of using Supabase policies.',
image: `${BASE_PATH}/img/auth-overview/auth-overview-docs.jpg`,
actions: [
{
label: 'Docs',
href: 'https://supabase.com/docs/guides/auth',
icon: <BookOpen />,
},
],
},
{
label: 'Assistant',
title: 'Explain authentication errors',
description: 'Our Assistant can help you debug and fix authentication errors.',
image: `${BASE_PATH}/img/auth-overview/auth-overview-assistant.jpg`,
actions: [
{
label: 'Ask Assistant',
onClick: () => {
aiSnap.newChat({
name: 'Authentication Help',
open: true,
initialInput: 'Help me debug and fix authentication errors in my Supabase project',
suggestions: {
title: 'I can help you with authentication issues. Here are some common problems:',
prompts: [
{
label: 'Login Issues',
description: 'Why are users unable to log in to my app?',
},
{
label: 'JWT Problems',
description: 'Help me understand and fix JWT token issues',
},
{
label: 'RLS Policies',
description: 'Explain my Row Level Security policies and fix issues',
},
{
label: 'Provider Setup',
description: 'Help me configure OAuth providers correctly',
},
],
},
})
},
icon: <AiIconAnimation size={14} />,
},
],
},
{
label: 'Logs',
title: 'Dive into the logs',
description:
'Our authentication logs provide a deeper view into your auth requests and errors.',
image: `${BASE_PATH}/img/auth-overview/auth-overview-logs.jpg`,
actions: [
{
label: 'Go to logs',
href: `/project/${ref}/logs/auth-logs`,
icon: <Logs />,
},
],
},
]

return (
<ScaffoldSection isFullWidth>
<ScaffoldSectionTitle className="mb-4">Learn more</ScaffoldSectionTitle>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 opacity-30 pointer-events-none">
<Card className="aspect-square" />
<Card className="aspect-square" />
<Card className="aspect-square" />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-3">
{LearnMoreCards.map((card) => (
<Card key={card.label} className="relative">
<CardHeader className="absolute top-0 left-0 right-0 border-b-0">
<CardTitle className="text-foreground-lighter">{card.label}</CardTitle>
</CardHeader>
<CardContent className="p-0">
<div className="bg-black/20 flex w-full">
<Image
src={card.image && card?.image}
alt={card.title}
width={620}
height={324}
className="object-fit"
/>
</div>
<div className="p-4">
<div className="flex flex-col gap-1 mb-4">
<h4>{card.title}</h4>
<p className="text-sm text-foreground-lighter">{card.description}</p>
</div>
<div className="flex flex-col gap-2 items-start">
{card.actions.map((action) => {
if ('href' in action) {
return (
<Button
key={action.label}
className="inline-flex"
type="default"
icon={action.icon}
asChild
>
<Link href={action.href} className="inline-flex">
{action.label}
</Link>
</Button>
)
} else {
return (
<Button
key={action.label}
onClick={action.onClick}
type="default"
className="inline-flex"
icon={action.icon}
>
{action.label}
</Button>
)
}
})}
</div>
</div>
</CardContent>
</Card>
))}
</div>
</ScaffoldSection>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const LockedRenameQuerySection = ({
</p>
</div>
<p className="font-mono tracking-tighter">
<span className="text-[#569cd6]">alter</span> policy {oldName}
<span className="text-[#569cd6]">alter</span> policy "{oldName}"
</p>
</div>
<div className="flex items-center" style={{ fontSize: '14px' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const TerminalInstructions = forwardRef<
return (
<>
<span className="text-brand-600">curl</span> -L -X POST '{functionsEndpoint}
/hello-world' -H 'Authorization: Bearer [YOUR ANON KEY]' s
/hello-world' -H 'Authorization: Bearer [YOUR ANON KEY]'
{anonKey?.type === 'publishable' ? " -H 'apikey: [YOUR ANON KEY]' " : ''}
{`--data '{"name":"Functions"}'`}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import CardButton from 'components/ui/CardButton'
import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper'
import type { IntegrationProjectConnection } from 'data/integrations/integrations.types'
import { ProjectIndexPageLink } from 'data/prefetchers/project.$ref'
import { OrgProject } from 'data/projects/projects-infinite-query'
import { OrgProject } from 'data/projects/org-projects-infinite-query'
import type { ResourceWarning } from 'data/usage/resource-warnings-query'
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import NoSearchResults from 'components/ui/NoSearchResults'
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { useOrgProjectsInfiniteQuery } from 'data/projects/projects-infinite-query'
import { useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query'
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import InlineSVG from 'react-inlinesvg'

import { ComputeBadgeWrapper } from 'components/ui/ComputeBadgeWrapper'
import type { IntegrationProjectConnection } from 'data/integrations/integrations.types'
import { OrgProject } from 'data/projects/projects-infinite-query'
import { OrgProject } from 'data/projects/org-projects-infinite-query'
import type { ResourceWarning } from 'data/usage/resource-warnings-query'
import { BASE_PATH } from 'lib/constants'
import { Organization } from 'types'
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/components/interfaces/HomePageActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Link from 'next/link'

import { useDebounce } from '@uidotdev/usehooks'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { useOrgProjectsInfiniteQuery } from 'data/projects/projects-infinite-query'
import { useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { PROJECT_STATUS } from 'lib/constants'
Expand Down
Loading
Loading