mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 18:35:07 +03:00
* fix: response error codes * upgrade docs * remove request url modification middleware * move api routes for self-hosted to platform folder * remove some lib/common/fetch usage * docs: use middleware for openapi-fetch (#30600) Get rid of the unauthedAllowedPost function (I don't think there's any harm in letting any requests that require authentication to just 403, they should be disabled at the React Query level and if not they will fail gracefully enough...) * fix local count query * add default values for clone mutation * fix ts and codegen * add missing lodash dep to playwright tests * Fix the playwright tests to match the new folder structure for selfhosted variant. * remove unused import * Remove unused state * remove unused sql debug mutation * remove unused export * fix notifications query * fix jwt updating status * fix typescript * save sql snippet after renaming * update codegen & fix ts error * override array querySerializer --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> Co-authored-by: Joshen Lim <joshenlimek@gmail.com> Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
244 lines
8.0 KiB
TypeScript
244 lines
8.0 KiB
TypeScript
import dayjs from 'dayjs'
|
|
import sumBy from 'lodash/sumBy'
|
|
import { Archive, ChevronDown, Database, Key, Zap } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { useState } from 'react'
|
|
|
|
import { useParams } from 'common'
|
|
import BarChart from 'components/ui/Charts/BarChart'
|
|
import Panel from 'components/ui/Panel'
|
|
import {
|
|
ProjectLogStatsVariables,
|
|
UsageApiCounts,
|
|
useProjectLogStatsQuery,
|
|
} from 'data/analytics/project-log-stats-query'
|
|
import { useFillTimeseriesSorted } from 'hooks/analytics/useFillTimeseriesSorted'
|
|
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
|
import type { ChartIntervals } from 'types'
|
|
import {
|
|
Button,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuRadioGroup,
|
|
DropdownMenuRadioItem,
|
|
DropdownMenuTrigger,
|
|
Loading,
|
|
} from 'ui'
|
|
|
|
const CHART_INTERVALS: ChartIntervals[] = [
|
|
{
|
|
key: 'minutely',
|
|
label: '60 minutes',
|
|
startValue: 1,
|
|
startUnit: 'hour',
|
|
format: 'MMM D, h:mma',
|
|
},
|
|
{ key: 'hourly', label: '24 hours', startValue: 24, startUnit: 'hour', format: 'MMM D, ha' },
|
|
{ key: 'daily', label: '7 days', startValue: 7, startUnit: 'day', format: 'MMM D' },
|
|
]
|
|
|
|
const ProjectUsage = () => {
|
|
const router = useRouter()
|
|
const { ref: projectRef } = useParams()
|
|
|
|
const { projectAuthAll: authEnabled, projectStorageAll: storageEnabled } = useIsFeatureEnabled([
|
|
'project_auth:all',
|
|
'project_storage:all',
|
|
])
|
|
|
|
const [interval, setInterval] = useState<ProjectLogStatsVariables['interval']>('hourly')
|
|
|
|
const { data, isLoading } = useProjectLogStatsQuery({ projectRef, interval })
|
|
|
|
const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1]
|
|
const startDateLocal = dayjs().subtract(
|
|
selectedInterval.startValue,
|
|
selectedInterval.startUnit as dayjs.ManipulateType
|
|
)
|
|
const endDateLocal = dayjs()
|
|
const { data: charts } = useFillTimeseriesSorted(
|
|
data?.result || [],
|
|
'timestamp',
|
|
[
|
|
'total_auth_requests',
|
|
'total_rest_requests',
|
|
'total_storage_requests',
|
|
'total_realtime_requests',
|
|
],
|
|
0,
|
|
startDateLocal.toISOString(),
|
|
endDateLocal.toISOString(),
|
|
5
|
|
)
|
|
const datetimeFormat = selectedInterval.format || 'MMM D, ha'
|
|
|
|
const handleBarClick = (
|
|
value: UsageApiCounts,
|
|
// TODO (ziinc): link to edge logs with correct filter applied
|
|
_type: 'rest' | 'realtime' | 'storage' | 'auth'
|
|
) => {
|
|
const unit = selectedInterval.startUnit
|
|
const selectedStart = dayjs(value?.timestamp)
|
|
const selectedEnd = selectedStart.add(1, unit)
|
|
router.push(
|
|
`/project/${projectRef}/logs/edge-logs?ite=${encodeURIComponent(selectedEnd.toISOString())}`
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex flex-row items-center gap-x-2">
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button type="default" iconRight={<ChevronDown size={14} />}>
|
|
<span>{selectedInterval.label}</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent side="bottom" align="start">
|
|
<DropdownMenuRadioGroup
|
|
value={interval}
|
|
onValueChange={(interval) =>
|
|
setInterval(interval as ProjectLogStatsVariables['interval'])
|
|
}
|
|
>
|
|
{CHART_INTERVALS.map((i) => (
|
|
<DropdownMenuRadioItem key={i.key} value={i.key}>
|
|
{i.label}
|
|
</DropdownMenuRadioItem>
|
|
))}
|
|
</DropdownMenuRadioGroup>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
<span className="text-xs text-foreground-light">
|
|
Statistics for past {selectedInterval.label}
|
|
</span>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-4 lg:grid-cols-4">
|
|
<Panel>
|
|
<Panel.Content className="space-y-4">
|
|
<PanelHeader
|
|
icon={
|
|
<div className="rounded bg-surface-300 p-1.5 text-foreground-light shadow-sm">
|
|
<Database strokeWidth={2} size={16} />
|
|
</div>
|
|
}
|
|
title="Database"
|
|
href={`/project/${projectRef}/editor`}
|
|
/>
|
|
|
|
<Loading active={isLoading}>
|
|
<BarChart
|
|
title="REST Requests"
|
|
data={charts}
|
|
xAxisKey="timestamp"
|
|
yAxisKey="total_rest_requests"
|
|
onBarClick={(v: unknown) => handleBarClick(v as UsageApiCounts, 'rest')}
|
|
customDateFormat={datetimeFormat}
|
|
highlightedValue={sumBy(charts, 'total_rest_requests')}
|
|
/>
|
|
</Loading>
|
|
</Panel.Content>
|
|
</Panel>
|
|
{authEnabled && (
|
|
<Panel>
|
|
<Panel.Content className="space-y-4">
|
|
<PanelHeader
|
|
icon={
|
|
<div className="rounded bg-surface-300 p-1.5 text-foreground-light shadow-sm">
|
|
<Key strokeWidth={2} size={16} />
|
|
</div>
|
|
}
|
|
title="Auth"
|
|
href={`/project/${projectRef}/auth/users`}
|
|
/>
|
|
<Loading active={isLoading}>
|
|
<BarChart
|
|
title="Auth Requests"
|
|
data={charts}
|
|
xAxisKey="timestamp"
|
|
yAxisKey="total_auth_requests"
|
|
onBarClick={(v: unknown) => handleBarClick(v as UsageApiCounts, 'auth')}
|
|
customDateFormat={datetimeFormat}
|
|
highlightedValue={sumBy(charts || [], 'total_auth_requests')}
|
|
/>
|
|
</Loading>
|
|
</Panel.Content>
|
|
</Panel>
|
|
)}
|
|
{storageEnabled && (
|
|
<Panel>
|
|
<Panel.Content className="space-y-4">
|
|
<PanelHeader
|
|
icon={
|
|
<div className="rounded bg-surface-300 p-1.5 text-foreground-light shadow-sm">
|
|
<Archive strokeWidth={2} size={16} />
|
|
</div>
|
|
}
|
|
title="Storage"
|
|
href={`/project/${projectRef}/storage/buckets`}
|
|
/>
|
|
|
|
<Loading active={isLoading}>
|
|
<BarChart
|
|
title="Storage Requests"
|
|
data={charts}
|
|
xAxisKey="timestamp"
|
|
yAxisKey="total_storage_requests"
|
|
onBarClick={(v: unknown) => handleBarClick(v as UsageApiCounts, 'storage')}
|
|
customDateFormat={datetimeFormat}
|
|
highlightedValue={sumBy(charts, 'total_storage_requests')}
|
|
/>
|
|
</Loading>
|
|
</Panel.Content>
|
|
</Panel>
|
|
)}
|
|
<Panel>
|
|
<Panel.Content className="space-y-4">
|
|
<PanelHeader
|
|
icon={
|
|
<div className="rounded bg-surface-300 p-1.5 text-foreground-light shadow-sm">
|
|
<Zap strokeWidth={2} size={16} />
|
|
</div>
|
|
}
|
|
title="Realtime"
|
|
/>
|
|
|
|
<Loading active={isLoading}>
|
|
<BarChart
|
|
title="Realtime Requests"
|
|
data={charts}
|
|
xAxisKey="timestamp"
|
|
yAxisKey="total_realtime_requests"
|
|
onBarClick={(v: unknown) => handleBarClick(v as UsageApiCounts, 'realtime')}
|
|
customDateFormat={datetimeFormat}
|
|
highlightedValue={sumBy(charts, 'total_realtime_requests')}
|
|
/>
|
|
</Loading>
|
|
</Panel.Content>
|
|
</Panel>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
export default ProjectUsage
|
|
|
|
const PanelHeader = (props: any) => {
|
|
const Tag = props?.href ? Link : 'div'
|
|
return (
|
|
<Tag href={props.href}>
|
|
<div
|
|
className={
|
|
'flex items-center space-x-3 opacity-80 transition ' +
|
|
(props.href ? 'cursor-pointer hover:text-gray-1200 hover:opacity-100' : '')
|
|
}
|
|
>
|
|
<div>{props.icon}</div>
|
|
<span className="flex items-center space-x-1">
|
|
<h4 className="mb-0 text-lg">{props.title}</h4>
|
|
</span>
|
|
</div>
|
|
</Tag>
|
|
)
|
|
}
|