mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
[Feature] Add custom meter percentages (#942)
This commit is contained in:
@@ -11,17 +11,30 @@ import { useState } from "react"
|
|||||||
import languages from "@/lib/languages"
|
import languages from "@/lib/languages"
|
||||||
import { dynamicActivate } from "@/lib/i18n"
|
import { dynamicActivate } from "@/lib/i18n"
|
||||||
import { useLingui } from "@lingui/react/macro"
|
import { useLingui } from "@lingui/react/macro"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
// import { setLang } from "@/lib/i18n"
|
||||||
import { Unit } from "@/lib/enums"
|
import { Unit } from "@/lib/enums"
|
||||||
|
|
||||||
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
|
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const { i18n } = useLingui()
|
const { i18n } = useLingui()
|
||||||
|
|
||||||
|
// Remove all per-metric threshold state and UI
|
||||||
|
// Only keep general yellow/red threshold state and UI
|
||||||
|
const [yellow, setYellow] = useState(userSettings.meterThresholds?.yellow ?? 65)
|
||||||
|
const [red, setRed] = useState(userSettings.meterThresholds?.red ?? 90)
|
||||||
|
|
||||||
|
function handleResetThresholds() {
|
||||||
|
setYellow(65)
|
||||||
|
setRed(90)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const formData = new FormData(e.target as HTMLFormElement)
|
const formData = new FormData(e.target as HTMLFormElement)
|
||||||
const data = Object.fromEntries(formData) as Partial<UserSettings>
|
const data = Object.fromEntries(formData) as Partial<UserSettings>
|
||||||
|
data.meterThresholds = { yellow, red }
|
||||||
await saveSettings(data)
|
await saveSettings(data)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
@@ -101,6 +114,45 @@ 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>Dashboard meter thresholds</Trans>
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||||
|
<Trans>Choose when the dashboard meters changes colors, based on percentage values.</Trans>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-4 items-end">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="yellow-threshold"><Trans>Warning threshold (%)</Trans></Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
id="yellow-threshold"
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
value={yellow}
|
||||||
|
onChange={e => setYellow(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="red-threshold"><Trans>Danger threshold (%)</Trans></Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
id="red-threshold"
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
value={red}
|
||||||
|
onChange={e => setRed(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button type="button" variant="outline" onClick={handleResetThresholds} disabled={isLoading} className="mt-4">
|
||||||
|
<Trans>Reset to default</Trans>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
{/* Unit preferences section fixed and wrapped in a div */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="mb-1 text-lg font-medium">
|
<h3 className="mb-1 text-lg font-medium">
|
||||||
@@ -133,7 +185,6 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="block" htmlFor="unitNet">
|
<Label className="block" htmlFor="unitNet">
|
||||||
<Trans comment="Context: Bytes or bits">Network unit</Trans>
|
<Trans comment="Context: Bytes or bits">Network unit</Trans>
|
||||||
@@ -156,7 +207,6 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="block" htmlFor="unitDisk">
|
<Label className="block" htmlFor="unitDisk">
|
||||||
<Trans>Disk unit</Trans>
|
<Trans>Disk unit</Trans>
|
||||||
|
@@ -51,6 +51,49 @@ import AlertButton from "../alerts/alert-button"
|
|||||||
|
|
||||||
type ViewMode = "table" | "grid"
|
type ViewMode = "table" | "grid"
|
||||||
|
|
||||||
|
function CellFormatter(info: CellContext<SystemRecord, unknown>) {
|
||||||
|
const val = (info.getValue() as number) || 0
|
||||||
|
const userSettings = useStore($userSettings)
|
||||||
|
const yellow = userSettings?.meterThresholds?.yellow ?? 65
|
||||||
|
const red = userSettings?.meterThresholds?.red ?? 90
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
||||||
|
<span className="min-w-8">{decimalString(val, 1)}%</span>
|
||||||
|
<span className="grow min-w-8 block bg-muted h-[1em] relative rounded-sm overflow-hidden">
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"absolute inset-0 w-full h-full origin-left",
|
||||||
|
(info.row.original.status !== "up" && "bg-primary/30") ||
|
||||||
|
(val < yellow! && "bg-green-500") ||
|
||||||
|
(val < red! && "bg-yellow-500") ||
|
||||||
|
"bg-red-600"
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
transform: `scalex(${val / 100})`,
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortableHeader(context: HeaderContext<SystemRecord, unknown>) {
|
||||||
|
const { column } = context
|
||||||
|
// @ts-ignore
|
||||||
|
const { Icon, hideSort, name }: { Icon: React.ElementType; name: () => string; hideSort: boolean } = column.columnDef
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="h-9 px-3 flex"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||||
|
>
|
||||||
|
{Icon && <Icon className="me-2 size-4" />}
|
||||||
|
{name()}
|
||||||
|
{hideSort || <ArrowUpDownIcon className="ms-2 size-4" />}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function SystemsTable() {
|
export default function SystemsTable() {
|
||||||
const data = useStore($systems)
|
const data = useStore($systems)
|
||||||
const { i18n, t } = useLingui()
|
const { i18n, t } = useLingui()
|
||||||
|
@@ -28,6 +28,10 @@ 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 || ""],
|
||||||
|
meterThresholds: {
|
||||||
|
yellow: 65,
|
||||||
|
red: 90,
|
||||||
|
},
|
||||||
// unitTemp: "celsius",
|
// unitTemp: "celsius",
|
||||||
// unitNet: "mbps",
|
// unitNet: "mbps",
|
||||||
// unitDisk: "mbps",
|
// unitDisk: "mbps",
|
||||||
|
4
beszel/site/src/types.d.ts
vendored
4
beszel/site/src/types.d.ts
vendored
@@ -228,6 +228,10 @@ export interface UserSettings {
|
|||||||
chartTime: ChartTimes
|
chartTime: ChartTimes
|
||||||
emails?: string[]
|
emails?: string[]
|
||||||
webhooks?: string[]
|
webhooks?: string[]
|
||||||
|
meterThresholds?: {
|
||||||
|
yellow?: number
|
||||||
|
red?: number
|
||||||
|
}
|
||||||
unitTemp?: Unit
|
unitTemp?: Unit
|
||||||
unitNet?: Unit
|
unitNet?: Unit
|
||||||
unitDisk?: Unit
|
unitDisk?: Unit
|
||||||
|
Reference in New Issue
Block a user