diff --git a/src/site/biome.json b/src/site/biome.json new file mode 100644 index 00000000..330e804f --- /dev/null +++ b/src/site/biome.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.3/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "indentWidth": 2, + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "asNeeded", + "trailingCommas": "es5" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} \ No newline at end of file diff --git a/src/site/bun.lockb b/src/site/bun.lockb index 19985005..06672e78 100755 Binary files a/src/site/bun.lockb and b/src/site/bun.lockb differ diff --git a/src/site/package.json b/src/site/package.json index 64c7d498..cc8fbaf2 100644 --- a/src/site/package.json +++ b/src/site/package.json @@ -8,7 +8,11 @@ "build": "lingui extract --overwrite && lingui compile && vite build", "preview": "vite preview", "sync": "lingui extract --overwrite && lingui compile", - "sync_and_purge": "lingui extract --overwrite --clean && lingui compile" + "sync_and_purge": "lingui extract --overwrite --clean && lingui compile", + "format": "biome format --write .", + "lint": "biome lint .", + "check": "biome check .", + "check:fix": "biome check --fix ." }, "dependencies": { "@henrygd/queue": "^1.0.7", @@ -49,6 +53,7 @@ "valibot": "^0.42.1" }, "devDependencies": { + "@biomejs/biome": "2.2.3", "@lingui/cli": "^5.4.1", "@lingui/swc-plugin": "^5.6.1", "@lingui/vite-plugin": "^5.4.1", diff --git a/src/site/src/components/routes/home.tsx b/src/site/src/components/routes/home.tsx index 09312e8d..46327f75 100644 --- a/src/site/src/components/routes/home.tsx +++ b/src/site/src/components/routes/home.tsx @@ -1,22 +1,22 @@ -import { Suspense, memo, useEffect, useMemo } from "react" -import { Card, CardContent, CardHeader, CardTitle } from "../ui/card" -import { $alerts, $allSystemsById } from "@/lib/stores" -import { useStore } from "@nanostores/react" -import { GithubIcon } from "lucide-react" -import { Separator } from "../ui/separator" -import { AlertRecord } from "@/types" -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" -import { $router, Link } from "../router" import { Plural, Trans, useLingui } from "@lingui/react/macro" +import { useStore } from "@nanostores/react" import { getPagePath } from "@nanostores/router" -import { alertInfo } from "@/lib/alerts" +import { GithubIcon } from "lucide-react" +import { memo, Suspense, useEffect, useMemo } from "react" +import { $router, Link } from "@/components/router" import SystemsTable from "@/components/systems-table/systems-table" +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Separator } from "@/components/ui/separator" +import { alertInfo } from "@/lib/alerts" +import { $alerts, $allSystemsById } from "@/lib/stores" +import type { AlertRecord } from "@/types" -export default memo(function () { +export default memo(() => { const { t } = useLingui() useEffect(() => { - document.title = t`Dashboard` + " / Beszel" + document.title = `${t`Dashboard`} / Beszel` }, [t]) return useMemo( @@ -32,6 +32,7 @@ export default memo(function () { href="https://github.com/henrygd/beszel" target="_blank" className="flex items-center gap-0.5 text-muted-foreground hover:text-foreground duration-75" + rel="noopener" > GitHub @@ -40,6 +41,7 @@ export default memo(function () { href="https://github.com/henrygd/beszel/releases" target="_blank" className="text-muted-foreground hover:text-foreground duration-75" + rel="noopener" > Beszel {globalThis.BESZEL.HUB_VERSION} @@ -71,6 +73,7 @@ const ActiveAlerts = () => { return { activeAlerts, alertsKey } }, [alerts]) + // biome-ignore lint/correctness/useExhaustiveDependencies: alertsKey is inclusive return useMemo(() => { if (activeAlerts.length === 0) { return null diff --git a/src/site/src/components/routes/system.tsx b/src/site/src/components/routes/system.tsx index 22a76ff0..3fcb32a4 100644 --- a/src/site/src/components/routes/system.tsx +++ b/src/site/src/components/routes/system.tsx @@ -1,24 +1,34 @@ import { t } from "@lingui/core/macro" -import { Plural, Trans } from "@lingui/react/macro" +import { Plural, Trans, useLingui } from "@lingui/react/macro" +import { useStore } from "@nanostores/react" +import { getPagePath } from "@nanostores/router" +import { timeTicks } from "d3-time" +import { ClockArrowUp, CpuIcon, GlobeIcon, LayoutGridIcon, MonitorIcon, XIcon } from "lucide-react" +import { subscribeKeys } from "nanostores" +import React, { type JSX, memo, useCallback, useEffect, useMemo, useRef, useState } from "react" +import AreaChartDefault from "@/components/charts/area-chart" +import ContainerChart from "@/components/charts/container-chart" +import DiskChart from "@/components/charts/disk-chart" +import GpuPowerChart from "@/components/charts/gpu-power-chart" +import { useContainerChartConfigs } from "@/components/charts/hooks" +import LoadAverageChart from "@/components/charts/load-average-chart" +import MemChart from "@/components/charts/mem-chart" +import SwapChart from "@/components/charts/swap-chart" +import TemperatureChart from "@/components/charts/temperature-chart" +import { getPbTimestamp, pb } from "@/lib/api" +import { ChartType, Os, SystemStatus, Unit } from "@/lib/enums" +import { batteryStateTranslations } from "@/lib/i18n" import { - $systems, + $allSystemsByName, $chartTime, $containerFilter, - $userSettings, $direction, $maxValues, + $systems, $temperatureFilter, - $allSystemsByName, + $userSettings, } from "@/lib/stores" -import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types" -import { useContainerChartConfigs } from "@/components/charts/hooks" -import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums" -import React, { memo, useCallback, useEffect, useMemo, useRef, useState, type JSX } 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 { useIntersectionObserver } from "@/lib/use-intersection-observer" import { chartTimeData, cn, @@ -30,34 +40,33 @@ import { toFixedFloat, useBrowserStorage, } from "@/lib/utils" -import { getPbTimestamp, pb } from "@/lib/api" +import type { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types" +import ChartTimeSelect from "../charts/chart-time-select" +import { $router, navigate } from "../router" +import Spinner from "../spinner" +import { Button } from "../ui/button" +import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card" +import { AppleIcon, ChartAverage, ChartMax, FreeBsdIcon, Rows, TuxIcon, WindowsIcon } from "../ui/icons" +import { Input } from "../ui/input" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" 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, WindowsIcon, AppleIcon, FreeBsdIcon } 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 { useLingui } from "@lingui/react/macro" -import { $router, navigate } from "../router" -import { getPagePath } from "@nanostores/router" -import { batteryStateTranslations } from "@/lib/i18n" -import AreaChartDefault from "@/components/charts/area-chart" -import ContainerChart from "@/components/charts/container-chart" -import MemChart from "@/components/charts/mem-chart" -import DiskChart from "@/components/charts/disk-chart" -import SwapChart from "@/components/charts/swap-chart" -import TemperatureChart from "@/components/charts/temperature-chart" -import GpuPowerChart from "@/components/charts/gpu-power-chart" -import LoadAverageChart from "@/components/charts/load-average-chart" -import { subscribeKeys } from "nanostores" -const cache = new Map() +type ChartTimeData = { + time: number + data: { + ticks: number[] + domain: number[] + } + chartTime: ChartTimes +} + + +const cache = new Map() // create ticks and domain for charts function getTimeData(chartTime: ChartTimes, lastCreated: number) { - const cached = cache.get("td") + const cached = cache.get("td") as ChartTimeData | undefined if (cached && cached.chartTime === chartTime) { if (!lastCreated || cached.time >= lastCreated) { return cached.data @@ -79,7 +88,7 @@ function getTimeData(chartTime: ChartTimes, lastCreated: number) { function addEmptyValues( prevRecords: T[], newRecords: T[], - expectedInterval: number + expectedInterval: number, ) { const modifiedRecords: T[] = [] let prevTime = (prevRecords.at(-1)?.created ?? 0) as number @@ -90,7 +99,7 @@ function addEmptyValues( const interval = record.created - prevTime // if interval is too large, add a null record if (interval > expectedInterval / 2 + expectedInterval) { - // @ts-ignore + // @ts-expect-error modifiedRecords.push({ created: null, stats: null }) } } @@ -100,8 +109,9 @@ function addEmptyValues( return modifiedRecords } -async function getStats(collection: string, system: SystemRecord, chartTime: ChartTimes): Promise { - const lastCached = cache.get(`${system.id}_${chartTime}_${collection}`)?.at(-1)?.created as number +async function getStats(collection: string, system: SystemRecord, chartTime: ChartTimes): Promise { + const cachedStats = cache.get(`${system.id}_${chartTime}_${collection}`) as T[] | undefined + const lastCached = cachedStats?.at(-1)?.created as number return await pb.collection(collection).getFullList({ filter: pb.filter("system={:id} && created > {:created} && type={:type}", { id: system.id, @@ -137,6 +147,7 @@ export default memo(function SystemDetail({ name }: { name: string }) { const [chartLoading, setChartLoading] = useState(true) const isLongerChart = chartTime !== "1h" const userSettings = $userSettings.get() + const chartWrapRef = useRef(null) useEffect(() => { document.title = `${name} / Beszel` @@ -160,10 +171,11 @@ export default memo(function SystemDetail({ name }: { name: string }) { }) }, [name]) + // biome-ignore lint/correctness/useExhaustiveDependencies: not necessary const chartData: ChartData = useMemo(() => { const lastCreated = Math.max( (systemStats.at(-1)?.created as number) ?? 0, - (containerData.at(-1)?.created as number) ?? 0 + (containerData.at(-1)?.created as number) ?? 0, ) return { systemStats, @@ -178,8 +190,29 @@ export default memo(function SystemDetail({ name }: { name: string }) { // Share chart config computation for all container charts const containerChartConfigs = useContainerChartConfigs(containerData) + // make container stats for charts + const makeContainerData = useCallback((containers: ContainerStatsRecord[]) => { + const containerData = [] as ChartData["containerData"] + for (let { created, stats } of containers) { + if (!created) { + // @ts-expect-error add null value for gaps + containerData.push({ created: null }) + continue + } + created = new Date(created).getTime() + // @ts-expect-error not dealing with this rn + const containerStats: ChartData["containerData"][0] = { created } + for (const container of stats) { + containerStats[container.n] = container + } + containerData.push(containerStats) + } + setContainerData(containerData) + }, []) + // get stats - useEffect(() => { + // biome-ignore lint/correctness/useExhaustiveDependencies: not necessary + useEffect(() => { if (!system.id || !chartTime) { return } @@ -223,25 +256,6 @@ export default memo(function SystemDetail({ name }: { name: string }) { }) }, [system, chartTime]) - // make container stats for charts - const makeContainerData = useCallback((containers: ContainerStatsRecord[]) => { - const containerData = [] as ChartData["containerData"] - for (let { created, stats } of containers) { - if (!created) { - // @ts-ignore add null value for gaps - containerData.push({ created: null }) - continue - } - created = new Date(created).getTime() - // @ts-ignore not dealing with this rn - let containerStats: ChartData["containerData"][0] = { created } - for (let container of stats) { - containerStats[container.n] = container - } - containerData.push(containerStats) - } - setContainerData(containerData) - }, []) // values for system info bar const systemInfo = useMemo(() => { @@ -303,10 +317,10 @@ export default memo(function SystemDetail({ name }: { name: string }) { ] as { value: string | number | undefined label?: string - Icon: any + Icon: React.ElementType hide?: boolean }[] - }, [system.info, t]) + }, [system, t]) /** Space for tooltip if more than 12 containers */ useEffect(() => { @@ -315,12 +329,12 @@ export default memo(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 = chartWrapRef.current as HTMLDivElement const wrapperRect = wrapperEl.getBoundingClientRect() const chartRect = netCardRef.current.getBoundingClientRect() const distanceToBottom = wrapperRect.bottom - chartRect.bottom setBottomSpacing(tooltipHeight - distanceToBottom) - }, [netCardRef, containerData]) + }, [containerData]) // keyboard navigation between systems useEffect(() => { @@ -343,15 +357,17 @@ export default memo(function SystemDetail({ name }: { name: string }) { } switch (e.key) { case "ArrowLeft": - case "h": + case "h": { const prevIndex = (currentIndex - 1 + systems.length) % systems.length persistChartTime.current = true return navigate(getPagePath($router, "system", { name: systems[prevIndex].name })) + } case "ArrowRight": - case "l": + case "l": { const nextIndex = (currentIndex + 1) % systems.length persistChartTime.current = true return navigate(getPagePath($router, "system", { name: systems[nextIndex].name })) + } } } return listen(document, "keyup", handleKeyUp) @@ -380,7 +396,7 @@ export default memo(function SystemDetail({ name }: { name: string }) { return ( <> -
+
{/* system info */}
@@ -406,7 +422,7 @@ export default memo(function SystemDetail({ name }: { name: string }) { {translatedStatus}
- {systemInfo.map(({ value, label, Icon, hide }, i) => { + {systemInfo.map(({ value, label, Icon, hide }) => { if (hide || !value) { return null } @@ -416,7 +432,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
) return ( -
+
{label ? ( @@ -479,8 +495,8 @@ export default memo(function SystemDetail({ name }: { name: string }) { opacity: 0.4, }, ]} - tickFormatter={(val) => toFixedFloat(val, 2) + "%"} - contentFormatter={({ value }) => decimalString(value) + "%"} + tickFormatter={(val) => `${toFixedFloat(val, 2)}%`} + contentFormatter={({ value }) => `${decimalString(value)}%`} /> @@ -558,11 +574,11 @@ export default memo(function SystemDetail({ name }: { name: string }) { ]} tickFormatter={(val) => { const { value, unit } = formatBytes(val, true, userSettings.unitDisk, true) - return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={({ value }) => { const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, true) - return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit + return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}` }} /> @@ -603,12 +619,12 @@ export default memo(function SystemDetail({ name }: { name: string }) { }, ]} tickFormatter={(val) => { - let { value, unit } = formatBytes(val, true, userSettings.unitNet, false) - return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit + const { value, unit } = formatBytes(val, true, userSettings.unitNet, false) + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={(data) => { const { value, unit } = formatBytes(data.value, true, userSettings.unitNet, false) - return decimalString(value, value >= 100 ? 1 : 2) + " " + unit + return `${decimalString(value, value >= 100 ? 1 : 2)} ${unit}` }} /> @@ -682,7 +698,7 @@ export default memo(function SystemDetail({ name }: { name: string }) { description={`${t({ message: "Current state", comment: "Context: Battery state", - })}: ${batteryStateTranslations[systemStats.at(-1)?.stats.bat![1] ?? 0]()}`} + })}: ${batteryStateTranslations[systemStats.at(-1)?.stats.bat?.[1] ?? 0]()}`} > toFixedFloat(val, 2) + "%"} - contentFormatter={({ value }) => decimalString(value) + "%"} + tickFormatter={(val) => `${toFixedFloat(val, 2)}%`} + contentFormatter={({ value }) => `${decimalString(value)}%`} /> { const { value, unit } = formatBytes(val, false, Unit.Bytes, true) - return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={({ value }) => { const { value: convertedValue, unit } = formatBytes(value, false, Unit.Bytes, true) - return decimalString(convertedValue) + " " + unit + return `${decimalString(convertedValue)} ${unit}` }} /> @@ -819,11 +835,11 @@ export default memo(function SystemDetail({ name }: { name: string }) { maxToggled={maxValues} tickFormatter={(val) => { const { value, unit } = formatBytes(val, true, userSettings.unitDisk, true) - return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit + return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` }} contentFormatter={({ value }) => { const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, true) - return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit + return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}` }} /> @@ -846,7 +862,7 @@ function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilt const handleChange = useCallback((e: React.ChangeEvent) => { store.set(e.target.value) - }, []) + }, [store]) return ( <> diff --git a/src/site/src/components/ui/alert-dialog.tsx b/src/site/src/components/ui/alert-dialog.tsx index 443fedd5..ab18153b 100644 --- a/src/site/src/components/ui/alert-dialog.tsx +++ b/src/site/src/components/ui/alert-dialog.tsx @@ -1,8 +1,7 @@ -import * as React from "react" import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" - -import { cn } from "@/lib/utils" +import * as React from "react" import { buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" const AlertDialog = AlertDialogPrimitive.Root diff --git a/src/site/src/components/ui/badge.tsx b/src/site/src/components/ui/badge.tsx index 2d34fa02..6c654f77 100644 --- a/src/site/src/components/ui/badge.tsx +++ b/src/site/src/components/ui/badge.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" +import type * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/button.tsx b/src/site/src/components/ui/button.tsx index 3d7041fa..4d7710ad 100644 --- a/src/site/src/components/ui/button.tsx +++ b/src/site/src/components/ui/button.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/chart.tsx b/src/site/src/components/ui/chart.tsx index d08c8ce5..10cd64ed 100644 --- a/src/site/src/components/ui/chart.tsx +++ b/src/site/src/components/ui/chart.tsx @@ -1,10 +1,8 @@ +import type { JSX } from "react" import * as React from "react" import * as RechartsPrimitive from "recharts" - import { chartTimeData, cn } from "@/lib/utils" -import { ChartData } from "@/types" - -import type { JSX } from "react" +import type { ChartData } from "@/types" // Format: { THEME_NAME: CSS_SELECTOR } const THEMES = { light: "", dark: ".dark" } as const @@ -134,7 +132,7 @@ const ChartTooltipContent = React.forwardRef< payload = payload?.filter((item) => (item.name as string)?.toLowerCase().includes(filter.toLowerCase())) } if (itemSorter) { - // @ts-ignore + // @ts-expect-error payload?.sort(itemSorter) } }, [itemSorter, payload]) @@ -331,7 +329,7 @@ function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: } let cachedAxis: JSX.Element -const xAxis = function ({ domain, ticks, chartTime }: ChartData) { +const xAxis = ({ domain, ticks, chartTime }: ChartData) => { if (cachedAxis && domain[0] === cachedAxis.props.domain[0]) { return cachedAxis } diff --git a/src/site/src/components/ui/checkbox.tsx b/src/site/src/components/ui/checkbox.tsx index 1f2edb7b..772f0eda 100644 --- a/src/site/src/components/ui/checkbox.tsx +++ b/src/site/src/components/ui/checkbox.tsx @@ -1,8 +1,8 @@ "use client" -import * as React from "react" import * as CheckboxPrimitive from "@radix-ui/react-checkbox" import { Check } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/collapsible.tsx b/src/site/src/components/ui/collapsible.tsx index f30abcb0..ca7cd89b 100644 --- a/src/site/src/components/ui/collapsible.tsx +++ b/src/site/src/components/ui/collapsible.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import { ChevronDownIcon, HourglassIcon } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" import { Button } from "./button" @@ -17,11 +17,7 @@ export function Collapsible({ title, children, description, defaultOpen = false, return (
- - {description && ( -
- {description} -
- )} + {description &&
{description}
} {isOpen && (
-
- {children} -
+
{children}
)}
) -} \ No newline at end of file +} diff --git a/src/site/src/components/ui/command.tsx b/src/site/src/components/ui/command.tsx index 31f8f922..db170f30 100644 --- a/src/site/src/components/ui/command.tsx +++ b/src/site/src/components/ui/command.tsx @@ -1,9 +1,8 @@ -import * as React from "react" import { Command as CommandPrimitive } from "cmdk" import { SearchIcon } from "lucide-react" - -import { cn } from "@/lib/utils" +import type * as React from "react" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { cn } from "@/lib/utils" function Command({ className, ...props }: React.ComponentProps) { return ( diff --git a/src/site/src/components/ui/dialog.tsx b/src/site/src/components/ui/dialog.tsx index 793f6431..6510f5f2 100644 --- a/src/site/src/components/ui/dialog.tsx +++ b/src/site/src/components/ui/dialog.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/dropdown-menu.tsx b/src/site/src/components/ui/dropdown-menu.tsx index d3042c0c..52a436e0 100644 --- a/src/site/src/components/ui/dropdown-menu.tsx +++ b/src/site/src/components/ui/dropdown-menu.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import { Check, ChevronRight, Circle } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/icons.tsx b/src/site/src/components/ui/icons.tsx index 3c493cbb..96d2cc1b 100644 --- a/src/site/src/components/ui/icons.tsx +++ b/src/site/src/components/ui/icons.tsx @@ -1,4 +1,4 @@ -import { SVGProps } from "react" +import type { SVGProps } from "react" // linux-logo-bold from https://github.com/phosphor-icons/core (MIT license) export function TuxIcon(props: SVGProps) { diff --git a/src/site/src/components/ui/input-copy.tsx b/src/site/src/components/ui/input-copy.tsx index 47f00e75..319faf0a 100644 --- a/src/site/src/components/ui/input-copy.tsx +++ b/src/site/src/components/ui/input-copy.tsx @@ -1,9 +1,9 @@ -import { copyToClipboard } from "@/lib/utils" -import { Input } from "./input" import { Trans } from "@lingui/react/macro" -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip" import { CopyIcon } from "lucide-react" +import { copyToClipboard } from "@/lib/utils" import { Button } from "./button" +import { Input } from "./input" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip" export function InputCopy({ value, id, name }: { value: string; id: string; name: string }) { return ( diff --git a/src/site/src/components/ui/input-tags.tsx b/src/site/src/components/ui/input-tags.tsx index dc4340a1..cf9c4099 100644 --- a/src/site/src/components/ui/input-tags.tsx +++ b/src/site/src/components/ui/input-tags.tsx @@ -1,9 +1,9 @@ +import { XIcon } from "lucide-react" import * as React from "react" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { XIcon } from "lucide-react" -import { type InputProps } from "./input" import { cn } from "@/lib/utils" +import type { InputProps } from "./input" type InputTagsProps = Omit & { value: string[] diff --git a/src/site/src/components/ui/input.tsx b/src/site/src/components/ui/input.tsx index 56398504..bcd2f073 100644 --- a/src/site/src/components/ui/input.tsx +++ b/src/site/src/components/ui/input.tsx @@ -1,4 +1,4 @@ -import * as React from "react" +import type * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/label.tsx b/src/site/src/components/ui/label.tsx index f966e86d..b01b5c86 100644 --- a/src/site/src/components/ui/label.tsx +++ b/src/site/src/components/ui/label.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/otp.tsx b/src/site/src/components/ui/otp.tsx index 45118863..ab561470 100644 --- a/src/site/src/components/ui/otp.tsx +++ b/src/site/src/components/ui/otp.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import { OTPInput, OTPInputContext } from "input-otp" import { MinusIcon } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/select.tsx b/src/site/src/components/ui/select.tsx index c72a27c6..e7b1483b 100644 --- a/src/site/src/components/ui/select.tsx +++ b/src/site/src/components/ui/select.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as SelectPrimitive from "@radix-ui/react-select" import { Check, ChevronDown, ChevronUp } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" @@ -78,8 +78,7 @@ const SelectContent = React.forwardRef< {children} diff --git a/src/site/src/components/ui/separator.tsx b/src/site/src/components/ui/separator.tsx index e309634e..0fbea8e7 100644 --- a/src/site/src/components/ui/separator.tsx +++ b/src/site/src/components/ui/separator.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/sheet.tsx b/src/site/src/components/ui/sheet.tsx index 187fd959..b14a88cb 100644 --- a/src/site/src/components/ui/sheet.tsx +++ b/src/site/src/components/ui/sheet.tsx @@ -1,6 +1,6 @@ -import * as React from "react" import * as SheetPrimitive from "@radix-ui/react-dialog" import { XIcon } from "lucide-react" +import type * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/slider.tsx b/src/site/src/components/ui/slider.tsx index fabb9368..bc34e4e3 100644 --- a/src/site/src/components/ui/slider.tsx +++ b/src/site/src/components/ui/slider.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as SliderPrimitive from "@radix-ui/react-slider" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/switch.tsx b/src/site/src/components/ui/switch.tsx index dcb54f77..1ab30f22 100644 --- a/src/site/src/components/ui/switch.tsx +++ b/src/site/src/components/ui/switch.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as SwitchPrimitives from "@radix-ui/react-switch" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/tabs.tsx b/src/site/src/components/ui/tabs.tsx index 842f3029..5b48294d 100644 --- a/src/site/src/components/ui/tabs.tsx +++ b/src/site/src/components/ui/tabs.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as TabsPrimitive from "@radix-ui/react-tabs" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/toast.tsx b/src/site/src/components/ui/toast.tsx index df5c33e6..d1d60901 100644 --- a/src/site/src/components/ui/toast.tsx +++ b/src/site/src/components/ui/toast.tsx @@ -1,7 +1,7 @@ -import * as React from "react" import * as ToastPrimitives from "@radix-ui/react-toast" import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react" +import * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/toaster.tsx b/src/site/src/components/ui/toaster.tsx index 43255ab2..0ad35d12 100644 --- a/src/site/src/components/ui/toaster.tsx +++ b/src/site/src/components/ui/toaster.tsx @@ -6,18 +6,16 @@ export function Toaster() { return ( - {toasts.map(function ({ id, title, description, action, ...props }) { - return ( - -
- {title && {title}} - {description && {description}} -
- {action} - -
- ) - })} + {toasts.map(({ id, title, description, action, ...props }) => ( + +
+ {title && {title}} + {description && {description}} +
+ {action} + +
+ ))}
) diff --git a/src/site/src/components/ui/tooltip.tsx b/src/site/src/components/ui/tooltip.tsx index 3d907709..d273e8f2 100644 --- a/src/site/src/components/ui/tooltip.tsx +++ b/src/site/src/components/ui/tooltip.tsx @@ -1,5 +1,5 @@ -import * as React from "react" import * as TooltipPrimitive from "@radix-ui/react-tooltip" +import type * as React from "react" import { cn } from "@/lib/utils" diff --git a/src/site/src/components/ui/use-toast.ts b/src/site/src/components/ui/use-toast.ts index b76ec1ab..403efff6 100644 --- a/src/site/src/components/ui/use-toast.ts +++ b/src/site/src/components/ui/use-toast.ts @@ -103,7 +103,7 @@ export const reducer = (state: State, action: Action): State => { ? { ...t, open: false, - } + } : t ), } diff --git a/src/site/src/lib/alerts.ts b/src/site/src/lib/alerts.ts index f94cd0eb..c5f93be5 100644 --- a/src/site/src/lib/alerts.ts +++ b/src/site/src/lib/alerts.ts @@ -1,9 +1,9 @@ -import type { AlertInfo, AlertRecord } from "@/types" -import type { RecordSubscription } from "pocketbase" -import { $alerts } from "@/lib/stores" -import { EthernetIcon } from "@/components/ui/icons" -import { ServerIcon, CpuIcon, MemoryStickIcon, HardDriveIcon, ThermometerIcon, HourglassIcon } from "lucide-react" import { t } from "@lingui/core/macro" +import { CpuIcon, HardDriveIcon, HourglassIcon, MemoryStickIcon, ServerIcon, ThermometerIcon } from "lucide-react" +import type { RecordSubscription } from "pocketbase" +import { EthernetIcon } from "@/components/ui/icons" +import { $alerts } from "@/lib/stores" +import type { AlertInfo, AlertRecord } from "@/types" import { pb } from "./api" /** Alert info for each alert type */ @@ -14,7 +14,7 @@ export const alertInfo: Record = { icon: ServerIcon, desc: () => t`Triggers when status switches between up and down`, /** "for x minutes" is appended to desc when only one value */ - singleDesc: () => t`System` + " " + t`Down`, + singleDesc: () => `${t`System`} ${t`Down`}`, }, CPU: { name: () => t`CPU Usage`, @@ -127,7 +127,7 @@ export const alertManager = (() => { return (data: RecordSubscription) => { const { record } = data batch.set(`${record.system}${record.name}`, data) - clearTimeout(timeout!) + clearTimeout(timeout) timeout = setTimeout(() => { const groups = { create: [], update: [], delete: [] } as Record for (const { action, record } of batch.values()) { diff --git a/src/site/src/lib/api.ts b/src/site/src/lib/api.ts index a81e9232..38a52145 100644 --- a/src/site/src/lib/api.ts +++ b/src/site/src/lib/api.ts @@ -1,10 +1,10 @@ -import { ChartTimes, UserSettings } from "@/types" -import { $alerts, $allSystemsByName, $userSettings } from "./stores" -import { toast } from "@/components/ui/use-toast" import { t } from "@lingui/core/macro" -import { chartTimeData } from "./utils" import PocketBase from "pocketbase" import { basePath } from "@/components/router" +import { toast } from "@/components/ui/use-toast" +import type { ChartTimes, UserSettings } from "@/types" +import { $alerts, $allSystemsByName, $userSettings } from "./stores" +import { chartTimeData } from "./utils" /** PocketBase JS Client */ export const pb = new PocketBase(basePath) @@ -46,7 +46,7 @@ export async function updateUserSettings() { } // create user settings if error fetching existing try { - const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record!.id }) + const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record?.id }) $userSettings.set(createdSettings.settings) } catch (e) { console.error("create settings", e) diff --git a/src/site/src/lib/i18n.ts b/src/site/src/lib/i18n.ts index ac8a2752..c8fcb12b 100644 --- a/src/site/src/lib/i18n.ts +++ b/src/site/src/lib/i18n.ts @@ -1,11 +1,11 @@ -import { $direction } from "./stores" -import { i18n } from "@lingui/core" import type { Messages } from "@lingui/core" +import { i18n } from "@lingui/core" +import { t } from "@lingui/core/macro" +import { detect, fromNavigator, fromStorage } from "@lingui/detect-locale" import languages from "@/lib/languages" -import { detect, fromStorage, fromNavigator } from "@lingui/detect-locale" import { messages as enMessages } from "@/locales/en/en" import { BatteryState } from "./enums" -import { t } from "@lingui/core/macro" +import { $direction } from "./stores" // activates locale function activateLocale(locale: string, messages: Messages = enMessages) { @@ -18,7 +18,7 @@ function activateLocale(locale: string, messages: Messages = enMessages) { // dynamically loads translations for the given locale export async function dynamicActivate(locale: string) { - if (locale == "en") { + if (locale === "en") { activateLocale(locale) } else { try { diff --git a/src/site/src/lib/stores.ts b/src/site/src/lib/stores.ts index 81a4cb65..917ac669 100644 --- a/src/site/src/lib/stores.ts +++ b/src/site/src/lib/stores.ts @@ -1,7 +1,7 @@ -import { atom, computed, map, ReadableAtom } from "nanostores" -import { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types" -import { Unit } from "./enums" +import { atom, computed, map, type ReadableAtom } from "nanostores" +import type { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types" import { pb } from "./api" +import { Unit } from "./enums" /** Store if user is authenticated */ export const $authenticated = atom(pb.authStore.isValid) diff --git a/src/site/src/lib/systemsManager.ts b/src/site/src/lib/systemsManager.ts index 4570c193..39afb5d5 100644 --- a/src/site/src/lib/systemsManager.ts +++ b/src/site/src/lib/systemsManager.ts @@ -1,15 +1,15 @@ -import { SystemRecord } from "@/types" -import { PreinitializedMapStore } from "nanostores" +import type { PreinitializedMapStore } from "nanostores" import { pb, verifyAuth } from "@/lib/api" import { - $allSystemsByName, - $upSystems, - $downSystems, - $pausedSystems, $allSystemsById, + $allSystemsByName, + $downSystems, $longestSystemNameLen, + $pausedSystems, + $upSystems, } from "@/lib/stores" -import { updateFavicon, FAVICON_DEFAULT, FAVICON_GREEN, FAVICON_RED } from "@/lib/utils" +import { FAVICON_DEFAULT, FAVICON_GREEN, FAVICON_RED, updateFavicon } from "@/lib/utils" +import type { SystemRecord } from "@/types" import { SystemStatus } from "./enums" const COLLECTION = pb.collection("systems") diff --git a/src/site/src/lib/utils.ts b/src/site/src/lib/utils.ts index 134a57cb..e7d42e04 100644 --- a/src/site/src/lib/utils.ts +++ b/src/site/src/lib/utils.ts @@ -1,13 +1,13 @@ import { t } from "@lingui/core/macro" -import { toast } from "@/components/ui/use-toast" import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" -import { $copyContent, $userSettings } from "./stores" -import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types" import { timeDay, timeHour } from "d3-time" import { useEffect, useState } from "react" -import { MeterState, Unit } from "./enums" +import { twMerge } from "tailwind-merge" import { prependBasePath } from "@/components/router" +import { toast } from "@/components/ui/use-toast" +import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types" +import { MeterState, Unit } from "./enums" +import { $copyContent, $userSettings } from "./stores" export const FAVICON_DEFAULT = "favicon.svg" export const FAVICON_GREEN = "favicon-green.svg" @@ -31,7 +31,7 @@ export async function copyToClipboard(content: string) { duration, description: t`Copied to clipboard`, }) - } catch (e: any) { + } catch (e) { $copyContent.set(content) } } @@ -113,7 +113,7 @@ export function toFixedFloat(num: number, digits: number) { return parseFloat((digits === 0 ? Math.ceil(num) : num).toFixed(digits)) } -let decimalFormatters: Map = new Map() +const decimalFormatters: Map = new Map() /** Format number to x decimal places, maintaining trailing zeros */ export function decimalString(num: number, digits = 2) { if (digits === 0) { @@ -131,7 +131,7 @@ export function decimalString(num: number, digits = 2) { } /** Get value from local or session storage */ -function getStorageValue(key: string, defaultValue: any, storageInterface: Storage = localStorage) { +function getStorageValue(key: string, defaultValue: unknown, storageInterface: Storage = localStorage) { const saved = storageInterface?.getItem(key) return saved ? JSON.parse(saved) : defaultValue } @@ -142,6 +142,7 @@ export function useBrowserStorage(key: string, defaultValue: T, storageInterf const [value, setValue] = useState(() => { return getStorageValue(key, defaultValue, storageInterface) }) + // biome-ignore lint/correctness/useExhaustiveDependencies: storageInterface won't change useEffect(() => { storageInterface?.setItem(key, JSON.stringify(value)) }, [key, value]) @@ -155,7 +156,7 @@ export function formatTemperature(celsius: number, unit?: Unit): { value: number unit = $userSettings.get().unitTemp || Unit.Celsius } // need loose equality check due to form data being strings - if (unit == Unit.Fahrenheit) { + if (unit === Unit.Fahrenheit) { return { value: celsius * 1.8 + 32, unit: "°F", @@ -178,7 +179,7 @@ export function formatBytes( if (isMegabytes) size *= 1024 * 1024 // need loose equality check due to form data being strings - if (unit == Unit.Bits) { + if (unit === Unit.Bits) { const bits = size * 8 const suffix = perSecond ? "ps" : "" if (bits < 1000) return { value: bits, unit: `b${suffix}` } @@ -314,6 +315,7 @@ export function getMeterState(value: number): MeterState { return value >= colorCrit ? MeterState.Crit : value >= colorWarn ? MeterState.Warn : MeterState.Good } +// biome-ignore lint/suspicious/noExplicitAny: any is used to allow any function to be passed in export function debounce any>(func: T, wait: number): (...args: Parameters) => void { let timeout: ReturnType return (...args: Parameters) => { @@ -323,8 +325,10 @@ export function debounce any>(func: T, wait: numbe } // Cache for runOnce +// biome-ignore lint/complexity/noBannedTypes: Function is used to allow any function to be passed in const runOnceCache = new WeakMap() /** Run a function only once */ +// biome-ignore lint/suspicious/noExplicitAny: any is used to allow any function to be passed in export function runOnce any>(fn: T): T { return ((...args: Parameters) => { let state = runOnceCache.get(fn) diff --git a/src/site/src/main.tsx b/src/site/src/main.tsx index d241d475..9b444114 100644 --- a/src/site/src/main.tsx +++ b/src/site/src/main.tsx @@ -1,21 +1,21 @@ import "./index.css" -// import { Suspense, lazy, useEffect, StrictMode } from "react" -import { Suspense, lazy, memo, useEffect } from "react" -import ReactDOM from "react-dom/client" -import { ThemeProvider } from "./components/theme-provider.tsx" -import { DirectionProvider } from "@radix-ui/react-direction" -import { $authenticated, $publicKey, $copyContent, $direction } from "./lib/stores.ts" -import { pb, updateUserSettings } from "./lib/api.ts" -import * as systemsManager from "./lib/systemsManager.ts" -import { useStore } from "@nanostores/react" -import { Toaster } from "./components/ui/toaster.tsx" -import { $router } from "./components/router.tsx" -import Navbar from "./components/navbar.tsx" -import { I18nProvider } from "@lingui/react" import { i18n } from "@lingui/core" -import { getLocale, dynamicActivate } from "./lib/i18n" -import { alertManager } from "./lib/alerts" -import Settings from "./components/routes/settings/layout.tsx" +import { I18nProvider } from "@lingui/react" +import { useStore } from "@nanostores/react" +import { DirectionProvider } from "@radix-ui/react-direction" +// import { Suspense, lazy, useEffect, StrictMode } from "react" +import { lazy, memo, Suspense, useEffect } from "react" +import ReactDOM from "react-dom/client" +import Navbar from "@/components/navbar.tsx" +import { $router } from "@/components/router.tsx" +import Settings from "@/components/routes/settings/layout.tsx" +import { ThemeProvider } from "@/components/theme-provider.tsx" +import { Toaster } from "@/components/ui/toaster.tsx" +import { alertManager } from "@/lib/alerts" +import { pb, updateUserSettings } from "@/lib/api.ts" +import { dynamicActivate, getLocale } from "@/lib/i18n" +import { $authenticated, $copyContent, $direction, $publicKey } from "@/lib/stores.ts" +import * as systemsManager from "@/lib/systemsManager.ts" const LoginPage = lazy(() => import("@/components/login/login.tsx")) const Home = lazy(() => import("@/components/routes/home.tsx")) @@ -114,7 +114,7 @@ const I18nApp = () => { ) } -ReactDOM.createRoot(document.getElementById("app")!).render( +ReactDOM.createRoot(document.getElementById("app") as HTMLElement).render( // strict mode in dev mounts / unmounts components twice // and breaks the clipboard dialog //