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
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,7 @@ export const auth: NavMenuConstant = {
name: 'Server-Side Rendering',
url: '/guides/auth/server-side',
items: [
{ name: 'Overview', url: '/guides/auth/server-side' },
{ name: 'Creating a client', url: '/guides/auth/server-side/creating-a-client' },
{
name: 'Migrating from Auth Helpers',
Expand Down
29 changes: 29 additions & 0 deletions apps/docs/spec/cli_v1_commands.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,35 @@ commands:
- id: supabase-storage-cp
title: supabase storage cp
summary: Copy objects from src to dst path
description: |
Relies on standard uploads to move files between local and remote storage. [Not suitable for moving files over 6MB in size](https://supabase.com/docs/guides/storage/uploads/standard-uploads?queryGroups=language&language=js#uploading).
examples:
- id: upload-file-to-bucket
name: Upload file to bucket
code: supabase storage cp -r path/to/file ss:///bucket_name --experimental
response: |
Uploading: /path/to/file/example.txt => /bucket_name/example.txt
- id: Upload-file-to-folder
name: Upload file to folder
code: supabase storage cp -r path/to/local/file ss:///bucket_name/path/to/folder --experimental
response: |
Uploading: /path/to/file/example.txt => /bucket_name/path/to/folder/example.txt
- id: upload-entire-folder-to-bucket
name: Upload folder to bucket
code: supabase storage cp -r path/to/local/folder ss:///bucket_name --experimental
response: |
Uploading: /path/to/folder/example1.txt => /bucket_name/folder/example1.txt
Uploading: /path/to/folder/example2.txt => /bucket_name/folder/example2.txt
Uploading: /path/to/folder/example3.txt => /bucket_name/folder/example3.txt
...
- id: download-entire-bucket
name: Download bucket
code: supabase storage cp -r ss://bucket_name path/to/local/folder --experimental
response: |
Downloading: bucket_name/example1.txt => path/to/local/folder/bucket_name/example1.txt
Downloading: bucket_name/example2.txt => path/to/local/folder/bucket_name/example2.txt
Downloading: bucket_name/example3.txt => path/to/local/folder/bucket_name/example3.txt
...
tags: []
links: []
usage: supabase storage cp <src> <dst> [flags]
Expand Down
10 changes: 5 additions & 5 deletions apps/studio/components/interfaces/Linter/LintDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Link from 'next/link'
import ReactMarkdown from 'react-markdown'

import { createLintSummaryPrompt, lintInfoMap } from 'components/interfaces/Linter/Linter.utils'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
Expand All @@ -10,6 +9,7 @@ import { ExternalLink } from 'lucide-react'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import { AiIconAnimation, Button } from 'ui'
import { Markdown } from '../Markdown'
import { EntityTypeIcon, LintCTA, LintEntity } from './Linter.utils'

interface LintDetailProps {
Expand Down Expand Up @@ -48,13 +48,13 @@ const LintDetail = ({ lint, projectRef, onAskAssistant }: LintDetailProps) => {
</div>

<h3 className="text-sm mb-2">Issue</h3>
<ReactMarkdown className="leading-6 text-sm text-foreground-light mb-6">
<Markdown className="leading-6 text-sm text-foreground-light mb-6">
{lint.detail.replace(/\\`/g, '`')}
</ReactMarkdown>
</Markdown>
<h3 className="text-sm mb-2">Description</h3>
<ReactMarkdown className="text-sm text-foreground-light mb-6">
<Markdown className="text-sm text-foreground-light mb-6">
{lint.description.replace(/\\`/g, '`')}
</ReactMarkdown>
</Markdown>

<h3 className="text-sm mb-2">Resolve</h3>
<div className="flex items-center gap-2">
Expand Down
20 changes: 20 additions & 0 deletions apps/studio/components/interfaces/Linter/Linter.utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,26 @@ export const lintInfoMap: LintInfo[] = [
docsLink: `${DOCS_URL}/guides/platform/upgrading`,
category: 'security',
},
{
name: 'sensitive_columns_exposed',
title: 'Sensitive Columns Exposed',
icon: <Eye className="text-foreground-muted" size={15} strokeWidth={1.5} />,
link: ({ projectRef, metadata }) =>
`/project/${projectRef}/editor?schema=${metadata?.schema}&table=${metadata?.name}`,
linkText: 'View table',
docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0023_sensitive_columns_exposed`,
category: 'security',
},
{
name: 'rls_policy_always_true',
title: 'RLS Policy Always True',
icon: <Table2 className="text-foreground-muted" size={15} strokeWidth={1} />,
link: ({ projectRef, metadata }) =>
`/project/${projectRef}/auth/policies?schema=${metadata?.schema}&search=${metadata?.name}`,
linkText: 'View policies',
docsLink: `${DOCS_URL}/guides/database/database-linter?lint=0024_permissive_rls_policy`,
category: 'security',
},
]

export const LintCTA = ({
Expand Down
32 changes: 7 additions & 25 deletions apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import dayjs from 'dayjs'
import { BookOpen, Check, ChevronDown, Copy, ExternalLink, X } from 'lucide-react'
import Link from 'next/link'
import { ReactNode, useState } from 'react'
import { ReactNode, useEffect, useState } from 'react'

import { IS_PLATFORM } from 'common'
import Table from 'components/to-be-cleaned/Table'
Expand Down Expand Up @@ -32,8 +31,7 @@ import { LogsWarning, LogTemplate } from './Logs.types'

export interface LogsQueryPanelProps {
templates?: LogTemplate[]
defaultFrom: string
defaultTo: string
value: DatePickerValue
warnings: LogsWarning[]
onSelectTemplate: (template: LogTemplate) => void
onSelectSource: (source: string) => void
Expand All @@ -51,8 +49,7 @@ function DropdownMenuItemContent({ name, desc }: { name: ReactNode; desc?: strin

const LogsQueryPanel = ({
templates = [],
defaultFrom,
defaultTo,
value,
warnings,
onSelectTemplate,
onSelectSource,
Expand All @@ -77,26 +74,11 @@ const LogsQueryPanel = ({
})
.map(([, value]) => value)

function getDefaultDatePickerValue() {
if (defaultFrom && defaultTo) {
return {
to: defaultTo,
from: defaultFrom,
text: `${dayjs(defaultFrom).format('DD MMM, HH:mm')} - ${dayjs(defaultTo).format('DD MMM, HH:mm')}`,
isHelper: false,
}
}
return {
to: EXPLORER_DATEPICKER_HELPERS[0].calcTo(),
from: EXPLORER_DATEPICKER_HELPERS[0].calcFrom(),
text: EXPLORER_DATEPICKER_HELPERS[0].text,
isHelper: true,
}
}
const [selectedDatePickerValue, setSelectedDatePickerValue] = useState<DatePickerValue>(value)

const [selectedDatePickerValue, setSelectedDatePickerValue] = useState<DatePickerValue>(
getDefaultDatePickerValue()
)
useEffect(() => {
setSelectedDatePickerValue(value)
}, [value.from, value.to, value.text, value.isHelper])

return (
<div className="border-b bg-surface-100">
Expand Down
55 changes: 55 additions & 0 deletions apps/studio/components/interfaces/Settings/Logs/logsDateRange.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import dayjs from 'dayjs'

import { EXPLORER_DATEPICKER_HELPERS, getDefaultHelper } from './Logs.constants'
import type { DatePickerValue } from './Logs.DatePickers'
import type { DatetimeHelper } from './Logs.types'

export interface ResolvedLogDateRange {
from: string
to: string
}

export interface ResolvedLogParams extends ResolvedLogDateRange {
sql: string
}

const findHelper = (value: DatePickerValue, helpers: DatetimeHelper[]) => {
if (!value.text) return undefined
return helpers.find((helper) => helper.text === value.text)
}

const ensureEnd = (candidate: string | undefined, now: dayjs.Dayjs) => {
if (candidate && candidate.length > 0) return candidate
return now.toISOString()
}

export const resolveLogDateRange = (
value: DatePickerValue,
helpers: DatetimeHelper[] = EXPLORER_DATEPICKER_HELPERS
): ResolvedLogDateRange => {
const now = dayjs()
if (value.isHelper) {
const matchedHelper = findHelper(value, helpers) ?? getDefaultHelper(helpers)
const from = matchedHelper?.calcFrom() ?? value.from ?? ''
const to = ensureEnd(matchedHelper?.calcTo() ?? value.to, now)
return { from, to }
}

const defaultHelper = getDefaultHelper(helpers)
const from = value.from || defaultHelper.calcFrom()
const to = ensureEnd(value.to, now)
return { from, to }
}

export const buildLogQueryParams = (
value: DatePickerValue,
sql: string,
helpers: DatetimeHelper[] = EXPLORER_DATEPICKER_HELPERS
): ResolvedLogParams => {
const range = resolveLogDateRange(value, helpers)
return {
sql,
from: range.from,
to: range.to,
}
}
15 changes: 14 additions & 1 deletion apps/studio/components/ui/AlertError.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { SupportCategories } from '@supabase/shared-types/out/constants'
import { SupportLink } from 'components/interfaces/Support/SupportLink'
import { PropsWithChildren } from 'react'
import { PropsWithChildren, useEffect, useRef } from 'react'

import { Admonition } from 'ui-patterns/admonition'

import { Button } from 'ui'
import { useTrack } from 'lib/telemetry/track'

export interface AlertErrorProps {
projectRef?: string
Expand Down Expand Up @@ -52,10 +53,22 @@ export const AlertError = ({
children,
additionalActions,
}: PropsWithChildren<AlertErrorProps>) => {
const track = useTrack()
const hasTrackedRef = useRef(false)

const formattedErrorMessage = error?.message?.includes('503')
? '503 Service Temporarily Unavailable'
: error?.message

useEffect(() => {
if (!hasTrackedRef.current) {
hasTrackedRef.current = true
track('dashboard_error_created', {
source: 'admonition',
})
}
}, [track])

return (
<Admonition
type="warning"
Expand Down
7 changes: 6 additions & 1 deletion apps/studio/components/ui/CodeEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ const CodeEditor = ({
...actions,
}

const runQueryCallbackRef = useRef(runQuery.callback)
useEffect(() => {
runQueryCallbackRef.current = runQuery.callback
}, [runQuery.callback])

const showPlaceholderDefault = placeholder !== undefined && (value ?? '').trim().length === 0
const [showPlaceholder, setShowPlaceholder] = useState(showPlaceholderDefault)

Expand Down Expand Up @@ -134,7 +139,7 @@ const CodeEditor = ({
const selectedValue = (editorRef?.current as any)
.getModel()
.getValueInRange((editorRef?.current as any)?.getSelection())
runQuery.callback(selectedValue || (editorRef?.current as any)?.getValue())
runQueryCallbackRef.current(selectedValue || (editorRef?.current as any)?.getValue())
},
})
}
Expand Down
Loading
Loading