Compare commits

..

8 Commits

Author SHA1 Message Date
henrygd
330d375997 change to atomic.bool for fetching details / smart 2025-12-18 15:02:59 -05:00
henrygd
8627e3ee97 updates 2025-12-18 12:34:11 -05:00
henrygd
5d04ee5a65 consolidate info bar data 2025-12-17 19:03:31 -05:00
henrygd
d93067ec34 updates 2025-12-17 17:32:59 -05:00
henrygd
82bd953941 add arch 2025-12-16 18:33:32 -05:00
henrygd
996444abeb update 2025-12-16 17:45:26 -05:00
henrygd
aef4baff5e rm index 2025-12-15 18:59:25 -05:00
henrygd
3dea061e93 progress 2025-12-15 18:29:51 -05:00
14 changed files with 108 additions and 91 deletions

View File

@@ -103,7 +103,7 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
agent.dockerManager = newDockerManager()
// initialize system info
agent.refreshSystemDetails()
agent.refreshStaticInfo()
// initialize connection manager
agent.connectionManager = newConnectionManager(agent)
@@ -166,7 +166,7 @@ func (a *Agent) gatherStats(options common.DataRequestOptions) *system.CombinedD
Info: a.systemInfo,
}
// Include static system details only when requested
// Include static info only when requested
if options.IncludeDetails {
data.Details = &a.systemDetails
}
@@ -233,8 +233,7 @@ func (a *Agent) getFingerprint() string {
// if no fingerprint is found, generate one
fingerprint, err := host.HostID()
// we ignore a commonly known "product_uuid" known not to be unique
if err != nil || fingerprint == "" || fingerprint == "03000200-0400-0500-0006-000700080009" {
if err != nil || fingerprint == "" {
fingerprint = a.systemDetails.Hostname + a.systemDetails.CpuModel
}

View File

@@ -757,6 +757,7 @@ func (dm *dockerManager) GetHostInfo() (info container.HostInfo, err error) {
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
slog.Error("Failed to decode Docker version response", "error", err)
return info, err
}

View File

@@ -550,9 +550,6 @@ func createTestCombinedData() *system.CombinedData {
DiskUsed: 549755813888, // 512GB
DiskPct: 50.0,
},
Details: &system.Details{
Hostname: "test-host",
},
Info: system.Info{
Uptime: 3600,
AgentVersion: "0.12.0",

View File

@@ -30,7 +30,7 @@ type prevDisk struct {
}
// Sets initial / non-changing values about the host system
func (a *Agent) refreshSystemDetails() {
func (a *Agent) refreshStaticInfo() {
a.systemInfo.AgentVersion = beszel.Version
// get host info from Docker if available
@@ -246,6 +246,7 @@ func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats {
a.systemInfo.Uptime, _ = host.Uptime()
a.systemInfo.BandwidthBytes = systemStats.Bandwidth[0] + systemStats.Bandwidth[1]
a.systemInfo.Threads = a.systemDetails.Threads
slog.Debug("sysinfo", "data", a.systemInfo)
return systemStats
}

View File

@@ -34,12 +34,15 @@ type ApiStats struct {
MemoryStats MemoryStats `json:"memory_stats"`
}
// Docker system info from /info API endpoint
// Docker system info from /info
type HostInfo struct {
OperatingSystem string `json:"OperatingSystem"`
KernelVersion string `json:"KernelVersion"`
NCPU int `json:"NCPU"`
MemTotal uint64 `json:"MemTotal"`
// OSVersion string `json:"OSVersion"`
// OSType string `json:"OSType"`
// Architecture string `json:"Architecture"`
}
func (s *ApiStats) CalculateCpuPercentLinux(prevCpuContainer uint64, prevCpuSystem uint64) float64 {

View File

@@ -125,22 +125,22 @@ const (
// Core system data that is needed in All Systems table
type Info struct {
Hostname string `json:"h,omitempty" cbor:"0,keyasint,omitempty"` // deprecated - moved to Details struct
KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"` // deprecated - moved to Details struct
Cores int `json:"c,omitzero" cbor:"2,keyasint,omitzero"` // deprecated - moved to Details struct
Hostname string `json:"h,omitempty" cbor:"0,keyasint,omitempty"` // deprecated - moved to Details struct
KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"` // deprecated - moved to Details struct
Cores int `json:"c,omitzero" cbor:"2,keyasint,omitzero"` // deprecated - moved to Details struct
CpuModel string `json:"m,omitempty" cbor:"4,keyasint,omitempty"` // deprecated - moved to Details struct
Podman bool `json:"p,omitempty" cbor:"11,keyasint,omitempty"` // deprecated - moved to Details struct
Os Os `json:"os,omitempty" cbor:"14,keyasint,omitempty"` // deprecated - moved to Details struct
// Threads is needed in Info struct to calculate load average thresholds
Threads int `json:"t,omitempty" cbor:"3,keyasint,omitempty"`
CpuModel string `json:"m,omitempty" cbor:"4,keyasint,omitempty"` // deprecated - moved to Details struct
Uptime uint64 `json:"u" cbor:"5,keyasint"`
Cpu float64 `json:"cpu" cbor:"6,keyasint"`
MemPct float64 `json:"mp" cbor:"7,keyasint"`
DiskPct float64 `json:"dp" cbor:"8,keyasint"`
Bandwidth float64 `json:"b" cbor:"9,keyasint"`
AgentVersion string `json:"v" cbor:"10,keyasint"`
Podman bool `json:"p,omitempty" cbor:"11,keyasint,omitempty"` // deprecated - moved to Details struct
GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"`
DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"`
Os Os `json:"os,omitempty" cbor:"14,keyasint,omitempty"` // deprecated - moved to Details struct
LoadAvg1 float64 `json:"l1,omitempty" cbor:"15,keyasint,omitempty"` // deprecated - use `la` array instead
LoadAvg5 float64 `json:"l5,omitempty" cbor:"16,keyasint,omitempty"` // deprecated - use `la` array instead
LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"` // deprecated - use `la` array instead

View File

@@ -415,11 +415,7 @@ func TestExpiryMap_RemoveValue_WithExpiration(t *testing.T) {
// Wait for first value to expire
time.Sleep(time.Millisecond * 20)
// Trigger lazy cleanup of the expired key
_, ok := em.GetOk("key1")
assert.False(t, ok)
// Try to remove the remaining "value1" entry (key3)
// Try to remove the expired value - should remove one of the "value1" entries
removedValue, ok := em.RemovebyValue("value1")
assert.True(t, ok)
assert.Equal(t, "value1", removedValue)
@@ -427,9 +423,14 @@ func TestExpiryMap_RemoveValue_WithExpiration(t *testing.T) {
// Should still have key2 (different value)
assert.True(t, em.Has("key2"))
// key1 should be gone due to expiration and key3 should be removed by value.
assert.False(t, em.Has("key1"))
assert.False(t, em.Has("key3"))
// Should have removed one of the "value1" entries (either key1 or key3)
// But we can't predict which one due to map iteration order
key1Exists := em.Has("key1")
key3Exists := em.Has("key3")
// Exactly one of key1 or key3 should be gone
assert.False(t, key1Exists && key3Exists) // Both shouldn't exist
assert.True(t, key1Exists || key3Exists) // At least one should still exist
}
func TestExpiryMap_ValueOperations_Integration(t *testing.T) {

View File

@@ -123,25 +123,10 @@ func (sys *System) update() error {
if !sys.detailsFetched.Load() {
options.IncludeDetails = true
}
data, err := sys.fetchDataFromAgent(options)
if err != nil {
return err
if err == nil {
_, err = sys.createRecords(data)
}
// create system records
_, err = sys.createRecords(data)
// Fetch and save SMART devices when system first comes online
if backgroundSmartFetchEnabled() && !sys.smartFetched.Load() && sys.smartFetching.CompareAndSwap(false, true) {
go func() {
defer sys.smartFetching.Store(false)
if err := sys.FetchAndSaveSmartDevices(); err == nil {
sys.smartFetched.Store(true)
}
}()
}
return err
}
@@ -223,6 +208,18 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
return nil
})
// Fetch and save SMART devices when system first comes online
if err == nil {
if !sys.smartFetched.Load() && sys.smartFetching.CompareAndSwap(false, true) {
go func() {
defer sys.smartFetching.Store(false)
if err := sys.FetchAndSaveSmartDevices(); err == nil {
sys.smartFetched.Store(true)
}
}()
}
}
return systemRecord, err
}
@@ -398,7 +395,8 @@ func (sys *System) fetchStringFromAgentViaSSH(action common.WebSocketAction, req
if err := session.Shell(); err != nil {
return false, err
}
req := common.HubRequest[any]{Action: action, Data: requestData}
reqDataBytes, _ := cbor.Marshal(requestData)
req := common.HubRequest[cbor.RawMessage]{Action: action, Data: reqDataBytes}
_ = cbor.NewEncoder(stdin).Encode(req)
_ = stdin.Close()
var resp common.AgentResponse
@@ -462,7 +460,8 @@ func (sys *System) FetchSystemdInfoFromAgent(serviceName string) (systemd.Servic
return false, err
}
req := common.HubRequest[any]{Action: common.GetSystemdInfo, Data: common.SystemdInfoRequest{ServiceName: serviceName}}
reqDataBytes, _ := cbor.Marshal(common.SystemdInfoRequest{ServiceName: serviceName})
req := common.HubRequest[cbor.RawMessage]{Action: common.GetSystemdInfo, Data: reqDataBytes}
if err := cbor.NewEncoder(stdin).Encode(req); err != nil {
return false, err
}
@@ -510,7 +509,8 @@ func (sys *System) fetchDataViaSSH(options common.DataRequestOptions) (*system.C
*sys.data = system.CombinedData{}
if sys.agentVersion.GTE(beszel.MinVersionAgentResponse) && stdinErr == nil {
req := common.HubRequest[any]{Action: common.GetData, Data: options}
reqDataBytes, _ := cbor.Marshal(options)
req := common.HubRequest[cbor.RawMessage]{Action: common.GetData, Data: reqDataBytes}
_ = cbor.NewEncoder(stdin).Encode(req)
_ = stdin.Close()

View File

@@ -1,10 +0,0 @@
//go:build !testing
// +build !testing
package systems
// Background SMART fetching is enabled in production but disabled for tests (systems_test_helpers.go).
//
// The hub integration tests create/replace systems and clean up the test apps quickly.
// Background SMART fetching can outlive teardown and crash in PocketBase internals (nil DB).
func backgroundSmartFetchEnabled() bool { return true }

View File

@@ -10,13 +10,6 @@ import (
entities "github.com/henrygd/beszel/internal/entities/system"
)
// The hub integration tests create/replace systems and cleanup the test apps quickly.
// Background SMART fetching can outlive teardown and crash in PocketBase internals (nil DB).
//
// We keep the explicit SMART refresh endpoint / method available, but disable
// the automatic background fetch during tests.
func backgroundSmartFetchEnabled() bool { return false }
// TESTING ONLY: GetSystemCount returns the number of systems in the store
func (sm *SystemManager) GetSystemCount() int {
return sm.systems.Length()

View File

@@ -36,8 +36,8 @@
"lucide-react": "^0.452.0",
"nanostores": "^0.11.4",
"pocketbase": "^0.26.2",
"react": "^19.1.2",
"react-dom": "^19.1.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"recharts": "^2.15.4",
"shiki": "^3.13.0",
"tailwind-merge": "^3.3.1",
@@ -811,9 +811,9 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
"react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="],
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
@@ -851,7 +851,7 @@
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@@ -971,6 +971,8 @@
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"pseudolocale/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
@@ -979,18 +981,28 @@
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.0.1", "", {}, "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.0.1", "", {}, "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.0.1", "", {}, "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="],
}
}

View File

@@ -46,7 +46,6 @@ import type {
ChartTimes,
ContainerStatsRecord,
GPUData,
SystemDetailsRecord,
SystemInfo,
SystemRecord,
SystemStats,
@@ -167,7 +166,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
const isLongerChart = !["1m", "1h"].includes(chartTime) // true if chart time is not 1m or 1h
const userSettings = $userSettings.get()
const chartWrapRef = useRef<HTMLDivElement>(null)
const [details, setDetails] = useState<SystemDetailsRecord>({} as SystemDetailsRecord)
const [isPodman, setIsPodman] = useState(system.info?.p ?? false)
useEffect(() => {
return () => {
@@ -177,7 +176,6 @@ export default memo(function SystemDetail({ id }: { id: string }) {
persistChartTime.current = false
setSystemStats([])
setContainerData([])
setDetails({} as SystemDetailsRecord)
$containerFilter.set("")
}
}, [id])
@@ -205,22 +203,6 @@ export default memo(function SystemDetail({ id }: { id: string }) {
}
}, [system?.info?.v])
// fetch system details
useEffect(() => {
// if system.info.m exists, agent is old version without system details
if (!system.id || system.info?.m) {
return
}
pb.collection<SystemDetailsRecord>("system_details")
.getOne(system.id, {
fields: "hostname,kernel,cores,threads,cpu,os,os_name,arch,memory,podman",
headers: {
"Cache-Control": "public, max-age=60",
},
})
.then(setDetails)
}, [system.id])
// subscribe to realtime metrics if chart time is 1m
// biome-ignore lint/correctness/useExhaustiveDependencies: not necessary
useEffect(() => {
@@ -340,6 +322,10 @@ export default memo(function SystemDetail({ id }: { id: string }) {
})
}, [system, chartTime])
useEffect(() => {
setIsPodman(system.info?.p ?? false)
}, [system.info?.p])
/** Space for tooltip if more than 10 sensors and no containers table */
useEffect(() => {
const sensors = Object.keys(systemStats.at(-1)?.stats.t ?? {})
@@ -407,14 +393,12 @@ export default memo(function SystemDetail({ id }: { id: string }) {
const hasGpuData = lastGpuVals.length > 0
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined || gpu.pp !== undefined)
const hasGpuEnginesData = lastGpuVals.some((gpu) => gpu.e !== undefined)
const isLinux = (details?.os ?? system.info?.os) === Os.Linux
const isPodman = details?.podman ?? system.info?.p ?? false
return (
<>
<div ref={chartWrapRef} className="grid gap-4 mb-14 overflow-x-clip">
{/* system info */}
<InfoBar system={system} chartData={chartData} grid={grid} setGrid={setGrid} details={details} />
<InfoBar system={system} chartData={chartData} grid={grid} setGrid={setGrid} setIsPodman={setIsPodman} />
{/* <Tabs defaultValue="overview" className="w-full">
<TabsList className="w-full h-11">
@@ -857,7 +841,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
<LazyContainersTable systemId={system.id} />
)}
{isLinux && compareSemVer(chartData.agentVersion, parseSemVer("0.16.0")) >= 0 && (
{system.info?.os === Os.Linux && compareSemVer(chartData.agentVersion, parseSemVer("0.16.0")) >= 0 && (
<LazySystemdTable systemId={system.id} />
)}
</div>

View File

@@ -11,13 +11,14 @@ import {
MonitorIcon,
Rows,
} from "lucide-react"
import { useMemo } from "react"
import { useEffect, useMemo, useState } from "react"
import ChartTimeSelect from "@/components/charts/chart-time-select"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { FreeBsdIcon, TuxIcon, WebSocketIcon, WindowsIcon } from "@/components/ui/icons"
import { Separator } from "@/components/ui/separator"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { pb } from "@/lib/api"
import { ConnectionType, connectionTypeLabels, Os, SystemStatus } from "@/lib/enums"
import { cn, formatBytes, getHostDisplayValue, secondsToString, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemDetailsRecord, SystemRecord } from "@/types"
@@ -27,15 +28,44 @@ export default function InfoBar({
chartData,
grid,
setGrid,
details,
setIsPodman,
}: {
system: SystemRecord
chartData: ChartData
grid: boolean
setGrid: (grid: boolean) => void
details: SystemDetailsRecord | null
setIsPodman: (isPodman: boolean) => void
}) {
const { t } = useLingui()
const [details, setDetails] = useState<SystemDetailsRecord | null>(null)
// Fetch system_details on mount / when system changes
useEffect(() => {
let active = true
setDetails(null)
// skip fetching system details if agent is older version which includes details in Info struct
if (!system.id || system.info?.m) {
return
}
pb.collection<SystemDetailsRecord>("system_details")
.getOne(system.id, {
fields: "hostname,kernel,cores,threads,cpu,os,os_name,arch,memory,podman",
headers: {
"Cache-Control": "public, max-age=60",
},
})
.then((details) => {
if (active) {
setDetails(details)
setIsPodman(details.podman)
}
})
.catch(() => {})
return () => {
active = false
}
}, [system.id])
// values for system info bar - use details with fallback to system.info
const systemInfo = useMemo(() => {

View File

@@ -1,3 +1,9 @@
## 0.18.0
- Remove `la1`, `la5`, `la15` fields from `Info` struct in favor of `la` array.
- Remove `MB` bandwidth values in favor of bytes.
## 0.17.0
- Add quiet hours to silence alerts during specific time periods. (#265)