mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-23 22:16:18 +01:00
Compare commits
7 Commits
d352ce00fa
...
v0.13.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbf3f94247 | ||
|
|
8a81c7bbac | ||
|
|
d24150c78b | ||
|
|
013da18789 | ||
|
|
5b663621e4 | ||
|
|
4056345216 | ||
|
|
d00c0488c3 |
@@ -152,7 +152,12 @@ func (a *Agent) gatherStats(cacheTimeMs uint16) *system.CombinedData {
|
|||||||
data.Stats.ExtraFs = make(map[string]*system.FsStats)
|
data.Stats.ExtraFs = make(map[string]*system.FsStats)
|
||||||
for name, stats := range a.fsStats {
|
for name, stats := range a.fsStats {
|
||||||
if !stats.Root && stats.DiskTotal > 0 {
|
if !stats.Root && stats.DiskTotal > 0 {
|
||||||
data.Stats.ExtraFs[name] = stats
|
// Use custom name if available, otherwise use device name
|
||||||
|
key := name
|
||||||
|
if stats.Name != "" {
|
||||||
|
key = stats.Name
|
||||||
|
}
|
||||||
|
data.Stats.ExtraFs[key] = stats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slog.Debug("Extra FS", "data", data.Stats.ExtraFs)
|
slog.Debug("Extra FS", "data", data.Stats.ExtraFs)
|
||||||
|
|||||||
@@ -143,7 +143,9 @@ func (client *WebSocketClient) OnOpen(conn *gws.Conn) {
|
|||||||
// OnClose handles WebSocket connection closure.
|
// OnClose handles WebSocket connection closure.
|
||||||
// It logs the closure reason and notifies the connection manager.
|
// It logs the closure reason and notifies the connection manager.
|
||||||
func (client *WebSocketClient) OnClose(conn *gws.Conn, err error) {
|
func (client *WebSocketClient) OnClose(conn *gws.Conn, err error) {
|
||||||
slog.Warn("Connection closed", "err", strings.TrimPrefix(err.Error(), "gws: "))
|
if err != nil {
|
||||||
|
slog.Warn("Connection closed", "err", strings.TrimPrefix(err.Error(), "gws: "))
|
||||||
|
}
|
||||||
client.agent.connectionManager.eventChan <- WebSocketDisconnect
|
client.agent.connectionManager.eventChan <- WebSocketDisconnect
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +248,13 @@ func (client *WebSocketClient) sendMessage(data any) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return client.Conn.WriteMessage(gws.OpcodeBinary, bytes)
|
err = client.Conn.WriteMessage(gws.OpcodeBinary, bytes)
|
||||||
|
if err != nil {
|
||||||
|
// If writing fails (e.g., broken pipe due to network issues),
|
||||||
|
// close the connection to trigger reconnection logic (#1263)
|
||||||
|
client.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendResponse sends a response with optional request ID for the new protocol
|
// sendResponse sends a response with optional request ID for the new protocol
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ import (
|
|||||||
"github.com/shirou/gopsutil/v4/disk"
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// parseFilesystemEntry parses a filesystem entry in the format "device__customname"
|
||||||
|
// Returns the device/filesystem part and the custom name part
|
||||||
|
func parseFilesystemEntry(entry string) (device, customName string) {
|
||||||
|
entry = strings.TrimSpace(entry)
|
||||||
|
if parts := strings.SplitN(entry, "__", 2); len(parts) == 2 {
|
||||||
|
device = strings.TrimSpace(parts[0])
|
||||||
|
customName = strings.TrimSpace(parts[1])
|
||||||
|
} else {
|
||||||
|
device = entry
|
||||||
|
}
|
||||||
|
return device, customName
|
||||||
|
}
|
||||||
|
|
||||||
// Sets up the filesystems to monitor for disk usage and I/O.
|
// Sets up the filesystems to monitor for disk usage and I/O.
|
||||||
func (a *Agent) initializeDiskInfo() {
|
func (a *Agent) initializeDiskInfo() {
|
||||||
filesystem, _ := GetEnv("FILESYSTEM")
|
filesystem, _ := GetEnv("FILESYSTEM")
|
||||||
@@ -37,7 +50,7 @@ func (a *Agent) initializeDiskInfo() {
|
|||||||
slog.Debug("Disk I/O", "diskstats", diskIoCounters)
|
slog.Debug("Disk I/O", "diskstats", diskIoCounters)
|
||||||
|
|
||||||
// Helper function to add a filesystem to fsStats if it doesn't exist
|
// Helper function to add a filesystem to fsStats if it doesn't exist
|
||||||
addFsStat := func(device, mountpoint string, root bool) {
|
addFsStat := func(device, mountpoint string, root bool, customName ...string) {
|
||||||
var key string
|
var key string
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
key = device
|
key = device
|
||||||
@@ -66,7 +79,11 @@ func (a *Agent) initializeDiskInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
a.fsStats[key] = &system.FsStats{Root: root, Mountpoint: mountpoint}
|
fsStats := &system.FsStats{Root: root, Mountpoint: mountpoint}
|
||||||
|
if len(customName) > 0 && customName[0] != "" {
|
||||||
|
fsStats.Name = customName[0]
|
||||||
|
}
|
||||||
|
a.fsStats[key] = fsStats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,11 +103,14 @@ func (a *Agent) initializeDiskInfo() {
|
|||||||
|
|
||||||
// Add EXTRA_FILESYSTEMS env var values to fsStats
|
// Add EXTRA_FILESYSTEMS env var values to fsStats
|
||||||
if extraFilesystems, exists := GetEnv("EXTRA_FILESYSTEMS"); exists {
|
if extraFilesystems, exists := GetEnv("EXTRA_FILESYSTEMS"); exists {
|
||||||
for _, fs := range strings.Split(extraFilesystems, ",") {
|
for _, fsEntry := range strings.Split(extraFilesystems, ",") {
|
||||||
|
// Parse custom name from format: device__customname
|
||||||
|
fs, customName := parseFilesystemEntry(fsEntry)
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
for _, p := range partitions {
|
for _, p := range partitions {
|
||||||
if strings.HasSuffix(p.Device, fs) || p.Mountpoint == fs {
|
if strings.HasSuffix(p.Device, fs) || p.Mountpoint == fs {
|
||||||
addFsStat(p.Device, p.Mountpoint, false)
|
addFsStat(p.Device, p.Mountpoint, false, customName)
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -98,7 +118,7 @@ func (a *Agent) initializeDiskInfo() {
|
|||||||
// if not in partitions, test if we can get disk usage
|
// if not in partitions, test if we can get disk usage
|
||||||
if !found {
|
if !found {
|
||||||
if _, err := disk.Usage(fs); err == nil {
|
if _, err := disk.Usage(fs); err == nil {
|
||||||
addFsStat(filepath.Base(fs), fs, false)
|
addFsStat(filepath.Base(fs), fs, false, customName)
|
||||||
} else {
|
} else {
|
||||||
slog.Error("Invalid filesystem", "name", fs, "err", err)
|
slog.Error("Invalid filesystem", "name", fs, "err", err)
|
||||||
}
|
}
|
||||||
@@ -120,7 +140,8 @@ func (a *Agent) initializeDiskInfo() {
|
|||||||
|
|
||||||
// Check if device is in /extra-filesystems
|
// Check if device is in /extra-filesystems
|
||||||
if strings.HasPrefix(p.Mountpoint, efPath) {
|
if strings.HasPrefix(p.Mountpoint, efPath) {
|
||||||
addFsStat(p.Device, p.Mountpoint, false)
|
device, customName := parseFilesystemEntry(p.Mountpoint)
|
||||||
|
addFsStat(device, p.Mountpoint, false, customName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +156,8 @@ func (a *Agent) initializeDiskInfo() {
|
|||||||
mountpoint := filepath.Join(efPath, folder.Name())
|
mountpoint := filepath.Join(efPath, folder.Name())
|
||||||
slog.Debug("/extra-filesystems", "mountpoint", mountpoint)
|
slog.Debug("/extra-filesystems", "mountpoint", mountpoint)
|
||||||
if !existingMountpoints[mountpoint] {
|
if !existingMountpoints[mountpoint] {
|
||||||
addFsStat(folder.Name(), mountpoint, false)
|
device, customName := parseFilesystemEntry(folder.Name())
|
||||||
|
addFsStat(device, mountpoint, false, customName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
235
agent/disk_test.go
Normal file
235
agent/disk_test.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
//go:build testing
|
||||||
|
// +build testing
|
||||||
|
|
||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/henrygd/beszel/internal/entities/system"
|
||||||
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFilesystemEntry(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expectedFs string
|
||||||
|
expectedName string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple device name",
|
||||||
|
input: "sda1",
|
||||||
|
expectedFs: "sda1",
|
||||||
|
expectedName: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "device with custom name",
|
||||||
|
input: "sda1__my-storage",
|
||||||
|
expectedFs: "sda1",
|
||||||
|
expectedName: "my-storage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "full device path with custom name",
|
||||||
|
input: "/dev/sdb1__backup-drive",
|
||||||
|
expectedFs: "/dev/sdb1",
|
||||||
|
expectedName: "backup-drive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NVMe device with custom name",
|
||||||
|
input: "nvme0n1p2__fast-ssd",
|
||||||
|
expectedFs: "nvme0n1p2",
|
||||||
|
expectedName: "fast-ssd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace trimmed",
|
||||||
|
input: " sda2__trimmed-name ",
|
||||||
|
expectedFs: "sda2",
|
||||||
|
expectedName: "trimmed-name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty custom name",
|
||||||
|
input: "sda3__",
|
||||||
|
expectedFs: "sda3",
|
||||||
|
expectedName: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty device name",
|
||||||
|
input: "__just-custom",
|
||||||
|
expectedFs: "",
|
||||||
|
expectedName: "just-custom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple underscores in custom name",
|
||||||
|
input: "sda1__my_custom_drive",
|
||||||
|
expectedFs: "sda1",
|
||||||
|
expectedName: "my_custom_drive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom name with spaces",
|
||||||
|
input: "sda1__My Storage Drive",
|
||||||
|
expectedFs: "sda1",
|
||||||
|
expectedName: "My Storage Drive",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
fsEntry := strings.TrimSpace(tt.input)
|
||||||
|
var fs, customName string
|
||||||
|
if parts := strings.SplitN(fsEntry, "__", 2); len(parts) == 2 {
|
||||||
|
fs = strings.TrimSpace(parts[0])
|
||||||
|
customName = strings.TrimSpace(parts[1])
|
||||||
|
} else {
|
||||||
|
fs = fsEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedFs, fs)
|
||||||
|
assert.Equal(t, tt.expectedName, customName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitializeDiskInfoWithCustomNames(t *testing.T) {
|
||||||
|
// Set up environment variables
|
||||||
|
oldEnv := os.Getenv("EXTRA_FILESYSTEMS")
|
||||||
|
defer func() {
|
||||||
|
if oldEnv != "" {
|
||||||
|
os.Setenv("EXTRA_FILESYSTEMS", oldEnv)
|
||||||
|
} else {
|
||||||
|
os.Unsetenv("EXTRA_FILESYSTEMS")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Test with custom names
|
||||||
|
os.Setenv("EXTRA_FILESYSTEMS", "sda1__my-storage,/dev/sdb1__backup-drive,nvme0n1p2")
|
||||||
|
|
||||||
|
// Mock disk partitions (we'll just test the parsing logic)
|
||||||
|
// Since the actual disk operations are system-dependent, we'll focus on the parsing
|
||||||
|
testCases := []struct {
|
||||||
|
envValue string
|
||||||
|
expectedFs []string
|
||||||
|
expectedNames map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
envValue: "sda1__my-storage,sdb1__backup-drive",
|
||||||
|
expectedFs: []string{"sda1", "sdb1"},
|
||||||
|
expectedNames: map[string]string{
|
||||||
|
"sda1": "my-storage",
|
||||||
|
"sdb1": "backup-drive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
envValue: "sda1,nvme0n1p2__fast-ssd",
|
||||||
|
expectedFs: []string{"sda1", "nvme0n1p2"},
|
||||||
|
expectedNames: map[string]string{
|
||||||
|
"nvme0n1p2": "fast-ssd",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run("env_"+tc.envValue, func(t *testing.T) {
|
||||||
|
os.Setenv("EXTRA_FILESYSTEMS", tc.envValue)
|
||||||
|
|
||||||
|
// Create mock partitions that would match our test cases
|
||||||
|
partitions := []disk.PartitionStat{}
|
||||||
|
for _, fs := range tc.expectedFs {
|
||||||
|
if strings.HasPrefix(fs, "/dev/") {
|
||||||
|
partitions = append(partitions, disk.PartitionStat{
|
||||||
|
Device: fs,
|
||||||
|
Mountpoint: fs,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
partitions = append(partitions, disk.PartitionStat{
|
||||||
|
Device: "/dev/" + fs,
|
||||||
|
Mountpoint: "/" + fs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the parsing logic by calling the relevant part
|
||||||
|
// We'll create a simplified version to test just the parsing
|
||||||
|
extraFilesystems := tc.envValue
|
||||||
|
for _, fsEntry := range strings.Split(extraFilesystems, ",") {
|
||||||
|
// Parse the entry
|
||||||
|
fsEntry = strings.TrimSpace(fsEntry)
|
||||||
|
var fs, customName string
|
||||||
|
if parts := strings.SplitN(fsEntry, "__", 2); len(parts) == 2 {
|
||||||
|
fs = strings.TrimSpace(parts[0])
|
||||||
|
customName = strings.TrimSpace(parts[1])
|
||||||
|
} else {
|
||||||
|
fs = fsEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the device is in our expected list
|
||||||
|
assert.Contains(t, tc.expectedFs, fs, "parsed device should be in expected list")
|
||||||
|
|
||||||
|
// Check if custom name should exist
|
||||||
|
if expectedName, exists := tc.expectedNames[fs]; exists {
|
||||||
|
assert.Equal(t, expectedName, customName, "custom name should match expected")
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, customName, "custom name should be empty when not expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFsStatsWithCustomNames(t *testing.T) {
|
||||||
|
// Test that FsStats properly stores custom names
|
||||||
|
fsStats := &system.FsStats{
|
||||||
|
Mountpoint: "/mnt/storage",
|
||||||
|
Name: "my-custom-storage",
|
||||||
|
DiskTotal: 100.0,
|
||||||
|
DiskUsed: 50.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "my-custom-storage", fsStats.Name)
|
||||||
|
assert.Equal(t, "/mnt/storage", fsStats.Mountpoint)
|
||||||
|
assert.Equal(t, 100.0, fsStats.DiskTotal)
|
||||||
|
assert.Equal(t, 50.0, fsStats.DiskUsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtraFsKeyGeneration(t *testing.T) {
|
||||||
|
// Test the logic for generating ExtraFs keys with custom names
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
deviceName string
|
||||||
|
customName string
|
||||||
|
expectedKey string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "with custom name",
|
||||||
|
deviceName: "sda1",
|
||||||
|
customName: "my-storage",
|
||||||
|
expectedKey: "my-storage",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without custom name",
|
||||||
|
deviceName: "sda1",
|
||||||
|
customName: "",
|
||||||
|
expectedKey: "sda1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty custom name falls back to device",
|
||||||
|
deviceName: "nvme0n1p2",
|
||||||
|
customName: "",
|
||||||
|
expectedKey: "nvme0n1p2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Simulate the key generation logic from agent.go
|
||||||
|
key := tc.deviceName
|
||||||
|
if tc.customName != "" {
|
||||||
|
key = tc.customName
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc.expectedKey, key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ const (
|
|||||||
dockerTimeoutMs = 2100
|
dockerTimeoutMs = 2100
|
||||||
// Maximum realistic network speed (5 GB/s) to detect bad deltas
|
// Maximum realistic network speed (5 GB/s) to detect bad deltas
|
||||||
maxNetworkSpeedBps uint64 = 5e9
|
maxNetworkSpeedBps uint64 = 5e9
|
||||||
|
// Maximum conceivable memory usage of a container (100TB) to detect bad memory stats
|
||||||
|
maxMemoryUsage uint64 = 100 * 1024 * 1024 * 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
type dockerManager struct {
|
type dockerManager struct {
|
||||||
@@ -198,17 +200,17 @@ func calculateMemoryUsage(apiStats *container.ApiStats, isWindows bool) (uint64,
|
|||||||
return apiStats.MemoryStats.PrivateWorkingSet, nil
|
return apiStats.MemoryStats.PrivateWorkingSet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if container has valid data, otherwise may be in restart loop (#103)
|
|
||||||
if apiStats.MemoryStats.Usage == 0 {
|
|
||||||
return 0, fmt.Errorf("no memory stats available")
|
|
||||||
}
|
|
||||||
|
|
||||||
memCache := apiStats.MemoryStats.Stats.InactiveFile
|
memCache := apiStats.MemoryStats.Stats.InactiveFile
|
||||||
if memCache == 0 {
|
if memCache == 0 {
|
||||||
memCache = apiStats.MemoryStats.Stats.Cache
|
memCache = apiStats.MemoryStats.Stats.Cache
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiStats.MemoryStats.Usage - memCache, nil
|
usedDelta := apiStats.MemoryStats.Usage - memCache
|
||||||
|
if usedDelta <= 0 || usedDelta > maxMemoryUsage {
|
||||||
|
return 0, fmt.Errorf("bad memory stats")
|
||||||
|
}
|
||||||
|
|
||||||
|
return usedDelta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNetworkTracker returns the DeltaTracker for a specific cache time, creating it if needed
|
// getNetworkTracker returns the DeltaTracker for a specific cache time, creating it if needed
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import "github.com/blang/semver"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Version is the current version of the application.
|
// Version is the current version of the application.
|
||||||
Version = "0.13.1"
|
Version = "0.13.2"
|
||||||
// AppName is the name of the application.
|
// AppName is the name of the application.
|
||||||
AppName = "beszel"
|
AppName = "beszel"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ type FsStats struct {
|
|||||||
Time time.Time `json:"-"`
|
Time time.Time `json:"-"`
|
||||||
Root bool `json:"-"`
|
Root bool `json:"-"`
|
||||||
Mountpoint string `json:"-"`
|
Mountpoint string `json:"-"`
|
||||||
|
Name string `json:"-"`
|
||||||
DiskTotal float64 `json:"d" cbor:"0,keyasint"`
|
DiskTotal float64 `json:"d" cbor:"0,keyasint"`
|
||||||
DiskUsed float64 `json:"du" cbor:"1,keyasint"`
|
DiskUsed float64 `json:"du" cbor:"1,keyasint"`
|
||||||
TotalRead uint64 `json:"-"`
|
TotalRead uint64 `json:"-"`
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="manifest" href="./static/manifest.json" />
|
<link rel="manifest" href="./static/manifest.json" />
|
||||||
<link rel="icon" type="image/svg+xml" href="./static/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="./static/icon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
<title>Beszel</title>
|
<title>Beszel</title>
|
||||||
|
|||||||
4
internal/site/package-lock.json
generated
4
internal/site/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.13.1",
|
"version": "0.13.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.13.1",
|
"version": "0.13.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@henrygd/queue": "^1.0.7",
|
"@henrygd/queue": "^1.0.7",
|
||||||
"@henrygd/semaphore": "^0.0.2",
|
"@henrygd/semaphore": "^0.0.2",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.13.1",
|
"version": "0.13.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -4,7 +4,7 @@ import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
|
|||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
|
||||||
import { Unit } from "@/lib/enums"
|
import { Unit } from "@/lib/enums"
|
||||||
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
|
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
|
||||||
import type { ChartData } from "@/types"
|
import type { ChartData, SystemStatsRecord } from "@/types"
|
||||||
import { useYAxisWidth } from "./hooks"
|
import { useYAxisWidth } from "./hooks"
|
||||||
|
|
||||||
export default memo(function DiskChart({
|
export default memo(function DiskChart({
|
||||||
@@ -12,7 +12,7 @@ export default memo(function DiskChart({
|
|||||||
diskSize,
|
diskSize,
|
||||||
chartData,
|
chartData,
|
||||||
}: {
|
}: {
|
||||||
dataKey: string
|
dataKey: string | ((data: SystemStatsRecord) => number | undefined)
|
||||||
diskSize: number
|
diskSize: number
|
||||||
chartData: ChartData
|
chartData: ChartData
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -932,7 +932,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
>
|
>
|
||||||
<DiskChart
|
<DiskChart
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
dataKey={`stats.efs.${extraFsName}.du`}
|
dataKey={({ stats }: SystemStatsRecord) => stats?.efs?.[extraFsName]?.du}
|
||||||
diskSize={systemStats.at(-1)?.stats.efs?.[extraFsName].d ?? NaN}
|
diskSize={systemStats.at(-1)?.stats.efs?.[extraFsName].d ?? NaN}
|
||||||
/>
|
/>
|
||||||
</ChartCard>
|
</ChartCard>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: no\n"
|
"Language: no\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-28 23:21\n"
|
"PO-Revision-Date: 2025-10-06 07:37\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Norwegian\n"
|
"Language-Team: Norwegian\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -46,7 +46,7 @@ msgstr "1 time"
|
|||||||
#. Load average
|
#. Load average
|
||||||
#: src/components/charts/load-average-chart.tsx
|
#: src/components/charts/load-average-chart.tsx
|
||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr "1 min"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 minute"
|
msgid "1 minute"
|
||||||
@@ -989,7 +989,7 @@ msgstr "System"
|
|||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "System load averages over time"
|
msgid "System load averages over time"
|
||||||
msgstr ""
|
msgstr "Systembelastning gjennomsnitt over tid"
|
||||||
|
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Systems"
|
msgid "Systems"
|
||||||
@@ -1197,7 +1197,7 @@ msgstr "Se mer"
|
|||||||
|
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "View your 200 most recent alerts."
|
msgid "View your 200 most recent alerts."
|
||||||
msgstr ""
|
msgstr "Vis de 200 siste varslene."
|
||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Visible Fields"
|
msgid "Visible Fields"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: pt\n"
|
"Language: pt\n"
|
||||||
"Project-Id-Version: beszel\n"
|
"Project-Id-Version: beszel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2025-08-28 23:21\n"
|
"PO-Revision-Date: 2025-10-09 12:03\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Portuguese\n"
|
"Language-Team: Portuguese\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -184,7 +184,7 @@ msgstr "Utilização média dos motores GPU"
|
|||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
#: src/components/navbar.tsx
|
#: src/components/navbar.tsx
|
||||||
msgid "Backups"
|
msgid "Backups"
|
||||||
msgstr "Backups"
|
msgstr "Cópias de segurança"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/lib/alerts.ts
|
#: src/lib/alerts.ts
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ const App = memo(() => {
|
|||||||
// subscribe to new alert updates
|
// subscribe to new alert updates
|
||||||
.then(alertManager.subscribe)
|
.then(alertManager.subscribe)
|
||||||
return () => {
|
return () => {
|
||||||
// updateFavicon("favicon.svg")
|
|
||||||
alertManager.unsubscribe()
|
alertManager.unsubscribe()
|
||||||
systemsManager.unsubscribe()
|
systemsManager.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
## 0.13.2
|
||||||
|
|
||||||
|
- Add ability to set custom name for extra filesystems. (#379)
|
||||||
|
|
||||||
|
- Improve WebSocket agent reconnection after network interruptions. (#1263)
|
||||||
|
|
||||||
|
- Allow more latency in one minute charts before visually disconnecting points. (#1247)
|
||||||
|
|
||||||
|
- Update favicon and add add down systems count in bubble.
|
||||||
|
|
||||||
## 0.13.1
|
## 0.13.1
|
||||||
|
|
||||||
- Fix one minute charts on systems without Docker. (#1237)
|
- Fix one minute charts on systems without Docker. (#1237)
|
||||||
|
|||||||
Reference in New Issue
Block a user