mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-29 00:46:16 +01:00
Compare commits
8 Commits
d3d102516c
...
split-syst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
330d375997 | ||
|
|
8627e3ee97 | ||
|
|
5d04ee5a65 | ||
|
|
d93067ec34 | ||
|
|
82bd953941 | ||
|
|
996444abeb | ||
|
|
aef4baff5e | ||
|
|
3dea061e93 |
@@ -103,7 +103,7 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
|
|||||||
agent.dockerManager = newDockerManager()
|
agent.dockerManager = newDockerManager()
|
||||||
|
|
||||||
// initialize system info
|
// initialize system info
|
||||||
agent.refreshSystemDetails()
|
agent.refreshStaticInfo()
|
||||||
|
|
||||||
// initialize connection manager
|
// initialize connection manager
|
||||||
agent.connectionManager = newConnectionManager(agent)
|
agent.connectionManager = newConnectionManager(agent)
|
||||||
@@ -166,7 +166,7 @@ func (a *Agent) gatherStats(options common.DataRequestOptions) *system.CombinedD
|
|||||||
Info: a.systemInfo,
|
Info: a.systemInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include static system details only when requested
|
// Include static info only when requested
|
||||||
if options.IncludeDetails {
|
if options.IncludeDetails {
|
||||||
data.Details = &a.systemDetails
|
data.Details = &a.systemDetails
|
||||||
}
|
}
|
||||||
@@ -233,8 +233,7 @@ func (a *Agent) getFingerprint() string {
|
|||||||
|
|
||||||
// if no fingerprint is found, generate one
|
// if no fingerprint is found, generate one
|
||||||
fingerprint, err := host.HostID()
|
fingerprint, err := host.HostID()
|
||||||
// we ignore a commonly known "product_uuid" known not to be unique
|
if err != nil || fingerprint == "" {
|
||||||
if err != nil || fingerprint == "" || fingerprint == "03000200-0400-0500-0006-000700080009" {
|
|
||||||
fingerprint = a.systemDetails.Hostname + a.systemDetails.CpuModel
|
fingerprint = a.systemDetails.Hostname + a.systemDetails.CpuModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -757,6 +757,7 @@ func (dm *dockerManager) GetHostInfo() (info container.HostInfo, err error) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
||||||
|
slog.Error("Failed to decode Docker version response", "error", err)
|
||||||
return info, err
|
return info, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -550,9 +550,6 @@ func createTestCombinedData() *system.CombinedData {
|
|||||||
DiskUsed: 549755813888, // 512GB
|
DiskUsed: 549755813888, // 512GB
|
||||||
DiskPct: 50.0,
|
DiskPct: 50.0,
|
||||||
},
|
},
|
||||||
Details: &system.Details{
|
|
||||||
Hostname: "test-host",
|
|
||||||
},
|
|
||||||
Info: system.Info{
|
Info: system.Info{
|
||||||
Uptime: 3600,
|
Uptime: 3600,
|
||||||
AgentVersion: "0.12.0",
|
AgentVersion: "0.12.0",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type prevDisk struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sets initial / non-changing values about the host system
|
// Sets initial / non-changing values about the host system
|
||||||
func (a *Agent) refreshSystemDetails() {
|
func (a *Agent) refreshStaticInfo() {
|
||||||
a.systemInfo.AgentVersion = beszel.Version
|
a.systemInfo.AgentVersion = beszel.Version
|
||||||
|
|
||||||
// get host info from Docker if available
|
// 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.Uptime, _ = host.Uptime()
|
||||||
a.systemInfo.BandwidthBytes = systemStats.Bandwidth[0] + systemStats.Bandwidth[1]
|
a.systemInfo.BandwidthBytes = systemStats.Bandwidth[0] + systemStats.Bandwidth[1]
|
||||||
a.systemInfo.Threads = a.systemDetails.Threads
|
a.systemInfo.Threads = a.systemDetails.Threads
|
||||||
|
slog.Debug("sysinfo", "data", a.systemInfo)
|
||||||
|
|
||||||
return systemStats
|
return systemStats
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,15 @@ type ApiStats struct {
|
|||||||
MemoryStats MemoryStats `json:"memory_stats"`
|
MemoryStats MemoryStats `json:"memory_stats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Docker system info from /info API endpoint
|
// Docker system info from /info
|
||||||
type HostInfo struct {
|
type HostInfo struct {
|
||||||
OperatingSystem string `json:"OperatingSystem"`
|
OperatingSystem string `json:"OperatingSystem"`
|
||||||
KernelVersion string `json:"KernelVersion"`
|
KernelVersion string `json:"KernelVersion"`
|
||||||
NCPU int `json:"NCPU"`
|
NCPU int `json:"NCPU"`
|
||||||
MemTotal uint64 `json:"MemTotal"`
|
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 {
|
func (s *ApiStats) CalculateCpuPercentLinux(prevCpuContainer uint64, prevCpuSystem uint64) float64 {
|
||||||
|
|||||||
@@ -125,22 +125,22 @@ const (
|
|||||||
|
|
||||||
// Core system data that is needed in All Systems table
|
// Core system data that is needed in All Systems table
|
||||||
type Info struct {
|
type Info struct {
|
||||||
Hostname string `json:"h,omitempty" cbor:"0,keyasint,omitempty"` // 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
|
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
|
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 is needed in Info struct to calculate load average thresholds
|
||||||
Threads int `json:"t,omitempty" cbor:"3,keyasint,omitempty"`
|
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"`
|
Uptime uint64 `json:"u" cbor:"5,keyasint"`
|
||||||
Cpu float64 `json:"cpu" cbor:"6,keyasint"`
|
Cpu float64 `json:"cpu" cbor:"6,keyasint"`
|
||||||
MemPct float64 `json:"mp" cbor:"7,keyasint"`
|
MemPct float64 `json:"mp" cbor:"7,keyasint"`
|
||||||
DiskPct float64 `json:"dp" cbor:"8,keyasint"`
|
DiskPct float64 `json:"dp" cbor:"8,keyasint"`
|
||||||
Bandwidth float64 `json:"b" cbor:"9,keyasint"`
|
Bandwidth float64 `json:"b" cbor:"9,keyasint"`
|
||||||
AgentVersion string `json:"v" cbor:"10,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"`
|
GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"`
|
||||||
DashboardTemp float64 `json:"dt,omitempty" cbor:"13,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
|
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
|
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
|
LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"` // deprecated - use `la` array instead
|
||||||
|
|||||||
@@ -415,11 +415,7 @@ func TestExpiryMap_RemoveValue_WithExpiration(t *testing.T) {
|
|||||||
// Wait for first value to expire
|
// Wait for first value to expire
|
||||||
time.Sleep(time.Millisecond * 20)
|
time.Sleep(time.Millisecond * 20)
|
||||||
|
|
||||||
// Trigger lazy cleanup of the expired key
|
// Try to remove the expired value - should remove one of the "value1" entries
|
||||||
_, ok := em.GetOk("key1")
|
|
||||||
assert.False(t, ok)
|
|
||||||
|
|
||||||
// Try to remove the remaining "value1" entry (key3)
|
|
||||||
removedValue, ok := em.RemovebyValue("value1")
|
removedValue, ok := em.RemovebyValue("value1")
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, "value1", removedValue)
|
assert.Equal(t, "value1", removedValue)
|
||||||
@@ -427,9 +423,14 @@ func TestExpiryMap_RemoveValue_WithExpiration(t *testing.T) {
|
|||||||
// Should still have key2 (different value)
|
// Should still have key2 (different value)
|
||||||
assert.True(t, em.Has("key2"))
|
assert.True(t, em.Has("key2"))
|
||||||
|
|
||||||
// key1 should be gone due to expiration and key3 should be removed by value.
|
// Should have removed one of the "value1" entries (either key1 or key3)
|
||||||
assert.False(t, em.Has("key1"))
|
// But we can't predict which one due to map iteration order
|
||||||
assert.False(t, em.Has("key3"))
|
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) {
|
func TestExpiryMap_ValueOperations_Integration(t *testing.T) {
|
||||||
|
|||||||
@@ -123,25 +123,10 @@ func (sys *System) update() error {
|
|||||||
if !sys.detailsFetched.Load() {
|
if !sys.detailsFetched.Load() {
|
||||||
options.IncludeDetails = true
|
options.IncludeDetails = true
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := sys.fetchDataFromAgent(options)
|
data, err := sys.fetchDataFromAgent(options)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return err
|
_, 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +208,18 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
|
|||||||
return nil
|
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
|
return systemRecord, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +395,8 @@ func (sys *System) fetchStringFromAgentViaSSH(action common.WebSocketAction, req
|
|||||||
if err := session.Shell(); err != nil {
|
if err := session.Shell(); err != nil {
|
||||||
return false, err
|
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)
|
_ = cbor.NewEncoder(stdin).Encode(req)
|
||||||
_ = stdin.Close()
|
_ = stdin.Close()
|
||||||
var resp common.AgentResponse
|
var resp common.AgentResponse
|
||||||
@@ -462,7 +460,8 @@ func (sys *System) FetchSystemdInfoFromAgent(serviceName string) (systemd.Servic
|
|||||||
return false, err
|
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 {
|
if err := cbor.NewEncoder(stdin).Encode(req); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -510,7 +509,8 @@ func (sys *System) fetchDataViaSSH(options common.DataRequestOptions) (*system.C
|
|||||||
*sys.data = system.CombinedData{}
|
*sys.data = system.CombinedData{}
|
||||||
|
|
||||||
if sys.agentVersion.GTE(beszel.MinVersionAgentResponse) && stdinErr == nil {
|
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)
|
_ = cbor.NewEncoder(stdin).Encode(req)
|
||||||
_ = stdin.Close()
|
_ = stdin.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
@@ -10,13 +10,6 @@ import (
|
|||||||
entities "github.com/henrygd/beszel/internal/entities/system"
|
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
|
// TESTING ONLY: GetSystemCount returns the number of systems in the store
|
||||||
func (sm *SystemManager) GetSystemCount() int {
|
func (sm *SystemManager) GetSystemCount() int {
|
||||||
return sm.systems.Length()
|
return sm.systems.Length()
|
||||||
|
|||||||
@@ -36,8 +36,8 @@
|
|||||||
"lucide-react": "^0.452.0",
|
"lucide-react": "^0.452.0",
|
||||||
"nanostores": "^0.11.4",
|
"nanostores": "^0.11.4",
|
||||||
"pocketbase": "^0.26.2",
|
"pocketbase": "^0.26.2",
|
||||||
"react": "^19.1.2",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.2",
|
"react-dom": "^19.1.1",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"shiki": "^3.13.0",
|
"shiki": "^3.13.0",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
@@ -811,9 +811,9 @@
|
|||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"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=="],
|
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
"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/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/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/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/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=="],
|
"@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=="],
|
"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/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=="],
|
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.0.1", "", {}, "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import type {
|
|||||||
ChartTimes,
|
ChartTimes,
|
||||||
ContainerStatsRecord,
|
ContainerStatsRecord,
|
||||||
GPUData,
|
GPUData,
|
||||||
SystemDetailsRecord,
|
|
||||||
SystemInfo,
|
SystemInfo,
|
||||||
SystemRecord,
|
SystemRecord,
|
||||||
SystemStats,
|
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 isLongerChart = !["1m", "1h"].includes(chartTime) // true if chart time is not 1m or 1h
|
||||||
const userSettings = $userSettings.get()
|
const userSettings = $userSettings.get()
|
||||||
const chartWrapRef = useRef<HTMLDivElement>(null)
|
const chartWrapRef = useRef<HTMLDivElement>(null)
|
||||||
const [details, setDetails] = useState<SystemDetailsRecord>({} as SystemDetailsRecord)
|
const [isPodman, setIsPodman] = useState(system.info?.p ?? false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -177,7 +176,6 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
persistChartTime.current = false
|
persistChartTime.current = false
|
||||||
setSystemStats([])
|
setSystemStats([])
|
||||||
setContainerData([])
|
setContainerData([])
|
||||||
setDetails({} as SystemDetailsRecord)
|
|
||||||
$containerFilter.set("")
|
$containerFilter.set("")
|
||||||
}
|
}
|
||||||
}, [id])
|
}, [id])
|
||||||
@@ -205,22 +203,6 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
}
|
}
|
||||||
}, [system?.info?.v])
|
}, [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
|
// subscribe to realtime metrics if chart time is 1m
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: not necessary
|
// biome-ignore lint/correctness/useExhaustiveDependencies: not necessary
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -340,6 +322,10 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
})
|
})
|
||||||
}, [system, chartTime])
|
}, [system, chartTime])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsPodman(system.info?.p ?? false)
|
||||||
|
}, [system.info?.p])
|
||||||
|
|
||||||
/** Space for tooltip if more than 10 sensors and no containers table */
|
/** Space for tooltip if more than 10 sensors and no containers table */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sensors = Object.keys(systemStats.at(-1)?.stats.t ?? {})
|
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 hasGpuData = lastGpuVals.length > 0
|
||||||
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined || gpu.pp !== undefined)
|
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined || gpu.pp !== undefined)
|
||||||
const hasGpuEnginesData = lastGpuVals.some((gpu) => gpu.e !== 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={chartWrapRef} className="grid gap-4 mb-14 overflow-x-clip">
|
<div ref={chartWrapRef} className="grid gap-4 mb-14 overflow-x-clip">
|
||||||
{/* system info */}
|
{/* 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">
|
{/* <Tabs defaultValue="overview" className="w-full">
|
||||||
<TabsList className="w-full h-11">
|
<TabsList className="w-full h-11">
|
||||||
@@ -857,7 +841,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
<LazyContainersTable systemId={system.id} />
|
<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} />
|
<LazySystemdTable systemId={system.id} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ import {
|
|||||||
MonitorIcon,
|
MonitorIcon,
|
||||||
Rows,
|
Rows,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useMemo } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import ChartTimeSelect from "@/components/charts/chart-time-select"
|
import ChartTimeSelect from "@/components/charts/chart-time-select"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Card } from "@/components/ui/card"
|
import { Card } from "@/components/ui/card"
|
||||||
import { FreeBsdIcon, TuxIcon, WebSocketIcon, WindowsIcon } from "@/components/ui/icons"
|
import { FreeBsdIcon, TuxIcon, WebSocketIcon, WindowsIcon } from "@/components/ui/icons"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
||||||
|
import { pb } from "@/lib/api"
|
||||||
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, secondsToString, toFixedFloat } from "@/lib/utils"
|
||||||
import type { ChartData, SystemDetailsRecord, SystemRecord } from "@/types"
|
import type { ChartData, SystemDetailsRecord, SystemRecord } from "@/types"
|
||||||
@@ -27,15 +28,44 @@ export default function InfoBar({
|
|||||||
chartData,
|
chartData,
|
||||||
grid,
|
grid,
|
||||||
setGrid,
|
setGrid,
|
||||||
details,
|
setIsPodman,
|
||||||
}: {
|
}: {
|
||||||
system: SystemRecord
|
system: SystemRecord
|
||||||
chartData: ChartData
|
chartData: ChartData
|
||||||
grid: boolean
|
grid: boolean
|
||||||
setGrid: (grid: boolean) => void
|
setGrid: (grid: boolean) => void
|
||||||
details: SystemDetailsRecord | null
|
setIsPodman: (isPodman: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
const { t } = useLingui()
|
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
|
// values for system info bar - use details with fallback to system.info
|
||||||
const systemInfo = useMemo(() => {
|
const systemInfo = useMemo(() => {
|
||||||
|
|||||||
@@ -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
|
## 0.17.0
|
||||||
|
|
||||||
- Add quiet hours to silence alerts during specific time periods. (#265)
|
- Add quiet hours to silence alerts during specific time periods. (#265)
|
||||||
|
|||||||
Reference in New Issue
Block a user