mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-23 05:56:17 +01:00
Compare commits
1 Commits
49ae424698
...
feat/displ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d759112ee9 |
@@ -17,9 +17,9 @@ type UserSettings struct {
|
||||
ChartTime string `json:"chartTime"`
|
||||
NotificationEmails []string `json:"emails"`
|
||||
NotificationWebhooks []string `json:"webhooks"`
|
||||
// UnitTemp uint8 `json:"unitTemp"` // 0 for Celsius, 1 for Fahrenheit
|
||||
// UnitNet uint8 `json:"unitNet"` // 0 for bytes, 1 for bits
|
||||
// UnitDisk uint8 `json:"unitDisk"` // 0 for bytes, 1 for bits
|
||||
// TemperatureUnit int `json:"unitTemp"` // 0 for Celsius, 1 for Fahrenheit
|
||||
// NetworkUnit int `json:"unitNet"` // 0 for bytes, 1 for bits
|
||||
// DiskUnit int `json:"unitDisk"` // 0 for bytes, 1 for bits
|
||||
}
|
||||
|
||||
func NewUserManager(app core.App) *UserManager {
|
||||
@@ -41,6 +41,7 @@ func (um *UserManager) InitializeUserSettings(e *core.RecordEvent) error {
|
||||
record := e.Record
|
||||
// intialize settings with defaults
|
||||
settings := UserSettings{
|
||||
// Language: "en",
|
||||
ChartTime: "1h",
|
||||
NotificationEmails: []string{},
|
||||
NotificationWebhooks: []string{},
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||
import { memo, useMemo } from "react"
|
||||
import { useYAxisWidth, cn, formatShortDate, chartMargin, toFixedFloat, formatBytes, decimalString } from "@/lib/utils"
|
||||
import {
|
||||
useYAxisWidth,
|
||||
cn,
|
||||
formatShortDate,
|
||||
chartMargin,
|
||||
toFixedWithoutTrailingZeros,
|
||||
formatBytes,
|
||||
decimalString,
|
||||
toFixedFloat,
|
||||
} from "@/lib/utils"
|
||||
// import Spinner from '../spinner'
|
||||
import { useStore } from "@nanostores/react"
|
||||
import { $containerFilter, $userSettings } from "@/lib/stores"
|
||||
import { ChartData } from "@/types"
|
||||
import { Separator } from "../ui/separator"
|
||||
import { ChartType, Unit } from "@/lib/enums"
|
||||
import { ChartType, DataUnit } from "@/lib/enums"
|
||||
|
||||
export default memo(function ContainerChart({
|
||||
dataKey,
|
||||
@@ -76,11 +85,11 @@ export default memo(function ContainerChart({
|
||||
// tick formatter
|
||||
if (chartType === ChartType.CPU) {
|
||||
obj.tickFormatter = (value) => {
|
||||
const val = toFixedFloat(value, 2) + unit
|
||||
const val = toFixedWithoutTrailingZeros(value, 2) + unit
|
||||
return updateYAxisWidth(val)
|
||||
}
|
||||
} else {
|
||||
const chartUnit = isNetChart ? userSettings.unitNet : Unit.Bytes
|
||||
const chartUnit = isNetChart ? userSettings.unitNet : DataUnit.Bytes
|
||||
obj.tickFormatter = (val) => {
|
||||
const { value, unit } = formatBytes(val, isNetChart, chartUnit, true)
|
||||
return updateYAxisWidth(toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit)
|
||||
@@ -109,7 +118,7 @@ export default memo(function ContainerChart({
|
||||
}
|
||||
} else if (chartType === ChartType.Memory) {
|
||||
obj.toolTipFormatter = (item: any) => {
|
||||
const { value, unit } = formatBytes(item.value, false, Unit.Bytes, true)
|
||||
const { value, unit } = formatBytes(item.value, false, DataUnit.Bytes, true)
|
||||
return decimalString(value) + " " + unit
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||
import { useYAxisWidth, cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||
import { useYAxisWidth, cn, formatShortDate, decimalString, toFixedFloat, chartMargin, formatBytes } from "@/lib/utils"
|
||||
import { ChartData } from "@/types"
|
||||
import { memo } from "react"
|
||||
import { useLingui } from "@lingui/react/macro"
|
||||
import { Unit } from "@/lib/enums"
|
||||
import { DataUnit } from "@/lib/enums"
|
||||
|
||||
export default memo(function DiskChart({
|
||||
dataKey,
|
||||
@@ -47,7 +47,7 @@ export default memo(function DiskChart({
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(val) => {
|
||||
const { value, unit } = formatBytes(val * 1024, false, Unit.Bytes, true)
|
||||
const { value, unit } = formatBytes(val * 1024, false, DataUnit.Bytes, true)
|
||||
return updateYAxisWidth(toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit)
|
||||
}}
|
||||
/>
|
||||
@@ -59,7 +59,7 @@ export default memo(function DiskChart({
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
||||
contentFormatter={({ value }) => {
|
||||
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
|
||||
const { value: convertedValue, unit } = formatBytes(value * 1024, false, DataUnit.Bytes, true)
|
||||
return decimalString(convertedValue) + " " + unit
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,14 @@ import {
|
||||
ChartTooltipContent,
|
||||
xAxis,
|
||||
} from "@/components/ui/chart"
|
||||
import { useYAxisWidth, cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils"
|
||||
import {
|
||||
useYAxisWidth,
|
||||
cn,
|
||||
formatShortDate,
|
||||
toFixedWithoutTrailingZeros,
|
||||
decimalString,
|
||||
chartMargin,
|
||||
} from "@/lib/utils"
|
||||
import { ChartData } from "@/types"
|
||||
import { memo, useMemo } from "react"
|
||||
|
||||
@@ -65,7 +72,7 @@ export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData
|
||||
domain={[0, "auto"]}
|
||||
width={yAxisWidth}
|
||||
tickFormatter={(value) => {
|
||||
const val = toFixedFloat(value, 2)
|
||||
const val = toFixedWithoutTrailingZeros(value, 2)
|
||||
return updateYAxisWidth(val + "W")
|
||||
}}
|
||||
tickLine={false}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||
import { useYAxisWidth, cn, decimalString, formatShortDate, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||
import { useYAxisWidth, cn, toFixedFloat, decimalString, formatShortDate, chartMargin, formatBytes } from "@/lib/utils"
|
||||
import { memo } from "react"
|
||||
import { ChartData } from "@/types"
|
||||
import { useLingui } from "@lingui/react/macro"
|
||||
import { Unit } from "@/lib/enums"
|
||||
import { DataUnit } from "@/lib/enums"
|
||||
|
||||
export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
|
||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||
@@ -40,7 +40,7 @@ export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => {
|
||||
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
|
||||
const { value: convertedValue, unit } = formatBytes(value * 1024, false, DataUnit.Bytes, true)
|
||||
return updateYAxisWidth(toFixedFloat(convertedValue, value >= 10 ? 0 : 1) + " " + unit)
|
||||
}}
|
||||
/>
|
||||
@@ -57,7 +57,7 @@ export default memo(function MemChart({ chartData }: { chartData: ChartData }) {
|
||||
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
||||
contentFormatter={({ value }) => {
|
||||
// mem values are supplied as GB
|
||||
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
|
||||
const { value: convertedValue, unit } = formatBytes(value * 1024, false, DataUnit.Bytes, true)
|
||||
return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { t } from "@lingui/core/macro"
|
||||
import { t } from "@lingui/core/macro";
|
||||
|
||||
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||
import { useYAxisWidth, cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
|
||||
import {
|
||||
useYAxisWidth,
|
||||
cn,
|
||||
formatShortDate,
|
||||
toFixedWithoutTrailingZeros,
|
||||
decimalString,
|
||||
chartMargin,
|
||||
} from "@/lib/utils"
|
||||
import { ChartData } from "@/types"
|
||||
import { memo } from "react"
|
||||
import { $userSettings } from "@/lib/stores"
|
||||
import { useStore } from "@nanostores/react"
|
||||
|
||||
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {
|
||||
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
|
||||
const userSettings = useStore($userSettings)
|
||||
|
||||
if (chartData.systemStats.length === 0) {
|
||||
return null
|
||||
@@ -29,14 +33,11 @@ export default memo(function SwapChart({ chartData }: { chartData: ChartData })
|
||||
direction="ltr"
|
||||
orientation={chartData.orientation}
|
||||
className="tracking-tighter"
|
||||
domain={[0, () => toFixedFloat(chartData.systemStats.at(-1)?.stats.s ?? 0.04, 2)]}
|
||||
domain={[0, () => toFixedWithoutTrailingZeros(chartData.systemStats.at(-1)?.stats.s ?? 0.04, 2)]}
|
||||
width={yAxisWidth}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => {
|
||||
const { value: convertedValue, unit } = formatBytes(value * 1024, false, userSettings.unitDisk, true)
|
||||
return updateYAxisWidth(toFixedFloat(convertedValue, value >= 10 ? 0 : 1) + " " + unit)
|
||||
}}
|
||||
tickFormatter={(value) => updateYAxisWidth(value + " GB")}
|
||||
/>
|
||||
{xAxis(chartData)}
|
||||
<ChartTooltip
|
||||
@@ -45,11 +46,7 @@ export default memo(function SwapChart({ chartData }: { chartData: ChartData })
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
||||
contentFormatter={({ value }) => {
|
||||
// mem values are supplied as GB
|
||||
const { value: convertedValue, unit } = formatBytes(value * 1024, false, userSettings.unitDisk, true)
|
||||
return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit
|
||||
}}
|
||||
contentFormatter={(item) => decimalString(item.value) + " GB"}
|
||||
// indicator="line"
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
useYAxisWidth,
|
||||
cn,
|
||||
formatShortDate,
|
||||
toFixedFloat,
|
||||
toFixedWithoutTrailingZeros,
|
||||
chartMargin,
|
||||
formatTemperature,
|
||||
convertTemperature,
|
||||
decimalString,
|
||||
} from "@/lib/utils"
|
||||
import { ChartData } from "@/types"
|
||||
@@ -75,8 +75,8 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
|
||||
domain={[0, "auto"]}
|
||||
width={yAxisWidth}
|
||||
tickFormatter={(val) => {
|
||||
const { value, unit } = formatTemperature(val, userSettings.unitTemp)
|
||||
return updateYAxisWidth(toFixedFloat(value, 2) + " " + unit)
|
||||
const { value, unit } = convertTemperature(val, userSettings.unitTemp)
|
||||
return updateYAxisWidth(toFixedWithoutTrailingZeros(value, 2) + " " + unit)
|
||||
}}
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
@@ -91,7 +91,7 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
|
||||
contentFormatter={(item) => {
|
||||
const { value, unit } = formatTemperature(item.value, userSettings.unitTemp)
|
||||
const { value, unit } = convertTemperature(item.value, userSettings.unitTemp)
|
||||
return decimalString(value) + " " + unit
|
||||
}}
|
||||
filter={filter}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useState } from "react"
|
||||
import languages from "@/lib/languages"
|
||||
import { dynamicActivate } from "@/lib/i18n"
|
||||
import { useLingui } from "@lingui/react/macro"
|
||||
import { Unit } from "@/lib/enums"
|
||||
import { DataUnit, TemperatureUnit } from "@/lib/enums"
|
||||
|
||||
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@@ -118,41 +118,33 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
<Select
|
||||
name="unitTemp"
|
||||
key={userSettings.unitTemp}
|
||||
defaultValue={userSettings.unitTemp?.toString() || String(Unit.Celsius)}
|
||||
defaultValue={userSettings.unitTemp?.toString() || String(TemperatureUnit.Celsius)}
|
||||
>
|
||||
<SelectTrigger id="unitTemp">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={String(Unit.Celsius)}>
|
||||
<Trans>Celsius (°C)</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={String(Unit.Fahrenheit)}>
|
||||
<Trans>Fahrenheit (°F)</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={String(TemperatureUnit.Celsius)}>Celsius (°C)</SelectItem>
|
||||
<SelectItem value={String(TemperatureUnit.Fahrenheit)}>Fahrenheit (°F)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="block" htmlFor="unitNet">
|
||||
<Label className="block" htmlFor="unitTemp">
|
||||
<Trans>Network unit</Trans>
|
||||
</Label>
|
||||
<Select
|
||||
name="unitNet"
|
||||
key={userSettings.unitNet}
|
||||
defaultValue={userSettings.unitNet?.toString() ?? String(Unit.Bytes)}
|
||||
defaultValue={userSettings.unitNet?.toString() ?? String(DataUnit.Bytes)}
|
||||
>
|
||||
<SelectTrigger id="unitNet">
|
||||
<SelectTrigger id="unitTemp">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={String(Unit.Bytes)}>
|
||||
<Trans>Bytes (KB/s, MB/s, GB/s)</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={String(Unit.Bits)}>
|
||||
<Trans>Bits (Kbps, Mbps, Gbps)</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={String(DataUnit.Bytes)}>Bytes (KB/s, MB/s, GB/s)</SelectItem>
|
||||
<SelectItem value={String(DataUnit.Bits)}>Bits (kbps, Mbps, Gbps)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -164,18 +156,14 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
<Select
|
||||
name="unitDisk"
|
||||
key={userSettings.unitDisk}
|
||||
defaultValue={userSettings.unitDisk?.toString() ?? String(Unit.Bytes)}
|
||||
defaultValue={userSettings.unitDisk?.toString() ?? String(DataUnit.Bytes)}
|
||||
>
|
||||
<SelectTrigger id="unitDisk">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={String(Unit.Bytes)}>
|
||||
<Trans>Bytes (KB/s, MB/s, GB/s)</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={String(Unit.Bits)}>
|
||||
<Trans>Bits (Kbps, Mbps, Gbps)</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={String(DataUnit.Bytes)}>Bytes (KB/s, MB/s, GB/s)</SelectItem>
|
||||
<SelectItem value={String(DataUnit.Bits)}>Bits (kbps, Mbps, Gbps)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
$temperatureFilter,
|
||||
} from "@/lib/stores"
|
||||
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
|
||||
import { ChartType, Unit, Os } from "@/lib/enums"
|
||||
import { ChartType, DataUnit, Os } from "@/lib/enums"
|
||||
import React, { lazy, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card"
|
||||
import { useStore } from "@nanostores/react"
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
getPbTimestamp,
|
||||
listen,
|
||||
toFixedFloat,
|
||||
toFixedWithoutTrailingZeros,
|
||||
useLocalStorage,
|
||||
} from "@/lib/utils"
|
||||
import { Separator } from "../ui/separator"
|
||||
@@ -478,7 +479,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
||||
chartData={chartData}
|
||||
chartName="CPU Usage"
|
||||
maxToggled={maxValues}
|
||||
tickFormatter={(val) => toFixedFloat(val, 2) + "%"}
|
||||
tickFormatter={(val) => toFixedWithoutTrailingZeros(val, 2) + "%"}
|
||||
contentFormatter={({ value }) => decimalString(value) + "%"}
|
||||
/>
|
||||
</ChartCard>
|
||||
@@ -555,6 +556,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
||||
maxToggled={maxValues}
|
||||
tickFormatter={(val) => {
|
||||
let { value, unit } = formatBytes(val, true, userSettings.unitNet, true)
|
||||
// value = value >= 10 ? Math.ceil(value) : value
|
||||
return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit
|
||||
}}
|
||||
contentFormatter={({ value }) => {
|
||||
@@ -626,6 +628,10 @@ export default function SystemDetail({ name }: { name: string }) {
|
||||
<div className="grid xl:grid-cols-2 gap-4">
|
||||
{Object.keys(systemStats.at(-1)?.stats.g ?? {}).map((id) => {
|
||||
const gpu = systemStats.at(-1)?.stats.g?.[id] as GPUData
|
||||
// const sizeFormatter = (value: number, decimals?: number) => {
|
||||
// const { v, u } = getSizeAndUnit(value, false)
|
||||
// return toFixedFloat(v, decimals || 1) + u
|
||||
// }
|
||||
return (
|
||||
<div key={id} className="contents">
|
||||
<ChartCard
|
||||
@@ -637,7 +643,7 @@ export default function SystemDetail({ name }: { name: string }) {
|
||||
<AreaChartDefault
|
||||
chartData={chartData}
|
||||
chartName={`g.${id}.u`}
|
||||
tickFormatter={(val) => toFixedFloat(val, 2) + "%"}
|
||||
tickFormatter={(val) => toFixedWithoutTrailingZeros(val, 2) + "%"}
|
||||
contentFormatter={({ value }) => decimalString(value) + "%"}
|
||||
/>
|
||||
</ChartCard>
|
||||
@@ -652,11 +658,11 @@ export default function SystemDetail({ name }: { name: string }) {
|
||||
chartName={`g.${id}.mu`}
|
||||
max={gpu.mt}
|
||||
tickFormatter={(val) => {
|
||||
const { value, unit } = formatBytes(val, false, Unit.Bytes, true)
|
||||
const { value, unit } = formatBytes(val, false, DataUnit.Bytes, true)
|
||||
return toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit
|
||||
}}
|
||||
contentFormatter={({ value }) => {
|
||||
const { value: convertedValue, unit } = formatBytes(value, false, Unit.Bytes, true)
|
||||
const { value: convertedValue, unit } = formatBytes(value, false, DataUnit.Bytes, true)
|
||||
return decimalString(convertedValue) + " " + unit
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -70,7 +70,7 @@ import {
|
||||
copyToClipboard,
|
||||
isReadOnlyUser,
|
||||
useLocalStorage,
|
||||
formatTemperature,
|
||||
convertTemperature,
|
||||
decimalString,
|
||||
formatBytes,
|
||||
} from "@/lib/utils"
|
||||
@@ -135,6 +135,7 @@ export default function SystemsTable() {
|
||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
|
||||
const [columnVisibility, setColumnVisibility] = useLocalStorage<VisibilityState>("cols", {})
|
||||
const [viewMode, setViewMode] = useLocalStorage<ViewMode>("viewMode", window.innerWidth > 1024 ? "table" : "grid")
|
||||
const userSettings = useStore($userSettings)
|
||||
|
||||
const locale = i18n.locale
|
||||
|
||||
@@ -229,7 +230,6 @@ export default function SystemsTable() {
|
||||
Icon: EthernetIcon,
|
||||
header: sortableHeader,
|
||||
cell(info) {
|
||||
const userSettings = useStore($userSettings)
|
||||
const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, true)
|
||||
return (
|
||||
<span className="tabular-nums whitespace-nowrap">
|
||||
@@ -291,8 +291,7 @@ export default function SystemsTable() {
|
||||
if (!val) {
|
||||
return null
|
||||
}
|
||||
const userSettings = useStore($userSettings)
|
||||
const { value, unit } = formatTemperature(val, userSettings.unitTemp)
|
||||
const { value, unit } = convertTemperature(val, userSettings.unitTemp)
|
||||
return (
|
||||
<span className={cn("tabular-nums whitespace-nowrap", viewMode === "table" && "ps-0.5")}>
|
||||
{decimalString(value, value >= 100 ? 1 : 2)} {unit}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/** Operating system */
|
||||
export enum Os {
|
||||
Linux = 0,
|
||||
Darwin,
|
||||
@@ -6,7 +5,6 @@ export enum Os {
|
||||
FreeBSD,
|
||||
}
|
||||
|
||||
/** Type of chart */
|
||||
export enum ChartType {
|
||||
Memory,
|
||||
Disk,
|
||||
@@ -14,10 +12,12 @@ export enum ChartType {
|
||||
CPU,
|
||||
}
|
||||
|
||||
/** Unit of measurement */
|
||||
export enum Unit {
|
||||
export enum DataUnit {
|
||||
Bytes,
|
||||
Bits,
|
||||
}
|
||||
|
||||
export enum TemperatureUnit {
|
||||
Celsius,
|
||||
Fahrenheit,
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ export const $maxValues = atom(false)
|
||||
export const $userSettings = map<UserSettings>({
|
||||
chartTime: "1h",
|
||||
emails: [pb.authStore.record?.email || ""],
|
||||
// unitTemp: "celsius",
|
||||
// unitNet: "mbps",
|
||||
// unitDisk: "mbps",
|
||||
temperatureUnit: "celsius",
|
||||
networkUnit: "mbps",
|
||||
diskUnit: "mbps",
|
||||
})
|
||||
// update local storage on change
|
||||
$userSettings.subscribe((value) => {
|
||||
|
||||
@@ -3,7 +3,16 @@ import { toast } from "@/components/ui/use-toast"
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { $alerts, $copyContent, $systems, $userSettings, pb } from "./stores"
|
||||
import { AlertInfo, AlertRecord, ChartTimeData, ChartTimes, FingerprintRecord, SystemRecord } from "@/types"
|
||||
import {
|
||||
AlertInfo,
|
||||
AlertRecord,
|
||||
ChartTimeData,
|
||||
ChartTimes,
|
||||
FingerprintRecord,
|
||||
SystemRecord,
|
||||
TemperatureConversion,
|
||||
DataUnitConversion,
|
||||
} from "@/types"
|
||||
import { RecordModel, RecordSubscription } from "pocketbase"
|
||||
import { WritableAtom } from "nanostores"
|
||||
import { timeDay, timeHour } from "d3-time"
|
||||
@@ -11,7 +20,7 @@ import { useEffect, useState } from "react"
|
||||
import { CpuIcon, HardDriveIcon, MemoryStickIcon, ServerIcon } from "lucide-react"
|
||||
import { EthernetIcon, HourglassIcon, ThermometerIcon } from "@/components/ui/icons"
|
||||
import { prependBasePath } from "@/components/router"
|
||||
import { Unit } from "./enums"
|
||||
import { DataUnit, TemperatureUnit } from "./enums"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
@@ -226,17 +235,17 @@ export function useYAxisWidth() {
|
||||
return { yAxisWidth, updateYAxisWidth }
|
||||
}
|
||||
|
||||
/** Format number to x decimal places, without trailing zeros */
|
||||
export function toFixedWithoutTrailingZeros(num: number, digits: number) {
|
||||
return parseFloat(num.toFixed(digits)).toString()
|
||||
}
|
||||
|
||||
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()
|
||||
/** Format number to x decimal places, maintaining trailing zeros */
|
||||
/** Format number to x decimal places */
|
||||
export function decimalString(num: number, digits = 2) {
|
||||
if (digits === 0) {
|
||||
return Math.ceil(num).toString()
|
||||
}
|
||||
let formatter = decimalFormatters.get(digits)
|
||||
if (!formatter) {
|
||||
formatter = new Intl.NumberFormat(undefined, {
|
||||
@@ -267,13 +276,11 @@ export function useLocalStorage<T>(key: string, defaultValue: T) {
|
||||
return [value, setValue]
|
||||
}
|
||||
|
||||
/** Format temperature to user's preferred unit */
|
||||
export function formatTemperature(celsius: number, unit?: Unit): { value: number; unit: string } {
|
||||
if (!unit) {
|
||||
unit = $userSettings.get().unitTemp || Unit.Celsius
|
||||
}
|
||||
export function convertTemperature(celsius: number, unit = TemperatureUnit.Celsius): TemperatureConversion {
|
||||
const userSettings = $userSettings.get()
|
||||
unit ||= userSettings.unitTemp || TemperatureUnit.Celsius
|
||||
// need loose equality check due to form data being strings
|
||||
if (unit == Unit.Fahrenheit) {
|
||||
if (unit == TemperatureUnit.Fahrenheit) {
|
||||
return {
|
||||
value: celsius * 1.8 + 32,
|
||||
unit: "°F",
|
||||
@@ -285,18 +292,17 @@ export function formatTemperature(celsius: number, unit?: Unit): { value: number
|
||||
}
|
||||
}
|
||||
|
||||
/** Format bytes to user's preferred unit */
|
||||
export function formatBytes(
|
||||
size: number,
|
||||
perSecond = false,
|
||||
unit = Unit.Bytes,
|
||||
unit = DataUnit.Bytes,
|
||||
isMegabytes = false
|
||||
): { value: number; unit: string } {
|
||||
): DataUnitConversion {
|
||||
// Convert MB to bytes if isMegabytes is true
|
||||
if (isMegabytes) size *= 1024 * 1024
|
||||
|
||||
// need loose equality check due to form data being strings
|
||||
if (unit == Unit.Bits) {
|
||||
if (unit == DataUnit.Bits) {
|
||||
const bits = size * 8
|
||||
const suffix = perSecond ? "ps" : ""
|
||||
if (bits < 1000) return { value: bits, unit: `b${suffix}` }
|
||||
@@ -316,7 +322,7 @@ export function formatBytes(
|
||||
unit: `Tb${suffix}`,
|
||||
}
|
||||
}
|
||||
// bytes
|
||||
|
||||
const suffix = perSecond ? "/s" : ""
|
||||
if (size < 100) return { value: size, unit: `B${suffix}` }
|
||||
if (size < 1000 * 1024) return { value: size / 1024, unit: `KB${suffix}` }
|
||||
@@ -336,24 +342,40 @@ export function formatBytes(
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch or create user settings in database */
|
||||
export async function updateUserSettings() {
|
||||
try {
|
||||
const req = await pb.collection("user_settings").getFirstListItem("", { fields: "settings" })
|
||||
$userSettings.set(req.settings)
|
||||
return
|
||||
} catch (e) {
|
||||
console.error("get settings", e)
|
||||
console.log("get settings", e)
|
||||
}
|
||||
// create user settings if error fetching existing
|
||||
try {
|
||||
const createdSettings = await pb.collection("user_settings").create({ user: pb.authStore.record!.id })
|
||||
$userSettings.set(createdSettings.settings)
|
||||
} catch (e) {
|
||||
console.error("create settings", e)
|
||||
console.log("create settings", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value and unit of size (TB, GB, or MB) for a given size
|
||||
* @param n size in gigabytes or megabytes
|
||||
* @param isGigabytes boolean indicating if n represents gigabytes (true) or megabytes (false)
|
||||
* @returns an object containing the value and unit of size
|
||||
*/
|
||||
export const getSizeAndUnit = (n: number, isGigabytes = true) => {
|
||||
const sizeInGB = isGigabytes ? n : n / 1_000
|
||||
|
||||
if (sizeInGB >= 1_000) {
|
||||
return { v: sizeInGB / 1_000, u: " TB" }
|
||||
} else if (sizeInGB >= 1) {
|
||||
return { v: sizeInGB, u: " GB" }
|
||||
}
|
||||
return { v: isGigabytes ? sizeInGB * 1_000 : n, u: " MB" }
|
||||
}
|
||||
|
||||
export const chartMargin = { top: 12 }
|
||||
|
||||
export const alertInfo: Record<string, AlertInfo> = {
|
||||
|
||||
19
beszel/site/src/types.d.ts
vendored
19
beszel/site/src/types.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
import { RecordModel } from "pocketbase"
|
||||
import { Unit, Os } from "./lib/enums"
|
||||
import { DataUnit, Os, TemperatureUnit } from "./lib/enums"
|
||||
|
||||
// global window properties
|
||||
declare global {
|
||||
@@ -22,6 +22,17 @@ export interface FingerprintRecord extends RecordModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Unit conversion result types
|
||||
export interface TemperatureConversion {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
|
||||
export interface DataUnitConversion {
|
||||
value: number
|
||||
unit: string
|
||||
}
|
||||
|
||||
export interface SystemRecord extends RecordModel {
|
||||
name: string
|
||||
host: string
|
||||
@@ -205,9 +216,9 @@ export type UserSettings = {
|
||||
chartTime: ChartTimes
|
||||
emails?: string[]
|
||||
webhooks?: string[]
|
||||
unitTemp?: Unit
|
||||
unitNet?: Unit
|
||||
unitDisk?: Unit
|
||||
unitTemp?: TemperatureUnit
|
||||
unitNet?: DataUnit
|
||||
unitDisk?: DataUnit
|
||||
}
|
||||
|
||||
type ChartDataContainer = {
|
||||
|
||||
Reference in New Issue
Block a user