diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 414afee868ffa..1d8b37fb1e6c4 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -15,6 +15,5 @@
/apps/studio/csp.js @supabase/security
/apps/studio/components/interfaces/Billing/Payment @supabase/security
-/apps/studio/components/interfaces/Organization/BillingSettings/ @supabase/security
/apps/studio/components/interfaces/Organization/Documents/ @supabase/security
/apps/studio/pages/new/index.tsx @supabase/security
diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
index 01c28b99b1922..fab190e7fc994 100644
--- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
+++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts
@@ -793,6 +793,7 @@ export const auth: NavMenuConstant = {
{ name: 'Password Security', url: '/guides/auth/password-security' },
{ name: 'Rate Limits', url: '/guides/auth/rate-limits' },
{ name: 'Bot Detection (CAPTCHA)', url: '/guides/auth/auth-captcha' },
+ { name: 'Audit Logs', url: '/guides/auth/audit-logs' },
{
name: 'JSON Web Tokens (JWT)',
url: '/guides/auth/jwts',
diff --git a/apps/docs/content/guides/auth/audit-logs.mdx b/apps/docs/content/guides/auth/audit-logs.mdx
new file mode 100644
index 0000000000000..69456d59614c7
--- /dev/null
+++ b/apps/docs/content/guides/auth/audit-logs.mdx
@@ -0,0 +1,90 @@
+---
+id: 'auth-audit-logs'
+title: 'Auth Audit Logs'
+description: 'Monitor and track authentication events with audit logging.'
+subtitle: 'Monitor and track authentication events with audit logging.'
+---
+
+Auth audit logs provide comprehensive tracking of authentication events in your Supabase project. Audit logs are automatically captured for all authentication events and help you monitor user authentication activities, detect suspicious behavior, and maintain compliance with security requirements.
+
+## What gets logged
+
+Supabase auth audit logs automatically capture all authentication events including:
+
+- User signups and logins
+- Password changes and resets
+- Email verification events
+- Token refresh and logout events
+
+## Storage options
+
+By default, audit logs are stored in two places:
+
+1. **Your project's Postgres database** - Stored in the `auth.audit_log_entries` table, searchable via SQL but uses database storage
+2. **External log storage** - Cost-efficient storage accessible through the dashboard
+
+You can disable Postgres storage to reduce database storage costs while keeping the external log storage.
+
+### Configuring audit log storage
+
+1. Navigate to your project dashboard
+2. Go to **Authentication**
+3. Find the **Audit Logs** under **Configuration** section
+4. Toggle on "Disable writing auth audit logs to project database" to disable database storage
+
+
+
+Disabling Postgres storage reduces your database storage costs. Audit logs will still be available through the dashboard.
+
+
+
+## Log format
+
+Audit logs contain detailed information about each authentication event:
+
+```json
+{
+ "timestamp": "2025-08-01T10:30:00Z",
+ "user_id": "uuid",
+ "action": "user_signedup",
+ "ip_address": "192.168.1.1",
+ "user_agent": "Mozilla/5.0...",
+ "metadata": {
+ "provider": "email"
+ }
+}
+```
+
+### Log actions reference
+
+| Action | Description |
+| ------------------------------- | --------------------------------------- |
+| `login` | User login attempt |
+| `logout` | User logout |
+| `invite_accepted` | Team invitation accepted |
+| `user_signedup` | New user registration |
+| `user_invited` | User invitation sent |
+| `user_deleted` | User account deleted |
+| `user_modified` | User profile updated |
+| `user_recovery_requested` | Password reset request |
+| `user_reauthenticate_requested` | User reauthentication required |
+| `user_confirmation_requested` | Email/phone confirmation requested |
+| `user_repeated_signup` | Duplicate signup attempt |
+| `user_updated_password` | Password change completed |
+| `token_revoked` | Refresh token revoked |
+| `token_refreshed` | Refresh token used to obtain new tokens |
+| `generate_recovery_codes` | MFA recovery codes generated |
+| `factor_in_progress` | MFA factor enrollment started |
+| `factor_unenrolled` | MFA factor removed |
+| `challenge_created` | MFA challenge initiated |
+| `verification_attempted` | MFA verification attempt |
+| `factor_deleted` | MFA factor deleted |
+| `recovery_codes_deleted` | MFA recovery codes deleted |
+| `factor_updated` | MFA factor settings updated |
+| `mfa_code_login` | Login with MFA code |
+| `identity_unlinked` | An identity unlinked from account |
+
+## Limitations
+
+- There may be a short delay before logs appear
+- Query capabilities are limited to the dashboard interface
diff --git a/apps/docs/content/guides/auth/jwts.mdx b/apps/docs/content/guides/auth/jwts.mdx
index bbdf43a77389b..9edce569f2862 100644
--- a/apps/docs/content/guides/auth/jwts.mdx
+++ b/apps/docs/content/guides/auth/jwts.mdx
@@ -179,7 +179,7 @@ Supabase Auth exposes a [JSON Web Key](https://datatracker.ietf.org/doc/html/rfc
GET https://project-id.supabase.co/auth/v1/.well-known/jwks.json
```
-Which responds with JWKS object containing one or more asymmetric [JWT signing keys](/docs/guides/auth/signing-keys) (only their public keys).
+Which responds with JWKS object containing one or more asymmetric [JWT signing keys](/docs/guides/auth/signing-keys) (only their public keys). Be aware that this endpoint does not return any keys if you are not using asymmetric JWT signing keys.
```json
{
diff --git a/apps/docs/content/guides/auth/users.mdx b/apps/docs/content/guides/auth/users.mdx
index 2b537566047a5..52306d68b728c 100644
--- a/apps/docs/content/guides/auth/users.mdx
+++ b/apps/docs/content/guides/auth/users.mdx
@@ -56,23 +56,23 @@ A user with an email or phone identity will be able to sign in with either a pas
The user object contains the following attributes:
-| Attributes | Type | Description |
-| ------------------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| id | `string` | The unique id of the identity of the user. |
-| aud | `string` | The audience claim. |
-| role | `string` | The role claim used by Postgres to perform Row Level Security (RLS) checks. |
-| email | `string` | The user's email address. |
-| email_confirmed_at | `string` | The timestamp that the user's email was confirmed. If null, it means that the user's email is not confirmed. |
-| phone | `string` | The user's phone number. |
-| phone_confirmed_at | `string` | The timestamp that the user's phone was confirmed. If null, it means that the user's phone is not confirmed. |
-| confirmed_at | `string` | The timestamp that either the user's email or phone was confirmed. If null, it means that the user does not have a confirmed email address and phone number. |
-| last_sign_in_at | `string` | The timestamp that the user last signed in. |
-| app_metadata | `object` | The `provider` attribute indicates the first provider that the user used to sign up with. The `providers` attribute indicates the list of providers that the user can use to login with. |
-| user_metadata | `object` | Defaults to the first provider's identity data but can contain additional custom user metadata if specified. Refer to [**User Identity**](/docs/guides/auth/auth-identity-linking#the-user-identity) for more information about the identity object. |
-| identities | `UserIdentity[]` | Contains an object array of identities linked to the user. |
-| created_at | `string` | The timestamp that the user was created. |
-| updated_at | `string` | The timestamp that the user was last updated. |
-| is_anonymous | `boolean` | Is true if the user is an anonymous user. |
+| Attributes | Type | Description |
+| ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| id | `string` | The unique id of the identity of the user. |
+| aud | `string` | The audience claim. |
+| role | `string` | The role claim used by Postgres to perform Row Level Security (RLS) checks. |
+| email | `string` | The user's email address. |
+| email_confirmed_at | `string` | The timestamp that the user's email was confirmed. If null, it means that the user's email is not confirmed. |
+| phone | `string` | The user's phone number. |
+| phone_confirmed_at | `string` | The timestamp that the user's phone was confirmed. If null, it means that the user's phone is not confirmed. |
+| confirmed_at | `string` | The timestamp that either the user's email or phone was confirmed. If null, it means that the user does not have a confirmed email address and phone number. |
+| last_sign_in_at | `string` | The timestamp that the user last signed in. |
+| app_metadata | `object` | The `provider` attribute indicates the first provider that the user used to sign up with. The `providers` attribute indicates the list of providers that the user can use to login with. |
+| user_metadata | `object` | Defaults to the first provider's identity data but can contain additional custom user metadata if specified. Refer to [**User Identity**](/docs/guides/auth/auth-identity-linking#the-user-identity) for more information about the identity object. Don't rely on the order of information in this field. Do not use it in security sensitive context (such as in RLS policies or authorization logic), as this value is editable by the user without any checks. |
+| identities | `UserIdentity[]` | Contains an object array of identities linked to the user. |
+| created_at | `string` | The timestamp that the user was created. |
+| updated_at | `string` | The timestamp that the user was last updated. |
+| is_anonymous | `boolean` | Is true if the user is an anonymous user. |
## Resources
diff --git a/apps/docs/spec/cli_v1_commands.yaml b/apps/docs/spec/cli_v1_commands.yaml
index 85d09e05af316..3951a586f39c6 100644
--- a/apps/docs/spec/cli_v1_commands.yaml
+++ b/apps/docs/spec/cli_v1_commands.yaml
@@ -1,7 +1,7 @@
clispec: '001'
info:
id: cli
- version: 2.39.2
+ version: 2.40.7
title: Supabase CLI
language: sh
source: https://github.com/supabase/cli
@@ -2253,7 +2253,6 @@ commands:
- local-dev
links: []
subcommands:
- - supabase-gen-keys
- supabase-gen-signing-key
- supabase-gen-types
flags: []
@@ -2293,13 +2292,16 @@ commands:
default_value: 'false'
- id: postgrest-v9-compat
name: --postgrest-v9-compat
- description: |
- Generate types compatible with PostgREST v9 and below. Only use together with --db-url.
+ description: Generate types compatible with PostgREST v9 and below.
default_value: 'false'
- id: project-id
name: --project-id
description: Generate types from a project ID.
default_value: ''
+ - id: query-timeout
+ name: --query-timeout
+ description: Maximum timeout allowed for the database query.
+ default_value: 15s
- id: schema
name: -s, --schema
description: Comma separated list of schema to include.
@@ -2344,27 +2346,6 @@ commands:
name: --append
description: Append new key to existing keys file instead of overwriting.
default_value: 'false'
- - id: supabase-gen-keys
- title: supabase gen keys
- summary: Generate keys for preview branch
- tags: []
- links: []
- usage: supabase gen keys [flags]
- subcommands: []
- flags:
- - id: override-name
- name: --override-name
- description: Override specific variable names.
- default_value: '[]'
- - id: project-ref
- name: --project-ref
- description: Project ref of the Supabase project.
- default_value: ''
- - id: experimental
- name: --experimental
- description: enable experimental features
- required: true
- default_value: 'false'
- id: supabase-functions
title: supabase functions
summary: Manage Supabase Edge functions
diff --git a/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx b/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx
new file mode 100644
index 0000000000000..eb9ac33b0963f
--- /dev/null
+++ b/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx
@@ -0,0 +1,190 @@
+import { yupResolver } from '@hookform/resolvers/yup'
+import { PermissionAction } from '@supabase/shared-types/out/constants'
+import { useEffect } from 'react'
+import { useForm } from 'react-hook-form'
+import { toast } from 'sonner'
+import { boolean, object } from 'yup'
+
+import { useParams } from 'common'
+import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold'
+import { InlineLink } from 'components/ui/InlineLink'
+import { NoPermission } from 'components/ui/NoPermission'
+import { useAuthConfigQuery } from 'data/auth/auth-config-query'
+import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
+import { useTablesQuery } from 'data/tables/tables-query'
+import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
+import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import {
+ AlertDescription_Shadcn_,
+ AlertTitle_Shadcn_,
+ Alert_Shadcn_,
+ Button,
+ Card,
+ CardContent,
+ CardFooter,
+ FormControl_Shadcn_,
+ FormField_Shadcn_,
+ Form_Shadcn_,
+ Switch,
+ WarningIcon,
+} from 'ui'
+import { Admonition } from 'ui-patterns'
+import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
+
+const schema = object({
+ AUDIT_LOG_DISABLE_POSTGRES: boolean().required(),
+})
+
+const AUDIT_LOG_ENTRIES_TABLE = 'audit_log_entries'
+
+export const AuditLogsForm = () => {
+ const { ref: projectRef } = useParams()
+ const { data: project } = useSelectedProjectQuery()
+ const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue')
+ const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
+
+ const { data: tables = [] } = useTablesQuery({
+ projectRef: project?.ref,
+ connectionString: project?.connectionString,
+ includeColumns: false,
+ schema: 'auth',
+ })
+ const auditLogTable = tables.find((x) => x.name === AUDIT_LOG_ENTRIES_TABLE)
+
+ const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef })
+
+ const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation({
+ onError: (error) => {
+ toast.error(`Failed to update audit logs: ${error?.message}`)
+ },
+ onSuccess: () => {
+ toast.success('Successfully updated audit logs settings')
+ },
+ })
+
+ const form = useForm({
+ resolver: yupResolver(schema),
+ defaultValues: { AUDIT_LOG_DISABLE_POSTGRES: false },
+ })
+ const { AUDIT_LOG_DISABLE_POSTGRES: formValueDisablePostgres } = form.watch()
+ const currentlyDisabled = authConfig?.AUDIT_LOG_DISABLE_POSTGRES ?? false
+ const isDisabling = !currentlyDisabled && formValueDisablePostgres
+
+ const onSubmitAuditLogs = (values: any) => {
+ if (!projectRef) return console.error('Project ref is required')
+ updateAuthConfig({ projectRef: projectRef, config: values })
+ }
+
+ useEffect(() => {
+ if (authConfig) {
+ form.reset({ AUDIT_LOG_DISABLE_POSTGRES: authConfig?.AUDIT_LOG_DISABLE_POSTGRES ?? false })
+ }
+ }, [authConfig])
+
+ if (isError) {
+ return (
+
+
+ Failed to retrieve auth configuration
+ {authConfigError.message}
+
+ )
+ }
+
+ if (!canReadConfig) {
+ return
+ }
+
+ return (
+
+
+ Settings
+
+
+
+
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx b/apps/studio/components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx
index 9e7a6dec940ef..55ac23228a4a6 100644
--- a/apps/studio/components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx
+++ b/apps/studio/components/interfaces/Billing/Payment/AddPaymentMethodForm.tsx
@@ -2,7 +2,7 @@ import { useQueryClient } from '@tanstack/react-query'
import {
NewPaymentMethodElement,
type PaymentMethodElementRef,
-} from 'components/interfaces/Organization/BillingSettings/PaymentMethods/NewPaymentMethodElement'
+} from 'components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement'
import { organizationKeys } from 'data/organizations/keys'
import { useOrganizationCustomerProfileQuery } from 'data/organizations/organization-customer-profile-query'
import { useOrganizationCustomerProfileUpdateMutation } from 'data/organizations/organization-customer-profile-update-mutation'
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/ChangePaymentMethodModal.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/ChangePaymentMethodModal.tsx
similarity index 100%
rename from apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/ChangePaymentMethodModal.tsx
rename to apps/studio/components/interfaces/Billing/Payment/PaymentMethods/ChangePaymentMethodModal.tsx
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/CreditCard.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CreditCard.tsx
similarity index 100%
rename from apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/CreditCard.tsx
rename to apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CreditCard.tsx
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/CurrentPaymentMethod.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CurrentPaymentMethod.tsx
similarity index 95%
rename from apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/CurrentPaymentMethod.tsx
rename to apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CurrentPaymentMethod.tsx
index 557f8ee2f3150..31480f91c49b8 100644
--- a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/CurrentPaymentMethod.tsx
+++ b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/CurrentPaymentMethod.tsx
@@ -1,12 +1,12 @@
+import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
import { CreditCardIcon } from 'lucide-react'
import Link from 'next/link'
-import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useOrganizationPaymentMethodsQuery } from 'data/organizations/organization-payment-methods-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
-import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
+import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { Button } from 'ui'
import CreditCard from './CreditCard'
@@ -31,7 +31,7 @@ const CurrentPaymentMethod = () => {
const defaultPaymentMethod = paymentMethods?.data.find((pm) => pm.is_default)
- const canReadPaymentMethods = useCheckPermissions(
+ const { can: canReadPaymentMethods } = useAsyncCheckProjectPermissions(
PermissionAction.BILLING_READ,
'stripe.payment_methods'
)
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/DeletePaymentMethodModal.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/DeletePaymentMethodModal.tsx
similarity index 100%
rename from apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/DeletePaymentMethodModal.tsx
rename to apps/studio/components/interfaces/Billing/Payment/PaymentMethods/DeletePaymentMethodModal.tsx
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/NewPaymentMethodElement.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx
similarity index 99%
rename from apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/NewPaymentMethodElement.tsx
rename to apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx
index 11a41994047cb..62a0fb75d629b 100644
--- a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/NewPaymentMethodElement.tsx
+++ b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement.tsx
@@ -31,7 +31,10 @@ import {
PopoverContent_Shadcn_ as PopoverContent,
PopoverTrigger_Shadcn_ as PopoverTrigger,
} from 'ui'
-import { TAX_IDS, type TaxId } from '../BillingCustomerData/TaxID.constants'
+import {
+ TAX_IDS,
+ type TaxId,
+} from '../../../Organization/BillingSettings/BillingCustomerData/TaxID.constants'
import { z } from 'zod'
import { useForm } from 'react-hook-form'
import { Form } from '@ui/components/shadcn/ui/form'
diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/PaymentMethods.tsx b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/PaymentMethods.tsx
similarity index 97%
rename from apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/PaymentMethods.tsx
rename to apps/studio/components/interfaces/Billing/Payment/PaymentMethods/PaymentMethods.tsx
index b850afc7d135a..95d7d1b1076f6 100644
--- a/apps/studio/components/interfaces/Organization/BillingSettings/PaymentMethods/PaymentMethods.tsx
+++ b/apps/studio/components/interfaces/Billing/Payment/PaymentMethods/PaymentMethods.tsx
@@ -19,17 +19,14 @@ import PartnerManagedResource from 'components/ui/PartnerManagedResource'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useOrganizationPaymentMethodsQuery } from 'data/organizations/organization-payment-methods-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
-import {
- useAsyncCheckProjectPermissions,
- useCheckPermissions,
-} from 'hooks/misc/useCheckPermissions'
+import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
+import { MANAGED_BY } from 'lib/constants/infrastructure'
import { getURL } from 'lib/helpers'
import { Alert, Button } from 'ui'
import ChangePaymentMethodModal from './ChangePaymentMethodModal'
import CreditCard from './CreditCard'
import DeletePaymentMethodModal from './DeletePaymentMethodModal'
-import { MANAGED_BY } from 'lib/constants/infrastructure'
const PaymentMethods = () => {
const { slug } = useParams()
@@ -47,9 +44,9 @@ const PaymentMethods = () => {
isSuccess,
} = useOrganizationPaymentMethodsQuery({ slug })
- const { isSuccess: isPermissionsLoaded, can: canReadPaymentMethods } =
+ const { can: canReadPaymentMethods, isSuccess: isPermissionsLoaded } =
useAsyncCheckProjectPermissions(PermissionAction.BILLING_READ, 'stripe.payment_methods')
- const canUpdatePaymentMethods = useCheckPermissions(
+ const { can: canUpdatePaymentMethods } = useAsyncCheckProjectPermissions(
PermissionAction.BILLING_WRITE,
'stripe.payment_methods'
)
diff --git a/apps/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx b/apps/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx
index 7340883f745d6..a53ac137264b8 100644
--- a/apps/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx
+++ b/apps/studio/components/interfaces/Database/Hooks/HooksList/HooksList.tsx
@@ -10,10 +10,7 @@ import { DocsButton } from 'components/ui/DocsButton'
import NoSearchResults from 'components/ui/NoSearchResults'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { useDatabaseHooksQuery } from 'data/database-triggers/database-triggers-query'
-import {
- useAsyncCheckProjectPermissions,
- usePermissionsLoaded,
-} from 'hooks/misc/useCheckPermissions'
+import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { noop } from 'lib/void'
import { Input } from 'ui'
@@ -49,11 +46,8 @@ export const HooksList = ({
)
const filteredHookSchemas = lodashMap(uniqBy(filteredHooks, 'schema'), 'schema')
- const { can: canCreateWebhooks } = useAsyncCheckProjectPermissions(
- PermissionAction.TENANT_SQL_ADMIN_WRITE,
- 'triggers'
- )
- const isPermissionsLoaded = usePermissionsLoaded()
+ const { can: canCreateWebhooks, isSuccess: isPermissionsLoaded } =
+ useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'triggers')
return (
- {showDisabledState ? 'Pipeline Not Running' : 'No table status information'}
-
-
- {showDisabledState
- ? `The replication pipeline is currently ${statusName || 'not active'}. Table status
+ {!isStatusLoading && tableStatuses.length === 0 && (
+
+
+
+
+
+
+
+ {showDisabledState ? 'Pipeline not running' : 'No table status information'}
+
+
+ {showDisabledState
+ ? `The replication pipeline is currently ${statusName || 'not active'}. Table status
information is not available while the pipeline is in this state.`
- : `This pipeline doesn't have any table replication status data available yet. The status will appear here once replication begins.`}
+ : `This pipeline doesn't have any table replication status data available yet. The status will appear here once replication begins.`}
+
+ During the update process, the replication pauses and resumes.
+
+
+ If a long‑running transaction is in progress, some records may be reprocessed due to
+ PostgreSQL logical replication limitations.
+
+
+ ),
+ }}
+ >
+
+ Pipeline will be updated from {currentVersionName ?? 'Current version'} to{' '}
+ {newVersionName ?? 'New version'}.
+
+
Confirm to update pipeline? This action cannot be undone.
+
+ )
+}
diff --git a/apps/studio/components/interfaces/LogDrains/LogDrains.constants.tsx b/apps/studio/components/interfaces/LogDrains/LogDrains.constants.tsx
index e73608d482dde..813a795db473a 100644
--- a/apps/studio/components/interfaces/LogDrains/LogDrains.constants.tsx
+++ b/apps/studio/components/interfaces/LogDrains/LogDrains.constants.tsx
@@ -42,6 +42,10 @@ export const DATADOG_REGIONS = [
label: 'AP1',
value: 'AP1',
},
+ {
+ label: 'AP2',
+ value: 'AP2',
+ },
{
label: 'EU',
value: 'EU',
diff --git a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
index d9265a582ce08..f6e1fa8bc1779 100644
--- a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
+++ b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
@@ -8,7 +8,7 @@ import { useEffect, useState } from 'react'
import { LogDetailsPanel } from 'components/interfaces/AuditLogs'
import { LogsDatePicker } from 'components/interfaces/Settings/Logs/Logs.DatePickers'
-import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
+import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import Table from 'components/to-be-cleaned/Table'
import AlertError from 'components/ui/AlertError'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
@@ -23,7 +23,7 @@ import {
import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectsQuery } from 'data/projects/projects-query'
-import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
+import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
@@ -32,13 +32,15 @@ import {
WarningIcon,
} from 'ui'
+const logsUpgradeError = 'upgrade to Team or Enterprise Plan to access audit logs.'
+
// [Joshen considerations]
// - Maybe fix the height of the table to the remaining height of the viewport, so that the search input is always visible
// - We'll need pagination as well if the audit logs get too large, but that needs to be implemented on the API side first if possible
// - I've hidden time input in the date picker for now cause the time support in the component is a bit iffy, need to investigate
// - Maybe a rule to follow from here is just everytime we call dayjs, use UTC(), one TZ to rule them all
-const AuditLogs = () => {
+export const AuditLogs = () => {
const { slug } = useParams()
const currentTime = dayjs().utc().set('millisecond', 0)
const [dateSortDesc, setDateSortDesc] = useState(true)
@@ -52,7 +54,8 @@ const AuditLogs = () => {
projects: [], // project_ref[]
})
- const canReadAuditLogs = useCheckPermissions(PermissionAction.READ, 'notifications')
+ const { can: canReadAuditLogs, isLoading: isLoadingPermissions } =
+ useAsyncCheckProjectPermissions(PermissionAction.READ, 'notifications')
const { data: projectsData } = useProjectsQuery()
const projects = projectsData?.projects ?? []
@@ -69,13 +72,15 @@ const AuditLogs = () => {
{
enabled: canReadAuditLogs,
retry: false,
+ refetchOnWindowFocus: (query) => {
+ return !query.state.error?.message.endsWith(logsUpgradeError)
+ },
}
)
const activeMembers = (members ?? []).filter((x) => !x.invited_at)
const roles = [...(rolesData?.org_scoped_roles ?? []), ...(rolesData?.project_scoped_roles ?? [])]
- const retentionPeriod = data?.retention_period ?? 0
const logs = data?.result ?? []
const sortedLogs = logs
?.sort((a, b) =>
@@ -99,8 +104,6 @@ const AuditLogs = () => {
})
const currentOrganization = organizations?.find((o) => o.slug === slug)
- const minDate = dayjs().subtract(retentionPeriod, 'days')
- const maxDate = dayjs()
// This feature depends on the subscription tier of the user. Free user can view logs up to 1 day
// in the past. The API limits the logs to maximum of 1 day and 5 minutes so when the page is
@@ -119,300 +122,296 @@ const AuditLogs = () => {
return () => clearInterval(interval)
}, [dateRange.from, dateRange.to])
- if (!canReadAuditLogs) {
- return (
-
-
-
- )
- }
-
return (
<>
-
-
+
+ ) : error.message.includes('range exceeded') ? (
+
+
+ Date range too large
+
+ The selected date range exceeds the maximum allowed period. Please select a
+ smaller time range.
+
+
) : (
-
- This will be reflected in every upcoming invoice, past invoices are not affected
+ Changes will be reflected in every upcoming invoice, past invoices are not affected
+
+
+ A Tax ID is only required for registered businesses.