mirror of
https://github.com/supabase/supabase.git
synced 2026-10-18 07:39:07 +03:00
Merge branch 'master' into views
This commit is contained in:
22 files changed
+235
-157
No files matched your search
@@ -34,6 +34,7 @@ Guilherme Souza
|
||||
Haydn Maley
|
||||
Hieu Pham
|
||||
Inian P
|
||||
Jenny Kibiri
|
||||
Jess Shears
|
||||
Joel Lee
|
||||
John Pena
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
clispec: '001'
|
||||
info:
|
||||
id: cli
|
||||
version: 1.187.3
|
||||
version: 1.187.10
|
||||
title: Supabase CLI
|
||||
language: sh
|
||||
source: https://github.com/supabase/cli
|
||||
|
||||
+11
-6
@@ -6,9 +6,10 @@ export interface Metric {
|
||||
key: string
|
||||
name: string
|
||||
units: string
|
||||
anchor: string
|
||||
anchor?: string
|
||||
category: string
|
||||
unitName: string
|
||||
unitName?: string
|
||||
tip?: string
|
||||
}
|
||||
|
||||
export const BILLING_BREAKDOWN_METRICS: Metric[] = [
|
||||
@@ -58,7 +59,6 @@ export const BILLING_BREAKDOWN_METRICS: Metric[] = [
|
||||
units: 'absolute',
|
||||
anchor: 'storageImageTransformations',
|
||||
category: 'Storage',
|
||||
unitName: 'image',
|
||||
},
|
||||
{
|
||||
key: PricingMetric.REALTIME_PEAK_CONNECTIONS,
|
||||
@@ -66,7 +66,6 @@ export const BILLING_BREAKDOWN_METRICS: Metric[] = [
|
||||
units: 'absolute',
|
||||
anchor: 'realtimePeakConnections',
|
||||
category: 'Realtime',
|
||||
unitName: 'connection',
|
||||
},
|
||||
{
|
||||
key: PricingMetric.REALTIME_MESSAGE_COUNT,
|
||||
@@ -74,7 +73,6 @@ export const BILLING_BREAKDOWN_METRICS: Metric[] = [
|
||||
units: 'absolute',
|
||||
anchor: 'realtimeMessageCount',
|
||||
category: 'Realtime',
|
||||
unitName: 'message',
|
||||
},
|
||||
{
|
||||
key: PricingMetric.FUNCTION_INVOCATIONS,
|
||||
@@ -82,6 +80,13 @@ export const BILLING_BREAKDOWN_METRICS: Metric[] = [
|
||||
units: 'absolute',
|
||||
anchor: 'funcInvocations',
|
||||
category: 'Edge Functions',
|
||||
unitName: 'invocation',
|
||||
},
|
||||
{
|
||||
key: PricingMetric.DISK_SIZE_GB_HOURS,
|
||||
name: 'Disk Size',
|
||||
units: 'absolute',
|
||||
unitName: 'GB-Hrs',
|
||||
category: 'Database',
|
||||
tip: 'Each project gets provisioned with 8 GB of disk. When you get close to the disk size limit, we autoscale your disk by 1.5x. The first 8 GB of disk is free for every single project.',
|
||||
},
|
||||
]
|
||||
+1
-1
@@ -52,7 +52,7 @@ const BillingBreakdown = () => {
|
||||
</ScaffoldSectionDetail>
|
||||
<ScaffoldSectionContent>
|
||||
{!canReadSubscriptions ? (
|
||||
<NoPermission resourceText="view this organization's billing" />
|
||||
<NoPermission resourceText="view this organization's billing breakdown" />
|
||||
) : (
|
||||
<>
|
||||
{isLoadingSubscription && (
|
||||
|
||||
+72
-50
@@ -36,21 +36,30 @@ const BillingMetric = ({
|
||||
} else if (
|
||||
(usageMeta.cost && usageMeta.cost > 0) ||
|
||||
!relativeToSubscription ||
|
||||
usageMeta.unlimited
|
||||
usageMeta.unlimited ||
|
||||
usageMeta.pricing_free_units === 0
|
||||
) {
|
||||
return metric.units === 'bytes'
|
||||
return metric.units === 'bytes' || metric.units === 'gigabytes'
|
||||
? `${usageMeta.usage.toLocaleString() ?? 0} GB`
|
||||
: usageMeta.usage.toLocaleString()
|
||||
: usageMeta.usage.toLocaleString() + (metric.unitName ? ` ${metric.unitName}` : '')
|
||||
} else {
|
||||
return metric.units === 'bytes'
|
||||
return metric.units === 'bytes' || metric.units === 'gigabytes'
|
||||
? `${usageMeta.usage.toLocaleString() ?? 0} / ${usageMeta.pricing_free_units ?? 0} GB`
|
||||
: `${usageMeta.usage.toLocaleString()} / ${usageMeta.pricing_free_units?.toLocaleString()}`
|
||||
: `${usageMeta.usage.toLocaleString()} / ${usageMeta.pricing_free_units?.toLocaleString()}` +
|
||||
(metric.unitName ? ` ${metric.unitName}` : '')
|
||||
}
|
||||
}, [usageMeta, relativeToSubscription, metric])
|
||||
|
||||
const sortedProjectAllocations = useMemo(() => {
|
||||
if (!usageMeta || !usageMeta.project_allocations) return []
|
||||
|
||||
return usageMeta.project_allocations.sort((a, b) => b.usage - a.usage)
|
||||
}, [usageMeta])
|
||||
|
||||
if (!usageMeta) return null
|
||||
|
||||
const usageRatio = usageMeta.usage / (usageMeta.pricing_free_units ?? 0)
|
||||
const usageRatio =
|
||||
usageMeta.usage === 0 ? 0 : usageMeta.usage / (usageMeta.pricing_free_units ?? 0)
|
||||
|
||||
const isUsageBillingEnabled = subscription?.usage_billing_enabled === true
|
||||
|
||||
@@ -61,24 +70,32 @@ const BillingMetric = ({
|
||||
const unit = billingMetricUnit(usageMeta.metric as PricingMetric)
|
||||
|
||||
const percentageLabel =
|
||||
usageRatio < 0.01 ? '<1%' : `${(+(usageRatio * 100).toFixed(0)).toLocaleString()}%`
|
||||
usageMeta.usage === 0 || usageMeta.pricing_free_units === 0
|
||||
? ''
|
||||
: usageRatio < 0.01
|
||||
? '(<1%)'
|
||||
: `(${(+(usageRatio * 100).toFixed(0)).toLocaleString()}%)`
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Link href={`/org/${slug}/usage#${metric.anchor}`}>
|
||||
<div className="group flex items-center space-x-2">
|
||||
<p className="text-sm text-foreground-light group-hover:text-foreground transition cursor-pointer">
|
||||
{metric.name}
|
||||
</p>
|
||||
<IconChevronRight strokeWidth={1.5} size={16} className="transition" />
|
||||
</div>
|
||||
</Link>
|
||||
{metric.anchor ? (
|
||||
<Link href={`/org/${slug}/usage#${metric.anchor}`}>
|
||||
<div className="group flex items-center space-x-2">
|
||||
<p className="text-sm text-foreground-light group-hover:text-foreground transition cursor-pointer">
|
||||
{metric.name}
|
||||
</p>
|
||||
<IconChevronRight strokeWidth={1.5} size={16} className="transition" />
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<p className="text-sm text-foreground-light">{metric.name}</p>
|
||||
)}
|
||||
<span className="text-sm">{usageLabel}</span>
|
||||
{usageMeta.cost && usageMeta.cost > 0 ? (
|
||||
{relativeToSubscription && usageMeta.cost && usageMeta.cost > 0 ? (
|
||||
<span className="text-sm">({formatCurrency(usageMeta.cost)})</span>
|
||||
) : usageMeta.available_in_plan && !usageMeta.unlimited && relativeToSubscription ? (
|
||||
<span className="text-sm">({percentageLabel})</span>
|
||||
<span className="text-sm">{percentageLabel}</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -130,39 +147,10 @@ const BillingMetric = ({
|
||||
<div className="text-xs text-foreground space-y-2">
|
||||
<p className="font-medium">{usageMeta.unit_price_desc}</p>
|
||||
|
||||
{usageMeta.project_allocations && usageMeta.project_allocations.length > 0 && (
|
||||
<table className="list-disc w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">Project</th>
|
||||
<th className="text-right">Usage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{usageMeta.project_allocations.map((allocation) => (
|
||||
<tr key={`${usageMeta.metric}_${allocation.ref}`}>
|
||||
<td>{allocation.name}</td>
|
||||
<td className="text-right">
|
||||
{formatUsage(usageMeta.metric as PricingMetric, allocation.usage)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr></tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td className="py-2 border-t text-left">
|
||||
Total{unit && <span> ({unit})</span>}
|
||||
</td>
|
||||
<td className="py-2 border-t text-right">
|
||||
{formatUsage(
|
||||
usageMeta.metric as PricingMetric,
|
||||
usageMeta.usage_original
|
||||
)}{' '}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
{metric.tip && (
|
||||
<div className="my-2">
|
||||
<p className="text-xs">{metric.tip}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{subscription.usage_billing_enabled === false &&
|
||||
@@ -176,6 +164,40 @@ const BillingMetric = ({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sortedProjectAllocations && sortedProjectAllocations.length > 0 && (
|
||||
<table className="list-disc w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">Project</th>
|
||||
<th className="text-right">Usage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedProjectAllocations.map((allocation) => (
|
||||
<tr key={`${usageMeta.metric}_${allocation.ref}`}>
|
||||
<td>{allocation.name}</td>
|
||||
<td className="text-right">
|
||||
{formatUsage(usageMeta.metric as PricingMetric, allocation)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr></tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td className="py-2 border-t text-left">
|
||||
Total{unit && <span> ({unit})</span>}
|
||||
</td>
|
||||
<td className="py-2 border-t text-right">
|
||||
{formatUsage(usageMeta.metric as PricingMetric, {
|
||||
usage: usageMeta.usage_original,
|
||||
})}{' '}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
|
||||
+42
-37
@@ -20,15 +20,21 @@ const ComputeMetric = ({ slug, metric, usage, relativeToSubscription }: ComputeM
|
||||
const usageMeta = usage.usages.find((x) => x.metric === metric.key)
|
||||
|
||||
const usageLabel = useMemo(() => {
|
||||
if (usageMeta?.cost && usageMeta.cost > 0) {
|
||||
return `${usageMeta?.usage?.toLocaleString() ?? 0} hours`
|
||||
} else {
|
||||
if (usageMeta?.pricing_free_units) {
|
||||
return `${usageMeta?.usage?.toLocaleString() ?? 0} / ${
|
||||
usageMeta?.pricing_free_units ?? 0
|
||||
} hours`
|
||||
} else {
|
||||
return `${usageMeta?.usage?.toLocaleString() ?? 0} hours`
|
||||
}
|
||||
}, [usageMeta])
|
||||
|
||||
const sortedProjectAllocations = useMemo(() => {
|
||||
if (!usageMeta || !usageMeta.project_allocations) return []
|
||||
|
||||
return usageMeta.project_allocations.sort((a, b) => b.usage - a.usage)
|
||||
}, [usageMeta])
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -57,40 +63,7 @@ const ComputeMetric = ({ slug, metric, usage, relativeToSubscription }: ComputeM
|
||||
<div className="text-xs text-foreground space-y-2">
|
||||
<p className="font-medium">{usageMeta?.unit_price_desc}</p>
|
||||
|
||||
{usageMeta?.project_allocations && usageMeta.project_allocations.length > 0 && (
|
||||
<table className="list-disc w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">Project</th>
|
||||
<th className="text-right">Usage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{usageMeta.project_allocations.map((allocation) => (
|
||||
<tr key={`${usageMeta.metric}_${allocation.ref}`}>
|
||||
<td>{allocation.name}</td>
|
||||
<td className="text-right">
|
||||
{formatUsage(usageMeta.metric as PricingMetric, allocation.usage)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr></tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td className="py-2 border-t text-left">Total (Hours)</td>
|
||||
<td className="py-2 border-t text-right">
|
||||
{formatUsage(
|
||||
usageMeta.metric as PricingMetric,
|
||||
usageMeta.usage_original
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
)}
|
||||
|
||||
<div className="mt-2">
|
||||
<div className="my-2">
|
||||
<p className="text-xs">
|
||||
Every project is a dedicated server and database. For every hour your project
|
||||
is active, it incurs compute costs based on the compute size of your project.
|
||||
@@ -104,6 +77,38 @@ const ComputeMetric = ({ slug, metric, usage, relativeToSubscription }: ComputeM
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{usageMeta && sortedProjectAllocations.length > 0 && (
|
||||
<table className="list-disc w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">Project</th>
|
||||
<th className="text-right">Usage</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedProjectAllocations.map((allocation) => (
|
||||
<tr key={`${usageMeta.metric}_${allocation.ref}`}>
|
||||
<td>{allocation.name}</td>
|
||||
<td className="text-right">
|
||||
{formatUsage(usageMeta.metric as PricingMetric, allocation)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<tr></tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td className="py-2 border-t text-left">Total (Hours)</td>
|
||||
<td className="py-2 border-t text-right">
|
||||
{formatUsage(usageMeta.metric as PricingMetric, {
|
||||
usage: usageMeta.usage_original,
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip.Content>
|
||||
|
||||
+2
-2
@@ -193,7 +193,7 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
|
||||
</td>
|
||||
<td className="py-2 pr-4 text-sm text-right tabular-nums max-w-[100px]">
|
||||
{fee.usage_original
|
||||
? `${formatUsage(fee.usage_metric!, fee.usage_original)}`
|
||||
? `${formatUsage(fee.usage_metric!, { usage: fee.usage_original })}`
|
||||
: fee.quantity
|
||||
? fee.quantity
|
||||
: null}
|
||||
@@ -223,7 +223,7 @@ const UpcomingInvoice = ({ slug }: UpcomingInvoiceProps) => {
|
||||
{breakdown.project_name}
|
||||
</td>
|
||||
<td className="pb-1 text-xs tabular-nums text-right pr-4">
|
||||
{formatUsage(fee.usage_metric!, breakdown.usage)}
|
||||
{formatUsage(fee.usage_metric!, breakdown)}
|
||||
</td>
|
||||
<td />
|
||||
<td />
|
||||
|
||||
@@ -6,11 +6,32 @@ const pricingMetricBytes = [
|
||||
PricingMetric.STORAGE_SIZE,
|
||||
]
|
||||
|
||||
export const formatUsage = (pricingMetric: PricingMetric, usage: number) => {
|
||||
export const formatUsage = (
|
||||
pricingMetric: PricingMetric,
|
||||
allocation: { usage: number; hours?: number }
|
||||
) => {
|
||||
if (allocation.hours) {
|
||||
return (
|
||||
allocation.usage.toLocaleString() +
|
||||
' (' +
|
||||
Math.round(allocation.usage / allocation.hours).toLocaleString() +
|
||||
'x' +
|
||||
allocation.hours.toLocaleString() +
|
||||
' hours)'
|
||||
)
|
||||
}
|
||||
|
||||
if (pricingMetricBytes.includes(pricingMetric)) {
|
||||
return +(usage / 1e9).toFixed(2).toLocaleString()
|
||||
const formattedUsage = +(allocation.usage / 1e9).toFixed(2).toLocaleString()
|
||||
|
||||
// To avoid very low usage displaying as "0", we will show "<0.01" instead
|
||||
if (allocation.usage > 0 && formattedUsage === 0) {
|
||||
return '<0.01'
|
||||
} else {
|
||||
return formattedUsage
|
||||
}
|
||||
} else {
|
||||
return usage.toLocaleString()
|
||||
return allocation.usage.toLocaleString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ const SidePanelGitHubRepoLinker = ({ projectRef }: SidePanelGitHubRepoLinkerProp
|
||||
const sidePanelStateSnapshot = useSidePanelsStateSnapshot()
|
||||
|
||||
const { data: gitHubAuthorization, isLoading: isLoadingGitHubAuthorization } =
|
||||
useGitHubAuthorizationQuery()
|
||||
useGitHubAuthorizationQuery({ enabled: sidePanelStateSnapshot.githubConnectionsOpen })
|
||||
|
||||
// [Alaister]: temp override with <any> until the typegen is fixed
|
||||
const { data: githubReposData, isLoading: isLoadingGitHubRepos } = useGitHubRepositoriesQuery<
|
||||
|
||||
@@ -61,7 +61,7 @@ const OAuthApps = () => {
|
||||
if (!canReadOAuthApps) {
|
||||
return (
|
||||
<ScaffoldContainerLegacy>
|
||||
<NoPermission resourceText="view invoices" />
|
||||
<NoPermission resourceText="view OAuth apps" />
|
||||
</ScaffoldContainerLegacy>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,9 +58,13 @@ const TotalUsage = ({
|
||||
)
|
||||
|
||||
const sortedBillingMetrics = useMemo(() => {
|
||||
if (!usage) return BILLING_BREAKDOWN_METRICS
|
||||
if (!usage) return []
|
||||
|
||||
return BILLING_BREAKDOWN_METRICS.slice().sort((a, b) => {
|
||||
const breakdownMetrics = BILLING_BREAKDOWN_METRICS.filter((metric) =>
|
||||
usage.usages.some((usage) => !usage.available_in_plan || usage.metric === metric.key)
|
||||
)
|
||||
|
||||
return breakdownMetrics.slice().sort((a, b) => {
|
||||
const usageMetaA = usage.usages.find((x) => x.metric === a.key)
|
||||
const usageRatioA =
|
||||
typeof usageMetaA !== 'number'
|
||||
@@ -92,8 +96,8 @@ const TotalUsage = ({
|
||||
section={{
|
||||
name: 'Usage Summary',
|
||||
description: isUsageBillingEnabled
|
||||
? 'Your plan includes a limited amount of usage. If the usage on your organization exceeds these quotas, your subscription will be charged for the overages. It may take up to 24 hours for usage stats to update.'
|
||||
: 'Your plan includes a limited amount of usage. If the usage on your organization exceeds these quotas, you may experience restrictions, as you are currently not billed for overages. It may take up to 24 hours for usage stats to update.',
|
||||
? `Your plan includes a limited amount of usage. If the usage on your organization exceeds these quotas, your subscription will be charged for the overages. It may take up to ${subscription?.usage_based_billing_project_addons ? '1 hour' : '24 hours'} for usage stats to update.`
|
||||
: `Your plan includes a limited amount of usage. If the usage on your organization exceeds these quotas, you may experience restrictions, as you are currently not billed for overages. It may take up to ${subscription?.usage_based_billing_project_addons ? '1 hour' : '24 hours'} for usage stats to update.`,
|
||||
links: [
|
||||
{
|
||||
name: 'How billing works',
|
||||
@@ -184,7 +188,8 @@ const TotalUsage = ({
|
||||
<div
|
||||
className={clsx(
|
||||
'col-span-12 md:col-span-6 space-y-4 py-4 border-overlay',
|
||||
i % 2 === 0 ? 'md:border-r md:pr-4' : 'md:pl-4'
|
||||
(i + sortedBillingMetrics.length) % 2 === 0 ? 'md:border-r md:pr-4' : 'md:pl-4',
|
||||
'border-b'
|
||||
)}
|
||||
key={metric}
|
||||
>
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface CategoryAttribute {
|
||||
key: string // Property from organization usage
|
||||
attributes: Attribute[] // For querying against stats-daily / infra-monitoring
|
||||
name: string
|
||||
unit: 'bytes' | 'absolute' | 'percentage' | 'hours'
|
||||
unit: 'bytes' | 'absolute' | 'percentage' | 'hours' | 'gigabytes'
|
||||
links?: {
|
||||
name: string
|
||||
url: string
|
||||
@@ -56,7 +56,7 @@ export interface CategoryAttribute {
|
||||
chartPrefix?: 'Max' | 'Average' | 'Cumulative'
|
||||
chartSuffix?: string
|
||||
chartDescription: string
|
||||
additionalInfo?: (subscription?: OrgSubscription, usage?: OrgUsageResponse) => JSX.Element | null
|
||||
additionalInfo?: (usage?: OrgUsageResponse) => JSX.Element | null
|
||||
}
|
||||
|
||||
export type CategoryMetaKey = 'bandwidth' | 'sizeCount' | 'activity' | 'compute'
|
||||
@@ -68,7 +68,9 @@ export interface CategoryMeta {
|
||||
attributes: CategoryAttribute[]
|
||||
}
|
||||
|
||||
export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
export const USAGE_CATEGORIES: (subscription?: OrgSubscription) => CategoryMeta[] = (
|
||||
subscription
|
||||
) => [
|
||||
{
|
||||
key: 'bandwidth',
|
||||
name: 'Bandwidth',
|
||||
@@ -106,15 +108,25 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
chartPrefix: 'Average',
|
||||
unit: 'bytes',
|
||||
description:
|
||||
'Database size refers to the monthly average storage usage, as reported by Postgres. Paid Plans use auto-scaling disks.\nBilling is based on the average daily database size used in GB throughout the billing period. Billing is independent of the provisioned disk size.',
|
||||
subscription?.usage_based_billing_project_addons === true
|
||||
? 'Database size refers to the monthly average database space usage, as reported by Postgres. Paid Plans use auto-scaling disks and are billed based on provisioned disk size, rather than database space used.'
|
||||
: 'Database size refers to the monthly average database space usage, as reported by Postgres. Paid Plans use auto-scaling disks.\nBilling is based on the average daily database size used in GB throughout the billing period. Billing is independent of the provisioned disk size.',
|
||||
links: [
|
||||
{
|
||||
name: 'Documentation',
|
||||
url: 'https://supabase.com/docs/guides/platform/database-size',
|
||||
},
|
||||
...(subscription?.usage_based_billing_project_addons === true
|
||||
? [
|
||||
{
|
||||
name: 'Disk Management',
|
||||
url: 'https://supabase.com/docs/guides/platform/database-size#disk-management',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
chartDescription: 'The data refreshes every 24 hours.',
|
||||
additionalInfo: (subscription?: OrgSubscription, usage?: OrgUsageResponse) => {
|
||||
additionalInfo: (usage?: OrgUsageResponse) => {
|
||||
const usageMeta = usage?.usages.find((x) => x.metric === PricingMetric.DATABASE_SIZE)
|
||||
const usageRatio =
|
||||
typeof usageMeta !== 'number'
|
||||
|
||||
@@ -38,6 +38,8 @@ export const ChartYFormatterCompactNumber = (number: number | string, unit: stri
|
||||
const formattedBytes = formatBytesCompact(number).replace(/\s/g, '')
|
||||
|
||||
return formattedBytes === '0bytes' ? '0' : formattedBytes
|
||||
} else if (unit === 'gigabytes') {
|
||||
return compactNumberFormatter.format(number) + 'GB'
|
||||
} else {
|
||||
return compactNumberFormatter.format(number)
|
||||
}
|
||||
@@ -53,6 +55,8 @@ export const ChartTooltipValueFormatter = (number: number | string, unit: string
|
||||
const formattedBytes = formatBytesPrecision(number).replace(/\s/g, '')
|
||||
|
||||
return formattedBytes === '0bytes' ? '0' : formattedBytes
|
||||
} else if (unit === 'gigabytes') {
|
||||
return compactNumberFormatter.format(number) + 'GB'
|
||||
} else {
|
||||
return compactNumberFormatter.format(number)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export interface UsageBarChartProps {
|
||||
data: DataPoint[]
|
||||
name: string // Used within the tooltip
|
||||
attributes: Attribute[]
|
||||
unit: 'bytes' | 'absolute' | 'percentage' | 'hours'
|
||||
unit: 'bytes' | 'absolute' | 'percentage' | 'hours' | 'gigabytes'
|
||||
yLimit?: number
|
||||
yMin?: number
|
||||
yLeftMargin?: number
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Attribute, COLOR_MAP } from './Usage.constants'
|
||||
|
||||
export interface SingleAttributeTooltipContentProps {
|
||||
name: string
|
||||
unit: 'bytes' | 'percentage' | 'absolute' | 'hours'
|
||||
unit: 'bytes' | 'percentage' | 'absolute' | 'hours' | 'gigabytes'
|
||||
value: any
|
||||
isAfterToday: boolean
|
||||
tooltipFormatter?: (value: any) => any
|
||||
@@ -37,7 +37,7 @@ export interface MultiAttributeTooltipContentProps {
|
||||
values: Payload<ValueType, string | number>[]
|
||||
isAfterToday: boolean
|
||||
tooltipFormatter?: (value: any) => any
|
||||
unit: 'bytes' | 'percentage' | 'absolute' | 'hours'
|
||||
unit: 'bytes' | 'percentage' | 'absolute' | 'hours' | 'gigabytes'
|
||||
}
|
||||
|
||||
const AttributeContent = ({
|
||||
@@ -51,7 +51,7 @@ const AttributeContent = ({
|
||||
attributeMeta?: Payload<ValueType, string | number>
|
||||
sumValue: number
|
||||
tooltipFormatter?: (value: any) => any
|
||||
unit: 'bytes' | 'percentage' | 'absolute' | 'hours'
|
||||
unit: 'bytes' | 'percentage' | 'absolute' | 'hours' | 'gigabytes'
|
||||
}) => {
|
||||
const attrValue = Number(attributeMeta?.value ?? 0)
|
||||
const percentageContribution = ((attrValue / sumValue) * 100).toFixed(1)
|
||||
|
||||
+32
-33
@@ -93,7 +93,7 @@ const AttributeUsage = ({
|
||||
|
||||
{isSuccess && (
|
||||
<>
|
||||
{usageMeta?.available_in_plan ? (
|
||||
{!usageMeta || usageMeta?.available_in_plan ? (
|
||||
<>
|
||||
{!projectRef && (
|
||||
<div className="space-y-2">
|
||||
@@ -150,7 +150,7 @@ const AttributeUsage = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{currentBillingCycleSelected && !usageMeta.unlimited && (
|
||||
{currentBillingCycleSelected && usageMeta && !usageMeta.unlimited && (
|
||||
<SparkBar
|
||||
type="horizontal"
|
||||
barClass={clsx(
|
||||
@@ -169,8 +169,8 @@ const AttributeUsage = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{
|
||||
<div>
|
||||
<div>
|
||||
{usageMeta && (
|
||||
<div className="flex items-center justify-between border-b py-1">
|
||||
<p className="text-xs text-foreground-light">
|
||||
Included in {subscription?.plan?.name.toLowerCase()} plan
|
||||
@@ -179,43 +179,42 @@ const AttributeUsage = ({
|
||||
<p className="text-xs">Unlimited</p>
|
||||
) : (
|
||||
<p className="text-xs">
|
||||
{attribute.unit === 'bytes'
|
||||
{attribute.unit === 'bytes' || attribute.unit === 'gigabytes'
|
||||
? `${usageMeta.pricing_free_units ?? 0} GB`
|
||||
: (usageMeta.pricing_free_units ?? 0).toLocaleString()}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{currentBillingCycleSelected && (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<p className="text-xs text-foreground-light">
|
||||
{attribute.chartPrefix || 'Used '} in period
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
{attribute.unit === 'bytes'
|
||||
? `${(usageMeta?.usage ?? 0).toFixed(2)} GB`
|
||||
: (usageMeta?.usage ?? 0).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{currentBillingCycleSelected &&
|
||||
(usageMeta?.pricing_free_units ?? 0) > 0 && (
|
||||
<div className="flex items-center justify-between border-t py-1">
|
||||
<p className="text-xs text-foreground-light">Overage in period</p>
|
||||
<p className="text-xs">
|
||||
{(usageMeta?.pricing_free_units ?? 0) === -1 || usageExcess < 0
|
||||
? `0${attribute.unit === 'bytes' ? ' GB' : ''}`
|
||||
: attribute.unit === 'bytes'
|
||||
? `${usageExcess.toFixed(2)} GB`
|
||||
: usageExcess.toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
{currentBillingCycleSelected && usageMeta && (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<p className="text-xs text-foreground-light">
|
||||
{attribute.chartPrefix || 'Used '} in period
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
{attribute.unit === 'bytes' || attribute.unit === 'gigabytes'
|
||||
? `${(usageMeta?.usage ?? 0).toFixed(2)} GB`
|
||||
: (usageMeta?.usage ?? 0).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{currentBillingCycleSelected && (usageMeta?.pricing_free_units ?? 0) > 0 && (
|
||||
<div className="flex items-center justify-between border-t py-1">
|
||||
<p className="text-xs text-foreground-light">Overage in period</p>
|
||||
<p className="text-xs">
|
||||
{(usageMeta?.pricing_free_units ?? 0) === -1 || usageExcess < 0
|
||||
? `0${attribute.unit === 'bytes' || attribute.unit === 'gigabytes' ? ' GB' : ''}`
|
||||
: attribute.unit === 'bytes' || attribute.unit === 'gigabytes'
|
||||
? `${usageExcess.toFixed(2)} GB`
|
||||
: usageExcess.toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{attribute.additionalInfo?.(subscription, usage)}
|
||||
{attribute.additionalInfo?.(usage)}
|
||||
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm">
|
||||
|
||||
@@ -35,7 +35,9 @@ const UsageSection = ({
|
||||
isSuccess: isSuccessUsage,
|
||||
} = useOrgUsageQuery({ orgSlug })
|
||||
|
||||
const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === categoryKey)
|
||||
const categoryMeta = USAGE_CATEGORIES(subscription).find(
|
||||
(category) => category.key === categoryKey
|
||||
)
|
||||
if (!categoryMeta) return null
|
||||
|
||||
return (
|
||||
|
||||
@@ -22,7 +22,7 @@ const NoPermission = ({ resourceText, isFullPage = false }: NoPermissionProps) =
|
||||
<p className="text-sm">You need additional permissions to {resourceText}</p>
|
||||
<div>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Contact your organization owner or adminstrator for assistance.
|
||||
Contact your organization owner or administrator for assistance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,7 @@ export enum PricingMetric {
|
||||
EGRESS = 'EGRESS',
|
||||
DATABASE_SIZE = 'DATABASE_SIZE',
|
||||
STORAGE_SIZE = 'STORAGE_SIZE',
|
||||
DISK_SIZE_GB_HOURS = 'DISK_SIZE_GB_HOURS',
|
||||
MONTHLY_ACTIVE_USERS = 'MONTHLY_ACTIVE_USERS',
|
||||
MONTHLY_ACTIVE_SSO_USERS = 'MONTHLY_ACTIVE_SSO_USERS',
|
||||
FUNCTION_INVOCATIONS = 'FUNCTION_INVOCATIONS',
|
||||
|
||||
@@ -10,7 +10,7 @@ version: "3.8"
|
||||
services:
|
||||
studio:
|
||||
container_name: supabase-studio
|
||||
image: supabase/studio:20240722-c670708
|
||||
image: supabase/studio:20240729-ce42139
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test:
|
||||
|
||||
Generated
+3
-2
@@ -7844,8 +7844,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"license": "MIT"
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.25",
|
||||
|
||||
Vendored
+3
-3
@@ -3911,7 +3911,7 @@ export interface components {
|
||||
| 'STORAGE_IMAGES_TRANSFORMED'
|
||||
| 'REALTIME_MESSAGE_COUNT'
|
||||
| 'REALTIME_PEAK_CONNECTIONS'
|
||||
| 'DISK_GB_HOURS'
|
||||
| 'DISK_SIZE_GB_HOURS'
|
||||
| 'COMPUTE_HOURS_BRANCH'
|
||||
| 'COMPUTE_HOURS_XS'
|
||||
| 'COMPUTE_HOURS_SM'
|
||||
@@ -3941,7 +3941,6 @@ export interface components {
|
||||
usage_original: number
|
||||
}
|
||||
OrgUsageResponse: {
|
||||
slugs: string[]
|
||||
usage_billing_enabled: boolean
|
||||
usages: components['schemas']['OrgMetricUsage'][]
|
||||
}
|
||||
@@ -4308,6 +4307,7 @@ export interface components {
|
||||
price_type: components['schemas']['ProjectAddonVariantPricingType']
|
||||
}
|
||||
ProjectAllocation: {
|
||||
hours?: number
|
||||
name: string
|
||||
ref: string
|
||||
usage: number
|
||||
@@ -7803,7 +7803,7 @@ export interface operations {
|
||||
| 'STORAGE_IMAGES_TRANSFORMED'
|
||||
| 'REALTIME_MESSAGE_COUNT'
|
||||
| 'REALTIME_PEAK_CONNECTIONS'
|
||||
| 'DISK_GB_HOURS'
|
||||
| 'DISK_SIZE_GB_HOURS'
|
||||
| 'COMPUTE_HOURS_BRANCH'
|
||||
| 'COMPUTE_HOURS_XS'
|
||||
| 'COMPUTE_HOURS_SM'
|
||||
|
||||
Reference in new issue
Block a user