{name}
- {desc} + {desc}{value || 'n/a'}
-{value ? `${value.toFixed(1)}%` : 'n/a'}
+ {value ? ( ++ {value.toFixed(1)}% +
+ ) : ( +–
+ )}{(value / 1000).toFixed(2) + 's' || 'n/a'}
+ {isTime && typeof value === 'number' && !isNaN(value) && isFinite(value) ? ( ++ {(value / 1000).toFixed(2) + 's'} +
+ ) : ( +–
+ )} ++ {value.toLocaleString()} +
+ ) : ( +–
+ )} ++ {value.toFixed(0)}ms +
+ ) : ( +–
+ )} ++ {value.toLocaleString()} +
+ ) : ( +–
+ )} ++ {cacheHitRateToNumber(value).toFixed(2)}% +
+ ) : ( +–
+ )} +{value}
+–
)}
- {provider}
+ {providerLabel}
{projectHomepageShowInstanceSize && (
<>
•
@@ -231,6 +234,9 @@ export const ReplicaNode = ({ data }: NodeProps {region.name}
- {provider}
+ {providerLabel}
{projectHomepageShowInstanceSize && !!computeSize && (
<>
•
diff --git a/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx b/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx
index e1bc9f7b15217..da329451b081c 100644
--- a/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx
+++ b/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx
@@ -41,7 +41,7 @@ export function useReportsGotoCommands(options?: CommandOptions) {
{
id: 'nav-reports-query-performance',
name: 'Query Performance Reports',
- route: `/project/${ref}/reports/query-performance`,
+ route: `/project/${ref}/advisors/query-performance`,
defaultHidden: true,
},
]
diff --git a/apps/studio/data/auth/users-infinite-query.ts b/apps/studio/data/auth/users-infinite-query.ts
index f64d68f7711a4..8fbf6d50ba079 100644
--- a/apps/studio/data/auth/users-infinite-query.ts
+++ b/apps/studio/data/auth/users-infinite-query.ts
@@ -43,9 +43,6 @@ export const getUsersSQL = ({
const hasValidKeywords = keywords && keywords !== ''
const conditions: string[] = []
- const baseQueryUsers = `
- select *, coalesce((select array_agg(distinct i.provider) from auth.identities i where i.user_id = auth.users.id), '{}'::text[]) as providers from auth.users
- `.trim()
if (hasValidKeywords) {
// [Joshen] Escape single quotes properly
@@ -81,7 +78,52 @@ export const getUsersSQL = ({
const sortOn = sort ?? 'created_at'
const sortOrder = order ?? 'desc'
- return `${baseQueryUsers}${conditions.length > 0 ? ` where ${combinedConditions}` : ''} order by "${sortOn}" ${sortOrder} nulls last limit ${USERS_PAGE_LIMIT} offset ${offset};`
+ const usersQuery = `
+with
+ users_data as (
+ select
+ id,
+ email,
+ banned_until,
+ created_at,
+ confirmed_at,
+ confirmation_sent_at,
+ is_anonymous,
+ is_sso_user,
+ invited_at,
+ last_sign_in_at,
+ phone,
+ raw_app_meta_data,
+ raw_user_meta_data,
+ updated_at
+ from
+ auth.users
+ ${conditions.length > 0 ? ` where ${combinedConditions}` : ''}
+ order by
+ "${sortOn}" ${sortOrder} nulls last
+ limit
+ ${USERS_PAGE_LIMIT}
+ offset
+ ${offset}
+ )
+select
+ *,
+ coalesce(
+ (
+ select
+ array_agg(distinct i.provider)
+ from
+ auth.identities i
+ where
+ i.user_id = users_data.id
+ ),
+ '{}'::text[]
+ ) as providers
+from
+ users_data;
+ `.trim()
+
+ return usersQuery
}
export type UsersData = { result: User[] }
diff --git a/apps/studio/hooks/custom-content/CustomContent.types.ts b/apps/studio/hooks/custom-content/CustomContent.types.ts
index 8d79b162c0387..7fbe87e074151 100644
--- a/apps/studio/hooks/custom-content/CustomContent.types.ts
+++ b/apps/studio/hooks/custom-content/CustomContent.types.ts
@@ -28,6 +28,7 @@ export type CustomContentTypes = {
logsDefaultQuery: string
infraCloudProviders: CloudProvider[]
+ infraAwsNimbusLabel: string
sslCertificateUrl: string
}
diff --git a/apps/studio/hooks/custom-content/custom-content.json b/apps/studio/hooks/custom-content/custom-content.json
index 65686e6f1294d..2702c8c2728c8 100644
--- a/apps/studio/hooks/custom-content/custom-content.json
+++ b/apps/studio/hooks/custom-content/custom-content.json
@@ -50,6 +50,7 @@
"logs:default_query": null,
"infra:cloud_providers": ["AWS", "AWS_K8S", "FLY"],
+ "infra:aws_nimbus_label": "AWS Nimbus",
"ssl:certificate_url": "https://supabase-downloads.s3-ap-southeast-1.amazonaws.com/${env}/ssl/${env}-ca-2021.crt"
}
diff --git a/apps/studio/hooks/custom-content/custom-content.sample.json b/apps/studio/hooks/custom-content/custom-content.sample.json
index 3573fc9471aa7..b7adc83af68c2 100644
--- a/apps/studio/hooks/custom-content/custom-content.sample.json
+++ b/apps/studio/hooks/custom-content/custom-content.sample.json
@@ -49,42 +49,8 @@
"logs:default_query": "This is a sample query",
- "connect:frameworks": {
- "key": "frameworks",
- "label": "Frameworks",
- "obj": [
- {
- "key": "framework-1",
- "label": "Framework 1",
- "icon": "https://supabase.com/dashboard/img/supabase-logo.svg",
- "guideLink": "https://supabase.com/docs",
- "children": [],
- "files": [
- {
- "name": "sample_env",
- "content": "NEXT_PUBLIC_SUPABASE_URL={{apiUrl}}\nNEXT_PUBLIC_SUPABASE_ANON_KEY={{anonKey}}"
- },
- {
- "name": "sample.ts",
- "content": "import { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\nconst supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\nexport const supabase = createClient(supabaseUrl, supabaseKey);"
- }
- ]
- },
- {
- "key": "framework-2",
- "label": "Framework 2",
- "icon": "https://supabase.com/dashboard/img/supabase-logo.svg",
- "guideLink": "https://supabase.com/docs",
- "children": [],
- "files": [
- { "name": "file3.tsx", "content": "Content of File 3" },
- { "name": "file4.tsx", "content": "Content of File 4" }
- ]
- }
- ]
- },
-
"infra:cloud_providers": ["AWS_NIMBUS"],
+ "infra:aws_nimbus_label": "AWS Nimbus",
"ssl:certificate_url": "https://supabase-downloads.s3-ap-southeast-1.amazonaws.com/${env}/ssl/${env}-ca-2021.crt"
}
diff --git a/apps/studio/hooks/custom-content/custom-content.schema.json b/apps/studio/hooks/custom-content/custom-content.schema.json
index bb7f5f8a8a0a0..5569c4d71fa51 100644
--- a/apps/studio/hooks/custom-content/custom-content.schema.json
+++ b/apps/studio/hooks/custom-content/custom-content.schema.json
@@ -74,6 +74,10 @@
"enum": ["AWS", "AWS_K8S", "AWS_NIMBUS", "FLY"]
}
},
+ "infra:aws_nimbus_label": {
+ "type": "string",
+ "description": "The label of the AWS Nimbus provider"
+ },
"ssl:certificate_url": {
"type": "string",
@@ -86,6 +90,7 @@
"project_homepage:example_projects",
"logs:default_query",
"infra:cloud_providers",
+ "infra:aws_nimbus_label",
"ssl:certificate_url"
],
"additionalProperties": false
diff --git a/apps/studio/next.config.js b/apps/studio/next.config.js
index 4645351dbf470..c3846e9c7ca08 100644
--- a/apps/studio/next.config.js
+++ b/apps/studio/next.config.js
@@ -304,7 +304,7 @@ const nextConfig = {
},
{
permanent: true,
- source: '/project/:ref/reports/query-performance',
+ source: '/project/:ref/query-performance',
destination: '/project/:ref/advisors/query-performance',
},
{
diff --git a/apps/www/app/api-v2/cms-posts/route.ts b/apps/www/app/api-v2/cms-posts/route.ts
index 2243cab04db47..04f64cf768073 100644
--- a/apps/www/app/api-v2/cms-posts/route.ts
+++ b/apps/www/app/api-v2/cms-posts/route.ts
@@ -6,6 +6,12 @@ import { generateReadingTime } from '~/lib/helpers'
// Lightweight runtime for better performance
export const runtime = 'edge'
+// CORS headers for cross-origin requests
+const corsHeaders = {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
+}
+
// Lightweight TOC generation for edge runtime
type TocItem = { content: string; slug: string; lvl: number }
@@ -115,6 +121,14 @@ function convertRichTextToMarkdown(content: any): string {
.join('\n\n')
}
+// Handle preflight requests
+export async function OPTIONS() {
+ return new Response(null, {
+ status: 200,
+ headers: corsHeaders,
+ })
+}
+
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url)
@@ -212,12 +226,15 @@ export async function GET(request: NextRequest) {
_status: latestVersion._status,
}
- return NextResponse.json({
- success: true,
- post: processedPost,
- mode,
- isDraft: shouldFetchDraft,
- })
+ return NextResponse.json(
+ {
+ success: true,
+ post: processedPost,
+ mode,
+ isDraft: shouldFetchDraft,
+ },
+ { headers: corsHeaders }
+ )
}
}
}
@@ -289,12 +306,15 @@ export async function GET(request: NextRequest) {
_status: post._status,
}
- return NextResponse.json({
- success: true,
- post: processedPost,
- mode,
- isDraft: shouldFetchDraft,
- })
+ return NextResponse.json(
+ {
+ success: true,
+ post: processedPost,
+ mode,
+ isDraft: shouldFetchDraft,
+ },
+ { headers: corsHeaders }
+ )
}
}
@@ -364,12 +384,15 @@ export async function GET(request: NextRequest) {
_status: post._status,
}
- return NextResponse.json({
- success: true,
- post: processedPost,
- mode,
- isDraft: shouldFetchDraft,
- })
+ return NextResponse.json(
+ {
+ success: true,
+ post: processedPost,
+ mode,
+ isDraft: shouldFetchDraft,
+ },
+ { headers: corsHeaders }
+ )
}
}
}
@@ -452,16 +475,21 @@ export async function GET(request: NextRequest) {
toc_depth: post.toc_depth || 3,
}
- return NextResponse.json({
- success: true,
- post: processedPost,
- mode,
- source: 'versions-api',
- })
+ return NextResponse.json(
+ {
+ success: true,
+ post: processedPost,
+ mode,
+ source: 'versions-api',
+ },
+ { headers: corsHeaders }
+ )
}
}
} else {
- console.log('[cms-posts] Versions API failed, response:', await versionsResponse.text())
+ if (process.env.NODE_ENV !== 'production') {
+ console.log('[cms-posts] Versions API failed, response:', await versionsResponse.text())
+ }
}
// Strategy 2: If versions API didn't work, try finding the parent post first, then get its latest published version
@@ -556,12 +584,15 @@ export async function GET(request: NextRequest) {
richContent: mode === 'full' ? post.content : undefined,
}
- return NextResponse.json({
- success: true,
- post: processedPost,
- mode,
- source: 'versions-by-parent-api',
- })
+ return NextResponse.json(
+ {
+ success: true,
+ post: processedPost,
+ mode,
+ source: 'versions-by-parent-api',
+ },
+ { headers: corsHeaders }
+ )
}
}
}
@@ -590,6 +621,12 @@ export async function GET(request: NextRequest) {
// For individual post requests, don't cache to ensure fresh data
cache: slug ? 'no-store' : 'default',
next: slug ? undefined : { revalidate: 300 },
+ // Add SSL configuration for production
+ ...(process.env.NODE_ENV === 'production' && {
+ // Allow self-signed certificates in development, but use proper SSL in production
+ // This helps with Vercel's internal networking
+ agent: false,
+ }),
})
if (!response.ok) {
@@ -600,7 +637,7 @@ export async function GET(request: NextRequest) {
error: 'Failed to fetch posts from CMS',
status: response.status,
},
- { status: response.status }
+ { status: response.status, headers: corsHeaders }
)
}
@@ -613,7 +650,7 @@ export async function GET(request: NextRequest) {
error: 'CMS returned non-JSON response',
contentType,
},
- { status: 502 }
+ { status: 502, headers: corsHeaders }
)
}
@@ -695,20 +732,26 @@ export async function GET(request: NextRequest) {
// For single post requests, return the post directly
if (slug && posts.length > 0) {
- return NextResponse.json({
- success: true,
- post: posts[0],
- mode,
- })
+ return NextResponse.json(
+ {
+ success: true,
+ post: posts[0],
+ mode,
+ },
+ { headers: corsHeaders }
+ )
}
- return NextResponse.json({
- success: true,
- posts,
- total: posts.length,
- mode,
- cached: true,
- })
+ return NextResponse.json(
+ {
+ success: true,
+ posts,
+ total: posts.length,
+ mode,
+ cached: true,
+ },
+ { headers: corsHeaders }
+ )
} catch (error) {
console.error('[cms-posts] Error:', error)
return NextResponse.json(
@@ -717,7 +760,7 @@ export async function GET(request: NextRequest) {
error: 'Internal server error',
message: error instanceof Error ? error.message : 'Unknown error',
},
- { status: 500 }
+ { status: 500, headers: corsHeaders }
)
}
}
diff --git a/apps/www/app/api-v2/cms/revalidate/route.ts b/apps/www/app/api-v2/cms/revalidate/route.ts
index b743c4870dde7..3ff73f9e8d8af 100644
--- a/apps/www/app/api-v2/cms/revalidate/route.ts
+++ b/apps/www/app/api-v2/cms/revalidate/route.ts
@@ -1,27 +1,32 @@
-import type { NextApiRequest, NextApiResponse } from 'next'
+import { revalidatePath } from 'next/cache'
+import { NextRequest, NextResponse } from 'next/server'
-export default async function handler(req: NextApiRequest, res: NextApiResponse) {
- // Check for secret to confirm this is a valid request
- if (req.query.secret !== process.env.CMS_PREVIEW_SECRET) {
- return res.status(401).json({ message: 'Invalid token' })
- }
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json()
+ const { secret, path } = body
- const { path } = req.query
+ // Check for secret to confirm this is a valid request
+ if (secret !== process.env.CMS_PREVIEW_SECRET) {
+ return NextResponse.json({ message: 'Invalid token' }, { status: 401 })
+ }
- if (!path) {
- return res.status(400).json({ message: 'Missing path parameter' })
- }
+ if (!path) {
+ return NextResponse.json({ message: 'Missing path parameter' }, { status: 400 })
+ }
- try {
// This will revalidate the specific page
- await res.revalidate(String(path))
+ revalidatePath(path)
- return res.json({ revalidated: true, path })
+ return NextResponse.json({ revalidated: true, path })
} catch (error) {
console.error('[Revalidate API] Error during revalidation:', error)
- return res.status(500).json({
- message: 'Error revalidating',
- error: error instanceof Error ? error.message : 'Unknown error',
- })
+ return NextResponse.json(
+ {
+ message: 'Error revalidating',
+ error: error instanceof Error ? error.message : 'Unknown error',
+ },
+ { status: 500 }
+ )
}
}
diff --git a/apps/www/app/api-v2/ticket-og/route.tsx b/apps/www/app/api-v2/ticket-og/route.tsx
index 6c9ba7807a3db..46b216f66f448 100644
--- a/apps/www/app/api-v2/ticket-og/route.tsx
+++ b/apps/www/app/api-v2/ticket-og/route.tsx
@@ -24,7 +24,7 @@ const FONT_URLS = {
const LW_TABLE = 'tickets'
const LW_MATERIALIZED_VIEW = 'tickets_view'
-export async function GET(req: Request, res: Response) {
+export async function GET(req: Request) {
const url = new URL(req.url)
// Just here to silence snyk false positives
diff --git a/apps/www/app/blog/[slug]/page.tsx b/apps/www/app/blog/[slug]/page.tsx
index 7f142ae402609..dd3ce29f66fab 100644
--- a/apps/www/app/blog/[slug]/page.tsx
+++ b/apps/www/app/blog/[slug]/page.tsx
@@ -28,7 +28,7 @@ async function getCMSPostFromAPI(
const fetchOptions = isDraft
? {
// For draft mode: always fresh data, no caching
- cache: 'no-store' as const,
+ // cache: 'no-store' as const,
next: { revalidate: 0 },
}
: {
diff --git a/apps/www/app/blog/categories/[category]/page.tsx b/apps/www/app/blog/categories/[category]/page.tsx
index edafaa817a448..b0c8f9ab3be2f 100644
--- a/apps/www/app/blog/categories/[category]/page.tsx
+++ b/apps/www/app/blog/categories/[category]/page.tsx
@@ -15,6 +15,7 @@ export async function generateStaticParams() {
return categories.map((category: string) => ({ category }))
}
+export const revalidate = 30
export const dynamic = 'force-static'
export async function generateMetadata({ params }: { params: Params }): Promise