mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 11:55:05 +03:00
* update deps + image codemod (studio) * update next links (studio) * update deps * update links (ui) * remove next-transpile-modules * move next-themes dependency * chore: update ConfirmDialog * chore: remove old ConfirmModal js file. migrated to TS * dependency wrangling * remove empty page * update next links (www) * First run bump react-data-grid-v7 beta 4 * fix package-lock.json * more deps wrangling * update recharts * update sentry options * fix some broken things in www * studio fixes * fix graphiql * fix studio build * fix menu hydration * small build error * update turbo * fix www typescript errors * docs image codemod * links codemod docs * fix docs typescript errors * move useConsent to ui to prevent circular deps * Fix links * Fix homepage * Fix links * move studio/ to apps/ * Revert "move studio/ to apps/" This reverts commit1b0a985fcb. * disable outputFileTracingRoot * remove outputFileTracingRoot * fix homepage product cards * fix PrivacySettings links * Fix links * Fix the build for www. * Minor fixes for JWTGenerator. * Fix the docs and ui tests. * Revert codehike back to 0.8.3 * remove ConfirmAlert() * reenable babel because mobx hates me * fix blog image and comparison page avatar * Fix svg errors * update image synthax * Fix code hike * Move the button in a div so that it doesn't inherit its parent height and make the button look weird. * When components are defined in a component, they get recreated on each render. This makes them unstable in certain cases and causes infinite rerenders. * Replace the next/head usage with next/script. * Chore/upgrade next 13 fix table editor (#18431) * fix table editor styling and fix row deletion logic * Fix deleting selected rows from header, and fix checkboxes not clearing up * Fix deleting all rows when filter applied, and fix deleting all rows * Fix grid size styling issue * Fix TS error * Hydration errors * studio org pages fixes * fix more studio links * audit logs fixes * dropdown icon styling fixes * fix some images in www * upgrade to next 14 * try new sentry wrapper for api * see if this is even invoked * Revert "see if this is even invoked" This reverts commit86c3973ffa. * Revert "try new sentry wrapper for api" This reverts commitf67623ebad. * Revert "upgrade to next 14" This reverts commita24dd6131e. * chore: allow node version 19/20 * Try to fix the LogTable so that it renders with the newer "react-data-grid" version. * Fix type errors in the log renderer code. * Fix the replication screen. * Add the CSS for the GraphiQL. * Fix SQL editor results rendering * Lint * Fix SQL editor results height issue * Fix auth RLS not invalidating RQ when toggling RLS * Fix database tables new/edit column regressed * Fix migrations page empty state if migrations schema not yet created * Fix API side panel docs temp remove postgrest text for column description PK and FK * Fix + improve timeout handling in SQL editor --------- Co-authored-by: Jonathan Summers-Muir <MildTomato@users.noreply.github.com> Co-authored-by: Joshen Lim <joshenlimek@gmail.com> Co-authored-by: Francesco Sansalvadore <f.sansalvadore@gmail.com> Co-authored-by: Terry Sutton <saltcod@gmail.com> Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com> Co-authored-by: Kevin Grüneberg <k.grueneberg1994@gmail.com>
262 lines
9.8 KiB
TypeScript
262 lines
9.8 KiB
TypeScript
import clsx from 'clsx'
|
|
import dayjs from 'dayjs'
|
|
import { useEffect, useMemo, useState } from 'react'
|
|
|
|
import { useParams } from 'common'
|
|
import { ScaffoldContainer } from 'components/layouts/Scaffold'
|
|
import DateRangePicker from 'components/to-be-cleaned/DateRangePicker'
|
|
import AlertError from 'components/ui/AlertError'
|
|
import InformationBox from 'components/ui/InformationBox'
|
|
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
|
import { useProjectsQuery } from 'data/projects/projects-query'
|
|
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
|
|
import { useOrgUsageQuery } from 'data/usage/org-usage-query'
|
|
import { useSelectedOrganization } from 'hooks'
|
|
import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants'
|
|
import Link from 'next/link'
|
|
import { Alert, Button, IconExternalLink, IconInfo, Listbox } from 'ui'
|
|
import Activity from './Activity'
|
|
import Bandwidth from './Bandwidth'
|
|
import SizeAndCounts from './SizeAndCounts'
|
|
|
|
const Usage = () => {
|
|
const { slug, projectRef } = useParams()
|
|
const [dateRange, setDateRange] = useState<any>()
|
|
const [selectedProjectRef, setSelectedProjectRef] = useState<string>()
|
|
|
|
const organization = useSelectedOrganization()
|
|
const { data: projects, isSuccess } = useProjectsQuery()
|
|
const {
|
|
data: subscription,
|
|
error: subscriptionError,
|
|
isLoading: isLoadingSubscription,
|
|
isError: isErrorSubscription,
|
|
isSuccess: isSuccessSubscription,
|
|
} = useOrgSubscriptionQuery({ orgSlug: slug })
|
|
|
|
const { data: usage } = useOrgUsageQuery({ orgSlug: slug })
|
|
|
|
const orgProjects = projects?.filter((project) => project.organization_id === organization?.id)
|
|
|
|
useEffect(() => {
|
|
if (projectRef && isSuccess && orgProjects !== undefined) {
|
|
if (orgProjects.find((project) => project.ref === projectRef)) {
|
|
setSelectedProjectRef(projectRef)
|
|
}
|
|
}
|
|
// [Joshen] Since we're already looking at isSuccess
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [projectRef, isSuccess])
|
|
|
|
const billingCycleStart = dayjs.unix(subscription?.current_period_start ?? 0).utc()
|
|
const billingCycleEnd = dayjs.unix(subscription?.current_period_end ?? 0).utc()
|
|
|
|
const currentBillingCycleSelected = useMemo(() => {
|
|
// Selected by default
|
|
if (!dateRange?.period_start || !dateRange?.period_end || !subscription) return true
|
|
|
|
const { current_period_start, current_period_end } = subscription
|
|
|
|
return (
|
|
dayjs(dateRange.period_start.date).isSame(new Date(current_period_start * 1000)) &&
|
|
dayjs(dateRange.period_end.date).isSame(new Date(current_period_end * 1000))
|
|
)
|
|
}, [dateRange, subscription])
|
|
|
|
const startDate = useMemo(() => {
|
|
// If end date is in future, set end date to now
|
|
if (!dateRange?.period_start?.date) {
|
|
return undefined
|
|
} else {
|
|
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
|
|
return new Date(dateRange?.period_start?.date).toISOString().slice(0, -5) + 'Z'
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [dateRange, subscription])
|
|
|
|
const endDate = useMemo(() => {
|
|
// If end date is in future, set end date to end of current day
|
|
if (dateRange?.period_end?.date && dayjs(dateRange.period_end.date).isAfter(dayjs())) {
|
|
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
|
|
// In order to have full days from Prometheus metrics when using 1d interval,
|
|
// the time needs to be greater or equal than the time of the start date
|
|
return dayjs().endOf('day').toISOString().slice(0, -5) + 'Z'
|
|
} else if (dateRange?.period_end?.date) {
|
|
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
|
|
return new Date(dateRange.period_end.date).toISOString().slice(0, -5) + 'Z'
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [dateRange, subscription])
|
|
|
|
const selectedProject = selectedProjectRef
|
|
? orgProjects?.find((it) => it.ref === selectedProjectRef)
|
|
: undefined
|
|
|
|
const hasExceededAnyLimits = Boolean(
|
|
usage?.usages.find(
|
|
(metric) =>
|
|
!metric.unlimited && metric.capped && metric.usage > (metric?.pricing_free_units ?? 0)
|
|
)
|
|
)
|
|
|
|
return (
|
|
<>
|
|
<ScaffoldContainer className="sticky top-0 border-b bg-scale-200 z-10 overflow-hidden">
|
|
<div className="py-4 flex items-center space-x-4">
|
|
{isLoadingSubscription && <ShimmeringLoader className="w-[250px]" />}
|
|
|
|
{isErrorSubscription && (
|
|
<AlertError
|
|
className="w-full"
|
|
subject="Failed to retrieve usage data"
|
|
error={subscriptionError}
|
|
/>
|
|
)}
|
|
|
|
{isSuccessSubscription && (
|
|
<>
|
|
<DateRangePicker
|
|
onChange={setDateRange}
|
|
value={TIME_PERIODS_BILLING[0].key}
|
|
options={[...TIME_PERIODS_BILLING, ...TIME_PERIODS_REPORTS]}
|
|
loading={isLoadingSubscription}
|
|
currentBillingPeriodStart={subscription?.current_period_start}
|
|
currentBillingPeriodEnd={subscription?.current_period_end}
|
|
/>
|
|
|
|
<Listbox
|
|
size="tiny"
|
|
name="schema"
|
|
className="w-[180px]"
|
|
value={selectedProjectRef}
|
|
onChange={(value: any) => {
|
|
if (value === 'all-projects') setSelectedProjectRef(undefined)
|
|
else setSelectedProjectRef(value)
|
|
}}
|
|
>
|
|
<Listbox.Option
|
|
key="all-projects"
|
|
id="all-projects"
|
|
label="All projects"
|
|
value="all-projects"
|
|
>
|
|
All projects
|
|
</Listbox.Option>
|
|
{orgProjects?.map((project) => (
|
|
<Listbox.Option
|
|
key={project.ref}
|
|
id={project.ref}
|
|
value={project.ref}
|
|
label={project.name}
|
|
>
|
|
{project.name}
|
|
</Listbox.Option>
|
|
))}
|
|
</Listbox>
|
|
|
|
<div className="flex flex-col xl:flex-row xl:gap-3">
|
|
<p className={clsx('text-sm transition', isLoadingSubscription && 'opacity-50')}>
|
|
Organization is on the {subscription.plan.name} plan
|
|
</p>
|
|
<p className="text-sm text-foreground-light">
|
|
{billingCycleStart.format('DD MMM YYYY')} -{' '}
|
|
{billingCycleEnd.format('DD MMM YYYY')}
|
|
</p>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</ScaffoldContainer>
|
|
|
|
{!selectedProject && subscription && hasExceededAnyLimits && (
|
|
<ScaffoldContainer className="mt-5">
|
|
<Alert
|
|
withIcon
|
|
variant="danger"
|
|
title="Your organization's usage has exceeded its included quota"
|
|
actions={[
|
|
<Button key="upgrade-button" asChild type="default" className="ml-8">
|
|
<Link
|
|
href={`/org/${slug}/billing?panel=${
|
|
subscription.plan.id === 'free' ? 'subscriptionPlan' : 'costControl'
|
|
}`}
|
|
>
|
|
{subscription.plan.id === 'free' ? 'Upgrade plan' : 'Change spend cap'}
|
|
</Link>
|
|
</Button>,
|
|
]}
|
|
>
|
|
Your projects can become unresponsive or enter read only mode.{' '}
|
|
{subscription.plan.id === 'free'
|
|
? 'Please upgrade to the Pro plan to ensure that your projects remain available.'
|
|
: 'Please disable spend cap to ensure that your projects remain available.'}
|
|
</Alert>
|
|
</ScaffoldContainer>
|
|
)}
|
|
|
|
{selectedProjectRef && (
|
|
<ScaffoldContainer className="mt-5">
|
|
<InformationBox
|
|
title="Usage filtered by project"
|
|
description={
|
|
<div className="space-y-3">
|
|
<p>
|
|
You are currently viewing usage for the "
|
|
{selectedProject?.name || selectedProjectRef}" project. Since your organization is
|
|
using the new organization-based billing, the included quota is for your whole
|
|
organization and not just this project. For billing purposes, we sum up usage from
|
|
all your projects. To view your usage quota, set the project filter above back to
|
|
"All Projects".
|
|
</p>
|
|
<div>
|
|
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
|
<Link
|
|
href="https://supabase.com/docs/guides/platform/org-based-billing"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
Documentation
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
}
|
|
defaultVisibility
|
|
hideCollapse
|
|
icon={<IconInfo />}
|
|
/>
|
|
</ScaffoldContainer>
|
|
)}
|
|
|
|
<Bandwidth
|
|
orgSlug={slug as string}
|
|
projectRef={selectedProjectRef}
|
|
subscription={subscription}
|
|
startDate={startDate}
|
|
endDate={endDate}
|
|
currentBillingCycleSelected={currentBillingCycleSelected}
|
|
/>
|
|
|
|
<SizeAndCounts
|
|
orgSlug={slug as string}
|
|
projectRef={selectedProjectRef}
|
|
subscription={subscription}
|
|
startDate={startDate}
|
|
endDate={endDate}
|
|
currentBillingCycleSelected={currentBillingCycleSelected}
|
|
/>
|
|
|
|
<Activity
|
|
orgSlug={slug as string}
|
|
projectRef={selectedProjectRef}
|
|
subscription={subscription}
|
|
startDate={startDate}
|
|
endDate={endDate}
|
|
currentBillingCycleSelected={currentBillingCycleSelected}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default Usage
|