Files
supabase/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureActivity.tsx
T
Francesco SansalvadoreandJonathan Summers-Muir be9590c890 Multiple themes management (#18871)
* set up multiple themes in studio

* set up multiple themes in studio

* set up multiple themes in docs and www

* update all resolvedTheme to also include deep-dark

* update all resolvedTheme checks to also include deep-dark

* update tailwind.config.js tokens

* update tailwind.config.js tokens

* update leftover scale12 token

* update if resolvedTheme _doesn't_ include 'dark'

* update more styling tokens

* add dynamic themes to CmdK

* fix nav and footer for multi theme

* add data-theme selector output to transformTokens.js

* update code-hike.css to target data-theme css

* update tailwindcss to ^3.3.5

* ThemeImage with light and dark src for www and docs

* add brand-button styling token

* update old dark theme boolean

* update old dark theme boolean

* make homepage product visuals themeable

* update product page themed images

* update badge green with brand

* fix roles list appearance

* fix auth widget in auth page

* update more dark logic

* update more dark logic

* add button default bg and border

* update pricing page theme styling

* clean up Themeimage

* remove forceDark in homepage

* update dark:border-dark occurrences

* update dark:border-dark occurrences

* fix dark mode base colors

* remove foreground-strong

* fix notification badge bg

* remove some dark: selectors

* update dark: selectors

* update code-hike deep dark bg color

* fix comment typo

* update border-button-hover token

* fix customer story logo

* remove some more dark: selectors

* restore forceDark in www homepage

* fix auth react icon

* fix homepage product visuals

* remove theme

* add brand-link token

* fix checkbox bg

* npm install

* more visible EntityListItem active bg

* fix --background-alternative-default css vars

---------

Co-authored-by: Jonathan Summers-Muir <MildTomato@users.noreply.github.com>
2023-11-16 16:41:53 +00:00

399 lines
17 KiB
TypeScript

import { useParams } from 'common'
import dayjs from 'dayjs'
import Link from 'next/link'
import { Fragment, useMemo, useState } from 'react'
import { IconBarChart2, IconExternalLink } from 'ui'
import { getAddons } from 'components/interfaces/Billing/Subscription/Subscription.utils'
import {
CPUWarnings,
DiskIOBandwidthWarnings,
RAMWarnings,
} from 'components/interfaces/Billing/Usage/UsageWarningAlerts'
import UsageBarChart from 'components/interfaces/Organization/Usage/UsageBarChart'
import {
ScaffoldContainer,
ScaffoldDivider,
ScaffoldSection,
ScaffoldSectionContent,
ScaffoldSectionDetail,
} from 'components/layouts/Scaffold'
import DateRangePicker from 'components/to-be-cleaned/DateRangePicker'
import Panel from 'components/ui/Panel'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { DataPoint } from 'data/analytics/constants'
import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
import { useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
import { useSelectedOrganization } from 'hooks'
import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants'
import { INFRA_ACTIVITY_METRICS } from './Infrastructure.constants'
const InfrastructureActivity = () => {
const { ref: projectRef } = useParams()
const organization = useSelectedOrganization()
const [dateRange, setDateRange] = useState<any>()
const { data: subscription, isLoading: isLoadingSubscription } = useOrgSubscriptionQuery({
orgSlug: organization?.slug,
})
const isFreePlan = subscription?.plan?.id === 'free'
const { data: resourceWarnings } = useResourceWarningsQuery()
const projectResourceWarnings = resourceWarnings?.find((x) => x.project === projectRef)
const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef })
const selectedAddons = addons?.selected_addons ?? []
const { computeInstance } = getAddons(selectedAddons)
const currentComputeInstanceSpecs = computeInstance?.variant?.meta ?? {
baseline_disk_io_mbs: 87,
max_disk_io_mbs: 2085,
cpu_cores: 2,
cpu_dedicated: true,
memory_gb: 1,
}
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 upgradeUrl =
subscription === undefined
? `/`
: subscription.plan.id === 'free'
? `/org/${organization?.slug ?? '[slug]'}/billing#subscription`
: `/project/${projectRef}/settings/addons`
const categoryMeta = INFRA_ACTIVITY_METRICS.find((category) => category.key === 'infra')
const startDate = useMemo(() => {
if (dateRange?.period_start?.date === 'Invalid Date') return undefined
// 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 ?? 0).toISOString().slice(0, -5) + 'Z'
}
}, [dateRange, subscription])
const endDate = useMemo(() => {
if (dateRange?.period_end?.date === 'Invalid Date') return undefined
// 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 ?? 0).toISOString().slice(0, -5) + 'Z'
}
}, [dateRange, subscription])
// Switch to hourly interval, if the timeframe is <48 hours
let interval: '1d' | '1h' = '1d'
let dateFormat = 'DD MMM'
if (startDate && endDate) {
const diffInHours = dayjs(endDate).diff(startDate, 'hours')
if (diffInHours <= 48) {
interval = '1h'
dateFormat = 'h a'
}
}
const { data: cpuUsageData, isLoading: isLoadingCpuUsageData } = useInfraMonitoringQuery({
projectRef,
attribute: 'max_cpu_usage',
interval,
startDate,
endDate,
dateFormat,
})
const { data: memoryUsageData, isLoading: isLoadingMemoryUsageData } = useInfraMonitoringQuery({
projectRef,
attribute: 'ram_usage',
interval,
startDate,
endDate,
dateFormat,
})
const { data: ioBudgetData, isLoading: isLoadingIoBudgetData } = useInfraMonitoringQuery({
projectRef,
attribute: 'disk_io_consumption',
interval,
startDate,
endDate,
dateFormat,
})
const hasLatest = dayjs(endDate!).isAfter(dayjs().startOf('day'))
const latestIoBudgetConsumption =
hasLatest && ioBudgetData?.data?.slice(-1)?.[0]
? Number(ioBudgetData.data.slice(-1)[0].disk_io_consumption)
: 0
const highestIoBudgetConsumption = Math.max(
...(ioBudgetData?.data || []).map((x) => Number(x.disk_io_consumption) ?? 0),
0
)
const chartMeta: { [key: string]: { data: DataPoint[]; isLoading: boolean } } = {
max_cpu_usage: {
isLoading: isLoadingCpuUsageData,
data: cpuUsageData?.data ?? [],
},
ram_usage: {
isLoading: isLoadingMemoryUsageData,
data: memoryUsageData?.data ?? [],
},
disk_io_consumption: {
isLoading: isLoadingIoBudgetData,
data: ioBudgetData?.data ?? [],
},
}
return (
<>
<ScaffoldContainer>
<div className="mx-auto flex flex-col gap-10 pt-6">
<div>
<p className="text-xl">Infrastructure Activity</p>
<p className="text-sm text-foreground-light">
Activity statistics related to your server instance
</p>
</div>
</div>
</ScaffoldContainer>
<ScaffoldContainer className="sticky top-0 py-6 border-b bg-background z-10">
<div className="flex items-center space-x-4">
{!isLoadingSubscription && (
<>
<DateRangePicker
onChange={setDateRange}
value={TIME_PERIODS_REPORTS[0].key}
options={[...TIME_PERIODS_BILLING, ...TIME_PERIODS_REPORTS]}
loading={isLoadingSubscription}
currentBillingPeriodStart={subscription?.current_period_start}
currentBillingPeriodEnd={subscription?.current_period_end}
/>
<p className="text-sm text-foreground-light">
{dayjs(startDate).format('DD MMM YYYY')} - {dayjs(endDate).format('DD MMM YYYY')}
</p>
</>
)}
</div>
</ScaffoldContainer>
{categoryMeta?.attributes.map((attribute) => {
const chartData = chartMeta[attribute.key]?.data ?? []
return (
<Fragment key={attribute.key}>
<ScaffoldContainer id={attribute.anchor}>
<ScaffoldSection>
<ScaffoldSectionDetail>
<div className="sticky top-32 space-y-6">
<div className="space-y-1">
<div className="flex items-center space-x-2">
<h4 className="text-base capitalize m-0">{attribute.name}</h4>
</div>
<div className="grid gap-4">
{attribute.description.split('\n').map((value, idx) => (
<p key={`desc-${idx}`} className="text-sm text-foreground-light pr-8 m-0">
{value}
</p>
))}
</div>
</div>
<div className="space-y-2">
<p className="text-sm text-foreground mb-2">More information</p>
{attribute.links.map((link) => (
<div key={link.url}>
<Link href={link.url} target="_blank" rel="noreferrer">
<div className="flex items-center space-x-2 opacity-50 hover:opacity-100 transition">
<p className="text-sm m-0">{link.name}</p>
<IconExternalLink size={16} strokeWidth={1.5} />
</div>
</Link>
</div>
))}
</div>
</div>
</ScaffoldSectionDetail>
<ScaffoldSectionContent>
{attribute.key === 'disk_io_consumption' && (
<>
<DiskIOBandwidthWarnings
upgradeUrl={upgradeUrl}
isFreePlan={isFreePlan}
hasLatest={hasLatest}
currentBillingCycleSelected={currentBillingCycleSelected}
latestIoBudgetConsumption={latestIoBudgetConsumption}
highestIoBudgetConsumption={highestIoBudgetConsumption}
/>
<div className="space-y-1">
<p>Disk IO Bandwidth</p>
{currentComputeInstanceSpecs.baseline_disk_io_mbs ===
currentComputeInstanceSpecs.max_disk_io_mbs ? (
<p className="text-sm text-foreground-light">
Your current compute can has a baseline and maximum disk throughput of{' '}
{currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps.
</p>
) : (
<p className="text-sm text-foreground-light">
Your current compute can burst up to{' '}
{currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps for
30 minutes a day and reverts to the baseline performance of{' '}
{currentComputeInstanceSpecs.baseline_disk_io_mbs?.toLocaleString()}{' '}
Mbps.
</p>
)}
</div>
<div>
<p className="text-sm mb-2">Overview</p>
<div className="flex items-center justify-between border-b py-1">
<p className="text-xs text-foreground-light">Current compute instance</p>
<p className="text-xs">{computeInstance?.variant?.name ?? 'Micro'}</p>
</div>
<div className="flex items-center justify-between border-b py-1">
<p className="text-xs text-foreground-light">
Maximum IO Bandwidth (burst limit)
</p>
<p className="text-xs">
{currentComputeInstanceSpecs.max_disk_io_mbs?.toLocaleString()} Mbps
</p>
</div>
<div className="flex items-center justify-between border-b py-1">
<p className="text-xs text-foreground-light">Baseline IO Bandwidth</p>
<p className="text-xs">
{currentComputeInstanceSpecs.baseline_disk_io_mbs?.toLocaleString()}{' '}
Mbps
</p>
</div>
{currentComputeInstanceSpecs.max_disk_io_mbs !==
currentComputeInstanceSpecs?.baseline_disk_io_mbs && (
<div className="flex items-center justify-between py-1">
<p className="text-xs text-foreground-light">Daily burst time limit</p>
<p className="text-xs">30 mins</p>
</div>
)}
</div>
</>
)}
{attribute.key === 'max_cpu_usage' && (
<CPUWarnings
isFreePlan={isFreePlan}
upgradeUrl={upgradeUrl}
severity={projectResourceWarnings?.cpu_exhaustion}
/>
)}
{attribute.key === 'ram_usage' && (
<RAMWarnings
isFreePlan={isFreePlan}
upgradeUrl={upgradeUrl}
severity={projectResourceWarnings?.memory_and_swap_exhaustion}
/>
)}
<div className="space-y-1">
<div className="flex flex-row justify-between">
{attribute.key === 'disk_io_consumption' ? (
<p>Disk IO consumed per {interval === '1d' ? 'day' : 'hour'}</p>
) : (
<p>
Max{' '}
<span className={attribute.key === 'ram_usage' ? 'lowercase' : ''}>
{attribute.name}
</span>{' '}
utilization per {interval === '1d' ? 'day' : 'hour'}
</p>
)}
</div>
{attribute.key === 'ram_usage' && (
<div className="text-sm text-foreground-light">
<p>
Your compute instance has {currentComputeInstanceSpecs.memory_gb} GB of
memory.
</p>
{currentComputeInstanceSpecs.memory_gb === 1 && (
<p>
As your project is running on the smallest compute instance, it is not
unusual for your project to have a base memory usage of ~50%.
</p>
)}
</div>
)}
{attribute.key === 'max_cpu_usage' && (
<p className="text-sm text-foreground-light">
Your compute instance has {currentComputeInstanceSpecs.cpu_cores} CPU cores.
</p>
)}
{attribute.chartDescription.split('\n').map((paragraph, idx) => (
<p key={`para-${idx}`} className="text-sm text-foreground-light">
{paragraph}
</p>
))}
</div>
{chartMeta[attribute.key].isLoading ? (
<div className="space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
) : chartData.length ? (
<UsageBarChart
name={`${attribute.chartPrefix || ''}${attribute.name}`}
unit={attribute.unit}
attributes={attribute.attributes}
data={chartData}
yFormatter={(value) => `${Math.round(Number(value))}%`}
tooltipFormatter={(value) => `${value}%`}
yLimit={100}
/>
) : (
<Panel>
<Panel.Content>
<div className="flex flex-col items-center justify-center space-y-2">
<IconBarChart2 className="text-foreground-light mb-2" />
<p className="text-sm">No data in period</p>
<p className="text-sm text-foreground-light">
May take a few minutes to show
</p>
</div>
</Panel.Content>
</Panel>
)}
</ScaffoldSectionContent>
</ScaffoldSection>
</ScaffoldContainer>
<ScaffoldDivider />
</Fragment>
)
})}
</>
)
}
export default InfrastructureActivity