mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 09:49:28 +08:00
Adds display unit preference (#938)
* Adds temperature unit preference * add unit preferences for networking * adds options for MB/s and bps. * supports disk throughput unit preferences
This commit is contained in:
@@ -17,6 +17,9 @@ type UserSettings struct {
|
|||||||
ChartTime string `json:"chartTime"`
|
ChartTime string `json:"chartTime"`
|
||||||
NotificationEmails []string `json:"emails"`
|
NotificationEmails []string `json:"emails"`
|
||||||
NotificationWebhooks []string `json:"webhooks"`
|
NotificationWebhooks []string `json:"webhooks"`
|
||||||
|
TemperatureUnit string `json:"temperatureUnit"` // "celsius" or "fahrenheit"
|
||||||
|
NetworkUnit string `json:"networkUnit"` // "mbps" (MB/s) or "bps"
|
||||||
|
DiskUnit string `json:"diskUnit"` // "mbps" (MB/s) or "bps"
|
||||||
// Language string `json:"lang"`
|
// Language string `json:"lang"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +46,9 @@ func (um *UserManager) InitializeUserSettings(e *core.RecordEvent) error {
|
|||||||
ChartTime: "1h",
|
ChartTime: "1h",
|
||||||
NotificationEmails: []string{},
|
NotificationEmails: []string{},
|
||||||
NotificationWebhooks: []string{},
|
NotificationWebhooks: []string{},
|
||||||
|
TemperatureUnit: "celsius",
|
||||||
|
NetworkUnit: "mbps",
|
||||||
|
DiskUnit: "mbps",
|
||||||
}
|
}
|
||||||
record.UnmarshalJSONField("settings", &settings)
|
record.UnmarshalJSONField("settings", &settings)
|
||||||
if len(settings.NotificationEmails) == 0 {
|
if len(settings.NotificationEmails) == 0 {
|
||||||
|
@@ -9,11 +9,15 @@ import {
|
|||||||
toFixedWithoutTrailingZeros,
|
toFixedWithoutTrailingZeros,
|
||||||
decimalString,
|
decimalString,
|
||||||
chartMargin,
|
chartMargin,
|
||||||
|
convertNetworkSpeed,
|
||||||
|
convertDiskSpeed,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
// import Spinner from '../spinner'
|
// import Spinner from '../spinner'
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
import { useLingui } from "@lingui/react/macro"
|
import { useLingui } from "@lingui/react/macro"
|
||||||
|
import { useStore } from "@nanostores/react"
|
||||||
|
import { $userSettings } from "@/lib/stores"
|
||||||
|
|
||||||
/** [label, key, color, opacity] */
|
/** [label, key, color, opacity] */
|
||||||
type DataKeys = [string, string, number, number]
|
type DataKeys = [string, string, number, number]
|
||||||
@@ -47,11 +51,26 @@ export default memo(function AreaChartDefault({
|
|||||||
}) {
|
}) {
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
const { i18n } = useLingui()
|
const { i18n } = useLingui()
|
||||||
|
const userSettings = useStore($userSettings)
|
||||||
|
|
||||||
const { chartTime } = chartData
|
const { chartTime } = chartData
|
||||||
|
|
||||||
const showMax = chartTime !== "1h" && maxToggled
|
const showMax = chartTime !== "1h" && maxToggled
|
||||||
|
|
||||||
|
// Determine if this is a network chart or disk chart and adjust unit accordingly
|
||||||
|
const isNetworkChart = chartName === "bw"
|
||||||
|
const isDiskChart = chartName === "dio" || chartName.startsWith("efs")
|
||||||
|
const displayUnit = useMemo(() => {
|
||||||
|
if (isNetworkChart) {
|
||||||
|
const { symbol } = convertNetworkSpeed(1, userSettings.networkUnit)
|
||||||
|
return symbol
|
||||||
|
} else if (isDiskChart) {
|
||||||
|
const { symbol } = convertDiskSpeed(1, userSettings.diskUnit)
|
||||||
|
return symbol
|
||||||
|
}
|
||||||
|
return unit
|
||||||
|
}, [isNetworkChart, isDiskChart, userSettings.networkUnit, userSettings.diskUnit, unit])
|
||||||
|
|
||||||
const dataKeys: DataKeys[] = useMemo(() => {
|
const dataKeys: DataKeys[] = useMemo(() => {
|
||||||
// [label, key, color, opacity]
|
// [label, key, color, opacity]
|
||||||
if (chartName === "CPU Usage") {
|
if (chartName === "CPU Usage") {
|
||||||
@@ -102,8 +121,14 @@ export default memo(function AreaChartDefault({
|
|||||||
let val: string
|
let val: string
|
||||||
if (tickFormatter) {
|
if (tickFormatter) {
|
||||||
val = tickFormatter(value)
|
val = tickFormatter(value)
|
||||||
|
} else if (isNetworkChart) {
|
||||||
|
const { value: convertedValue, symbol } = convertNetworkSpeed(value, userSettings.networkUnit)
|
||||||
|
val = toFixedWithoutTrailingZeros(convertedValue, 2) + symbol
|
||||||
|
} else if (isDiskChart) {
|
||||||
|
const { value: convertedValue, symbol } = convertDiskSpeed(value, userSettings.diskUnit)
|
||||||
|
val = toFixedWithoutTrailingZeros(convertedValue, 2) + symbol
|
||||||
} else {
|
} else {
|
||||||
val = toFixedWithoutTrailingZeros(value, 2) + unit
|
val = toFixedWithoutTrailingZeros(value, 2) + displayUnit
|
||||||
}
|
}
|
||||||
return updateYAxisWidth(val)
|
return updateYAxisWidth(val)
|
||||||
}}
|
}}
|
||||||
@@ -120,8 +145,14 @@ export default memo(function AreaChartDefault({
|
|||||||
contentFormatter={({ value }) => {
|
contentFormatter={({ value }) => {
|
||||||
if (contentFormatter) {
|
if (contentFormatter) {
|
||||||
return contentFormatter(value)
|
return contentFormatter(value)
|
||||||
|
} else if (isNetworkChart) {
|
||||||
|
const { display } = convertNetworkSpeed(value, userSettings.networkUnit)
|
||||||
|
return display
|
||||||
|
} else if (isDiskChart) {
|
||||||
|
const { display } = convertDiskSpeed(value, userSettings.diskUnit)
|
||||||
|
return display
|
||||||
}
|
}
|
||||||
return decimalString(value) + unit
|
return decimalString(value) + displayUnit
|
||||||
}}
|
}}
|
||||||
// indicator="line"
|
// indicator="line"
|
||||||
/>
|
/>
|
||||||
|
@@ -10,10 +10,11 @@ import {
|
|||||||
toFixedFloat,
|
toFixedFloat,
|
||||||
getSizeAndUnit,
|
getSizeAndUnit,
|
||||||
toFixedWithoutTrailingZeros,
|
toFixedWithoutTrailingZeros,
|
||||||
|
convertNetworkSpeed,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
// import Spinner from '../spinner'
|
// import Spinner from '../spinner'
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { $containerFilter } from "@/lib/stores"
|
import { $containerFilter, $userSettings } from "@/lib/stores"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { Separator } from "../ui/separator"
|
import { Separator } from "../ui/separator"
|
||||||
import { ChartType } from "@/lib/enums"
|
import { ChartType } from "@/lib/enums"
|
||||||
@@ -30,6 +31,7 @@ export default memo(function ContainerChart({
|
|||||||
unit?: string
|
unit?: string
|
||||||
}) {
|
}) {
|
||||||
const filter = useStore($containerFilter)
|
const filter = useStore($containerFilter)
|
||||||
|
const userSettings = useStore($userSettings)
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|
||||||
const { containerData } = chartData
|
const { containerData } = chartData
|
||||||
@@ -87,10 +89,15 @@ export default memo(function ContainerChart({
|
|||||||
const val = toFixedWithoutTrailingZeros(value, 2) + unit
|
const val = toFixedWithoutTrailingZeros(value, 2) + unit
|
||||||
return updateYAxisWidth(val)
|
return updateYAxisWidth(val)
|
||||||
}
|
}
|
||||||
|
} else if (isNetChart) {
|
||||||
|
obj.tickFormatter = (value) => {
|
||||||
|
const { value: convertedValue, symbol } = convertNetworkSpeed(value, userSettings.networkUnit)
|
||||||
|
return updateYAxisWidth(`${toFixedFloat(convertedValue, 2)}${symbol}`)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
obj.tickFormatter = (value) => {
|
obj.tickFormatter = (value) => {
|
||||||
const { v, u } = getSizeAndUnit(value, false)
|
const { v, u } = getSizeAndUnit(value, false)
|
||||||
return updateYAxisWidth(`${toFixedFloat(v, 2)}${u}${isNetChart ? "/s" : ""}`)
|
return updateYAxisWidth(`${toFixedFloat(v, 2)}${u}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// tooltip formatter
|
// tooltip formatter
|
||||||
@@ -99,12 +106,14 @@ export default memo(function ContainerChart({
|
|||||||
try {
|
try {
|
||||||
const sent = item?.payload?.[key]?.ns ?? 0
|
const sent = item?.payload?.[key]?.ns ?? 0
|
||||||
const received = item?.payload?.[key]?.nr ?? 0
|
const received = item?.payload?.[key]?.nr ?? 0
|
||||||
|
const { display: receivedDisplay } = convertNetworkSpeed(received, userSettings.networkUnit)
|
||||||
|
const { display: sentDisplay } = convertNetworkSpeed(sent, userSettings.networkUnit)
|
||||||
return (
|
return (
|
||||||
<span className="flex">
|
<span className="flex">
|
||||||
{decimalString(received)} MB/s
|
{receivedDisplay}
|
||||||
<span className="opacity-70 ms-0.5"> rx </span>
|
<span className="opacity-70 ms-0.5"> rx </span>
|
||||||
<Separator orientation="vertical" className="h-3 mx-1.5 bg-primary/40" />
|
<Separator orientation="vertical" className="h-3 mx-1.5 bg-primary/40" />
|
||||||
{decimalString(sent)} MB/s
|
{sentDisplay}
|
||||||
<span className="opacity-70 ms-0.5"> tx</span>
|
<span className="opacity-70 ms-0.5"> tx</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
@@ -15,14 +15,16 @@ import {
|
|||||||
toFixedWithoutTrailingZeros,
|
toFixedWithoutTrailingZeros,
|
||||||
decimalString,
|
decimalString,
|
||||||
chartMargin,
|
chartMargin,
|
||||||
|
convertTemperature,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
import { ChartData } from "@/types"
|
import { ChartData } from "@/types"
|
||||||
import { memo, useMemo } from "react"
|
import { memo, useMemo } from "react"
|
||||||
import { $temperatureFilter } from "@/lib/stores"
|
import { $temperatureFilter, $userSettings } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
|
|
||||||
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
|
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
|
||||||
const filter = useStore($temperatureFilter)
|
const filter = useStore($temperatureFilter)
|
||||||
|
const userSettings = useStore($userSettings)
|
||||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||||
|
|
||||||
if (chartData.systemStats.length === 0) {
|
if (chartData.systemStats.length === 0) {
|
||||||
@@ -36,13 +38,17 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
|
|||||||
colors: Record<string, string>
|
colors: Record<string, string>
|
||||||
}
|
}
|
||||||
const tempSums = {} as Record<string, number>
|
const tempSums = {} as Record<string, number>
|
||||||
|
const unit = userSettings.temperatureUnit || "celsius"
|
||||||
|
|
||||||
for (let data of chartData.systemStats) {
|
for (let data of chartData.systemStats) {
|
||||||
let newData = { created: data.created } as Record<string, number | string>
|
let newData = { created: data.created } as Record<string, number | string>
|
||||||
let keys = Object.keys(data.stats?.t ?? {})
|
let keys = Object.keys(data.stats?.t ?? {})
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
let key = keys[i]
|
let key = keys[i]
|
||||||
newData[key] = data.stats.t![key]
|
const celsiusTemp = data.stats.t![key]
|
||||||
tempSums[key] = (tempSums[key] ?? 0) + newData[key]
|
const { value } = convertTemperature(celsiusTemp, unit)
|
||||||
|
newData[key] = value
|
||||||
|
tempSums[key] = (tempSums[key] ?? 0) + value
|
||||||
}
|
}
|
||||||
newChartData.data.push(newData)
|
newChartData.data.push(newData)
|
||||||
}
|
}
|
||||||
@@ -51,7 +57,7 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
|
|||||||
newChartData.colors[key] = `hsl(${((keys.indexOf(key) * 360) / keys.length) % 360}, 60%, 55%)`
|
newChartData.colors[key] = `hsl(${((keys.indexOf(key) * 360) / keys.length) % 360}, 60%, 55%)`
|
||||||
}
|
}
|
||||||
return newChartData
|
return newChartData
|
||||||
}, [chartData])
|
}, [chartData, userSettings.temperatureUnit])
|
||||||
|
|
||||||
const colors = Object.keys(newChartData.colors)
|
const colors = Object.keys(newChartData.colors)
|
||||||
|
|
||||||
@@ -74,7 +80,8 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
|
|||||||
width={yAxisWidth}
|
width={yAxisWidth}
|
||||||
tickFormatter={(value) => {
|
tickFormatter={(value) => {
|
||||||
const val = toFixedWithoutTrailingZeros(value, 2)
|
const val = toFixedWithoutTrailingZeros(value, 2)
|
||||||
return updateYAxisWidth(val + " °C")
|
const { symbol } = convertTemperature(0, userSettings.temperatureUnit || "celsius")
|
||||||
|
return updateYAxisWidth(val + " " + symbol)
|
||||||
}}
|
}}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
@@ -88,7 +95,10 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
|
|||||||
content={
|
content={
|
||||||
<ChartTooltipContent
|
<ChartTooltipContent
|
||||||
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
||||||
contentFormatter={(item) => decimalString(item.value) + " °C"}
|
contentFormatter={(item) => {
|
||||||
|
const { symbol } = convertTemperature(0, userSettings.temperatureUnit || "celsius")
|
||||||
|
return decimalString(item.value) + " " + symbol
|
||||||
|
}}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@@ -101,6 +101,63 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="mb-1 text-lg font-medium">
|
||||||
|
<Trans>Unit preferences</Trans>
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
|
<Trans>Adjust Display units for metrics.</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="block" htmlFor="temperatureUnit">
|
||||||
|
<Trans>Temperature unit</Trans>
|
||||||
|
</Label>
|
||||||
|
<Select name="temperatureUnit" key={userSettings.temperatureUnit} defaultValue={userSettings.temperatureUnit || "celsius"}>
|
||||||
|
<SelectTrigger id="temperatureUnit">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="celsius">Celsius (°C)</SelectItem>
|
||||||
|
<SelectItem value="fahrenheit">Fahrenheit (°F)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="block" htmlFor="networkUnit">
|
||||||
|
<Trans>Network unit</Trans>
|
||||||
|
</Label>
|
||||||
|
<Select name="networkUnit" key={userSettings.networkUnit} defaultValue={userSettings.networkUnit || "mbps"}>
|
||||||
|
<SelectTrigger id="networkUnit">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="mbps">MB/s (Megabytes per second)</SelectItem>
|
||||||
|
<SelectItem value="bps">bps (Bits per second)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="block" htmlFor="diskUnit">
|
||||||
|
<Trans>Disk unit</Trans>
|
||||||
|
</Label>
|
||||||
|
<Select name="diskUnit" key={userSettings.diskUnit} defaultValue={userSettings.diskUnit || "mbps"}>
|
||||||
|
<SelectTrigger id="diskUnit">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="mbps">MB/s (Megabytes per second)</SelectItem>
|
||||||
|
<SelectItem value="bps">bps (Bits per second)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
<Button type="submit" className="flex items-center gap-1.5 disabled:opacity-100" disabled={isLoading}>
|
<Button type="submit" className="flex items-center gap-1.5 disabled:opacity-100" disabled={isLoading}>
|
||||||
{isLoading ? <LoaderCircleIcon className="h-4 w-4 animate-spin" /> : <SaveIcon className="h-4 w-4" />}
|
{isLoading ? <LoaderCircleIcon className="h-4 w-4 animate-spin" /> : <SaveIcon className="h-4 w-4" />}
|
||||||
<Trans>Save Settings</Trans>
|
<Trans>Save Settings</Trans>
|
||||||
|
@@ -63,9 +63,17 @@ import {
|
|||||||
PenBoxIcon,
|
PenBoxIcon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import { $systems, pb } from "@/lib/stores"
|
import { $systems, $userSettings, pb } from "@/lib/stores"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { cn, copyToClipboard, decimalString, isReadOnlyUser, useLocalStorage } from "@/lib/utils"
|
import {
|
||||||
|
cn,
|
||||||
|
copyToClipboard,
|
||||||
|
decimalString,
|
||||||
|
isReadOnlyUser,
|
||||||
|
useLocalStorage,
|
||||||
|
convertTemperature,
|
||||||
|
convertNetworkSpeed,
|
||||||
|
} from "@/lib/utils"
|
||||||
import AlertsButton from "../alerts/alert-button"
|
import AlertsButton from "../alerts/alert-button"
|
||||||
import { $router, Link, navigate } from "../router"
|
import { $router, Link, navigate } from "../router"
|
||||||
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons"
|
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons"
|
||||||
@@ -222,7 +230,9 @@ export default function SystemsTable() {
|
|||||||
header: sortableHeader,
|
header: sortableHeader,
|
||||||
cell(info) {
|
cell(info) {
|
||||||
const val = info.getValue() as number
|
const val = info.getValue() as number
|
||||||
return <span className="tabular-nums whitespace-nowrap">{decimalString(val, val >= 100 ? 1 : 2)} MB/s</span>
|
const userSettings = useStore($userSettings)
|
||||||
|
const { display } = convertNetworkSpeed(val, userSettings.networkUnit)
|
||||||
|
return <span className="tabular-nums whitespace-nowrap">{display}</span>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -278,9 +288,11 @@ export default function SystemsTable() {
|
|||||||
if (!val) {
|
if (!val) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
const userSettings = useStore($userSettings)
|
||||||
|
const { value, symbol } = convertTemperature(val, userSettings.temperatureUnit)
|
||||||
return (
|
return (
|
||||||
<span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-0.5")}>
|
<span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-0.5")}>
|
||||||
{decimalString(val)} °C
|
{decimalString(value, value >= 100 ? 1 : 2)} {symbol}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@@ -28,6 +28,9 @@ export const $maxValues = atom(false)
|
|||||||
export const $userSettings = map<UserSettings>({
|
export const $userSettings = map<UserSettings>({
|
||||||
chartTime: "1h",
|
chartTime: "1h",
|
||||||
emails: [pb.authStore.record?.email || ""],
|
emails: [pb.authStore.record?.email || ""],
|
||||||
|
temperatureUnit: "celsius",
|
||||||
|
networkUnit: "mbps",
|
||||||
|
diskUnit: "mbps",
|
||||||
})
|
})
|
||||||
// update local storage on change
|
// update local storage on change
|
||||||
$userSettings.subscribe((value) => {
|
$userSettings.subscribe((value) => {
|
||||||
|
@@ -3,7 +3,7 @@ import { toast } from "@/components/ui/use-toast"
|
|||||||
import { type ClassValue, clsx } from "clsx"
|
import { type ClassValue, clsx } from "clsx"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import { $alerts, $copyContent, $systems, $userSettings, pb } from "./stores"
|
import { $alerts, $copyContent, $systems, $userSettings, pb } from "./stores"
|
||||||
import { AlertInfo, AlertRecord, ChartTimeData, ChartTimes, FingerprintRecord, SystemRecord } from "@/types"
|
import { AlertInfo, AlertRecord, ChartTimeData, ChartTimes, FingerprintRecord, SystemRecord, TemperatureUnit, TemperatureConversion, SpeedUnit, SpeedConversion } from "@/types"
|
||||||
import { RecordModel, RecordSubscription } from "pocketbase"
|
import { RecordModel, RecordSubscription } from "pocketbase"
|
||||||
import { WritableAtom } from "nanostores"
|
import { WritableAtom } from "nanostores"
|
||||||
import { timeDay, timeHour } from "d3-time"
|
import { timeDay, timeHour } from "d3-time"
|
||||||
@@ -266,6 +266,109 @@ export function useLocalStorage<T>(key: string, defaultValue: T) {
|
|||||||
return [value, setValue]
|
return [value, setValue]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Convert temperature from Celsius to the specified unit */
|
||||||
|
export function convertTemperature(
|
||||||
|
celsius: number,
|
||||||
|
unit: TemperatureUnit = "celsius"
|
||||||
|
): TemperatureConversion {
|
||||||
|
switch (unit) {
|
||||||
|
case "fahrenheit":
|
||||||
|
return { value: (celsius * 9) / 5 + 32, symbol: "°F" }
|
||||||
|
default:
|
||||||
|
return { value: celsius, symbol: "°C" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert network speed from MB/s to the specified unit */
|
||||||
|
export function convertNetworkSpeed(
|
||||||
|
mbps: number,
|
||||||
|
unit: SpeedUnit = "mbps"
|
||||||
|
): SpeedConversion {
|
||||||
|
switch (unit) {
|
||||||
|
case "bps": {
|
||||||
|
const bps = mbps * 8 * 1_000_000 // Convert MB/s to bits per second
|
||||||
|
|
||||||
|
// Format large numbers appropriately
|
||||||
|
if (bps >= 1_000_000_000) {
|
||||||
|
return {
|
||||||
|
value: bps / 1_000_000_000,
|
||||||
|
symbol: " Gbps",
|
||||||
|
display: `${decimalString(bps / 1_000_000_000, bps >= 10_000_000_000 ? 0 : 1)} Gbps`,
|
||||||
|
}
|
||||||
|
} else if (bps >= 1_000_000) {
|
||||||
|
return {
|
||||||
|
value: bps / 1_000_000,
|
||||||
|
symbol: " Mbps",
|
||||||
|
display: `${decimalString(bps / 1_000_000, bps >= 10_000_000 ? 0 : 1)} Mbps`,
|
||||||
|
}
|
||||||
|
} else if (bps >= 1_000) {
|
||||||
|
return {
|
||||||
|
value: bps / 1_000,
|
||||||
|
symbol: " Kbps",
|
||||||
|
display: `${decimalString(bps / 1_000, bps >= 10_000 ? 0 : 1)} Kbps`,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
value: bps,
|
||||||
|
symbol: " bps",
|
||||||
|
display: `${Math.round(bps)} bps`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
value: mbps,
|
||||||
|
symbol: " MB/s",
|
||||||
|
display: `${decimalString(mbps, mbps >= 100 ? 1 : 2)} MB/s`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert disk speed from MB/s to the specified unit */
|
||||||
|
export function convertDiskSpeed(
|
||||||
|
mbps: number,
|
||||||
|
unit: SpeedUnit = "mbps"
|
||||||
|
): SpeedConversion {
|
||||||
|
switch (unit) {
|
||||||
|
case "bps": {
|
||||||
|
const bps = mbps * 8 * 1_000_000 // Convert MB/s to bits per second
|
||||||
|
|
||||||
|
// Format large numbers appropriately
|
||||||
|
if (bps >= 1_000_000_000) {
|
||||||
|
return {
|
||||||
|
value: bps / 1_000_000_000,
|
||||||
|
symbol: " Gbps",
|
||||||
|
display: `${decimalString(bps / 1_000_000_000, bps >= 10_000_000_000 ? 0 : 1)} Gbps`,
|
||||||
|
}
|
||||||
|
} else if (bps >= 1_000_000) {
|
||||||
|
return {
|
||||||
|
value: bps / 1_000_000,
|
||||||
|
symbol: " Mbps",
|
||||||
|
display: `${decimalString(bps / 1_000_000, bps >= 10_000_000 ? 0 : 1)} Mbps`,
|
||||||
|
}
|
||||||
|
} else if (bps >= 1_000) {
|
||||||
|
return {
|
||||||
|
value: bps / 1_000,
|
||||||
|
symbol: " Kbps",
|
||||||
|
display: `${decimalString(bps / 1_000, bps >= 10_000 ? 0 : 1)} Kbps`,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
value: bps,
|
||||||
|
symbol: " bps",
|
||||||
|
display: `${Math.round(bps)} bps`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
value: mbps,
|
||||||
|
symbol: " MB/s",
|
||||||
|
display: `${decimalString(mbps, mbps >= 100 ? 1 : 2)} MB/s`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateUserSettings() {
|
export async function updateUserSettings() {
|
||||||
try {
|
try {
|
||||||
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "settings" })
|
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "settings" })
|
||||||
|
19
beszel/site/src/types.d.ts
vendored
19
beszel/site/src/types.d.ts
vendored
@@ -22,6 +22,22 @@ export interface FingerprintRecord extends RecordModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unit preference types
|
||||||
|
export type TemperatureUnit = "celsius" | "fahrenheit"
|
||||||
|
export type SpeedUnit = "mbps" | "bps"
|
||||||
|
|
||||||
|
// Unit conversion result types
|
||||||
|
export interface TemperatureConversion {
|
||||||
|
value: number
|
||||||
|
symbol: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpeedConversion {
|
||||||
|
value: number
|
||||||
|
symbol: string
|
||||||
|
display: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface SystemRecord extends RecordModel {
|
export interface SystemRecord extends RecordModel {
|
||||||
name: string
|
name: string
|
||||||
host: string
|
host: string
|
||||||
@@ -205,6 +221,9 @@ export type UserSettings = {
|
|||||||
chartTime: ChartTimes
|
chartTime: ChartTimes
|
||||||
emails?: string[]
|
emails?: string[]
|
||||||
webhooks?: string[]
|
webhooks?: string[]
|
||||||
|
temperatureUnit?: TemperatureUnit
|
||||||
|
networkUnit?: SpeedUnit
|
||||||
|
diskUnit?: SpeedUnit
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChartDataContainer = {
|
type ChartDataContainer = {
|
||||||
|
Reference in New Issue
Block a user