mirror of
https://github.com/henrygd/beszel.git
synced 2026-04-05 04:21:50 +02:00
ui: small refactoring / auto formatting
This commit is contained in:
@@ -54,36 +54,34 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
|
|||||||
fields: "id,name,image,cpu,memory,net,health,status,system,updated",
|
fields: "id,name,image,cpu,memory,net,health,status,system,updated",
|
||||||
filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
|
filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
|
||||||
})
|
})
|
||||||
.then(
|
.then(({ items }) => {
|
||||||
({ items }) => {
|
if (items.length === 0) {
|
||||||
if (items.length === 0) {
|
|
||||||
setData((curItems) => {
|
|
||||||
if (systemId) {
|
|
||||||
return curItems?.filter((item) => item.system !== systemId) ?? []
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setData((curItems) => {
|
setData((curItems) => {
|
||||||
const lastUpdated = Math.max(items[0].updated, items.at(-1)?.updated ?? 0)
|
if (systemId) {
|
||||||
const containerIds = new Set()
|
return curItems?.filter((item) => item.system !== systemId) ?? []
|
||||||
const newItems = []
|
|
||||||
for (const item of items) {
|
|
||||||
if (Math.abs(lastUpdated - item.updated) < 70_000) {
|
|
||||||
containerIds.add(item.id)
|
|
||||||
newItems.push(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (const item of curItems ?? []) {
|
return []
|
||||||
if (!containerIds.has(item.id) && lastUpdated - item.updated < 70_000) {
|
|
||||||
newItems.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newItems
|
|
||||||
})
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
)
|
setData((curItems) => {
|
||||||
|
const lastUpdated = Math.max(items[0].updated, items.at(-1)?.updated ?? 0)
|
||||||
|
const containerIds = new Set()
|
||||||
|
const newItems = []
|
||||||
|
for (const item of items) {
|
||||||
|
if (Math.abs(lastUpdated - item.updated) < 70_000) {
|
||||||
|
containerIds.add(item.id)
|
||||||
|
newItems.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of curItems ?? []) {
|
||||||
|
if (!containerIds.has(item.id) && lastUpdated - item.updated < 70_000) {
|
||||||
|
newItems.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newItems
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// initial load
|
// initial load
|
||||||
@@ -285,7 +283,7 @@ async function getInfoHtml(container: ContainerRecord): Promise<string> {
|
|||||||
])
|
])
|
||||||
try {
|
try {
|
||||||
info = JSON.stringify(JSON.parse(info), null, 2)
|
info = JSON.stringify(JSON.parse(info), null, 2)
|
||||||
} catch (_) { }
|
} catch (_) {}
|
||||||
return info ? highlighter.codeToHtml(info, { lang: "json", theme: syntaxTheme }) : t`No results.`
|
return info ? highlighter.codeToHtml(info, { lang: "json", theme: syntaxTheme }) : t`No results.`
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
@@ -342,12 +340,12 @@ function ContainerSheet({
|
|||||||
setLogsDisplay("")
|
setLogsDisplay("")
|
||||||
setInfoDisplay("")
|
setInfoDisplay("")
|
||||||
if (!container) return
|
if (!container) return
|
||||||
; (async () => {
|
;(async () => {
|
||||||
const [logsHtml, infoHtml] = await Promise.all([getLogsHtml(container), getInfoHtml(container)])
|
const [logsHtml, infoHtml] = await Promise.all([getLogsHtml(container), getInfoHtml(container)])
|
||||||
setLogsDisplay(logsHtml)
|
setLogsDisplay(logsHtml)
|
||||||
setInfoDisplay(infoHtml)
|
setInfoDisplay(infoHtml)
|
||||||
setTimeout(scrollLogsToBottom, 20)
|
setTimeout(scrollLogsToBottom, 20)
|
||||||
})()
|
})()
|
||||||
}, [container])
|
}, [container])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -473,7 +471,7 @@ const ContainerTableRow = memo(function ContainerTableRow({
|
|||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={cell.id}
|
key={cell.id}
|
||||||
className="py-0"
|
className="py-0 ps-4.5"
|
||||||
style={{
|
style={{
|
||||||
height: virtualRow.size,
|
height: virtualRow.size,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { FreeBsdIcon, TuxIcon, WebSocketIcon, WindowsIcon } from "@/components/u
|
|||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
|
||||||
import { ConnectionType, connectionTypeLabels, Os, SystemStatus } from "@/lib/enums"
|
import { ConnectionType, connectionTypeLabels, Os, SystemStatus } from "@/lib/enums"
|
||||||
import { cn, formatBytes, getHostDisplayValue, secondsToString, toFixedFloat } from "@/lib/utils"
|
import { cn, formatBytes, getHostDisplayValue, secondsToUptimeString, toFixedFloat } from "@/lib/utils"
|
||||||
import type { ChartData, SystemDetailsRecord, SystemRecord } from "@/types"
|
import type { ChartData, SystemDetailsRecord, SystemRecord } from "@/types"
|
||||||
|
|
||||||
export default function InfoBar({
|
export default function InfoBar({
|
||||||
@@ -77,14 +77,6 @@ export default function InfoBar({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
let uptime: string
|
|
||||||
if (system.info.u < 3600) {
|
|
||||||
uptime = secondsToString(system.info.u, "minute")
|
|
||||||
} else if (system.info.u < 360000) {
|
|
||||||
uptime = secondsToString(system.info.u, "hour")
|
|
||||||
} else {
|
|
||||||
uptime = secondsToString(system.info.u, "day")
|
|
||||||
}
|
|
||||||
const info = [
|
const info = [
|
||||||
{ value: getHostDisplayValue(system), Icon: GlobeIcon },
|
{ value: getHostDisplayValue(system), Icon: GlobeIcon },
|
||||||
{
|
{
|
||||||
@@ -94,7 +86,7 @@ export default function InfoBar({
|
|||||||
// hide if hostname is same as host or name
|
// hide if hostname is same as host or name
|
||||||
hide: hostname === system.host || hostname === system.name,
|
hide: hostname === system.host || hostname === system.name,
|
||||||
},
|
},
|
||||||
{ value: uptime, Icon: ClockArrowUp, label: t`Uptime`, hide: !system.info.u },
|
{ value: secondsToUptimeString(system.info.u), Icon: ClockArrowUp, label: t`Uptime`, hide: !system.info.u },
|
||||||
osInfo[os],
|
osInfo[os],
|
||||||
{
|
{
|
||||||
value: cpuModel,
|
value: cpuModel,
|
||||||
|
|||||||
@@ -174,8 +174,8 @@ export const columns: ColumnDef<SmartDeviceRecord>[] = [
|
|||||||
<HeaderButton column={column} name={t({ message: "Power On", comment: "Power On Time" })} Icon={Clock} />
|
<HeaderButton column={column} name={t({ message: "Power On", comment: "Power On Time" })} Icon={Clock} />
|
||||||
),
|
),
|
||||||
cell: ({ getValue }) => {
|
cell: ({ getValue }) => {
|
||||||
const hours = (getValue() ?? 0) as number
|
const hours = getValue() as number | undefined
|
||||||
if (!hours && hours !== 0) {
|
if (hours == null) {
|
||||||
return <div className="text-sm text-muted-foreground ms-1.5">N/A</div>
|
return <div className="text-sm text-muted-foreground ms-1.5">N/A</div>
|
||||||
}
|
}
|
||||||
const seconds = hours * 3600
|
const seconds = hours * 3600
|
||||||
@@ -195,7 +195,7 @@ export const columns: ColumnDef<SmartDeviceRecord>[] = [
|
|||||||
),
|
),
|
||||||
cell: ({ getValue }) => {
|
cell: ({ getValue }) => {
|
||||||
const cycles = getValue() as number | undefined
|
const cycles = getValue() as number | undefined
|
||||||
if (!cycles && cycles !== 0) {
|
if (cycles == null) {
|
||||||
return <div className="text-muted-foreground ms-1.5">N/A</div>
|
return <div className="text-muted-foreground ms-1.5">N/A</div>
|
||||||
}
|
}
|
||||||
return <span className="ms-1.5">{cycles.toLocaleString()}</span>
|
return <span className="ms-1.5">{cycles.toLocaleString()}</span>
|
||||||
@@ -206,9 +206,8 @@ export const columns: ColumnDef<SmartDeviceRecord>[] = [
|
|||||||
invertSorting: true,
|
invertSorting: true,
|
||||||
header: ({ column }) => <HeaderButton column={column} name={t`Temp`} Icon={ThermometerIcon} />,
|
header: ({ column }) => <HeaderButton column={column} name={t`Temp`} Icon={ThermometerIcon} />,
|
||||||
cell: ({ getValue }) => {
|
cell: ({ getValue }) => {
|
||||||
const temp = getValue() as number | undefined | null
|
const temp = getValue() as number | null | undefined
|
||||||
// Most devices won't report a real 0C temperature; treat 0 as "unknown".
|
if (!temp) {
|
||||||
if (temp == null || temp === 0) {
|
|
||||||
return <div className="text-muted-foreground ms-1.5">N/A</div>
|
return <div className="text-muted-foreground ms-1.5">N/A</div>
|
||||||
}
|
}
|
||||||
const { value, unit } = formatTemperature(temp)
|
const { value, unit } = formatTemperature(temp)
|
||||||
@@ -309,41 +308,41 @@ export default function DisksTable({ systemId }: { systemId?: string }) {
|
|||||||
? { fields: SMART_DEVICE_FIELDS, filter: pb.filter("system = {:system}", { system: systemId }) }
|
? { fields: SMART_DEVICE_FIELDS, filter: pb.filter("system = {:system}", { system: systemId }) }
|
||||||
: { fields: SMART_DEVICE_FIELDS }
|
: { fields: SMART_DEVICE_FIELDS }
|
||||||
|
|
||||||
; (async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
unsubscribe = await pb.collection("smart_devices").subscribe(
|
unsubscribe = await pb.collection("smart_devices").subscribe(
|
||||||
"*",
|
"*",
|
||||||
(event) => {
|
(event) => {
|
||||||
const record = event.record as SmartDeviceRecord
|
const record = event.record as SmartDeviceRecord
|
||||||
setSmartDevices((currentDevices) => {
|
setSmartDevices((currentDevices) => {
|
||||||
const devices = currentDevices ?? []
|
const devices = currentDevices ?? []
|
||||||
const matchesSystemScope = !systemId || record.system === systemId
|
const matchesSystemScope = !systemId || record.system === systemId
|
||||||
|
|
||||||
if (event.action === "delete") {
|
if (event.action === "delete") {
|
||||||
return devices.filter((device) => device.id !== record.id)
|
return devices.filter((device) => device.id !== record.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!matchesSystemScope) {
|
if (!matchesSystemScope) {
|
||||||
// Record moved out of scope; ensure it disappears locally.
|
// Record moved out of scope; ensure it disappears locally.
|
||||||
return devices.filter((device) => device.id !== record.id)
|
return devices.filter((device) => device.id !== record.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingIndex = devices.findIndex((device) => device.id === record.id)
|
const existingIndex = devices.findIndex((device) => device.id === record.id)
|
||||||
if (existingIndex === -1) {
|
if (existingIndex === -1) {
|
||||||
return [record, ...devices]
|
return [record, ...devices]
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = [...devices]
|
const next = [...devices]
|
||||||
next[existingIndex] = record
|
next[existingIndex] = record
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
pbOptions
|
pbOptions
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to subscribe to SMART device updates:", error)
|
console.error("Failed to subscribe to SMART device updates:", error)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe?.()
|
unsubscribe?.()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import {
|
|||||||
formatTemperature,
|
formatTemperature,
|
||||||
getMeterState,
|
getMeterState,
|
||||||
parseSemVer,
|
parseSemVer,
|
||||||
secondsToString,
|
secondsToUptimeString,
|
||||||
} from "@/lib/utils"
|
} from "@/lib/utils"
|
||||||
import { batteryStateTranslations } from "@/lib/i18n"
|
import { batteryStateTranslations } from "@/lib/i18n"
|
||||||
import type { SystemRecord } from "@/types"
|
import type { SystemRecord } from "@/types"
|
||||||
@@ -154,11 +154,7 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<Syste
|
|||||||
{name}
|
{name}
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link href={linkUrl} className="inset-0 absolute size-full" aria-label={name}></Link>
|
||||||
href={linkUrl}
|
|
||||||
className="inset-0 absolute size-full"
|
|
||||||
aria-label={name}
|
|
||||||
></Link>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -382,20 +378,13 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<Syste
|
|||||||
size: 50,
|
size: 50,
|
||||||
Icon: ClockArrowUp,
|
Icon: ClockArrowUp,
|
||||||
header: sortableHeader,
|
header: sortableHeader,
|
||||||
|
hideSort: true,
|
||||||
cell(info) {
|
cell(info) {
|
||||||
const uptime = info.getValue() as number
|
const uptime = info.getValue() as number
|
||||||
if (!uptime) {
|
if (!uptime) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
let formatted: string
|
return <span className="tabular-nums whitespace-nowrap">{secondsToUptimeString(uptime)}</span>
|
||||||
if (uptime < 3600) {
|
|
||||||
formatted = secondsToString(uptime, "minute")
|
|
||||||
} else if (uptime < 360000) {
|
|
||||||
formatted = secondsToString(uptime, "hour")
|
|
||||||
} else {
|
|
||||||
formatted = secondsToString(uptime, "day")
|
|
||||||
}
|
|
||||||
return <span className="tabular-nums whitespace-nowrap">{formatted}</span>
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -479,9 +468,9 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
|
|||||||
const meterClass = cn(
|
const meterClass = cn(
|
||||||
"h-full",
|
"h-full",
|
||||||
(info.row.original.status !== SystemStatus.Up && STATUS_COLORS.paused) ||
|
(info.row.original.status !== SystemStatus.Up && STATUS_COLORS.paused) ||
|
||||||
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
(threshold === MeterState.Good && STATUS_COLORS.up) ||
|
||||||
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
|
||||||
STATUS_COLORS.down
|
STATUS_COLORS.down
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2 items-center tabular-nums tracking-tight w-full">
|
<div className="flex gap-2 items-center tabular-nums tracking-tight w-full">
|
||||||
@@ -593,7 +582,7 @@ export function IndicatorDot({ system, className }: { system: SystemRecord; clas
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cn("shrink-0 size-2 rounded-full", className)}
|
className={cn("shrink-0 size-2 rounded-full", className)}
|
||||||
// style={{ marginBottom: "-1px" }}
|
// style={{ marginBottom: "-1px" }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ const SystemTableRow = memo(
|
|||||||
width: cell.column.getSize(),
|
width: cell.column.getSize(),
|
||||||
height: virtualRow.size,
|
height: virtualRow.size,
|
||||||
}}
|
}}
|
||||||
className="py-0"
|
className="py-0 ps-4.5"
|
||||||
>
|
>
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -465,4 +465,15 @@ export function secondsToString(seconds: number, unit: "hour" | "minute" | "day"
|
|||||||
case "day":
|
case "day":
|
||||||
return plural(count, { one: `${countString} day`, other: `${countString} days` })
|
return plural(count, { one: `${countString} day`, other: `${countString} days` })
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Format seconds to uptime string - "X minutes", "X hours", "X days" */
|
||||||
|
export function secondsToUptimeString(seconds: number): string {
|
||||||
|
if (seconds < 3600) {
|
||||||
|
return secondsToString(seconds, "minute")
|
||||||
|
} else if (seconds < 360000) {
|
||||||
|
return secondsToString(seconds, "hour")
|
||||||
|
} else {
|
||||||
|
return secondsToString(seconds, "day")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user