mirror of
https://github.com/fankes/beszel.git
synced 2025-10-18 09:19:27 +08:00
add biome and apply selected formatting / fixes
This commit is contained in:
38
src/site/biome.json
Normal file
38
src/site/biome.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
@@ -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",
|
||||
|
@@ -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"
|
||||
>
|
||||
<GithubIcon className="h-3 w-3" /> GitHub
|
||||
</a>
|
||||
@@ -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}
|
||||
</a>
|
||||
@@ -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
|
||||
|
@@ -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<string, any>()
|
||||
type ChartTimeData = {
|
||||
time: number
|
||||
data: {
|
||||
ticks: number[]
|
||||
domain: number[]
|
||||
}
|
||||
chartTime: ChartTimes
|
||||
}
|
||||
|
||||
|
||||
const cache = new Map<string, ChartTimeData | SystemStatsRecord[] | ContainerStatsRecord[]>()
|
||||
|
||||
// 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<T extends SystemStatsRecord | ContainerStatsRecord>(
|
||||
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<T extends SystemStatsRecord | ContainerStatsRecord>(
|
||||
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<T extends SystemStatsRecord | ContainerStatsRecord>(
|
||||
return modifiedRecords
|
||||
}
|
||||
|
||||
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
|
||||
async function getStats<T extends SystemStatsRecord | ContainerStatsRecord>(collection: string, system: SystemRecord, chartTime: ChartTimes): Promise<T[]> {
|
||||
const cachedStats = cache.get(`${system.id}_${chartTime}_${collection}`) as T[] | undefined
|
||||
const lastCached = cachedStats?.at(-1)?.created as number
|
||||
return await pb.collection<T>(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<HTMLDivElement>(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 (
|
||||
<>
|
||||
<div id="chartwrap" className="grid gap-4 mb-14 overflow-x-clip">
|
||||
<div ref={chartWrapRef} className="grid gap-4 mb-14 overflow-x-clip">
|
||||
{/* system info */}
|
||||
<Card>
|
||||
<div className="grid xl:flex gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
|
||||
@@ -406,7 +422,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
||||
</span>
|
||||
{translatedStatus}
|
||||
</div>
|
||||
{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 }) {
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div key={i} className="contents">
|
||||
<div key={value} className="contents">
|
||||
<Separator orientation="vertical" className="h-4 bg-primary/30" />
|
||||
{label ? (
|
||||
<TooltipProvider>
|
||||
@@ -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)}%`}
|
||||
/>
|
||||
</ChartCard>
|
||||
|
||||
@@ -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}`
|
||||
}}
|
||||
/>
|
||||
</ChartCard>
|
||||
@@ -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}`
|
||||
}}
|
||||
/>
|
||||
</ChartCard>
|
||||
@@ -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]()}`}
|
||||
>
|
||||
<AreaChartDefault
|
||||
chartData={chartData}
|
||||
@@ -738,8 +754,8 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
||||
opacity: 0.35,
|
||||
},
|
||||
]}
|
||||
tickFormatter={(val) => toFixedFloat(val, 2) + "%"}
|
||||
contentFormatter={({ value }) => decimalString(value) + "%"}
|
||||
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
|
||||
contentFormatter={({ value }) => `${decimalString(value)}%`}
|
||||
/>
|
||||
</ChartCard>
|
||||
<ChartCard
|
||||
@@ -761,11 +777,11 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
||||
max={gpu.mt}
|
||||
tickFormatter={(val) => {
|
||||
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}`
|
||||
}}
|
||||
/>
|
||||
</ChartCard>
|
||||
@@ -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}`
|
||||
}}
|
||||
/>
|
||||
</ChartCard>
|
||||
@@ -846,7 +862,7 @@ function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilt
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
store.set(e.target.value)
|
||||
}, [])
|
||||
}, [store])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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 (
|
||||
<div className={cn("border rounded-lg", className)}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-between p-4 font-semibold"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<Button variant="ghost" className="w-full justify-between p-4 font-semibold" onClick={() => setIsOpen(!isOpen)}>
|
||||
<div className="flex items-center gap-2">
|
||||
{icon}
|
||||
{title}
|
||||
@@ -32,18 +28,12 @@ export function Collapsible({ title, children, description, defaultOpen = false,
|
||||
})}
|
||||
/>
|
||||
</Button>
|
||||
{description && (
|
||||
<div className="px-4 pb-2 text-sm text-muted-foreground">
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
{description && <div className="px-4 pb-2 text-sm text-muted-foreground">{description}</div>}
|
||||
{isOpen && (
|
||||
<div className="px-4 pb-4">
|
||||
<div className="grid gap-3">
|
||||
{children}
|
||||
</div>
|
||||
<div className="grid gap-3">{children}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -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<typeof CommandPrimitive>) {
|
||||
return (
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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<SVGSVGElement>) {
|
||||
|
@@ -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 (
|
||||
|
@@ -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<InputProps, "value" | "onChange"> & {
|
||||
value: string[]
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import * as React from "react"
|
||||
import type * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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<
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)"
|
||||
position === "popper" && "h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -6,18 +6,16 @@ export function Toaster() {
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
{toasts.map(function ({ id, title, description, action, ...props }) {
|
||||
return (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && <ToastDescription>{description}</ToastDescription>}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
)
|
||||
})}
|
||||
{toasts.map(({ id, title, description, action, ...props }) => (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && <ToastDescription>{description}</ToastDescription>}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
))}
|
||||
<ToastViewport />
|
||||
</ToastProvider>
|
||||
)
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -103,7 +103,7 @@ export const reducer = (state: State, action: Action): State => {
|
||||
? {
|
||||
...t,
|
||||
open: false,
|
||||
}
|
||||
}
|
||||
: t
|
||||
),
|
||||
}
|
||||
|
@@ -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<string, AlertInfo> = {
|
||||
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<AlertRecord>) => {
|
||||
const { record } = data
|
||||
batch.set(`${record.system}${record.name}`, data)
|
||||
clearTimeout(timeout!)
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => {
|
||||
const groups = { create: [], update: [], delete: [] } as Record<string, AlertRecord[]>
|
||||
for (const { action, record } of batch.values()) {
|
||||
|
@@ -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)
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
@@ -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<SystemRecord>("systems")
|
||||
|
@@ -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<number, Intl.NumberFormat> = new Map()
|
||||
const decimalFormatters: Map<number, Intl.NumberFormat> = 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<T>(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<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void {
|
||||
let timeout: ReturnType<typeof setTimeout>
|
||||
return (...args: Parameters<T>) => {
|
||||
@@ -323,8 +325,10 @@ export function debounce<T extends (...args: any[]) => 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<Function, { done: boolean; result: unknown }>()
|
||||
/** Run a function only once */
|
||||
// biome-ignore lint/suspicious/noExplicitAny: any is used to allow any function to be passed in
|
||||
export function runOnce<T extends (...args: any[]) => any>(fn: T): T {
|
||||
return ((...args: Parameters<T>) => {
|
||||
let state = runOnceCache.get(fn)
|
||||
|
@@ -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
|
||||
//<StrictMode>
|
||||
|
Reference in New Issue
Block a user