diff --git a/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx b/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx
index edcd5584372fa..3de5cd9e0b0f0 100644
--- a/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx
+++ b/apps/studio/components/interfaces/Functions/TerminalInstructions.tsx
@@ -83,7 +83,7 @@ export const TerminalInstructions = forwardRef<
return (
<>
curl -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"}'`}
>
diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
index 35afb66f84174..f80c24edb754e 100644
--- a/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
+++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx
@@ -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'
diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx
index 927c0d86e65da..5bfb9b0214d73 100644
--- a/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx
+++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx
@@ -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'
diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectTableRow.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectTableRow.tsx
index 7039687da790a..20bc9dc1e406e 100644
--- a/apps/studio/components/interfaces/Home/ProjectList/ProjectTableRow.tsx
+++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectTableRow.tsx
@@ -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'
diff --git a/apps/studio/components/interfaces/HomePageActions.tsx b/apps/studio/components/interfaces/HomePageActions.tsx
index 64058004de517..7e07fcce3dde2 100644
--- a/apps/studio/components/interfaces/HomePageActions.tsx
+++ b/apps/studio/components/interfaces/HomePageActions.tsx
@@ -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'
diff --git a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
index 92b454ebc25f4..a6e644ce8b821 100644
--- a/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
+++ b/apps/studio/components/interfaces/Organization/AuditLogs/AuditLogs.tsx
@@ -3,8 +3,9 @@ import { useParams } from 'common'
import dayjs from 'dayjs'
import { ArrowDown, ArrowUp, RefreshCw, User } from 'lucide-react'
import Image from 'next/legacy/image'
-import { useEffect, useState } from 'react'
+import { useEffect, useMemo, useState } from 'react'
+import { useDebounce } from '@uidotdev/usehooks'
import { LogDetailsPanel } from 'components/interfaces/AuditLogs'
import { LogsDatePicker } from 'components/interfaces/Settings/Logs/Logs.DatePickers'
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
@@ -22,7 +23,7 @@ import {
} from 'data/organizations/organization-audit-logs-query'
import { useOrganizationMembersQuery } from 'data/organizations/organization-members-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
-import { useProjectsQuery } from 'data/projects/projects-query'
+import { useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query'
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
import {
AlertDescription_Shadcn_,
@@ -43,6 +44,7 @@ const logsUpgradeError = 'upgrade to Team or Enterprise Plan to access audit log
export const AuditLogs = () => {
const { slug } = useParams()
const currentTime = dayjs().utc().set('millisecond', 0)
+
const [dateSortDesc, setDateSortDesc] = useState(true)
const [dateRange, setDateRange] = useState({
from: currentTime.subtract(1, 'day').toISOString(),
@@ -54,16 +56,14 @@ export const AuditLogs = () => {
projects: [], // project_ref[]
})
+ const [search, setSearch] = useState('')
+ const debouncedSearch = useDebounce(search, 500)
+
const { can: canReadAuditLogs, isLoading: isLoadingPermissions } = useAsyncCheckPermissions(
PermissionAction.READ,
'notifications'
)
- const { data: projectsData } = useProjectsQuery()
- const projects = projectsData?.projects ?? []
- const { data: organizations } = useOrganizationsQuery()
- const { data: members } = useOrganizationMembersQuery({ slug })
- const { data: rolesData } = useOrganizationRolesV2Query({ slug })
const { data, error, isLoading, isSuccess, isError, isRefetching, refetch } =
useOrganizationAuditLogsQuery(
{
@@ -79,9 +79,31 @@ export const AuditLogs = () => {
},
}
)
+ const isLogsNotAvailableBasedOnPlan = isError && error.message.endsWith(logsUpgradeError)
+ const isRangeExceededError = isError && error.message.includes('range exceeded')
+ const showFilters = !isLoading && !isLogsNotAvailableBasedOnPlan
+
+ const {
+ data: projectsData,
+ isLoading: isLoadingProjects,
+ isFetching,
+ isFetchingNextPage,
+ hasNextPage,
+ fetchNextPage,
+ } = useOrgProjectsInfiniteQuery(
+ { slug, search: search.length === 0 ? search : debouncedSearch },
+ { keepPreviousData: true, enabled: showFilters }
+ )
+ const { data: organizations } = useOrganizationsQuery({
+ enabled: showFilters,
+ })
+ const { data: members } = useOrganizationMembersQuery({ slug }, { enabled: showFilters })
+ const { data: rolesData } = useOrganizationRolesV2Query({ slug }, { enabled: showFilters })
const activeMembers = (members ?? []).filter((x) => !x.invited_at)
const roles = [...(rolesData?.org_scoped_roles ?? []), ...(rolesData?.project_scoped_roles ?? [])]
+ const projects =
+ useMemo(() => projectsData?.pages.flatMap((page) => page.projects), [projectsData?.pages]) || []
const logs = data?.result ?? []
const sortedLogs = logs
@@ -105,8 +127,6 @@ export const AuditLogs = () => {
}
})
- const currentOrganization = organizations?.find((o) => o.slug === slug)
-
// 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
// viewed for more than 5 minutes, the call parameters needs to be updated. This also works with
@@ -124,81 +144,122 @@ export const AuditLogs = () => {
return () => clearInterval(interval)
}, [dateRange.from, dateRange.to])
+ if (isLogsNotAvailableBasedOnPlan) {
+ return (
+
+
+
+
+
+
+
+ Organization Audit Logs are not available on Free or Pro plans
+
+
+
+ Upgrade to Team or Enterprise to view up to 28 days of Audit Logs for your
+ organization.
+
+
+
+
+
+
+ Upgrade subscription
+
+
+
+
+
+
+ )
+ }
+
return (
<>
-
-
-
Filter by
-
setFilters({ ...filters, users: values })}
- />
- p.organization_id === currentOrganization?.id) ?? []
- }
- labelKey="name"
- valueKey="ref"
- activeOptions={filters.projects}
- onSaveFilters={(values) => setFilters({ ...filters, projects: values })}
- />
- setDateRange(value)}
- helpers={[
- {
- text: 'Last 1 hour',
- calcFrom: () => dayjs().subtract(1, 'hour').toISOString(),
- calcTo: () => dayjs().toISOString(),
- },
- {
- text: 'Last 3 hours',
- calcFrom: () => dayjs().subtract(3, 'hour').toISOString(),
- calcTo: () => dayjs().toISOString(),
- },
+ {showFilters && (
+
+
+
Filter by
+
setFilters({ ...filters, users: values })}
+ />
+ setFilters({ ...filters, projects: values })}
+ search={search}
+ setSearch={setSearch}
+ hasNextPage={hasNextPage}
+ isLoading={isLoadingProjects}
+ isFetching={isFetching}
+ isFetchingNextPage={isFetchingNextPage}
+ fetchNextPage={fetchNextPage}
+ />
+ setDateRange(value)}
+ helpers={[
+ {
+ text: 'Last 1 hour',
+ calcFrom: () => dayjs().subtract(1, 'hour').toISOString(),
+ calcTo: () => dayjs().toISOString(),
+ },
+ {
+ text: 'Last 3 hours',
+ calcFrom: () => dayjs().subtract(3, 'hour').toISOString(),
+ calcTo: () => dayjs().toISOString(),
+ },
- {
- text: 'Last 6 hours',
- calcFrom: () => dayjs().subtract(6, 'hour').toISOString(),
- calcTo: () => dayjs().toISOString(),
- },
- {
- text: 'Last 12 hours',
- calcFrom: () => dayjs().subtract(12, 'hour').toISOString(),
- calcTo: () => dayjs().toISOString(),
- },
- {
- text: 'Last 24 hours',
- calcFrom: () => dayjs().subtract(1, 'day').toISOString(),
- calcTo: () => dayjs().toISOString(),
- },
- ]}
- />
- {isSuccess && (
- <>
-
- Viewing {sortedLogs.length} logs in total
- >
- )}
+ {
+ text: 'Last 6 hours',
+ calcFrom: () => dayjs().subtract(6, 'hour').toISOString(),
+ calcTo: () => dayjs().toISOString(),
+ },
+ {
+ text: 'Last 12 hours',
+ calcFrom: () => dayjs().subtract(12, 'hour').toISOString(),
+ calcTo: () => dayjs().toISOString(),
+ },
+ {
+ text: 'Last 24 hours',
+ calcFrom: () => dayjs().subtract(1, 'day').toISOString(),
+ calcTo: () => dayjs().toISOString(),
+ },
+ ]}
+ />
+ {isSuccess && (
+ <>
+
+ Viewing {sortedLogs.length} logs in total
+ >
+ )}
+
+
}
+ onClick={() => refetch()}
+ >
+ {isRefetching ? 'Refreshing' : 'Refresh'}
+
- }
- onClick={() => refetch()}
- >
- {isRefetching ? 'Refreshing' : 'Refresh'}
-
-
+ )}
{isLoading || isLoadingPermissions ? (
@@ -210,34 +271,8 @@ export const AuditLogs = () => {
) : null}
- {isError ? (
- error.message.endsWith(logsUpgradeError) ? (
-
-
-
-
-
- Organization Audit Logs are not available on Free or Pro plans
-
-
-
- Upgrade to Team or Enterprise to view up to 28 days of Audit Logs for your
- organization.
-
-
-
-
-
-
- Upgrade subscription
-
-
-
-
- ) : error.message.includes('range exceeded') ? (
+ {isError &&
+ (isRangeExceededError ? (
Date range too large
@@ -248,8 +283,7 @@ export const AuditLogs = () => {
) : (
- )
- ) : null}
+ ))}
{isSuccess && (
<>
diff --git a/apps/studio/components/interfaces/Organization/OrganizationCard.tsx b/apps/studio/components/interfaces/Organization/OrganizationCard.tsx
index 0e3c77246e9c4..f9cb258318cf5 100644
--- a/apps/studio/components/interfaces/Organization/OrganizationCard.tsx
+++ b/apps/studio/components/interfaces/Organization/OrganizationCard.tsx
@@ -3,7 +3,7 @@ import Link from 'next/link'
import { useIsMFAEnabled } from 'common'
import { ActionCard } from 'components/ui/ActionCard'
-import { useProjectsQuery } from 'data/projects/projects-query'
+import { useOrgProjectsInfiniteQuery } from 'data/projects/org-projects-infinite-query'
import { Organization } from 'types'
import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
@@ -15,10 +15,8 @@ export const OrganizationCard = ({
href?: string
}) => {
const isUserMFAEnabled = useIsMFAEnabled()
- const { data } = useProjectsQuery()
- const allProjects = data?.projects ?? []
-
- const numProjects = allProjects.filter((x) => x.organization_slug === organization.slug).length
+ const { data } = useOrgProjectsInfiniteQuery({ slug: organization.slug })
+ const numProjects = data?.pages[0].pagination.count ?? 0
const isMfaRequired = organization.organization_requires_mfa
return (
diff --git a/apps/studio/components/interfaces/Organization/SSO/SSOConfig.tsx b/apps/studio/components/interfaces/Organization/SSO/SSOConfig.tsx
index 7bd3b6df393ee..98a38c1b19522 100644
--- a/apps/studio/components/interfaces/Organization/SSO/SSOConfig.tsx
+++ b/apps/studio/components/interfaces/Organization/SSO/SSOConfig.tsx
@@ -157,7 +157,7 @@ export const SSOConfig = () => {
return (
-
+
{!!plan && !canSetupSSOConfig ? (
{
Usage
-
+
{isLoadingSubscription || isLoadingPermissions ? (
diff --git a/apps/studio/components/interfaces/Storage/FilesBuckets.tsx b/apps/studio/components/interfaces/Storage/FilesBuckets.tsx
index b9bd99680197a..acd46b2c4b261 100644
--- a/apps/studio/components/interfaces/Storage/FilesBuckets.tsx
+++ b/apps/studio/components/interfaces/Storage/FilesBuckets.tsx
@@ -1,11 +1,18 @@
import { Edit, FolderOpen, MoreVertical, Search, Trash2 } from 'lucide-react'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
import { useState } from 'react'
import { useParams } from 'common'
import { ScaffoldSection } from 'components/layouts/Scaffold'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
+import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query'
import { Bucket, useBucketsQuery } from 'data/storage/buckets-query'
+import { useStoragePolicyCounts } from 'hooks/storage/useStoragePolicyCounts'
+import { IS_PLATFORM } from 'lib/constants'
+import { formatBytes } from 'lib/helpers'
import {
+ Badge,
Button,
Card,
DropdownMenu,
@@ -28,13 +35,20 @@ import { EmptyBucketModal } from './EmptyBucketModal'
import { EmptyBucketState } from './EmptyBucketState'
export const FilesBuckets = () => {
+ const router = useRouter()
const { ref } = useParams()
const [modal, setModal] = useState<'edit' | 'empty' | 'delete' | null>(null)
const [selectedBucket, setSelectedBucket] = useState
()
const [filterString, setFilterString] = useState('')
- const { data: buckets = [], isLoading } = useBucketsQuery({ projectRef: ref })
+ const { data } = useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM })
+ const { data: buckets = [], isLoading: isLoadingBuckets } = useBucketsQuery({ projectRef: ref })
+ const { getPolicyCount, isLoading: isLoadingPolicies } = useStoragePolicyCounts(buckets)
+
+ const formattedGlobalUploadLimit = formatBytes(data?.fileSizeLimit ?? 0)
+
+ const isLoading = isLoadingBuckets || isLoadingPolicies
const filesBuckets = buckets
.filter((bucket) => !('type' in bucket) || bucket.type === 'STANDARD')
.filter((bucket) =>
@@ -69,7 +83,9 @@ export const FilesBuckets = () => {
Name
- Visibility
+ Policies
+ File size limit
+ Allowed MIME types
@@ -85,28 +101,76 @@ export const FilesBuckets = () => {
)}
{filesBuckets.map((bucket) => (
-
+ {
+ const url = `/project/${ref}/storage/files/buckets/${bucket.id}`
+ if (event.metaKey) window.open(url, '_blank')
+ else router.push(url)
+ }}
+ >
+
+
+
{bucket.name}
+ {bucket.public &&
Public}
+
+
+
- {bucket.name}
+ {getPolicyCount(bucket.name)}
+
+
+
+ {bucket.file_size_limit
+ ? formatBytes(bucket.file_size_limit)
+ : `Unset (${formattedGlobalUploadLimit})`}
+
+
+
-
- {bucket.public ? 'Public' : 'Private'}
+
+ {bucket.allowed_mime_types ? bucket.allowed_mime_types.join(', ') : 'Any'}
+
-