@@ -594,23 +645,33 @@ export default memo(function SystemDetail({ name }: { name: string }) {
dataPoints={[
{
label: t({ message: "Write", comment: "Disk write" }),
- dataKey: ({ stats }: SystemStatsRecord) => (showMax ? stats?.dwm : stats?.dw),
+ dataKey: ({ stats }: SystemStatsRecord) => {
+ if (showMax) {
+ return stats?.dio?.[1] ?? (stats?.dwm ?? 0) * 1024 * 1024
+ }
+ return stats?.dio?.[1] ?? (stats?.dw ?? 0) * 1024 * 1024
+ },
color: 3,
opacity: 0.3,
},
{
label: t({ message: "Read", comment: "Disk read" }),
- dataKey: ({ stats }: SystemStatsRecord) => (showMax ? stats?.drm : stats?.dr),
+ dataKey: ({ stats }: SystemStatsRecord) => {
+ if (showMax) {
+ return stats?.diom?.[0] ?? (stats?.drm ?? 0) * 1024 * 1024
+ }
+ return stats?.dio?.[0] ?? (stats?.dr ?? 0) * 1024 * 1024
+ },
color: 1,
opacity: 0.3,
},
]}
tickFormatter={(val) => {
- const { value, unit } = formatBytes(val, true, userSettings.unitDisk, true)
+ const { value, unit } = formatBytes(val, true, userSettings.unitDisk, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
- const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, true)
+ const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
/>
@@ -791,7 +852,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
return (
stats?.efs?.[extraFsName]?.[showMax ? "wm" : "w"] ?? 0,
+ dataKey: ({ stats }) => {
+ if (showMax) {
+ return stats?.efs?.[extraFsName]?.wb ?? (stats?.efs?.[extraFsName]?.wm ?? 0) * 1024 * 1024
+ }
+ return stats?.efs?.[extraFsName]?.wb ?? (stats?.efs?.[extraFsName]?.w ?? 0) * 1024 * 1024
+ },
color: 3,
opacity: 0.3,
},
{
label: t`Read`,
- dataKey: ({ stats }) => stats?.efs?.[extraFsName]?.[showMax ? "rm" : "r"] ?? 0,
+ dataKey: ({ stats }) => {
+ if (showMax) {
+ return (
+ stats?.efs?.[extraFsName]?.rbm ?? (stats?.efs?.[extraFsName]?.rm ?? 0) * 1024 * 1024
+ )
+ }
+ return stats?.efs?.[extraFsName]?.rb ?? (stats?.efs?.[extraFsName]?.r ?? 0) * 1024 * 1024
+ },
color: 1,
opacity: 0.3,
},
]}
maxToggled={maxValues}
tickFormatter={(val) => {
- const { value, unit } = formatBytes(val, true, userSettings.unitDisk, true)
+ const { value, unit } = formatBytes(val, true, userSettings.unitDisk, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
- const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, true)
+ const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
/>
@@ -913,7 +986,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
})
function GpuEnginesChart({ chartData }: { chartData: ChartData }) {
- const dataPoints = []
+ const dataPoints: DataPoint[] = []
const engines = Object.keys(chartData.systemStats?.at(-1)?.stats.g?.[0]?.e ?? {}).sort()
for (const engine of engines) {
dataPoints.push({
diff --git a/internal/site/src/components/routes/system/network-sheet.tsx b/internal/site/src/components/routes/system/network-sheet.tsx
index c87e87ea..62eb16bc 100644
--- a/internal/site/src/components/routes/system/network-sheet.tsx
+++ b/internal/site/src/components/routes/system/network-sheet.tsx
@@ -53,7 +53,7 @@ export default memo(function NetworkSheet({
{hasOpened.current && (
-
+
{
return (
diff --git a/internal/site/src/lib/api.ts b/internal/site/src/lib/api.ts
index 38a52145..eed6469f 100644
--- a/internal/site/src/lib/api.ts
+++ b/internal/site/src/lib/api.ts
@@ -26,7 +26,7 @@ export const verifyAuth = () => {
}
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
-export async function logOut() {
+export function logOut() {
$allSystemsByName.set({})
$alerts.set({})
$userSettings.set({} as UserSettings)
diff --git a/internal/site/src/lib/utils.ts b/internal/site/src/lib/utils.ts
index d5b36eca..8c6a4782 100644
--- a/internal/site/src/lib/utils.ts
+++ b/internal/site/src/lib/utils.ts
@@ -1,7 +1,7 @@
import { t } from "@lingui/core/macro"
import { type ClassValue, clsx } from "clsx"
-import { timeDay, timeHour } from "d3-time"
import { listenKeys } from "nanostores"
+import { timeDay, timeHour, timeMinute } from "d3-time"
import { useEffect, useState } from "react"
import { twMerge } from "tailwind-merge"
import { prependBasePath } from "@/components/router"
@@ -54,9 +54,18 @@ const createShortDateFormatter = (hour12?: boolean) =>
hour12,
})
+const createHourWithSecondsFormatter = (hour12?: boolean) =>
+ new Intl.DateTimeFormat(undefined, {
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ hour12,
+ })
+
// Initialize formatters with default values
let hourWithMinutesFormatter = createHourWithMinutesFormatter()
let shortDateFormatter = createShortDateFormatter()
+let hourWithSecondsFormatter = createHourWithSecondsFormatter()
export const currentHour12 = () => shortDateFormatter.resolvedOptions().hour12
@@ -68,6 +77,10 @@ export const formatShortDate = (timestamp: string) => {
return shortDateFormatter.format(new Date(timestamp))
}
+export const hourWithSeconds = (timestamp: string) => {
+ return hourWithSecondsFormatter.format(new Date(timestamp))
+}
+
// Update the time formatters if user changes hourFormat
listenKeys($userSettings, ["hourFormat"], ({ hourFormat }) => {
if (!hourFormat) return
@@ -75,6 +88,7 @@ listenKeys($userSettings, ["hourFormat"], ({ hourFormat }) => {
if (currentHour12() !== newHour12) {
hourWithMinutesFormatter = createHourWithMinutesFormatter(newHour12)
shortDateFormatter = createShortDateFormatter(newHour12)
+ hourWithSecondsFormatter = createHourWithSecondsFormatter(newHour12)
}
})
@@ -91,6 +105,15 @@ export const updateFavicon = (newIcon: string) => {
}
export const chartTimeData: ChartTimeData = {
+ "1m": {
+ type: "1m",
+ expectedInterval: 1000,
+ label: () => t`1 minute`,
+ format: (timestamp: string) => hourWithSeconds(timestamp),
+ ticks: 3,
+ getOffset: (endTime: Date) => timeMinute.offset(endTime, -1),
+ minVersion: "0.13.0",
+ },
"1h": {
type: "1m",
expectedInterval: 60_000,
@@ -278,7 +301,7 @@ export const generateToken = () => {
}
/** Get the hub URL from the global BESZEL object */
-export const getHubURL = () => BESZEL?.HUB_URL || window.location.origin
+export const getHubURL = () => globalThis.BESZEL?.HUB_URL || window.location.origin
/** Map of system IDs to their corresponding tokens (used to avoid fetching in add-system dialog) */
export const tokenMap = new Map()
@@ -333,6 +356,17 @@ export const parseSemVer = (semVer = ""): SemVer => {
return { major: parts?.[0] ?? 0, minor: parts?.[1] ?? 0, patch: parts?.[2] ?? 0 }
}
+/** Compare two semver strings. Returns -1 if a is less than b, 0 if a is equal to b, and 1 if a is greater than b. */
+export function compareSemVer(a: SemVer, b: SemVer) {
+ if (a.major !== b.major) {
+ return a.major - b.major
+ }
+ if (a.minor !== b.minor) {
+ return a.minor - b.minor
+ }
+ return a.patch - b.patch
+}
+
/** Get meter state from 0-100 value. Used for color coding meters. */
export function getMeterState(value: number): MeterState {
const { colorWarn = 65, colorCrit = 90 } = $userSettings.get()
diff --git a/internal/site/src/types.d.ts b/internal/site/src/types.d.ts
index 8656fb81..66527407 100644
--- a/internal/site/src/types.d.ts
+++ b/internal/site/src/types.d.ts
@@ -123,6 +123,10 @@ export interface SystemStats {
drm?: number
/** max disk write (mb) */
dwm?: number
+ /** disk I/O bytes [read, write] */
+ dio?: [number, number]
+ /** max disk I/O bytes [read, write] */
+ diom?: [number, number]
/** network sent (mb) */
ns: number
/** network received (mb) */
@@ -177,6 +181,14 @@ export interface ExtraFsStats {
rm: number
/** max write (mb) */
wm: number
+ /** read per second (bytes) */
+ rb: number
+ /** write per second (bytes) */
+ wb: number
+ /** max read per second (bytes) */
+ rbm: number
+ /** max write per second (mb) */
+ wbm: number
}
export interface ContainerStatsRecord extends RecordModel {
@@ -224,7 +236,7 @@ export interface AlertsHistoryRecord extends RecordModel {
resolved?: string | null
}
-export type ChartTimes = "1h" | "12h" | "24h" | "1w" | "30d"
+export type ChartTimes = "1m" | "1h" | "12h" | "24h" | "1w" | "30d"
export interface ChartTimeData {
[key: string]: {
@@ -234,6 +246,7 @@ export interface ChartTimeData {
ticks?: number
format: (timestamp: string) => string
getOffset: (endTime: Date) => Date
+ minVersion?: string
}
}