mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Add chart component for usage
This commit is contained in:
1 parent
7abc50e4d6
commit
648bda3a2c
13 files changed
+563
-1
No files matched your search
@@ -0,0 +1,62 @@
|
||||
import { DataPoint } from 'data/analytics/constants'
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
ComposedChart,
|
||||
CartesianGrid,
|
||||
YAxis,
|
||||
XAxis,
|
||||
Bar,
|
||||
ReferenceLine,
|
||||
} from 'recharts'
|
||||
|
||||
// [Joshen] I don't think I should be replacing ChartHandler just yet as the styling of the usage charts
|
||||
// are slightly different from that on the other pages, just in case.
|
||||
|
||||
interface Reference {
|
||||
value: number
|
||||
label: string
|
||||
width: number
|
||||
height: number
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export interface BarChartProps {
|
||||
data: DataPoint[]
|
||||
attribute: string
|
||||
reference?: Reference
|
||||
}
|
||||
|
||||
const BarChart = ({ data, attribute, reference }: BarChartProps) => {
|
||||
return (
|
||||
<div className="w-full h-[200px]">
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<ComposedChart data={data} margin={{ top: 0, right: 0, left: 0, bottom: 0 }}>
|
||||
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
||||
<XAxis dataKey="" />
|
||||
<YAxis width={28} axisLine={false} tickLine={{ stroke: 'none' }} />
|
||||
<Bar dataKey={attribute} fill="#EEE" />
|
||||
{reference && (
|
||||
<ReferenceLine
|
||||
y={reference.value}
|
||||
label={
|
||||
<foreignObject
|
||||
x={reference.x}
|
||||
y={reference.y}
|
||||
width={reference.width}
|
||||
height={reference.height}
|
||||
>
|
||||
<div className="text-[0.7rem] font-bold text-amber-900 bg-amber-300 w-fit p-1.5 py-0.5 rounded-md">
|
||||
{reference.label}
|
||||
</div>
|
||||
</foreignObject>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BarChart
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useParams } from 'common'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query'
|
||||
import dayjs from 'dayjs'
|
||||
import { DATE_FORMAT } from 'lib/constants'
|
||||
import BarChart from './BarChart'
|
||||
import { generateUsageData } from './Usage.utils'
|
||||
|
||||
const Infrastructure = () => {
|
||||
const { ref } = useParams()
|
||||
|
||||
const currentDate = 1682669326710 / 1000
|
||||
const startDate = dayjs.unix(currentDate).subtract(24, 'hour').format(DATE_FORMAT)
|
||||
const endDate = dayjs.unix(currentDate).format(DATE_FORMAT)
|
||||
|
||||
const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({
|
||||
projectRef: ref,
|
||||
attribute: 'disk_io_budget',
|
||||
startDate,
|
||||
endDate,
|
||||
interval: '1h',
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="border-b">
|
||||
<div className="1xl:px-28 mx-auto flex flex-col gap-10 px-5 lg:px-16 2xl:px-32 py-16">
|
||||
<div className="grid grid-cols-12">
|
||||
<div className="col-span-5">
|
||||
<div className="sticky top-16">
|
||||
<p className="text-base">IO Budget</p>
|
||||
<p className="text-sm text-scale-1000">Some description here</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-7 space-y-6">
|
||||
<div className="space-y-1">
|
||||
<p>IO budget per day</p>
|
||||
<p className="text-sm text-scale-1000">
|
||||
Just FYI for now its a percentage for IO budget, which is different from what the
|
||||
figma designs are (in minutes). We will need o11y or infra to help update this
|
||||
</p>
|
||||
</div>
|
||||
{isLoadingIoBudgetData ? (
|
||||
<div className="space-y-2">
|
||||
<ShimmeringLoader />
|
||||
<ShimmeringLoader className="w-3/4" />
|
||||
<ShimmeringLoader className="w-1/2" />
|
||||
</div>
|
||||
) : (
|
||||
<BarChart
|
||||
attribute="disk_io_budget"
|
||||
data={generateUsageData('disk_io_budget', 30)}
|
||||
reference={{
|
||||
value: 65,
|
||||
label: 'FREE QUOTA',
|
||||
x: 60,
|
||||
y: 49,
|
||||
width: 200,
|
||||
height: 24,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Infrastructure
|
||||
@@ -0,0 +1,95 @@
|
||||
import { useParams } from 'common'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import { useProjectsQuery } from 'data/projects/projects-query'
|
||||
import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, IconCheckCircle, Listbox } from 'ui'
|
||||
import Infrastructure from './Infrastructure'
|
||||
|
||||
const Usage = () => {
|
||||
const { ref } = useParams()
|
||||
const [selectedProjectRef, setSelectedProjectRef] = useState<string>(ref as string)
|
||||
|
||||
const { data: projects, isLoading, isSuccess } = useProjectsQuery()
|
||||
const { data: subscription } = useProjectSubscriptionQuery({ projectRef: ref })
|
||||
|
||||
useEffect(() => {}, [isSuccess])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="border-b">
|
||||
<div className="1xl:px-28 mx-auto flex flex-col px-5 lg:px-16 2xl:px-32 pt-6 space-y-4">
|
||||
<h3 className="text-scale-1200 text-xl">Usage</h3>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* <Billing cycle picker /> */}
|
||||
{isLoading ? (
|
||||
<ShimmeringLoader className="w-[200px]" />
|
||||
) : (
|
||||
<Listbox size="small" value={ref} onChange={() => {}} className="w-[200px]">
|
||||
{(projects ?? []).map((project) => (
|
||||
<Listbox.Option
|
||||
key={project.ref}
|
||||
value={project.ref}
|
||||
label={project.name}
|
||||
className="!w-[200px]"
|
||||
>
|
||||
{project.name}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox>
|
||||
)}
|
||||
{subscription !== undefined && (
|
||||
<p className="text-sm">Project is on {subscription.tier.name}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button type="default">View invoices</Button>
|
||||
<Button type="default">View billing</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-6 !mt-2">
|
||||
<div className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer">
|
||||
<IconCheckCircle size={15} strokeWidth={2} className="text-brand-900" />
|
||||
<p className="text-sm">Infrastructure</p>
|
||||
</div>
|
||||
<div className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer">
|
||||
<IconCheckCircle size={15} strokeWidth={2} className="text-brand-900" />
|
||||
<p className="text-sm">Bandwidth</p>
|
||||
</div>
|
||||
<div className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer">
|
||||
<IconCheckCircle size={15} strokeWidth={2} className="text-brand-900" />
|
||||
<p className="text-sm">Size & Counts</p>
|
||||
</div>
|
||||
<div className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer">
|
||||
<IconCheckCircle size={15} strokeWidth={2} className="text-brand-900" />
|
||||
<p className="text-sm">Activity</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Infrastructure />
|
||||
|
||||
<div className="border-b">
|
||||
<div className="1xl:px-28 mx-auto flex flex-col gap-10 px-5 lg:px-16 2xl:px-32 py-16">
|
||||
Segment 2
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b">
|
||||
<div className="1xl:px-28 mx-auto flex flex-col gap-10 px-5 lg:px-16 2xl:px-32 py-16">
|
||||
Segment 3
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b">
|
||||
<div className="1xl:px-28 mx-auto flex flex-col gap-10 px-5 lg:px-16 2xl:px-32 py-16">
|
||||
Segment 4
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Usage
|
||||
@@ -0,0 +1,13 @@
|
||||
import { DataPoint } from 'data/analytics/constants'
|
||||
|
||||
// [Joshen] This is just for development
|
||||
export const generateUsageData = (attribute: string, days: number): DataPoint[] => {
|
||||
const tempArray = new Array(days).fill(0)
|
||||
return tempArray.map((x, idx) => {
|
||||
return {
|
||||
loopId: (idx + 1).toString(),
|
||||
period_start: '',
|
||||
[attribute]: Math.floor(Math.random() * 100).toString(),
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export type DataPoint = {
|
||||
loopId: number | string
|
||||
period_start: string
|
||||
} & {
|
||||
// Attribute name will be the key
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface AnalyticsData {
|
||||
data: DataPoint[]
|
||||
format: string
|
||||
total: number
|
||||
totalAverage: number
|
||||
yAxisLimit: number
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { analyticsKeys } from './keys'
|
||||
import { AnalyticsData } from './constants'
|
||||
|
||||
export type DailyStatsVariables = {
|
||||
projectRef?: string
|
||||
attribute?:
|
||||
| 'total_realtime_egress'
|
||||
| 'total_realtime_requests'
|
||||
| 'total_realtime_ingress'
|
||||
| 'total_rest_ingress'
|
||||
| 'total_rest_egress'
|
||||
| 'total_rest_requests'
|
||||
| 'total_rest_get_requests'
|
||||
| 'total_rest_post_requests'
|
||||
| 'total_rest_patch_requests'
|
||||
| 'total_rest_delete_requests'
|
||||
| 'total_rest_options_requests'
|
||||
| 'total_auth_billing_period_mau'
|
||||
| 'total_auth_billing_period_sso_mau'
|
||||
| 'total_auth_ingress'
|
||||
| 'total_auth_egress'
|
||||
| 'total_auth_requests'
|
||||
| 'total_auth_get_requests'
|
||||
| 'total_auth_post_requests'
|
||||
| 'total_auth_patch_requests'
|
||||
| 'total_auth_delete_requests'
|
||||
| 'total_auth_options_requests'
|
||||
| 'total_storage_ingress'
|
||||
| 'total_storage_egress'
|
||||
| 'total_storage_image_render_count'
|
||||
| 'total_storage_requests'
|
||||
| 'total_storage_get_requests'
|
||||
| 'total_storage_post_requests'
|
||||
| 'total_storage_patch_requests'
|
||||
| 'total_storage_delete_requests'
|
||||
| 'total_storage_options_requests'
|
||||
| 'total_ingress'
|
||||
| 'total_egress'
|
||||
| 'total_requests'
|
||||
| 'total_get_requests'
|
||||
| 'total_post_requests'
|
||||
| 'total_patch_requests'
|
||||
| 'total_delete_requests'
|
||||
| 'total_options_requests'
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
interval?: string
|
||||
}
|
||||
|
||||
export async function getDailyStats(
|
||||
{ projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!projectRef) throw new Error('Project ref is required')
|
||||
if (!attribute) throw new Error('Attribute is required')
|
||||
if (!startDate) throw new Error('Start date is required')
|
||||
if (!endDate) throw new Error('Start date is required')
|
||||
|
||||
const data = await get(
|
||||
`${API_URL}/projects/${projectRef}/daily-stats?attribute=${attribute}&startDate=${encodeURIComponent(
|
||||
startDate
|
||||
)}&endDate=${encodeURIComponent(endDate)}&interval=${interval}`,
|
||||
{ signal }
|
||||
)
|
||||
if (data.error) throw data.error
|
||||
return data as AnalyticsData
|
||||
}
|
||||
|
||||
export type DailyStatsData = Awaited<ReturnType<typeof getDailyStats>>
|
||||
export type DailyStatsError = unknown
|
||||
|
||||
export const useDailyStatsQuery = <TData = DailyStatsData>(
|
||||
{ projectRef, attribute, startDate, endDate, interval = '1d' }: DailyStatsVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<DailyStatsData, DailyStatsError, TData> = {}
|
||||
) =>
|
||||
useQuery<DailyStatsData, DailyStatsError, TData>(
|
||||
analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }),
|
||||
({ signal }) => getDailyStats({ projectRef, attribute, startDate, endDate, interval }, signal),
|
||||
{
|
||||
enabled: enabled && typeof projectRef !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
export const useDailyStatsPrefetch = ({
|
||||
projectRef,
|
||||
attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval = '1d',
|
||||
}: DailyStatsVariables) => {
|
||||
const client = useQueryClient()
|
||||
|
||||
return useCallback(() => {
|
||||
if (projectRef && attribute && startDate && endDate && interval) {
|
||||
client.prefetchQuery(
|
||||
analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }),
|
||||
({ signal }) =>
|
||||
getDailyStats({ projectRef, attribute, startDate, endDate, interval }, signal)
|
||||
)
|
||||
}
|
||||
}, [projectRef])
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { analyticsKeys } from './keys'
|
||||
import { AnalyticsData } from './constants'
|
||||
|
||||
export type InfraMonitoringVariables = {
|
||||
projectRef?: string
|
||||
attribute?: 'cpu_usage' | 'disk_io_budget' | 'ram_usage'
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
interval?: string
|
||||
}
|
||||
|
||||
export async function getInfraMonitoring(
|
||||
{ projectRef, attribute, startDate, endDate, interval = '1d' }: InfraMonitoringVariables,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
if (!projectRef) throw new Error('Project ref is required')
|
||||
if (!attribute) throw new Error('Attribute is required')
|
||||
if (!startDate) throw new Error('Start date is required')
|
||||
if (!endDate) throw new Error('Start date is required')
|
||||
|
||||
const data = await get(
|
||||
`${API_URL}/projects/${projectRef}/infra-monitoring?attribute=${attribute}&startDate=${encodeURIComponent(
|
||||
startDate
|
||||
)}&endDate=${encodeURIComponent(endDate)}&interval=${interval}`,
|
||||
{ signal }
|
||||
)
|
||||
if (data.error) throw data.error
|
||||
return data as AnalyticsData
|
||||
}
|
||||
|
||||
export type InfraMonitoringData = Awaited<ReturnType<typeof getInfraMonitoring>>
|
||||
export type InfraMonitoringError = unknown
|
||||
|
||||
export const useInfraMonitoringQuery = <TData = InfraMonitoringData>(
|
||||
{ projectRef, attribute, startDate, endDate, interval = '1d' }: InfraMonitoringVariables,
|
||||
{
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<InfraMonitoringData, InfraMonitoringError, TData> = {}
|
||||
) =>
|
||||
useQuery<InfraMonitoringData, InfraMonitoringError, TData>(
|
||||
analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }),
|
||||
({ signal }) =>
|
||||
getInfraMonitoring({ projectRef, attribute, startDate, endDate, interval }, signal),
|
||||
{
|
||||
enabled: enabled && typeof projectRef !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
export const useInfraMonitoringPrefetch = ({
|
||||
projectRef,
|
||||
attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval = '1d',
|
||||
}: InfraMonitoringVariables) => {
|
||||
const client = useQueryClient()
|
||||
|
||||
return useCallback(() => {
|
||||
if (projectRef && attribute && startDate && endDate && interval) {
|
||||
client.prefetchQuery(
|
||||
analyticsKeys.dailyStats(projectRef, { attribute, startDate, endDate, interval }),
|
||||
({ signal }) =>
|
||||
getInfraMonitoring({ projectRef, attribute, startDate, endDate, interval }, signal)
|
||||
)
|
||||
}
|
||||
}, [projectRef])
|
||||
}
|
||||
@@ -5,4 +5,29 @@ export const analyticsKeys = {
|
||||
projectRef: string | undefined,
|
||||
{ interval, functionId }: { functionId: string | undefined; interval: string | undefined }
|
||||
) => ['projects', projectRef, 'functions-inv-stats', { interval, functionId }] as const,
|
||||
dailyStats: (
|
||||
projectRef: string | undefined,
|
||||
{
|
||||
attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
}: { attribute?: string; startDate?: string; endDate?: string; interval?: string }
|
||||
) =>
|
||||
['projects', projectRef, 'daily-stats', { attribute, startDate, endDate, interval }] as const,
|
||||
infraMonitoring: (
|
||||
projectRef: string | undefined,
|
||||
{
|
||||
attribute,
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
}: { attribute?: string; startDate?: string; endDate?: string; interval?: string }
|
||||
) =>
|
||||
[
|
||||
'projects',
|
||||
projectRef,
|
||||
'infra-monitoring',
|
||||
{ attribute, startDate, endDate, interval },
|
||||
] as const,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export const projectKeys = {
|
||||
list: () => ['projects'] as const,
|
||||
detail: (projectRef: string | undefined) => ['projects', projectRef, 'detail'] as const,
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { Project } from 'types'
|
||||
import { projectKeys } from './keys'
|
||||
|
||||
export type ProjectDetailVariables = { ref?: string }
|
||||
|
||||
export async function getProjectDetail({ ref }: ProjectDetailVariables, signal?: AbortSignal) {
|
||||
if (!ref) throw new Error('Project ref is required')
|
||||
const data = await get(`${API_URL}/projects/${ref}`, { signal })
|
||||
if (data.error) throw data.error
|
||||
return data as Project
|
||||
}
|
||||
|
||||
export type ProjectDetailData = Awaited<ReturnType<typeof getProjectDetail>>
|
||||
export type ProjectDetailError = unknown
|
||||
|
||||
export const useProjectDetailQuery = <TData = ProjectDetailData>(
|
||||
{ ref }: ProjectDetailVariables,
|
||||
{ enabled = true, ...options }: UseQueryOptions<ProjectDetailData, ProjectDetailError, TData> = {}
|
||||
) =>
|
||||
useQuery<ProjectDetailData, ProjectDetailError, TData>(
|
||||
projectKeys.detail(ref),
|
||||
({ signal }) => getProjectDetail({ ref }, signal),
|
||||
{
|
||||
enabled: enabled && typeof ref !== 'undefined',
|
||||
...options,
|
||||
}
|
||||
)
|
||||
|
||||
export const useProjectDetailPrefetch = ({ ref }: ProjectDetailVariables) => {
|
||||
const client = useQueryClient()
|
||||
|
||||
return useCallback(() => {
|
||||
if (ref) {
|
||||
client.prefetchQuery(projectKeys.detail(ref), ({ signal }) =>
|
||||
getProjectDetail({ ref }, signal)
|
||||
)
|
||||
}
|
||||
}, [ref])
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useCallback } from 'react'
|
||||
import { Project } from 'types'
|
||||
import { projectKeys } from './keys'
|
||||
|
||||
export type ProjectsVariables = {
|
||||
ref?: string
|
||||
}
|
||||
|
||||
export async function getProjects(signal?: AbortSignal) {
|
||||
const data = await get(`${API_URL}/projects`, { signal })
|
||||
if (data.error) throw data.error
|
||||
return data as Project[]
|
||||
}
|
||||
|
||||
export type OrganizationRolesData = Awaited<ReturnType<typeof getProjects>>
|
||||
export type OrganizationRolesError = unknown
|
||||
|
||||
export const useProjectsQuery = <TData = OrganizationRolesData>({
|
||||
enabled = true,
|
||||
...options
|
||||
}: UseQueryOptions<OrganizationRolesData, OrganizationRolesError, TData> = {}) =>
|
||||
useQuery<OrganizationRolesData, OrganizationRolesError, TData>(
|
||||
projectKeys.list(),
|
||||
({ signal }) => getProjects(signal),
|
||||
{ enabled: enabled, ...options }
|
||||
)
|
||||
|
||||
export const useProjectsPrefetch = () => {
|
||||
const client = useQueryClient()
|
||||
|
||||
return useCallback(() => {
|
||||
client.prefetchQuery(projectKeys.list(), ({ signal }) => getProjects(signal))
|
||||
}, [])
|
||||
}
|
||||
@@ -12,9 +12,12 @@ import LoadingUI from 'components/ui/Loading'
|
||||
import DateRangePicker from 'components/to-be-cleaned/DateRangePicker'
|
||||
import { PAYGUsage } from 'components/interfaces/Billing'
|
||||
import ProjectUsageBars from 'components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars'
|
||||
import Usage from 'components/interfaces/BillingV2/Usage/Usage'
|
||||
|
||||
const ProjectBillingUsage: NextPageWithLayout = () => {
|
||||
const showUsageV2 = useFlag('usageV2')
|
||||
const showNewUsageUI = useFlag('usagev2')
|
||||
|
||||
if (showNewUsageUI) return <Usage />
|
||||
|
||||
return (
|
||||
<div className="w-full h-full overflow-y-auto content">
|
||||
|
||||
@@ -438,3 +438,17 @@
|
||||
@apply stroke-green-500;
|
||||
}
|
||||
}
|
||||
|
||||
.recharts-cartesian-axis-tick {
|
||||
text {
|
||||
@apply text-xs;
|
||||
}
|
||||
}
|
||||
.recharts-reference-line {
|
||||
line {
|
||||
@apply stroke-amber-900;
|
||||
}
|
||||
text {
|
||||
@apply text-xs fill-amber-900 bg-amber-600;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user