diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 05951f0b0cabd..a316fbb1b59fd 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -140,7 +140,6 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [ icon: 'dev-cli', href: '/guides/local-development' as `/${string}`, level: 'local_development', - enabled: localDevelopmentEnabled, }, { label: 'Deployment', @@ -2266,7 +2265,6 @@ export const ai: NavMenuConstant = { export const local_development: NavMenuConstant = { icon: 'dev-cli', title: 'Local Dev / CLI', - enabled: localDevelopmentEnabled, url: '/guides/local-development', items: [ { name: 'Overview', url: '/guides/local-development' }, @@ -2275,7 +2273,11 @@ export const local_development: NavMenuConstant = { url: undefined, items: [ { name: 'Getting started', url: '/guides/local-development/cli/getting-started' }, - { name: 'Configuration', url: '/guides/local-development/cli/config' }, + { + name: 'Configuration', + url: '/guides/local-development/cli/config', + enabled: localDevelopmentEnabled, + }, { name: 'CLI commands', url: '/reference/cli' }, ], }, @@ -2299,16 +2301,19 @@ export const local_development: NavMenuConstant = { { name: 'Restoring downloaded backup', url: '/guides/local-development/restoring-downloaded-backup' as `/${string}`, + enabled: localDevelopmentEnabled, }, { name: 'Customizing email templates', url: '/guides/local-development/customizing-email-templates' as `/${string}`, + enabled: localDevelopmentEnabled, }, ], }, { name: 'Testing', url: undefined, + enabled: localDevelopmentEnabled, items: [ { name: 'Getting started', url: '/guides/local-development/testing/overview' }, { diff --git a/apps/docs/docs/ref/cli/introduction.mdx b/apps/docs/docs/ref/cli/introduction.mdx index bf38fc0d95488..43daef09dc3c7 100644 --- a/apps/docs/docs/ref/cli/introduction.mdx +++ b/apps/docs/docs/ref/cli/introduction.mdx @@ -31,7 +31,7 @@ hideTitle: true ### Additional links - - [Install the Supabase CLI](/docs/guides/cli) + - [Install the Supabase CLI](/docs/guides/local-development/cli/getting-started) - [Source code](https://github.com/supabase/cli) - [Known bugs and issues](https://github.com/supabase/cli/issues) - [Supabase CLI v1 and Management API Beta](https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta) diff --git a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx index 984bdb8f3544f..5656f35a24ea9 100644 --- a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx @@ -317,7 +317,7 @@ export const MfaAuthSettingsForm = () => {
- + { e.preventDefault() maybeConfirmPhoneMFAOrSubmit() }} - className="space-y-4" > - + { - + - + { return ( <> - + - + { {/* Site URL and Authorization Path - Only show when OAuth Server is enabled */} {form.watch('OAUTH_SERVER_ENABLED') && ( <> - + The base URL of your application, configured in{' '} @@ -243,13 +244,15 @@ export const OAuthServerSettingsForm = () => { placeholder="https://example.com" /> - + + ( @@ -281,7 +284,7 @@ export const OAuthServerSettingsForm = () => { ) })()} - + { - + - + { - + + + {!canUpdateConfig || !canUpdateEmailLimit ? ( @@ -257,13 +259,14 @@ export const RateLimits = () => { - + + + {!canUpdateConfig || !canUpdateSMSRateLimit ? ( @@ -302,13 +305,14 @@ export const RateLimits = () => { - + + + {!canUpdateConfig && ( @@ -345,13 +349,14 @@ export const RateLimits = () => { - + + + {!canUpdateConfig && ( @@ -388,13 +393,14 @@ export const RateLimits = () => { - + + + {!canUpdateConfig || !canUpdateAnonymousUsersRateLimit ? ( @@ -431,13 +437,14 @@ export const RateLimits = () => { - + + + {!canUpdateConfig && ( @@ -474,13 +481,14 @@ export const RateLimits = () => { - + + + {!canUpdateConfig || !canUpdateWeb3RateLimit ? ( diff --git a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx index 6c89d9ba3af8c..12bb244a6af7d 100644 --- a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx @@ -207,7 +207,7 @@ export const SessionsAuthSettingsForm = () => { className="space-y-4" > - + { label="Refresh token reuse interval" description="Time interval where the same refresh token can be used multiple times to request for an access token. Recommendation: 10 seconds." > - + { label="Time-box user sessions" description="The amount of time before a user is forced to sign in again. Use 0 for never." > -
- - }> - - - -
+ + }> + + + )} /> @@ -349,17 +347,16 @@ export const SessionsAuthSettingsForm = () => { label="Inactivity timeout" description="The amount of time a user needs to be inactive to be forced to sign in again. Use 0 for never." > -
- - }> - - - -
+ + }> + + + )} /> diff --git a/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx b/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx index 25a00e1235c1c..2f1710d1b5ce7 100644 --- a/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx +++ b/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx @@ -112,9 +112,9 @@ const SiteUrl = () => { - + - + ({formatCurrency(usageMeta.cost)}) - ) : usageMeta.available_in_plan && !usageMeta.unlimited && relativeToSubscription ? ( + ) : usageMeta.available_in_plan && + usageMeta.pricing_free_units !== 0 && + !usageMeta.unlimited && + relativeToSubscription ? ( {percentageLabel} ) : null} @@ -112,7 +115,10 @@ export const BillingMetric = ({ ({formatCurrency(usageMeta.cost)}) - ) : usageMeta.available_in_plan && !usageMeta.unlimited && relativeToSubscription ? ( + ) : usageMeta.available_in_plan && + usageMeta.pricing_free_units !== 0 && + !usageMeta.unlimited && + relativeToSubscription ? ( {percentageLabel} ) : null} @@ -120,7 +126,9 @@ export const BillingMetric = ({ {usageMeta.available_in_plan ? (
- {relativeToSubscription && !usageMeta.unlimited ? ( + {relativeToSubscription && + !usageMeta.unlimited && + usageMeta.pricing_free_units !== 0 ? ( { // For non-platform customers, compute is broken down per project and contains a breakdown array const computeItems = - upcomingInvoice?.lines?.filter( - (item) => - item.description?.toLowerCase().includes('compute') && - item.breakdown && - item.breakdown?.length > 0 - ) || [] + upcomingInvoice?.lines?.filter((item) => item.description?.toLowerCase().includes('compute')) || + [] const computeCreditsItem = upcomingInvoice?.lines?.find((item) => item.description.startsWith('Compute Credits')) ?? null @@ -80,12 +76,14 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => { const replicaComputeItems = computeItems.filter((it) => it.metadata?.is_read_replica) const otherItems = - upcomingInvoice?.lines?.filter( - (item) => - // In case we have no per-project breakdown for compute, we treat it as a regular item for display purposes - (!item.usage_metric?.toString()?.startsWith('COMPUTE_HOURS') || !item.breakdown?.length) && - !item.description?.toLowerCase().includes('plan') - ) || [] + upcomingInvoice?.lines + ?.filter( + (item) => + !item.description?.toLowerCase().includes('compute') && + !item.description?.toLowerCase().includes('plan') && + item.amount_before_discount > 0 + ) + .sort((a, b) => b.amount_before_discount - a.amount_before_discount) || [] return ( <> @@ -357,6 +355,10 @@ function ComputeLineItem({ // descending by cost .sort((a, b) => b.computeCosts - a.computeCosts) + const computeItemsSortedByCost = computeItems + // descending by cost + .sort((a, b) => b.amount - a.amount) + const computeCosts = Math.max( 0, computeItems.reduce((prev, cur) => prev + cur.amount_before_discount, 0) @@ -395,6 +397,23 @@ function ComputeLineItem({ ))} + {/* Fallback to breakdown by instance size if project breakdown not available */} + {!computeProjects.length && + computeItemsSortedByCost.map((computeItem) => ( + + + {computeItem.description} - {computeItem.usage_original} Hours + + + + {formatCurrency(computeItem.amount)} + + + ))} + {computeCredits && ( Compute Credits diff --git a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx index 94cb875b0771a..6b0f5061a052a 100644 --- a/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx +++ b/apps/studio/components/interfaces/Organization/BillingSettings/Subscription/Subscription.tsx @@ -83,16 +83,18 @@ const Subscription = () => {
- - - + {canChangeTier && ( + + + + )} {!canChangeTier && (projectUpdateDisabled ? ( { + const { billingAll } = useIsFeatureEnabled(['billing:all']) + + const chartData: DataPoint[] = dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.ACTIVE_COMPUTE_HOURS + ) + + const notAllValuesZero = useMemo(() => { + return chartData.some( + (dataPoint) => + dataPoint['active_compute_hours'] && Number(dataPoint['active_compute_hours']) > 0 + ) + }, [chartData]) + + return ( +
+ + {isLoadingOrgDailyStats && } + + {!isLoadingOrgDailyStats && ( + <> +
+ {chartData.length > 0 && ( +
+
+

Active Compute Hours usage

+
+
+ )} + +
+

+ Active Compute Hours usage in period +

+

+ {chartData.reduce( + (prev, cur) => prev + ((cur['active_compute_hours'] as number) ?? 0), + 0 + )}{' '} + hours +

+
+
+ + {chartData.length > 0 && notAllValuesZero ? ( + + ) : ( + + +
+ +

No data in period

+

May take up to one hour to show

+
+
+
+ )} + + )} +
+
+ ) +} + +export default ActiveCompute diff --git a/apps/studio/components/interfaces/Organization/Usage/OrgLogUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/OrgLogUsage.tsx new file mode 100644 index 0000000000000..48f65c75eadb4 --- /dev/null +++ b/apps/studio/components/interfaces/Organization/Usage/OrgLogUsage.tsx @@ -0,0 +1,64 @@ +import { DataPoint } from 'data/analytics/constants' +import { PricingMetric, type OrgDailyUsageResponse } from 'data/analytics/org-daily-stats-query' +import type { OrgSubscription } from 'data/subscriptions/types' +import UsageSection from './UsageSection/UsageSection' +import { dailyUsageToDataPoints } from './Usage.utils' + +export interface OrgLogUsageProps { + orgSlug: string + projectRef?: string | null + startDate: string | undefined + endDate: string | undefined + subscription: OrgSubscription | undefined + currentBillingCycleSelected: boolean + orgDailyStats: OrgDailyUsageResponse | undefined + isLoadingOrgDailyStats: boolean +} + +const OrgLogUsage = ({ + orgSlug, + projectRef, + subscription, + currentBillingCycleSelected, + orgDailyStats, + isLoadingOrgDailyStats, +}: OrgLogUsageProps) => { + const chartMeta: { + [key: string]: { data: DataPoint[]; margin: number; isLoading: boolean } + } = { + [PricingMetric.LOG_INGESTION]: { + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.LOG_INGESTION + ), + margin: 18, + isLoading: isLoadingOrgDailyStats, + }, + [PricingMetric.LOG_QUERYING]: { + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.LOG_QUERYING + ), + margin: 20, + isLoading: isLoadingOrgDailyStats, + }, + [PricingMetric.LOG_STORAGE]: { + data: dailyUsageToDataPoints(orgDailyStats, (metric) => metric === PricingMetric.LOG_STORAGE), + margin: 0, + isLoading: isLoadingOrgDailyStats, + }, + } + + return ( + + ) +} + +export default OrgLogUsage diff --git a/apps/studio/components/interfaces/Organization/Usage/SizeAndCounts.tsx b/apps/studio/components/interfaces/Organization/Usage/SizeAndCounts.tsx index 0887860f75a4c..98fee9df80e7c 100644 --- a/apps/studio/components/interfaces/Organization/Usage/SizeAndCounts.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/SizeAndCounts.tsx @@ -40,6 +40,14 @@ const SizeAndCounts = ({ (metric) => metric === PricingMetric.STORAGE_SIZE ), }, + [PricingMetric.DATABASE_SIZE]: { + isLoading: isLoadingOrgDailyStats, + margin: 14, + data: dailyUsageToDataPoints( + orgDailyStats, + (metric) => metric === PricingMetric.DATABASE_SIZE + ), + }, } return ( diff --git a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx index d61e75dabc6f4..8e8c93deb44b1 100644 --- a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx @@ -33,6 +33,10 @@ const METRICS_TO_HIDE_WITH_NO_USAGE: PricingMetric[] = [ PricingMetric.DISK_SIZE_GB_HOURS_GP3, PricingMetric.DISK_SIZE_GB_HOURS_IO2, PricingMetric.DISK_THROUGHPUT_GP3, + PricingMetric.LOG_INGESTION, + PricingMetric.LOG_STORAGE, + PricingMetric.LOG_QUERYING, + PricingMetric.ACTIVE_COMPUTE_HOURS, ] export const TotalUsage = ({ diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx index 7f8471d2c6582..9ea413993637b 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.constants.tsx @@ -62,7 +62,7 @@ export interface CategoryAttribute { additionalInfo?: (usage?: OrgUsageResponse) => JSX.Element | null } -export type CategoryMetaKey = 'egress' | 'sizeCount' | 'activity' | 'compute' +export type CategoryMetaKey = 'egress' | 'sizeCount' | 'activity' | 'compute' | 'logs' export interface CategoryMeta { key: CategoryMetaKey @@ -118,6 +118,116 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ }, ] + const databaseAndStorageSizeAttributes: CategoryAttribute[] = [] + if (subscription?.plan.id === 'free') { + databaseAndStorageSizeAttributes.push({ + anchor: 'dbSize', + key: PricingMetric.DATABASE_SIZE, + attributes: [{ key: PricingMetric.DATABASE_SIZE.toLowerCase(), color: 'white' }], + name: 'Database size', + chartPrefix: 'Average', + unit: 'bytes', + description: + 'Database size refers to the actual amount of space used by all your database objects, as reported by Postgres.', + links: [ + { + name: 'Documentation', + url: `${DOCS_URL}/guides/platform/database-size`, + }, + ], + chartDescription: 'The data refreshes every hour.', + additionalInfo: (usage?: OrgUsageResponse) => { + const usageMeta = usage?.usages.find((x) => x.metric === PricingMetric.DATABASE_SIZE) + const usageRatio = + typeof usageMeta !== 'number' + ? (usageMeta?.usage ?? 0) / (usageMeta?.pricing_free_units ?? 0) + : 0 + const hasLimit = usageMeta && (usageMeta?.pricing_free_units ?? 0) > 0 + + const isApproachingLimit = hasLimit && usageRatio >= USAGE_APPROACHING_THRESHOLD + const isExceededLimit = hasLimit && usageRatio >= 1 + const isCapped = usageMeta?.capped + + const onFreePlan = subscription?.plan?.name === 'Free' + + return ( +
+ {(isApproachingLimit || isExceededLimit) && isCapped && ( + +
+
+ When you reach your database size limit, your project can go into read-only + mode.{' '} + {onFreePlan + ? 'Please upgrade your Plan.' + : "Disable your spend cap to scale seamlessly, and pay for over-usage beyond your Plan's quota."} +
+
+
+ )} +
+ ) + }, + }) + } else if (subscription?.plan.id !== 'platform') { + databaseAndStorageSizeAttributes.push({ + anchor: 'diskSize', + key: 'diskSize', + attributes: [], + name: 'Disk size', + chartPrefix: 'Average', + unit: 'bytes', + description: + "Each Supabase project comes with a dedicated disk. Each project gets 8 GB of disk for free. Billing is based on the provisioned disk size. Disk automatically scales up when you get close to it's size.\nEach hour your project is using more than 8 GB of GP3 disk, it incurs the overages in GB-Hrs, i.e. a 16 GB disk incurs 8 GB-Hrs every hour. Extra disk size costs $0.125/GB/month ($0.000171/GB-Hr).", + links: [ + { + name: 'Documentation', + url: `${DOCS_URL}/guides/platform/manage-your-usage/disk-size`, + }, + { + name: 'Disk Management', + url: `${DOCS_URL}/guides/platform/database-size#disk-management`, + }, + ], + chartDescription: '', + }) + } else if (subscription?.plan.id === 'platform') { + databaseAndStorageSizeAttributes.push({ + anchor: 'databaseSize', + key: PricingMetric.DATABASE_SIZE, + attributes: [{ key: PricingMetric.DATABASE_SIZE.toLowerCase(), color: 'white' }], + name: 'Database Size', + chartPrefix: 'Cumulative', + unit: 'bytes', + description: + 'Database size refers to the actual amount of space used by all your database objects, as reported by Postgres.\nBilling is prorated down to the hour and will be displayed GB-Hrs.', + chartDescription: 'The data refreshes every hour.', + }) + } + + databaseAndStorageSizeAttributes.push({ + anchor: 'storageSize', + key: PricingMetric.STORAGE_SIZE, + attributes: [{ key: PricingMetric.STORAGE_SIZE.toLowerCase(), color: 'white' }], + name: 'Storage Size', + chartPrefix: 'Average', + unit: 'bytes', + description: + 'Sum of all objects in your storage buckets.\nBilling is prorated down to the hour and will be displayed GB-Hrs.', + chartDescription: 'The data refreshes every hour.', + links: [ + { + name: 'Storage', + url: `${DOCS_URL}/guides/storage`, + }, + ], + }) + return [ { key: 'egress', @@ -129,105 +239,7 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ key: 'sizeCount', name: 'Database & Storage Size', description: 'Amount of resources your project is consuming', - attributes: [ - subscription?.plan.id === 'free' - ? { - anchor: 'dbSize', - key: PricingMetric.DATABASE_SIZE, - attributes: [{ key: PricingMetric.DATABASE_SIZE.toLowerCase(), color: 'white' }], - name: 'Database size', - chartPrefix: 'Average', - unit: 'bytes', - description: - 'Database size refers to the actual amount of space used by all your database objects, as reported by Postgres.', - links: [ - { - name: 'Documentation', - url: `${DOCS_URL}/guides/platform/database-size`, - }, - ], - chartDescription: 'The data refreshes every hour.', - additionalInfo: (usage?: OrgUsageResponse) => { - const usageMeta = usage?.usages.find( - (x) => x.metric === PricingMetric.DATABASE_SIZE - ) - const usageRatio = - typeof usageMeta !== 'number' - ? (usageMeta?.usage ?? 0) / (usageMeta?.pricing_free_units ?? 0) - : 0 - const hasLimit = usageMeta && (usageMeta?.pricing_free_units ?? 0) > 0 - - const isApproachingLimit = hasLimit && usageRatio >= USAGE_APPROACHING_THRESHOLD - const isExceededLimit = hasLimit && usageRatio >= 1 - const isCapped = usageMeta?.capped - - const onFreePlan = subscription?.plan?.name === 'Free' - - return ( -
- {(isApproachingLimit || isExceededLimit) && isCapped && ( - -
-
- When you reach your database size limit, your project can go into - read-only mode.{' '} - {onFreePlan - ? 'Please upgrade your Plan.' - : "Disable your spend cap to scale seamlessly, and pay for over-usage beyond your Plan's quota."} -
-
-
- )} -
- ) - }, - } - : { - anchor: 'diskSize', - key: 'diskSize', - attributes: [], - name: 'Disk size', - chartPrefix: 'Average', - unit: 'bytes', - description: - "Each Supabase project comes with a dedicated disk. Each project gets 8 GB of disk for free. Billing is based on the provisioned disk size. Disk automatically scales up when you get close to it's size.\nEach hour your project is using more than 8 GB of GP3 disk, it incurs the overages in GB-Hrs, i.e. a 16 GB disk incurs 8 GB-Hrs every hour. Extra disk size costs $0.125/GB/month ($0.000171/GB-Hr).", - links: [ - { - name: 'Documentation', - url: `${DOCS_URL}/guides/platform/manage-your-usage/disk-size`, - }, - { - name: 'Disk Management', - url: `${DOCS_URL}/guides/platform/database-size#disk-management`, - }, - ], - chartDescription: '', - }, - { - anchor: 'storageSize', - key: PricingMetric.STORAGE_SIZE, - attributes: [{ key: PricingMetric.STORAGE_SIZE.toLowerCase(), color: 'white' }], - name: 'Storage Size', - chartPrefix: 'Average', - unit: 'bytes', - description: - 'Sum of all objects in your storage buckets.\nBilling is prorated down to the hour and will be displayed GB-Hrs.', - chartDescription: 'The data refreshes every hour.', - links: [ - { - name: 'Storage', - url: `${DOCS_URL}/guides/storage`, - }, - ], - }, - ], + attributes: databaseAndStorageSizeAttributes, }, { key: 'activity', @@ -348,5 +360,46 @@ export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[ }, ], }, + + { + key: 'logs', + name: 'Logs', + description: 'Usage statistics related to your logs', + attributes: [ + { + anchor: 'logIngestion', + key: PricingMetric.LOG_INGESTION, + attributes: [{ key: PricingMetric.LOG_INGESTION.toLowerCase(), color: 'white' }], + name: 'Log Ingestion', + unit: 'absolute', + description: + 'Total amount of logs ingested across all projects.\nBilling is based on the total amount of logs ingested in Gigabyte.', + chartDescription: 'The data refreshes every hour.', + links: [], + }, + { + anchor: 'logQuery', + key: PricingMetric.LOG_QUERYING, + attributes: [{ key: PricingMetric.LOG_QUERYING.toLowerCase(), color: 'white' }], + name: 'Log Query', + unit: 'absolute', + description: + 'Total amount of logs queried across all projects.\nBilling is based on the total amount of logs queried in Gigabyte.', + chartDescription: 'The data refreshes every hour.', + links: [], + }, + { + anchor: 'logStorage', + key: PricingMetric.LOG_STORAGE, + attributes: [{ key: PricingMetric.LOG_STORAGE.toLowerCase(), color: 'white' }], + name: 'Log Storage', + unit: 'absolute', + description: + 'Total amount of logs stored on the platform. Log retention depends on your platform agreement.\nBilling is based on the total amount of logs stored and factors in the retention period.', + chartDescription: 'The data refreshes every hour.', + links: [], + }, + ], + }, ] } diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index e10934f27454b..c07c217e61250 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -30,6 +30,8 @@ import Compute from './Compute' import Egress from './Egress' import SizeAndCounts from './SizeAndCounts' import { TotalUsage } from './TotalUsage' +import ActiveCompute from './ActiveCompute' +import OrgLogUsage from './OrgLogUsage' export const Usage = () => { const { slug } = useParams() @@ -292,6 +294,13 @@ export const Usage = () => { )} + {subscription?.plan.id === 'platform' && ( + + )} + { orgDailyStats={orgDailyStats} isLoadingOrgDailyStats={isLoadingOrgDailyStats} /> + + {subscription?.plan.id === 'platform' && ( + + )} ) } diff --git a/apps/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx index 97b4ac4864953..b87e27d6547a9 100644 --- a/apps/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/UsageSection/AttributeUsage.tsx @@ -159,7 +159,7 @@ const AttributeUsage = ({ )}
- {usageMeta && ( + {usageMeta && usageMeta.pricing_free_units !== 0 && (

Included in {subscription?.plan?.name} Plan diff --git a/apps/studio/components/interfaces/Organization/Usage/UsageSection/DatabaseSizeUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/UsageSection/DatabaseSizeUsage.tsx index 0a9ea0f3f55a8..c468acdf6190e 100644 --- a/apps/studio/components/interfaces/Organization/Usage/UsageSection/DatabaseSizeUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/UsageSection/DatabaseSizeUsage.tsx @@ -64,22 +64,28 @@ const DatabaseSizeUsage = ({

-
-

- Included in {subscription?.plan?.name} Plan -

-

0.5 GB per project

-
+ {subscription?.plan.id !== 'platform' && ( + <> +
+

+ Included in {subscription?.plan?.name} Plan +

+

0.5 GB per project

+
-
-

Max database size

-

- {databaseSizeUsage?.usage ? formatBytes(databaseSizeUsage?.usage_original) : '-'} -

-
+
+

Max database size

+

+ {databaseSizeUsage?.usage + ? formatBytes(databaseSizeUsage?.usage_original) + : '-'} +

+
+ + )}
- {currentBillingCycleSelected ? ( + {currentBillingCycleSelected && subscription?.plan.id !== 'platform' ? (

Current database size per project

diff --git a/apps/studio/components/interfaces/Organization/Usage/UsageSection/UsageSection.tsx b/apps/studio/components/interfaces/Organization/Usage/UsageSection/UsageSection.tsx index f41aba76e50d2..a971fba54a981 100644 --- a/apps/studio/components/interfaces/Organization/Usage/UsageSection/UsageSection.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/UsageSection/UsageSection.tsx @@ -73,7 +73,7 @@ const UsageSection = ({ currentBillingCycleSelected={currentBillingCycleSelected} usage={usage} /> - ) : attribute.key === PricingMetric.DATABASE_SIZE ? ( + ) : attribute.key === PricingMetric.DATABASE_SIZE && subscription?.plan.id === 'free' ? ( { ) : isProjectActive ? ( - + ( { /> - -
- - - - - - -
+ + + + + + + + + + {!isLoadingPermissions && !canUpdateStorageSettings && ( diff --git a/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx b/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx index 81b293ff70396..47c8a6b1699bc 100644 --- a/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx +++ b/apps/studio/components/interfaces/Storage/StorageSettings/StorageSettings.tsx @@ -244,9 +244,9 @@ export const StorageSettings = () => { {isListV2Upgrading && } )} - + - + { hideMessage layout="flex-row-reverse" label="Global file size limit" + className="[&>div]:md:w-1/2 [&>div]:xl:w-2/5 [&>div>div]:w-full [&>div]:min-w-100" description={ <> Restrict the size of files uploaded across all buckets.{' '} @@ -301,11 +302,11 @@ export const StorageSettings = () => { } > -
+
{ onValueChange={unitField.onChange} disabled={isFreeTier || !canUpdateStorageSettings} > - + {storageUnit} diff --git a/apps/studio/components/ui/BannerStack/Banners/BannerMetricsAPI.tsx b/apps/studio/components/ui/BannerStack/Banners/BannerMetricsAPI.tsx index 3047e480f0a5d..9bf58bb446ff8 100644 --- a/apps/studio/components/ui/BannerStack/Banners/BannerMetricsAPI.tsx +++ b/apps/studio/components/ui/BannerStack/Banners/BannerMetricsAPI.tsx @@ -24,7 +24,7 @@ export const BannerMetricsAPI = () => { onDismiss={() => { setIsDismissed(true) dismissBanner('metrics-api-banner') - track('observability_banner_dismiss_button_clicked') + track('metrics_api_banner_dismiss_button_clicked') }} >
@@ -51,7 +51,7 @@ export const BannerMetricsAPI = () => { track('observability_banner_cta_button_clicked')} + onClick={() => track('metrics_api_banner_cta_button_clicked')} > Get started for free diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index 664b0a4f50e54..b7bbe87a25694 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -1089,8 +1089,20 @@ export interface ReportsDatabaseGrafanaBannerClickedEvent { * @source studio * @page /observability/* */ -export interface ObservabilityBannerCtaButtonClickedEvent { - action: 'observability_banner_cta_button_clicked' +export interface MetricsAPIBannerCtaButtonClickedEvent { + action: 'metrics_api_banner_cta_button_clicked' + groups: TelemetryGroups +} + +/** + * User clicked the dismiss button on a banner in studio Observability pages. + * + * @group Events + * @source studio + * @page /observability/* + */ +export interface MetricsAPIBannerDismissButtonClickedEvent { + action: 'metrics_api_banner_dismiss_button_clicked' groups: TelemetryGroups } @@ -1158,18 +1170,6 @@ export interface IndexAdvisorCreateIndexesButtonClickedEvent { groups: TelemetryGroups } -/** - * User clicked the dismiss button on a banner in studio Observability pages. - * - * @group Events - * @source studio - * @page /observability/* - */ -export interface ObservabilityBannerDismissButtonClickedEvent { - action: 'observability_banner_dismiss_button_clicked' - groups: TelemetryGroups -} - /** * User clicked the deploy button for an Edge Function. * @@ -2661,8 +2661,8 @@ export type TelemetryEvent = | StudioPricingPlanCtaClickedEvent | StudioPricingSidePanelOpenedEvent | ReportsDatabaseGrafanaBannerClickedEvent - | ObservabilityBannerCtaButtonClickedEvent - | ObservabilityBannerDismissButtonClickedEvent + | MetricsAPIBannerCtaButtonClickedEvent + | MetricsAPIBannerDismissButtonClickedEvent | IndexAdvisorBannerEnableButtonClickedEvent | IndexAdvisorBannerDismissButtonClickedEvent | IndexAdvisorDialogEnableButtonClickedEvent