add prettier config and format files site files

This commit is contained in:
Henry Dollman
2024-10-30 11:03:09 -04:00
parent 8827996553
commit 3505b215a2
75 changed files with 3096 additions and 3533 deletions

View File

@@ -1,40 +1,34 @@
import { $systems, pb, $chartTime, $containerFilter, $userSettings } from '@/lib/stores'
import {
ChartData,
ChartTimes,
ContainerStatsRecord,
SystemRecord,
SystemStatsRecord,
} from '@/types'
import React, { lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Card, CardHeader, CardTitle, CardDescription } from '../ui/card'
import { useStore } from '@nanostores/react'
import Spinner from '../spinner'
import { ClockArrowUp, CpuIcon, GlobeIcon, LayoutGridIcon, MonitorIcon, XIcon } from 'lucide-react'
import ChartTimeSelect from '../charts/chart-time-select'
import { chartTimeData, cn, getPbTimestamp, useLocalStorage } from '@/lib/utils'
import { Separator } from '../ui/separator'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'
import { Button } from '../ui/button'
import { Input } from '../ui/input'
import { ChartAverage, ChartMax, Rows, TuxIcon } from '../ui/icons'
import { useIntersectionObserver } from '@/lib/use-intersection-observer'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
import { timeTicks } from 'd3-time'
import { useTranslation } from 'react-i18next'
import { $systems, pb, $chartTime, $containerFilter, $userSettings } from "@/lib/stores"
import { ChartData, ChartTimes, ContainerStatsRecord, SystemRecord, SystemStatsRecord } from "@/types"
import React, { lazy, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card"
import { useStore } from "@nanostores/react"
import Spinner from "../spinner"
import { ClockArrowUp, CpuIcon, GlobeIcon, LayoutGridIcon, MonitorIcon, XIcon } from "lucide-react"
import ChartTimeSelect from "../charts/chart-time-select"
import { chartTimeData, cn, getPbTimestamp, useLocalStorage } from "@/lib/utils"
import { Separator } from "../ui/separator"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
import { Button } from "../ui/button"
import { Input } from "../ui/input"
import { ChartAverage, ChartMax, Rows, TuxIcon } from "../ui/icons"
import { useIntersectionObserver } from "@/lib/use-intersection-observer"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
import { timeTicks } from "d3-time"
import { useTranslation } from "react-i18next"
const AreaChartDefault = lazy(() => import('../charts/area-chart'))
const ContainerChart = lazy(() => import('../charts/container-chart'))
const MemChart = lazy(() => import('../charts/mem-chart'))
const DiskChart = lazy(() => import('../charts/disk-chart'))
const SwapChart = lazy(() => import('../charts/swap-chart'))
const TemperatureChart = lazy(() => import('../charts/temperature-chart'))
const AreaChartDefault = lazy(() => import("../charts/area-chart"))
const ContainerChart = lazy(() => import("../charts/container-chart"))
const MemChart = lazy(() => import("../charts/mem-chart"))
const DiskChart = lazy(() => import("../charts/disk-chart"))
const SwapChart = lazy(() => import("../charts/swap-chart"))
const TemperatureChart = lazy(() => import("../charts/temperature-chart"))
const cache = new Map<string, any>()
// create ticks and domain for charts
function getTimeData(chartTime: ChartTimes, lastCreated: number) {
const cached = cache.get('td')
const cached = cache.get("td")
if (cached && cached.chartTime === chartTime) {
if (!lastCreated || cached.time >= lastCreated) {
return cached.data
@@ -43,14 +37,12 @@ function getTimeData(chartTime: ChartTimes, lastCreated: number) {
const now = new Date()
const startTime = chartTimeData[chartTime].getOffset(now)
const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) =>
date.getTime()
)
const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime())
const data = {
ticks,
domain: [chartTimeData[chartTime].getOffset(now).getTime(), now.getTime()],
}
cache.set('td', { time: now.getTime(), data, chartTime })
cache.set("td", { time: now.getTime(), data, chartTime })
return data
}
@@ -79,20 +71,16 @@ function addEmptyValues<T extends SystemStatsRecord | ContainerStatsRecord>(
return modifiedRecords
}
async function getStats<T>(
collection: string,
system: SystemRecord,
chartTime: ChartTimes
): Promise<T[]> {
async function getStats<T>(collection: string, system: SystemRecord, chartTime: ChartTimes): Promise<T[]> {
const lastCached = cache.get(`${system.id}_${chartTime}_${collection}`)?.at(-1)?.created as number
return await pb.collection<T>(collection).getFullList({
filter: pb.filter('system={:id} && created > {:created} && type={:type}', {
filter: pb.filter("system={:id} && created > {:created} && type={:type}", {
id: system.id,
created: getPbTimestamp(chartTime, lastCached ? new Date(lastCached + 1000) : undefined),
type: chartTimeData[chartTime].type,
}),
fields: 'created,stats',
sort: 'created',
fields: "created,stats",
sort: "created",
})
}
@@ -105,15 +93,15 @@ export default function SystemDetail({ name }: { name: string }) {
const cpuMaxStore = useState(false)
const bandwidthMaxStore = useState(false)
const diskIoMaxStore = useState(false)
const [grid, setGrid] = useLocalStorage('grid', true)
const [grid, setGrid] = useLocalStorage("grid", true)
const [system, setSystem] = useState({} as SystemRecord)
const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[])
const [containerData, setContainerData] = useState([] as ChartData['containerData'])
const [containerData, setContainerData] = useState([] as ChartData["containerData"])
const netCardRef = useRef<HTMLDivElement>(null)
const [containerFilterBar, setContainerFilterBar] = useState(null as null | JSX.Element)
const [bottomSpacing, setBottomSpacing] = useState(0)
const [chartLoading, setChartLoading] = useState(false)
const isLongerChart = chartTime !== '1h'
const isLongerChart = chartTime !== "1h"
useEffect(() => {
document.title = `${name} / Beszel`
@@ -123,7 +111,7 @@ export default function SystemDetail({ name }: { name: string }) {
setSystemStats([])
setContainerData([])
setContainerFilterBar(null)
$containerFilter.set('')
$containerFilter.set("")
cpuMaxStore[1](false)
bandwidthMaxStore[1](false)
diskIoMaxStore[1](false)
@@ -153,11 +141,11 @@ export default function SystemDetail({ name }: { name: string }) {
if (!system.id) {
return
}
pb.collection<SystemRecord>('systems').subscribe(system.id, (e) => {
pb.collection<SystemRecord>("systems").subscribe(system.id, (e) => {
setSystem(e.record)
})
return () => {
pb.collection('systems').unsubscribe(system.id)
pb.collection("systems").unsubscribe(system.id)
}
}, [system.id])
@@ -182,8 +170,8 @@ export default function SystemDetail({ name }: { name: string }) {
// loading: true
setChartLoading(true)
Promise.allSettled([
getStats<SystemStatsRecord>('system_stats', system, chartTime),
getStats<ContainerStatsRecord>('container_stats', system, chartTime),
getStats<SystemStatsRecord>("system_stats", system, chartTime),
getStats<ContainerStatsRecord>("container_stats", system, chartTime),
]).then(([systemStats, containerStats]) => {
// loading: false
setChartLoading(false)
@@ -192,10 +180,8 @@ export default function SystemDetail({ name }: { name: string }) {
// make new system stats
const ss_cache_key = `${system.id}_${chartTime}_system_stats`
let systemData = (cache.get(ss_cache_key) || []) as SystemStatsRecord[]
if (systemStats.status === 'fulfilled' && systemStats.value.length) {
systemData = systemData.concat(
addEmptyValues(systemData, systemStats.value, expectedInterval)
)
if (systemStats.status === "fulfilled" && systemStats.value.length) {
systemData = systemData.concat(addEmptyValues(systemData, systemStats.value, expectedInterval))
if (systemData.length > 120) {
systemData = systemData.slice(-100)
}
@@ -205,10 +191,8 @@ export default function SystemDetail({ name }: { name: string }) {
// make new container stats
const cs_cache_key = `${system.id}_${chartTime}_container_stats`
let containerData = (cache.get(cs_cache_key) || []) as ContainerStatsRecord[]
if (containerStats.status === 'fulfilled' && containerStats.value.length) {
containerData = containerData.concat(
addEmptyValues(containerData, containerStats.value, expectedInterval)
)
if (containerStats.status === "fulfilled" && containerStats.value.length) {
containerData = containerData.concat(addEmptyValues(containerData, containerStats.value, expectedInterval))
if (containerData.length > 120) {
containerData = containerData.slice(-100)
}
@@ -225,7 +209,7 @@ export default function SystemDetail({ name }: { name: string }) {
// make container stats for charts
const makeContainerData = useCallback((containers: ContainerStatsRecord[]) => {
const containerData = [] as ChartData['containerData']
const containerData = [] as ChartData["containerData"]
for (let { created, stats } of containers) {
if (!created) {
// @ts-ignore add null value for gaps
@@ -234,7 +218,7 @@ export default function SystemDetail({ name }: { name: string }) {
}
created = new Date(created).getTime()
// @ts-ignore not dealing with this rn
let containerStats: ChartData['containerData'][0] = { created }
let containerStats: ChartData["containerData"][0] = { created }
for (let container of stats) {
containerStats[container.n] = container
}
@@ -251,7 +235,7 @@ export default function SystemDetail({ name }: { name: string }) {
let uptime: number | string = system.info.u
if (system.info.u < 172800) {
const hours = Math.trunc(uptime / 3600)
uptime = `${hours} hour${hours == 1 ? '' : 's'}`
uptime = `${hours} hour${hours == 1 ? "" : "s"}`
} else {
uptime = `${Math.trunc(system.info?.u / 86400)} days`
}
@@ -260,14 +244,14 @@ export default function SystemDetail({ name }: { name: string }) {
{
value: system.info.h,
Icon: MonitorIcon,
label: 'Hostname',
label: "Hostname",
// hide if hostname is same as host or name
hide: system.info.h === system.host || system.info.h === system.name,
},
{ value: uptime, Icon: ClockArrowUp, label: 'Uptime' },
{ value: system.info.k, Icon: TuxIcon, label: 'Kernel' },
{ value: uptime, Icon: ClockArrowUp, label: "Uptime" },
{ value: system.info.k, Icon: TuxIcon, label: "Kernel" },
{
value: `${system.info.m} (${system.info.c}c${system.info.t ? `/${system.info.t}t` : ''})`,
value: `${system.info.m} (${system.info.c}c${system.info.t ? `/${system.info.t}t` : ""})`,
Icon: CpuIcon,
hide: !system.info.m,
},
@@ -286,7 +270,7 @@ export default function SystemDetail({ name }: { name: string }) {
return
}
const tooltipHeight = (Object.keys(containerData[0]).length - 11) * 17.8 - 40
const wrapperEl = document.getElementById('chartwrap') as HTMLDivElement
const wrapperEl = document.getElementById("chartwrap") as HTMLDivElement
const wrapperRect = wrapperEl.getBoundingClientRect()
const chartRect = netCardRef.current.getBoundingClientRect()
const distanceToBottom = wrapperRect.bottom - chartRect.bottom
@@ -298,7 +282,7 @@ export default function SystemDetail({ name }: { name: string }) {
}
// if no data, show empty state
const dataEmpty = !chartLoading && chartData.systemStats.length === 0;
const dataEmpty = !chartLoading && chartData.systemStats.length === 0
return (
<>
@@ -310,19 +294,19 @@ export default function SystemDetail({ name }: { name: string }) {
<h1 className="text-[1.6rem] font-semibold mb-1.5">{system.name}</h1>
<div className="flex flex-wrap items-center gap-3 gap-y-2 text-sm opacity-90">
<div className="capitalize flex gap-2 items-center">
<span className={cn('relative flex h-3 w-3')}>
{system.status === 'up' && (
<span className={cn("relative flex h-3 w-3")}>
{system.status === "up" && (
<span
className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
style={{ animationDuration: '1.5s' }}
style={{ animationDuration: "1.5s" }}
></span>
)}
<span
className={cn('relative inline-flex rounded-full h-3 w-3', {
'bg-green-500': system.status === 'up',
'bg-red-500': system.status === 'down',
'bg-primary/40': system.status === 'paused',
'bg-yellow-500': system.status === 'pending',
className={cn("relative inline-flex rounded-full h-3 w-3", {
"bg-green-500": system.status === "up",
"bg-red-500": system.status === "down",
"bg-primary/40": system.status === "paused",
"bg-yellow-500": system.status === "pending",
})}
></span>
</span>
@@ -361,7 +345,7 @@ export default function SystemDetail({ name }: { name: string }) {
<Tooltip>
<TooltipTrigger asChild>
<Button
aria-label={t('monitor.toggle_grid')}
aria-label={t("monitor.toggle_grid")}
variant="outline"
size="icon"
className="hidden lg:flex p-0 text-primary"
@@ -374,7 +358,7 @@ export default function SystemDetail({ name }: { name: string }) {
)}
</Button>
</TooltipTrigger>
<TooltipContent>{t('monitor.toggle_grid')}</TooltipContent>
<TooltipContent>{t("monitor.toggle_grid")}</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
@@ -386,24 +370,21 @@ export default function SystemDetail({ name }: { name: string }) {
<ChartCard
empty={dataEmpty}
grid={grid}
title={t('monitor.total_cpu_usage')}
description={`${cpuMaxStore[0] && isLongerChart ? t('monitor.max_1_min') : t('monitor.average') } ${t('monitor.cpu_des')}`}
title={t("monitor.total_cpu_usage")}
description={`${cpuMaxStore[0] && isLongerChart ? t("monitor.max_1_min") : t("monitor.average")} ${t(
"monitor.cpu_des"
)}`}
cornerEl={isLongerChart ? <SelectAvgMax store={cpuMaxStore} /> : null}
>
<AreaChartDefault
chartData={chartData}
chartName="CPU Usage"
maxToggled={cpuMaxStore[0]}
unit="%"
/>
<AreaChartDefault chartData={chartData} chartName="CPU Usage" maxToggled={cpuMaxStore[0]} unit="%" />
</ChartCard>
{containerFilterBar && (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t('monitor.docker_cpu_usage')}
description={t('monitor.docker_cpu_des')}
title={t("monitor.docker_cpu_usage")}
description={t("monitor.docker_cpu_des")}
cornerEl={containerFilterBar}
>
<ContainerChart chartData={chartData} dataKey="c" chartName="cpu" />
@@ -413,8 +394,8 @@ export default function SystemDetail({ name }: { name: string }) {
<ChartCard
empty={dataEmpty}
grid={grid}
title={t('monitor.total_memory_usage')}
description={t('monitor.memory_des')}
title={t("monitor.total_memory_usage")}
description={t("monitor.memory_des")}
>
<MemChart chartData={chartData} />
</ChartCard>
@@ -423,15 +404,15 @@ export default function SystemDetail({ name }: { name: string }) {
<ChartCard
empty={dataEmpty}
grid={grid}
title={t('monitor.docker_memory_usage')}
description={t('monitor.docker_memory_des')}
title={t("monitor.docker_memory_usage")}
description={t("monitor.docker_memory_des")}
cornerEl={containerFilterBar}
>
<ContainerChart chartData={chartData} chartName="mem" dataKey="m" unit=" MB" />
</ChartCard>
)}
<ChartCard empty={dataEmpty} grid={grid} title={t('monitor.disk_space')} description={t('monitor.disk_des')}>
<ChartCard empty={dataEmpty} grid={grid} title={t("monitor.disk_space")} description={t("monitor.disk_des")}>
<DiskChart
chartData={chartData}
dataKey="stats.du"
@@ -442,42 +423,34 @@ export default function SystemDetail({ name }: { name: string }) {
<ChartCard
empty={dataEmpty}
grid={grid}
title={t('monitor.disk_io')}
description={t('monitor.disk_io_des')}
title={t("monitor.disk_io")}
description={t("monitor.disk_io_des")}
cornerEl={isLongerChart ? <SelectAvgMax store={diskIoMaxStore} /> : null}
>
<AreaChartDefault
chartData={chartData}
maxToggled={diskIoMaxStore[0]}
chartName="dio"
/>
<AreaChartDefault chartData={chartData} maxToggled={diskIoMaxStore[0]} chartName="dio" />
</ChartCard>
<ChartCard
empty={dataEmpty}
grid={grid}
title={t('monitor.bandwidth')}
title={t("monitor.bandwidth")}
cornerEl={isLongerChart ? <SelectAvgMax store={bandwidthMaxStore} /> : null}
description={t('monitor.bandwidth_des')}
description={t("monitor.bandwidth_des")}
>
<AreaChartDefault
chartData={chartData}
maxToggled={bandwidthMaxStore[0]}
chartName="bw"
/>
<AreaChartDefault chartData={chartData} maxToggled={bandwidthMaxStore[0]} chartName="bw" />
</ChartCard>
{containerFilterBar && containerData.length > 0 && (
<div
ref={netCardRef}
className={cn({
'col-span-full': !grid,
"col-span-full": !grid,
})}
>
<ChartCard
empty={dataEmpty}
title={t('monitor.docker_network_io')}
description={t('monitor.docker_network_io_des')}
title={t("monitor.docker_network_io")}
description={t("monitor.docker_network_io_des")}
cornerEl={containerFilterBar}
>
{/* @ts-ignore */}
@@ -487,13 +460,23 @@ export default function SystemDetail({ name }: { name: string }) {
)}
{(systemStats.at(-1)?.stats.su ?? 0) > 0 && (
<ChartCard empty={dataEmpty} grid={grid} title={t('monitor.swap_usage')} description={t('monitor.swap_des')}>
<ChartCard
empty={dataEmpty}
grid={grid}
title={t("monitor.swap_usage")}
description={t("monitor.swap_des")}
>
<SwapChart chartData={chartData} />
</ChartCard>
)}
{systemStats.at(-1)?.stats.t && (
<ChartCard empty={dataEmpty} grid={grid} title={t('monitor.temperature')} description={t('monitor.temperature_des')}>
<ChartCard
empty={dataEmpty}
grid={grid}
title={t("monitor.temperature")}
description={t("monitor.temperature_des")}
>
<TemperatureChart chartData={chartData} />
</ChartCard>
)}
@@ -508,8 +491,8 @@ export default function SystemDetail({ name }: { name: string }) {
<ChartCard
empty={dataEmpty}
grid={grid}
title={`${extraFsName} ${t('monitor.usage')}`}
description={`${t('monitor.disk_usage_of')} ${extraFsName}`}
title={`${extraFsName} ${t("monitor.usage")}`}
description={`${t("monitor.disk_usage_of")} ${extraFsName}`}
>
<DiskChart
chartData={chartData}
@@ -521,7 +504,7 @@ export default function SystemDetail({ name }: { name: string }) {
empty={dataEmpty}
grid={grid}
title={`${extraFsName} I/O`}
description={`${t('monitor.throughput_of')} ${extraFsName}`}
description={`${t("monitor.throughput_of")} ${extraFsName}`}
cornerEl={isLongerChart ? <SelectAvgMax store={diskIoMaxStore} /> : null}
>
<AreaChartDefault
@@ -554,12 +537,7 @@ function ContainerFilterBar() {
return (
<>
<Input
placeholder={t('filter')}
className="pl-4 pr-8"
value={containerFilter}
onChange={handleChange}
/>
<Input placeholder={t("filter")} className="pl-4 pr-8" value={containerFilter} onChange={handleChange} />
{containerFilter && (
<Button
type="button"
@@ -567,7 +545,7 @@ function ContainerFilterBar() {
size="icon"
aria-label="Clear"
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
onClick={() => $containerFilter.set('')}
onClick={() => $containerFilter.set("")}
>
<XIcon className="h-4 w-4" />
</Button>
@@ -576,28 +554,24 @@ function ContainerFilterBar() {
)
}
function SelectAvgMax({
store,
}: {
store: [boolean, React.Dispatch<React.SetStateAction<boolean>>]
}) {
function SelectAvgMax({ store }: { store: [boolean, React.Dispatch<React.SetStateAction<boolean>>] }) {
const { t } = useTranslation()
const [max, setMax] = store
const Icon = max ? ChartMax : ChartAverage
return (
<Select value={max ? 'max' : 'avg'} onValueChange={(e) => setMax(e === 'max')}>
<Select value={max ? "max" : "avg"} onValueChange={(e) => setMax(e === "max")}>
<SelectTrigger className="relative pl-10 pr-5">
<Icon className="h-4 w-4 absolute left-4 top-1/2 -translate-y-1/2 opacity-85" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem key="avg" value="avg">
{t('monitor.average')}
{t("monitor.average")}
</SelectItem>
<SelectItem key="max" value="max">
{t('monitor.max_1_min')}
{t("monitor.max_1_min")}
</SelectItem>
</SelectContent>
</Select>
@@ -616,24 +590,17 @@ function ChartCard({
description: string
children: React.ReactNode
grid?: boolean
empty?: boolean,
empty?: boolean
cornerEl?: JSX.Element | null
}) {
const { isIntersecting, ref } = useIntersectionObserver()
return (
<Card
className={cn('pb-2 sm:pb-4 odd:last-of-type:col-span-full', { 'col-span-full': !grid })}
ref={ref}
>
<Card className={cn("pb-2 sm:pb-4 odd:last-of-type:col-span-full", { "col-span-full": !grid })} ref={ref}>
<CardHeader className="pb-5 pt-4 relative space-y-1 max-sm:py-3 max-sm:px-4">
<CardTitle className="text-xl sm:text-2xl">{title}</CardTitle>
<CardDescription>{description}</CardDescription>
{cornerEl && (
<div className="relative py-1 block sm:w-44 sm:absolute sm:top-2.5 sm:right-3.5">
{cornerEl}
</div>
)}
{cornerEl && <div className="relative py-1 block sm:w-44 sm:absolute sm:top-2.5 sm:right-3.5">{cornerEl}</div>}
</CardHeader>
<div className="pl-0 w-[calc(100%-1.6em)] h-52 relative">
{<Spinner empty={empty} />}