diff --git a/apps/studio/components/ui/Charts/ComposedChart.tsx b/apps/studio/components/ui/Charts/ComposedChart.tsx
index 78e1e45a09c7b..5f891992832c2 100644
--- a/apps/studio/components/ui/Charts/ComposedChart.tsx
+++ b/apps/studio/components/ui/Charts/ComposedChart.tsx
@@ -235,7 +235,10 @@ export default function ComposedChart({
})
: []
- const stackedAttributes = chartData.filter((att) => !att.name.includes('max'))
+ const stackedAttributes = chartData.filter((att) => {
+ const attribute = attributes.find((attr) => attr.attribute === att.name)
+ return !attribute?.isMaxValue
+ })
const isPercentage = format === '%'
const isRamChart =
!chartData?.some((att: any) => att.name.toLowerCase() === 'ram_usage') &&
diff --git a/apps/studio/data/reports/database-charts.ts b/apps/studio/data/reports/database-charts.ts
index d57f7a0c4ee45..1f38e714f72a7 100644
--- a/apps/studio/data/reports/database-charts.ts
+++ b/apps/studio/data/reports/database-charts.ts
@@ -34,7 +34,7 @@ export const getReportAttributes = (org: Organization, project: Project): Report
},
{
id: 'avg_cpu_usage',
- label: 'CPU usage',
+ label: 'Average CPU usage',
syncId: 'database-reports',
format: '%',
valuePrecision: 2,
@@ -56,6 +56,30 @@ export const getReportAttributes = (org: Organization, project: Project): Report
},
],
},
+ {
+ id: 'max_cpu_usage',
+ label: 'Max CPU usage',
+ syncId: 'database-reports',
+ format: '%',
+ valuePrecision: 2,
+ availableIn: ['free', 'pro'],
+ hide: false,
+ showTooltip: false,
+ showLegend: false,
+ showMaxValue: false,
+ showGrid: false,
+ hideChartType: false,
+ defaultChartStyle: 'bar',
+ attributes: [
+ {
+ attribute: 'max_cpu_usage',
+ provider: 'infra-monitoring',
+ label: 'Max CPU usage',
+ format: '%',
+ tooltip: 'Max CPU usage',
+ },
+ ],
+ },
{
id: 'disk-iops',
label: 'Disk Input/Output operations per second (IOPS)',
diff --git a/apps/studio/state/tabs.tsx b/apps/studio/state/tabs.tsx
index 59f9bdd8c44e3..2d6fda0103554 100644
--- a/apps/studio/state/tabs.tsx
+++ b/apps/studio/state/tabs.tsx
@@ -1,9 +1,8 @@
-import { useConstant } from 'common'
import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants'
-import { useSelectedProject } from 'hooks/misc/useSelectedProject'
+import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { partition } from 'lodash'
import { NextRouter } from 'next/router'
-import { createContext, PropsWithChildren, ReactNode, useContext, useEffect } from 'react'
+import { createContext, PropsWithChildren, ReactNode, useContext, useEffect, useState } from 'react'
import { proxy, subscribe, useSnapshot } from 'valtio'
export const editorEntityTypes = {
@@ -78,6 +77,7 @@ const DEFAULT_TABS_STATE = {
openTabs: [] as string[],
tabsMap: {} as { [key: string]: Tab },
previewTabId: undefined as string | undefined,
+ recentItems: [],
}
const TABS_STORAGE_KEY = 'supabase_studio_tabs'
const getTabsStorageKey = (ref: string) => `${TABS_STORAGE_KEY}_${ref}`
@@ -399,8 +399,14 @@ export type TabsState = ReturnType
export const TabsStateContext = createContext(createTabsState(''))
export const TabsStateContextProvider = ({ children }: PropsWithChildren) => {
- const project = useSelectedProject()
- const state = useConstant(() => createTabsState(project?.ref ?? ''))
+ const { data: project } = useSelectedProjectQuery()
+ const [state, setState] = useState(createTabsState(project?.ref ?? ''))
+
+ useEffect(() => {
+ if (typeof window !== 'undefined' && !!project?.ref) {
+ setState(createTabsState(project?.ref ?? ''))
+ }
+ }, [project?.ref])
useEffect(() => {
if (typeof window !== 'undefined' && project?.ref) {
@@ -422,8 +428,7 @@ export const TabsStateContextProvider = ({ children }: PropsWithChildren) => {
)
})
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
+ }, [project?.ref, state])
return {children}
}
diff --git a/apps/ui-library/public/r/social-auth-tanstack.json b/apps/ui-library/public/r/social-auth-tanstack.json
index 5304e77f91ae1..ccc42a24aae97 100644
--- a/apps/ui-library/public/r/social-auth-tanstack.json
+++ b/apps/ui-library/public/r/social-auth-tanstack.json
@@ -43,7 +43,7 @@
},
{
"path": "registry/default/blocks/social-auth-tanstack/routes/auth/oauth.ts",
- "content": "import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'\nimport { type EmailOtpType } from '@supabase/supabase-js'\nimport { createFileRoute, redirect } from '@tanstack/react-router'\nimport { createServerFn } from '@tanstack/react-start'\nimport { getWebRequest } from '@tanstack/react-start/server'\n\nconst confirmFn = createServerFn({ method: 'GET' })\n .validator((searchParams: unknown) => {\n if (\n searchParams &&\n typeof searchParams === 'object' &&\n 'token_hash' in searchParams &&\n 'type' in searchParams &&\n 'next' in searchParams\n ) {\n return searchParams\n }\n throw new Error('Invalid search params')\n })\n .handler(async (ctx) => {\n const request = getWebRequest()\n\n if (!request) {\n throw redirect({ to: `/auth/error`, search: { error: 'No request' } })\n }\n\n const searchParams = ctx.data\n const token_hash = searchParams['token_hash'] as string\n const type = searchParams['type'] as EmailOtpType | null\n const _next = (searchParams['next'] ?? '/') as string\n const next = _next?.startsWith('/') ? _next : '/'\n\n if (token_hash && type) {\n const supabase = createClient()\n\n const { error } = await supabase.auth.verifyOtp({\n type,\n token_hash,\n })\n console.log(error?.message)\n if (!error) {\n // redirect user to specified redirect URL or root of app\n throw redirect({ href: next })\n } else {\n // redirect the user to an error page with some instructions\n throw redirect({\n to: `/auth/error`,\n search: { error: error?.message },\n })\n }\n }\n\n // redirect the user to an error page with some instructions\n throw redirect({\n to: `/auth/error`,\n search: { error: 'No token hash or type' },\n })\n })\n\nexport const Route = createFileRoute('/auth/confirm')({\n preload: false,\n loader: (opts) => confirmFn({ data: opts.location.search }),\n})\n",
+ "content": "import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'\nimport { createFileRoute, redirect } from '@tanstack/react-router'\nimport { createServerFn } from '@tanstack/react-start'\nimport { getWebRequest } from '@tanstack/react-start/server'\n\nconst confirmFn = createServerFn({ method: 'GET' })\n .validator((searchParams: unknown) => {\n if (\n searchParams &&\n typeof searchParams === 'object' &&\n 'code' in searchParams &&\n 'next' in searchParams\n ) {\n return searchParams\n }\n throw new Error('Invalid search params')\n })\n .handler(async (ctx) => {\n const request = getWebRequest()\n\n if (!request) {\n throw redirect({ to: `/auth/error`, search: { error: 'No request' } })\n }\n\n const searchParams = ctx.data\n const code = searchParams['code'] as string\n const _next = (searchParams['next'] ?? '/') as string\n const next = _next?.startsWith('/') ? _next : '/'\n\n if (code) {\n const supabase = createClient()\n\n const { error } = await supabase.auth.exchangeCodeForSession(code)\n if (!error) {\n // redirect user to specified redirect URL or root of app\n throw redirect({ href: next })\n } else {\n // redirect the user to an error page with some instructions\n throw redirect({\n to: `/auth/error`,\n search: { error: error?.message },\n })\n }\n }\n\n // redirect the user to an error page with some instructions\n throw redirect({\n to: `/auth/error`,\n search: { error: 'No code found' },\n })\n })\n\nexport const Route = createFileRoute('/auth/confirm')({\n preload: false,\n loader: (opts) => confirmFn({ data: opts.location.search }),\n})\n",
"type": "registry:file",
"target": "routes/auth/oauth.ts"
},
diff --git a/apps/ui-library/registry/default/blocks/social-auth-tanstack/routes/auth/oauth.ts b/apps/ui-library/registry/default/blocks/social-auth-tanstack/routes/auth/oauth.ts
index 2996a594a653b..529a6e539ab40 100644
--- a/apps/ui-library/registry/default/blocks/social-auth-tanstack/routes/auth/oauth.ts
+++ b/apps/ui-library/registry/default/blocks/social-auth-tanstack/routes/auth/oauth.ts
@@ -1,5 +1,4 @@
import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'
-import { type EmailOtpType } from '@supabase/supabase-js'
import { createFileRoute, redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { getWebRequest } from '@tanstack/react-start/server'
@@ -9,8 +8,7 @@ const confirmFn = createServerFn({ method: 'GET' })
if (
searchParams &&
typeof searchParams === 'object' &&
- 'token_hash' in searchParams &&
- 'type' in searchParams &&
+ 'code' in searchParams &&
'next' in searchParams
) {
return searchParams
@@ -25,19 +23,14 @@ const confirmFn = createServerFn({ method: 'GET' })
}
const searchParams = ctx.data
- const token_hash = searchParams['token_hash'] as string
- const type = searchParams['type'] as EmailOtpType | null
+ const code = searchParams['code'] as string
const _next = (searchParams['next'] ?? '/') as string
const next = _next?.startsWith('/') ? _next : '/'
- if (token_hash && type) {
+ if (code) {
const supabase = createClient()
- const { error } = await supabase.auth.verifyOtp({
- type,
- token_hash,
- })
- console.log(error?.message)
+ const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
// redirect user to specified redirect URL or root of app
throw redirect({ href: next })
@@ -53,7 +46,7 @@ const confirmFn = createServerFn({ method: 'GET' })
// redirect the user to an error page with some instructions
throw redirect({
to: `/auth/error`,
- search: { error: 'No token hash or type' },
+ search: { error: 'No code found' },
})
})