mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-29 00:46:16 +01:00
Compare commits
26 Commits
b1fd7e6695
...
v0.13.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbf3f94247 | ||
|
|
8a81c7bbac | ||
|
|
d24150c78b | ||
|
|
013da18789 | ||
|
|
5b663621e4 | ||
|
|
4056345216 | ||
|
|
d00c0488c3 | ||
|
|
d352ce00fa | ||
|
|
1623f5e751 | ||
|
|
612ad1238f | ||
|
|
1ad4409609 | ||
|
|
2a94e1d1ec | ||
|
|
75b372437c | ||
|
|
b661d00159 | ||
|
|
898dbf73c8 | ||
|
|
e099304948 | ||
|
|
b61b7a12dc | ||
|
|
37769050e5 | ||
|
|
d81e137291 | ||
|
|
ae820d348e | ||
|
|
ddb298ac7c | ||
|
|
cca7b36039 | ||
|
|
adda381d9d | ||
|
|
1630b1558f | ||
|
|
733c10ff31 | ||
|
|
ed3fd185d3 |
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +198,7 @@ func (client *WebSocketClient) handleAuthChallenge(msg *common.HubRequest[cbor.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
if authRequest.NeedSysInfo {
|
if authRequest.NeedSysInfo {
|
||||||
|
response.Name, _ = GetEnv("SYSTEM_NAME")
|
||||||
response.Hostname = client.agent.systemInfo.Hostname
|
response.Hostname = client.agent.systemInfo.Hostname
|
||||||
serverAddr := client.agent.connectionManager.serverOptions.Addr
|
serverAddr := client.agent.connectionManager.serverOptions.Addr
|
||||||
_, response.Port, _ = net.SplitHostPort(serverAddr)
|
_, response.Port, _ = net.SplitHostPort(serverAddr)
|
||||||
@@ -245,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
|
||||||
@@ -474,28 +476,49 @@ func newDockerManager(a *Agent) *dockerManager {
|
|||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check docker version
|
// this can take up to 5 seconds with retry, so run in goroutine
|
||||||
// (versions before 25.0.0 have a bug with one-shot which requires all requests to be made in one batch)
|
go manager.checkDockerVersion()
|
||||||
|
|
||||||
|
// give version check a chance to complete before returning
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkDockerVersion checks Docker version and sets goodDockerVersion if at least 25.0.0.
|
||||||
|
// Versions before 25.0.0 have a bug with one-shot which requires all requests to be made in one batch.
|
||||||
|
func (dm *dockerManager) checkDockerVersion() {
|
||||||
|
var err error
|
||||||
|
var resp *http.Response
|
||||||
var versionInfo struct {
|
var versionInfo struct {
|
||||||
Version string `json:"Version"`
|
Version string `json:"Version"`
|
||||||
}
|
}
|
||||||
resp, err := manager.client.Get("http://localhost/version")
|
const versionMaxTries = 2
|
||||||
|
for i := 1; i <= versionMaxTries; i++ {
|
||||||
|
resp, err = dm.client.Get("http://localhost/version")
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
if i < versionMaxTries {
|
||||||
|
slog.Debug("Failed to get Docker version; retrying", "attempt", i, "error", err)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return manager
|
return
|
||||||
}
|
}
|
||||||
|
if err := dm.decode(resp, &versionInfo); err != nil {
|
||||||
if err := manager.decode(resp, &versionInfo); err != nil {
|
return
|
||||||
return manager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if version > 24, one-shot works correctly and we can limit concurrent operations
|
// if version > 24, one-shot works correctly and we can limit concurrent operations
|
||||||
if dockerVersion, err := semver.Parse(versionInfo.Version); err == nil && dockerVersion.Major > 24 {
|
if dockerVersion, err := semver.Parse(versionInfo.Version); err == nil && dockerVersion.Major > 24 {
|
||||||
manager.goodDockerVersion = true
|
dm.goodDockerVersion = true
|
||||||
} else {
|
} else {
|
||||||
slog.Info(fmt.Sprintf("Docker %s is outdated. Upgrade if possible. See https://github.com/henrygd/beszel/issues/58", versionInfo.Version))
|
slog.Info(fmt.Sprintf("Docker %s is outdated. Upgrade if possible. See https://github.com/henrygd/beszel/issues/58", versionInfo.Version))
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decodes Docker API JSON response using a reusable buffer and decoder. Not thread safe.
|
// Decodes Docker API JSON response using a reusable buffer and decoder. Not thread safe.
|
||||||
|
|||||||
@@ -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.0-alpha.1"
|
Version = "0.13.2"
|
||||||
// AppName is the name of the application.
|
// AppName is the name of the application.
|
||||||
AppName = "beszel"
|
AppName = "beszel"
|
||||||
)
|
)
|
||||||
@@ -15,4 +15,4 @@ const (
|
|||||||
var MinVersionCbor = semver.MustParse("0.12.0")
|
var MinVersionCbor = semver.MustParse("0.12.0")
|
||||||
|
|
||||||
// MinVersionAgentResponse is the minimum supported version for AgentResponse compatibility.
|
// MinVersionAgentResponse is the minimum supported version for AgentResponse compatibility.
|
||||||
var MinVersionAgentResponse = semver.MustParse("0.13.0-alpha.1")
|
var MinVersionAgentResponse = semver.MustParse("0.13.0")
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -14,7 +14,7 @@ require (
|
|||||||
github.com/lxzan/gws v1.8.9
|
github.com/lxzan/gws v1.8.9
|
||||||
github.com/nicholas-fedor/shoutrrr v0.10.0
|
github.com/nicholas-fedor/shoutrrr v0.10.0
|
||||||
github.com/pocketbase/dbx v1.11.0
|
github.com/pocketbase/dbx v1.11.0
|
||||||
github.com/pocketbase/pocketbase v0.30.0
|
github.com/pocketbase/pocketbase v0.30.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.9
|
github.com/shirou/gopsutil/v4 v4.25.9
|
||||||
github.com/spf13/cast v1.10.0
|
github.com/spf13/cast v1.10.0
|
||||||
github.com/spf13/cobra v1.10.1
|
github.com/spf13/cobra v1.10.1
|
||||||
@@ -65,5 +65,5 @@ require (
|
|||||||
modernc.org/libc v1.66.3 // indirect
|
modernc.org/libc v1.66.3 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.38.2 // indirect
|
modernc.org/sqlite v1.39.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -90,8 +90,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
|||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
|
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
|
||||||
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
|
||||||
github.com/pocketbase/pocketbase v0.30.0 h1:7v9O3hBYyHyptnnFjdP8tEJIuyHEfjhG6PC4gjf5eoE=
|
github.com/pocketbase/pocketbase v0.30.1 h1:8lgfhH+HiSw1PyKVMq2sjtC4ZNvda2f/envTAzWMLOA=
|
||||||
github.com/pocketbase/pocketbase v0.30.0/go.mod h1:gZIwampw4VqMcEdGHwBZgSa54xWIDgVJb4uINUMXLmA=
|
github.com/pocketbase/pocketbase v0.30.1/go.mod h1:sUI+uekXZam5Wa0eh+DClc+HieKMCeqsHA7Ydd9vwyE=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
@@ -191,8 +191,6 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
|||||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
|
|
||||||
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
|
||||||
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
|
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
|
||||||
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ type FingerprintRequest struct {
|
|||||||
type FingerprintResponse struct {
|
type FingerprintResponse struct {
|
||||||
Fingerprint string `cbor:"0,keyasint"`
|
Fingerprint string `cbor:"0,keyasint"`
|
||||||
// Optional system info for universal token system creation
|
// Optional system info for universal token system creation
|
||||||
Hostname string `cbor:"1,keyasint,omitempty,omitzero"`
|
Hostname string `cbor:"1,keyasint,omitzero"`
|
||||||
Port string `cbor:"2,keyasint,omitempty,omitzero"`
|
Port string `cbor:"2,keyasint,omitzero"`
|
||||||
|
Name string `cbor:"3,keyasint,omitzero"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataRequestOptions struct {
|
type DataRequestOptions struct {
|
||||||
|
|||||||
@@ -23,4 +23,7 @@ COPY --from=builder /agent /agent
|
|||||||
# this is so we don't need to create the /tmp directory in the scratch container
|
# this is so we don't need to create the /tmp directory in the scratch container
|
||||||
COPY --from=builder /tmp /tmp
|
COPY --from=builder /tmp /tmp
|
||||||
|
|
||||||
|
# Ensure data persistence across container recreations
|
||||||
|
VOLUME ["/var/lib/beszel-agent"]
|
||||||
|
|
||||||
ENTRYPOINT ["/agent"]
|
ENTRYPOINT ["/agent"]
|
||||||
@@ -22,4 +22,7 @@ COPY --from=builder /agent /agent
|
|||||||
|
|
||||||
RUN apk add --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/testing igt-gpu-tools
|
RUN apk add --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/testing igt-gpu-tools
|
||||||
|
|
||||||
|
# Ensure data persistence across container recreations
|
||||||
|
VOLUME ["/var/lib/beszel-agent"]
|
||||||
|
|
||||||
ENTRYPOINT ["/agent"]
|
ENTRYPOINT ["/agent"]
|
||||||
@@ -24,4 +24,7 @@ COPY --from=builder /agent /agent
|
|||||||
# this is so we don't need to create the /tmp directory in the scratch container
|
# this is so we don't need to create the /tmp directory in the scratch container
|
||||||
COPY --from=builder /tmp /tmp
|
COPY --from=builder /tmp /tmp
|
||||||
|
|
||||||
|
# Ensure data persistence across container recreations
|
||||||
|
VOLUME ["/var/lib/beszel-agent"]
|
||||||
|
|
||||||
ENTRYPOINT ["/agent"]
|
ENTRYPOINT ["/agent"]
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ FROM scratch
|
|||||||
COPY --from=builder /beszel /
|
COPY --from=builder /beszel /
|
||||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
|
||||||
|
# Ensure data persistence across container recreations
|
||||||
|
VOLUME ["/beszel_data"]
|
||||||
|
|
||||||
EXPOSE 8090
|
EXPOSE 8090
|
||||||
|
|
||||||
ENTRYPOINT [ "/beszel" ]
|
ENTRYPOINT [ "/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:"-"`
|
||||||
|
|||||||
@@ -268,9 +268,12 @@ func (acr *agentConnectRequest) createSystem(agentFingerprint common.Fingerprint
|
|||||||
if agentFingerprint.Port == "" {
|
if agentFingerprint.Port == "" {
|
||||||
agentFingerprint.Port = "45876"
|
agentFingerprint.Port = "45876"
|
||||||
}
|
}
|
||||||
|
if agentFingerprint.Name == "" {
|
||||||
|
agentFingerprint.Name = agentFingerprint.Hostname
|
||||||
|
}
|
||||||
// create new record
|
// create new record
|
||||||
systemRecord := core.NewRecord(systemsCollection)
|
systemRecord := core.NewRecord(systemsCollection)
|
||||||
systemRecord.Set("name", agentFingerprint.Hostname)
|
systemRecord.Set("name", agentFingerprint.Name)
|
||||||
systemRecord.Set("host", remoteAddr)
|
systemRecord.Set("host", remoteAddr)
|
||||||
systemRecord.Set("port", agentFingerprint.Port)
|
systemRecord.Set("port", agentFingerprint.Port)
|
||||||
systemRecord.Set("users", []string{acr.userId})
|
systemRecord.Set("users", []string{acr.userId})
|
||||||
|
|||||||
@@ -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.12.12",
|
"version": "0.13.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.12.12",
|
"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.12.12",
|
"version": "0.13.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 70" fill="#22c55e"><path d="M35 70H0V0h35q4.4 0 8.2 1.7a21.4 21.4 0 0 1 6.6 4.5q2.9 2.8 4.5 6.6Q56 16.7 56 21a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5Q53 39.1 54 41a18.3 18.3 0 0 1 1.5 4 17.4 17.4 0 0 1 .5 3 15.3 15.3 0 0 1 0 1q0 4.4-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.6Q39.4 70 35 70ZM14 14v14h21a7 7 0 0 0 2.3-.3 6.6 6.6 0 0 0 .4-.2Q39 27 40 26a6.9 6.9 0 0 0 1.5-2.2q.5-1.3.5-2.8a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 17 40 16a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm0 28v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 54.9 40 54a7 7 0 0 0 1.5-2.2 6.9 6.9 0 0 0 .5-2.6 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 45 40 44a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 906 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 70" fill="#dc2626"><path d="M35 70H0V0h35q4.4 0 8.2 1.7a21.4 21.4 0 0 1 6.6 4.5q2.9 2.8 4.5 6.6Q56 16.7 56 21a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5Q53 39.1 54 41a18.3 18.3 0 0 1 1.5 4 17.4 17.4 0 0 1 .5 3 15.3 15.3 0 0 1 0 1q0 4.4-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.6Q39.4 70 35 70ZM14 14v14h21a7 7 0 0 0 2.3-.3 6.6 6.6 0 0 0 .4-.2Q39 27 40 26a6.9 6.9 0 0 0 1.5-2.2q.5-1.3.5-2.8a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 17 40 16a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm0 28v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 54.9 40 54a7 7 0 0 0 1.5-2.2 6.9 6.9 0 0 0 .5-2.6 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 45 40 44a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 906 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 70" fill="#888"><path d="M35 70H0V0h35q4.4 0 8.2 1.7a21.4 21.4 0 0 1 6.6 4.5q2.9 2.8 4.5 6.6Q56 16.7 56 21a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5Q53 39.1 54 41a18.3 18.3 0 0 1 1.5 4 17.4 17.4 0 0 1 .5 3 15.3 15.3 0 0 1 0 1q0 4.4-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.6Q39.4 70 35 70ZM14 14v14h21a7 7 0 0 0 2.3-.3 6.6 6.6 0 0 0 .4-.2Q39 27 40 26a6.9 6.9 0 0 0 1.5-2.2q.5-1.3.5-2.8a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 17 40 16a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm0 28v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 54.9 40 54a7 7 0 0 0 1.5-2.2 6.9 6.9 0 0 0 .5-2.6 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 45 40 44a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 903 B |
9
internal/site/public/static/icon.svg
Normal file
9
internal/site/public/static/icon.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 70">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="gradient" x1="0%" y1="20%" x2="100%" y2="120%">
|
||||||
|
<stop offset="0%" style="stop-color:#747bff"/>
|
||||||
|
<stop offset="100%" style="stop-color:#24eb5c"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path fill="url(#gradient)" d="M35 70H0V0h35q4.4 0 8.2 1.7a21.4 21.4 0 0 1 6.6 4.5q2.9 2.8 4.5 6.6Q56 16.7 56 21a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5Q53 39.1 54 41a18.3 18.3 0 0 1 1.5 4 17.4 17.4 0 0 1 .5 3 15.3 15.3 0 0 1 0 1q0 4.4-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.6Q39.4 70 35 70ZM14 14v14h21a7 7 0 0 0 2.3-.3 6.6 6.6 0 0 0 .4-.2Q39 27 40 26a6.9 6.9 0 0 0 1.5-2.2q.5-1.3.5-2.8a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 17 40 16a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm0 28v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 54.9 40 54a7 7 0 0 0 1.5-2.2 6.9 6.9 0 0 0 .5-2.6 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 45 40 44a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Z"/>
|
||||||
|
</svg>
|
||||||
|
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
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default memo(function CommandPalette({ open, setOpen }: { open: boolean;
|
|||||||
<CommandItem
|
<CommandItem
|
||||||
key={system.id}
|
key={system.id}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
navigate(getPagePath($router, "system", { name: system.name }))
|
navigate(getPagePath($router, "system", { id: system.id }))
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createRouter } from "@nanostores/router"
|
|||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
home: "/",
|
home: "/",
|
||||||
system: `/system/:name`,
|
system: `/system/:id`,
|
||||||
settings: `/settings/:name?`,
|
settings: `/settings/:name?`,
|
||||||
forgot_password: `/forgot-password`,
|
forgot_password: `/forgot-password`,
|
||||||
request_otp: `/request-otp`,
|
request_otp: `/request-otp`,
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ const ActiveAlerts = () => {
|
|||||||
)}
|
)}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name: systems[alert.system]?.name })}
|
href={getPagePath($router, "system", { id: systems[alert.system]?.id })}
|
||||||
className="absolute inset-0 w-full h-full"
|
className="absolute inset-0 w-full h-full"
|
||||||
aria-label="View system"
|
aria-label="View system"
|
||||||
></Link>
|
></Link>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { getPbTimestamp, pb } from "@/lib/api"
|
|||||||
import { ChartType, ConnectionType, connectionTypeLabels, Os, SystemStatus, Unit } from "@/lib/enums"
|
import { ChartType, ConnectionType, connectionTypeLabels, Os, SystemStatus, Unit } from "@/lib/enums"
|
||||||
import { batteryStateTranslations } from "@/lib/i18n"
|
import { batteryStateTranslations } from "@/lib/i18n"
|
||||||
import {
|
import {
|
||||||
|
$allSystemsById,
|
||||||
$allSystemsByName,
|
$allSystemsByName,
|
||||||
$chartTime,
|
$chartTime,
|
||||||
$containerFilter,
|
$containerFilter,
|
||||||
@@ -92,7 +93,8 @@ function getTimeData(chartTime: ChartTimes, lastCreated: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date()
|
const buffer = chartTime === "1m" ? 400 : 20_000
|
||||||
|
const now = new Date(Date.now() + buffer)
|
||||||
const startTime = chartTimeData[chartTime].getOffset(now)
|
const startTime = chartTimeData[chartTime].getOffset(now)
|
||||||
const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime())
|
const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime())
|
||||||
const data = {
|
const data = {
|
||||||
@@ -156,7 +158,7 @@ function dockerOrPodman(str: string, system: SystemRecord): string {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(function SystemDetail({ name }: { name: string }) {
|
export default memo(function SystemDetail({ id }: { id: string }) {
|
||||||
const direction = useStore($direction)
|
const direction = useStore($direction)
|
||||||
const { t } = useLingui()
|
const { t } = useLingui()
|
||||||
const systems = useStore($systems)
|
const systems = useStore($systems)
|
||||||
@@ -175,7 +177,6 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
|||||||
const chartWrapRef = useRef<HTMLDivElement>(null)
|
const chartWrapRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = `${name} / Beszel`
|
|
||||||
return () => {
|
return () => {
|
||||||
if (!persistChartTime.current) {
|
if (!persistChartTime.current) {
|
||||||
$chartTime.set($userSettings.get().chartTime)
|
$chartTime.set($userSettings.get().chartTime)
|
||||||
@@ -185,15 +186,23 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
|||||||
setContainerData([])
|
setContainerData([])
|
||||||
$containerFilter.set("")
|
$containerFilter.set("")
|
||||||
}
|
}
|
||||||
}, [name])
|
}, [id])
|
||||||
|
|
||||||
// find matching system and update when it changes
|
// find matching system and update when it changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return subscribeKeys($allSystemsByName, [name], (newSystems) => {
|
if (!systems.length) {
|
||||||
const sys = newSystems[name]
|
return
|
||||||
sys?.id && setSystem(sys)
|
}
|
||||||
|
// allow old system-name slug to work
|
||||||
|
const store = $allSystemsById.get()[id] ? $allSystemsById : $allSystemsByName
|
||||||
|
return subscribeKeys(store, [id], (newSystems) => {
|
||||||
|
const sys = newSystems[id]
|
||||||
|
if (sys) {
|
||||||
|
setSystem(sys)
|
||||||
|
document.title = `${sys?.name} / Beszel`
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}, [name])
|
}, [id, systems.length])
|
||||||
|
|
||||||
// hide 1m chart time if system agent version is less than 0.13.0
|
// hide 1m chart time if system agent version is less than 0.13.0
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -217,8 +226,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
|||||||
.subscribe(
|
.subscribe(
|
||||||
`rt_metrics`,
|
`rt_metrics`,
|
||||||
(data: { container: ContainerStatsRecord[]; info: SystemInfo; stats: SystemStats }) => {
|
(data: { container: ContainerStatsRecord[]; info: SystemInfo; stats: SystemStats }) => {
|
||||||
// console.log("received realtime metrics", data)
|
if (data.container?.length > 0) {
|
||||||
if (data.container.length > 0) {
|
|
||||||
const newContainerData = makeContainerData([
|
const newContainerData = makeContainerData([
|
||||||
{ created: Date.now(), stats: data.container } as unknown as ContainerStatsRecord,
|
{ created: Date.now(), stats: data.container } as unknown as ContainerStatsRecord,
|
||||||
])
|
])
|
||||||
@@ -416,7 +424,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
|||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const currentIndex = systems.findIndex((s) => s.name === name)
|
const currentIndex = systems.findIndex((s) => s.id === id)
|
||||||
if (currentIndex === -1 || systems.length <= 1) {
|
if (currentIndex === -1 || systems.length <= 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -425,18 +433,18 @@ export default memo(function SystemDetail({ name }: { name: string }) {
|
|||||||
case "h": {
|
case "h": {
|
||||||
const prevIndex = (currentIndex - 1 + systems.length) % systems.length
|
const prevIndex = (currentIndex - 1 + systems.length) % systems.length
|
||||||
persistChartTime.current = true
|
persistChartTime.current = true
|
||||||
return navigate(getPagePath($router, "system", { name: systems[prevIndex].name }))
|
return navigate(getPagePath($router, "system", { id: systems[prevIndex].id }))
|
||||||
}
|
}
|
||||||
case "ArrowRight":
|
case "ArrowRight":
|
||||||
case "l": {
|
case "l": {
|
||||||
const nextIndex = (currentIndex + 1) % systems.length
|
const nextIndex = (currentIndex + 1) % systems.length
|
||||||
persistChartTime.current = true
|
persistChartTime.current = true
|
||||||
return navigate(getPagePath($router, "system", { name: systems[nextIndex].name }))
|
return navigate(getPagePath($router, "system", { id: systems[nextIndex].id }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return listen(document, "keyup", handleKeyUp)
|
return listen(document, "keyup", handleKeyUp)
|
||||||
}, [name, systems])
|
}, [id, systems])
|
||||||
|
|
||||||
if (!system.id) {
|
if (!system.id) {
|
||||||
return null
|
return null
|
||||||
@@ -924,7 +932,7 @@ export default memo(function SystemDetail({ name }: { name: 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>
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
id: "system",
|
id: "system",
|
||||||
name: () => t`System`,
|
name: () => t`System`,
|
||||||
|
sortingFn: (a, b) => a.original.name.localeCompare(b.original.name),
|
||||||
filterFn: (() => {
|
filterFn: (() => {
|
||||||
let filterInput = ""
|
let filterInput = ""
|
||||||
let filterInputLower = ""
|
let filterInputLower = ""
|
||||||
@@ -110,7 +111,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
invertSorting: false,
|
invertSorting: false,
|
||||||
Icon: ServerIcon,
|
Icon: ServerIcon,
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const { name } = info.row.original
|
const { name, id } = info.row.original
|
||||||
const longestName = useStore($longestSystemNameLen)
|
const longestName = useStore($longestSystemNameLen)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -122,7 +123,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name })}
|
href={getPagePath($router, "system", { id })}
|
||||||
className="inset-0 absolute size-full"
|
className="inset-0 absolute size-full"
|
||||||
aria-label={name}
|
aria-label={name}
|
||||||
></Link>
|
></Link>
|
||||||
@@ -279,7 +280,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name: system.name })}
|
href={getPagePath($router, "system", { id: system.id })}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex gap-1.5 items-center md:pe-5 tabular-nums relative z-10",
|
"flex gap-1.5 items-center md:pe-5 tabular-nums relative z-10",
|
||||||
viewMode === "table" && "ps-0.5"
|
viewMode === "table" && "ps-0.5"
|
||||||
|
|||||||
@@ -486,7 +486,7 @@ const SystemCard = memo(
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<Link
|
<Link
|
||||||
href={getPagePath($router, "system", { name: row.original.name })}
|
href={getPagePath($router, "system", { id: row.original.id })}
|
||||||
className="inset-0 absolute w-full h-full"
|
className="inset-0 absolute w-full h-full"
|
||||||
>
|
>
|
||||||
<span className="sr-only">{row.original.name}</span>
|
<span className="sr-only">{row.original.name}</span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PocketBase from "pocketbase"
|
|||||||
import { basePath } from "@/components/router"
|
import { basePath } from "@/components/router"
|
||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
import type { ChartTimes, UserSettings } from "@/types"
|
import type { ChartTimes, UserSettings } from "@/types"
|
||||||
import { $alerts, $allSystemsByName, $userSettings } from "./stores"
|
import { $alerts, $allSystemsById, $allSystemsByName, $userSettings } from "./stores"
|
||||||
import { chartTimeData } from "./utils"
|
import { chartTimeData } from "./utils"
|
||||||
|
|
||||||
/** PocketBase JS Client */
|
/** PocketBase JS Client */
|
||||||
@@ -28,6 +28,7 @@ export const verifyAuth = () => {
|
|||||||
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
|
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
|
||||||
export function logOut() {
|
export function logOut() {
|
||||||
$allSystemsByName.set({})
|
$allSystemsByName.set({})
|
||||||
|
$allSystemsById.set({})
|
||||||
$alerts.set({})
|
$alerts.set({})
|
||||||
$userSettings.set({} as UserSettings)
|
$userSettings.set({} as UserSettings)
|
||||||
sessionStorage.setItem("lo", "t") // prevent auto login on logout
|
sessionStorage.setItem("lo", "t") // prevent auto login on logout
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
$pausedSystems,
|
$pausedSystems,
|
||||||
$upSystems,
|
$upSystems,
|
||||||
} from "@/lib/stores"
|
} from "@/lib/stores"
|
||||||
import { FAVICON_DEFAULT, FAVICON_GREEN, FAVICON_RED, updateFavicon } from "@/lib/utils"
|
import { updateFavicon } from "@/lib/utils"
|
||||||
import type { SystemRecord } from "@/types"
|
import type { SystemRecord } from "@/types"
|
||||||
import { SystemStatus } from "./enums"
|
import { SystemStatus } from "./enums"
|
||||||
|
|
||||||
@@ -74,9 +74,7 @@ export function init() {
|
|||||||
|
|
||||||
/** Update the longest system name length and favicon based on system status */
|
/** Update the longest system name length and favicon based on system status */
|
||||||
function onSystemsChanged(_: Record<string, SystemRecord>, changedSystem: SystemRecord | undefined) {
|
function onSystemsChanged(_: Record<string, SystemRecord>, changedSystem: SystemRecord | undefined) {
|
||||||
const upSystemsStore = $upSystems.get()
|
|
||||||
const downSystemsStore = $downSystems.get()
|
const downSystemsStore = $downSystems.get()
|
||||||
const upSystems = Object.values(upSystemsStore)
|
|
||||||
const downSystems = Object.values(downSystemsStore)
|
const downSystems = Object.values(downSystemsStore)
|
||||||
|
|
||||||
// Update longest system name length
|
// Update longest system name length
|
||||||
@@ -86,14 +84,7 @@ function onSystemsChanged(_: Record<string, SystemRecord>, changedSystem: System
|
|||||||
$longestSystemNameLen.set(nameLen)
|
$longestSystemNameLen.set(nameLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update favicon based on system status
|
updateFavicon(downSystems.length)
|
||||||
if (downSystems.length > 0) {
|
|
||||||
updateFavicon(FAVICON_RED)
|
|
||||||
} else if (upSystems.length > 0) {
|
|
||||||
updateFavicon(FAVICON_GREEN)
|
|
||||||
} else {
|
|
||||||
updateFavicon(FAVICON_DEFAULT)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fetch systems from collection */
|
/** Fetch systems from collection */
|
||||||
|
|||||||
@@ -4,16 +4,11 @@ import { listenKeys } from "nanostores"
|
|||||||
import { timeDay, timeHour, timeMinute } from "d3-time"
|
import { timeDay, timeHour, timeMinute } from "d3-time"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import { prependBasePath } from "@/components/router"
|
|
||||||
import { toast } from "@/components/ui/use-toast"
|
import { toast } from "@/components/ui/use-toast"
|
||||||
import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types"
|
import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types"
|
||||||
import { HourFormat, MeterState, Unit } from "./enums"
|
import { HourFormat, MeterState, Unit } from "./enums"
|
||||||
import { $copyContent, $userSettings } from "./stores"
|
import { $copyContent, $userSettings } from "./stores"
|
||||||
|
|
||||||
export const FAVICON_DEFAULT = "favicon.svg"
|
|
||||||
export const FAVICON_GREEN = "favicon-green.svg"
|
|
||||||
export const FAVICON_RED = "favicon-red.svg"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
@@ -100,14 +95,41 @@ export const formatDay = (timestamp: string) => {
|
|||||||
return dayFormatter.format(new Date(timestamp))
|
return dayFormatter.format(new Date(timestamp))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateFavicon = (newIcon: string) => {
|
export const updateFavicon = (() => {
|
||||||
;(document.querySelector("link[rel='icon']") as HTMLLinkElement).href = prependBasePath(`/static/${newIcon}`)
|
let prevDownCount = 0
|
||||||
}
|
return (downCount = 0) => {
|
||||||
|
if (downCount === prevDownCount) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prevDownCount = downCount
|
||||||
|
const svg = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56 70">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="gradient" x1="0%" y1="20%" x2="100%" y2="120%">
|
||||||
|
<stop offset="0%" style="stop-color:#747bff"/>
|
||||||
|
<stop offset="100%" style="stop-color:#24eb5c"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path fill="url(#gradient)" d="M35 70H0V0h35q4.4 0 8.2 1.7a21.4 21.4 0 0 1 6.6 4.5q2.9 2.8 4.5 6.6Q56 16.7 56 21a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5Q53 39.1 54 41a18.3 18.3 0 0 1 1.5 4 17.4 17.4 0 0 1 .5 3 15.3 15.3 0 0 1 0 1q0 4.4-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.6Q39.4 70 35 70ZM14 14v14h21a7 7 0 0 0 2.3-.3 6.6 6.6 0 0 0 .4-.2Q39 27 40 26a6.9 6.9 0 0 0 1.5-2.2q.5-1.3.5-2.8a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 17 40 16a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm0 28v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 54.9 40 54a7 7 0 0 0 1.5-2.2 6.9 6.9 0 0 0 .5-2.6 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 45 40 44a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Z"/>
|
||||||
|
${
|
||||||
|
downCount > 0 &&
|
||||||
|
`
|
||||||
|
<circle cx="40" cy="50" r="22" fill="#f00"/>
|
||||||
|
<text x="40" y="60" font-size="34" text-anchor="middle" fill="#fff" font-family="Arial" font-weight="bold">${downCount}</text>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
</svg>
|
||||||
|
`
|
||||||
|
const blob = new Blob([svg], { type: "image/svg+xml" })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
;(document.querySelector("link[rel='icon']") as HTMLLinkElement).href = url
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
export const chartTimeData: ChartTimeData = {
|
export const chartTimeData: ChartTimeData = {
|
||||||
"1m": {
|
"1m": {
|
||||||
type: "1m",
|
type: "1m",
|
||||||
expectedInterval: 1000,
|
expectedInterval: 2000, // allow a bit of latency for one second updates (#1247)
|
||||||
label: () => t`1 minute`,
|
label: () => t`1 minute`,
|
||||||
format: (timestamp: string) => hourWithSeconds(timestamp),
|
format: (timestamp: string) => hourWithSeconds(timestamp),
|
||||||
ticks: 3,
|
ticks: 3,
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 ساعة"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "دقيقة واحدة"
|
msgstr "دقيقة واحدة"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 دقيقة"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 أسبوع"
|
msgstr "1 أسبوع"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 час"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 минута"
|
msgstr "1 минута"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 минута"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 седмица"
|
msgstr "1 седмица"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 hodina"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr "1 min"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minuta"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 týden"
|
msgstr "1 týden"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 time"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minut"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 uge"
|
msgstr "1 uge"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: de\n"
|
"Language: de\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-05 16:13\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@@ -48,6 +48,10 @@ msgstr "1 Stunde"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 Min"
|
msgstr "1 Min"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 Minute"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 Woche"
|
msgstr "1 Woche"
|
||||||
@@ -455,7 +459,7 @@ msgstr "Offline ({downSystemsLength})"
|
|||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
msgstr "Download"
|
msgstr "Herunterladen"
|
||||||
|
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
@@ -1152,7 +1156,7 @@ msgstr "aktiv ({upSystemsLength})"
|
|||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Upload"
|
msgstr "Hochladen"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "Uptime"
|
msgid "Uptime"
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ msgstr "1 hour"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr "1 min"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minute"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 week"
|
msgstr "1 week"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 hora"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr "1 min"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minuto"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 semana"
|
msgstr "1 semana"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "۱ ساعت"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "۱ دقیقه"
|
msgstr "۱ دقیقه"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 دقیقه"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "۱ هفته"
|
msgstr "۱ هفته"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 heure"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minute"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 semaine"
|
msgstr "1 semaine"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 sat"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 minut"
|
msgstr "1 minut"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minuta"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 tjedan"
|
msgstr "1 tjedan"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 óra"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 perc"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 hét"
|
msgstr "1 hét"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 klukkustund"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 mínúta"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 vika"
|
msgstr "1 vika"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 ora"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr "1 min"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minuto"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 settimana"
|
msgstr "1 settimana"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1時間"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1分"
|
msgstr "1分"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1分"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1週間"
|
msgstr "1週間"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1시간"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1분"
|
msgstr "1분"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1분"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1주"
|
msgstr "1주"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 uur"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 minuut"
|
msgstr "1 minuut"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minuut"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 week"
|
msgstr "1 week"
|
||||||
|
|||||||
@@ -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,11 @@ 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
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minutt"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
@@ -985,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"
|
||||||
@@ -1193,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"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 godzina"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr "1 min"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minuta"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 tydzień"
|
msgstr "1 tydzień"
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -48,6 +48,10 @@ msgstr "1 hora"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr "1 min"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minuto"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 semana"
|
msgstr "1 semana"
|
||||||
@@ -180,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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ru\n"
|
"Language: ru\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-09-28 07:31\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Russian\n"
|
"Language-Team: Russian\n"
|
||||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||||
@@ -31,7 +31,7 @@ msgstr "{0, plural, one {# час} other {# часов}}"
|
|||||||
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
msgstr ""
|
msgstr "{0, plural, one {# минута} few {# минут} many {# минут} other {# минуты}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
@@ -48,6 +48,10 @@ msgstr "1 час"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 мин"
|
msgstr "1 мин"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 минута"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 неделя"
|
msgstr "1 неделя"
|
||||||
@@ -265,7 +269,7 @@ msgstr "Проверьте ваш сервис уведомлений"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Click on a system to view more information."
|
msgid "Click on a system to view more information."
|
||||||
msgstr ""
|
msgstr "Нажмите на систему для просмотра дополнительной информации."
|
||||||
|
|
||||||
#: src/components/ui/input-copy.tsx
|
#: src/components/ui/input-copy.tsx
|
||||||
msgid "Click to copy"
|
msgid "Click to copy"
|
||||||
@@ -346,7 +350,7 @@ msgstr "Скопировать YAML"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table-columns.tsx
|
#: src/components/systems-table/systems-table-columns.tsx
|
||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr "CPU"
|
msgstr "ЦП"
|
||||||
|
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
@@ -451,7 +455,7 @@ msgstr "Не в сети"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Down ({downSystemsLength})"
|
msgid "Down ({downSystemsLength})"
|
||||||
msgstr ""
|
msgstr "Не в сети ({downSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
@@ -809,7 +813,7 @@ msgstr "Пауза"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Paused ({pausedSystemsLength})"
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
msgstr ""
|
msgstr "Пауза ({pausedSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
@@ -1148,7 +1152,7 @@ msgstr "В сети"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Up ({upSystemsLength})"
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr ""
|
msgstr "В сети ({upSystemsLength})"
|
||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: sl\n"
|
"Language: sl\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-09-25 17:11\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Slovenian\n"
|
"Language-Team: Slovenian\n"
|
||||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
||||||
@@ -31,13 +31,13 @@ msgstr "{0, plural, one {# ura} two {# uri} few {# ur} other {# ur}}"
|
|||||||
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
#. placeholder {0}: Math.trunc(system.info.u / 60)
|
||||||
#: src/components/routes/system.tsx
|
#: src/components/routes/system.tsx
|
||||||
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
|
||||||
msgstr ""
|
msgstr "{0, plural, one {# minuta} few {# minuti} many {# minut} other {# minut}}"
|
||||||
|
|
||||||
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
|
||||||
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
#. placeholder {1}: table.getFilteredRowModel().rows.length
|
||||||
#: src/components/routes/settings/alerts-history-data-table.tsx
|
#: src/components/routes/settings/alerts-history-data-table.tsx
|
||||||
msgid "{0} of {1} row(s) selected."
|
msgid "{0} of {1} row(s) selected."
|
||||||
msgstr ""
|
msgstr "{0} od {1} vrstic izbranih."
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
@@ -46,7 +46,11 @@ msgstr "1 ura"
|
|||||||
#. 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
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minuta"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
@@ -206,7 +210,7 @@ msgstr "Binarno"
|
|||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
msgid "Bits (Kbps, Mbps, Gbps)"
|
msgid "Bits (Kbps, Mbps, Gbps)"
|
||||||
msgstr ""
|
msgstr "Biti (Kbps, Mbps, Gbps)"
|
||||||
|
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
@@ -809,7 +813,7 @@ msgstr "Zaustavljeno"
|
|||||||
|
|
||||||
#: src/components/systems-table/systems-table.tsx
|
#: src/components/systems-table/systems-table.tsx
|
||||||
msgid "Paused ({pausedSystemsLength})"
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
msgstr ""
|
msgstr "Pavzirano za {pausedSystemsLength}"
|
||||||
|
|
||||||
#: src/components/routes/settings/notifications.tsx
|
#: src/components/routes/settings/notifications.tsx
|
||||||
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 timme"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 min"
|
msgstr "1 min"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 minut"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 vecka"
|
msgstr "1 vecka"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 saat"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 dk"
|
msgstr "1 dk"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 dakika"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 hafta"
|
msgstr "1 hafta"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 година"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 хв"
|
msgstr "1 хв"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 хвилина"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 тиждень"
|
msgstr "1 тиждень"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 giờ"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 phút"
|
msgstr "1 phút"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 phút"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 tuần"
|
msgstr "1 tuần"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1 小时"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 分钟"
|
msgstr "1 分钟"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 分钟"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1 周"
|
msgstr "1 周"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1小時"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 分鐘"
|
msgstr "1 分鐘"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 分鐘"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1週"
|
msgstr "1週"
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ msgstr "1小時"
|
|||||||
msgid "1 min"
|
msgid "1 min"
|
||||||
msgstr "1 分鐘"
|
msgstr "1 分鐘"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "1 minute"
|
||||||
|
msgstr "1 分钟"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 week"
|
msgid "1 week"
|
||||||
msgstr "1週"
|
msgstr "1週"
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
@@ -59,7 +58,7 @@ const App = memo(() => {
|
|||||||
} else if (page.route === "home") {
|
} else if (page.route === "home") {
|
||||||
return <Home />
|
return <Home />
|
||||||
} else if (page.route === "system") {
|
} else if (page.route === "system") {
|
||||||
return <SystemDetail name={page.params.name} />
|
return <SystemDetail id={page.params.id} />
|
||||||
} else if (page.route === "settings") {
|
} else if (page.route === "settings") {
|
||||||
return <Settings />
|
return <Settings />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,31 @@
|
|||||||
|
## 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
|
||||||
|
|
||||||
|
- Fix one minute charts on systems without Docker. (#1237)
|
||||||
|
|
||||||
|
- Change system permalinks to use ID instead of name. (#1231)
|
||||||
|
|
||||||
|
## 0.13.0
|
||||||
|
|
||||||
|
- Add one minute chart with one second interval.
|
||||||
|
|
||||||
|
- Improve accuracy of disk I/O statistics.
|
||||||
|
|
||||||
|
- Add `SYSTEM_NAME` environment variable to override system name on universal token registration. (#1184)
|
||||||
|
|
||||||
|
- Add `noindex` HTML meta tag. (#1218)
|
||||||
|
|
||||||
|
- Update Go dependencies.
|
||||||
|
|
||||||
## 0.12.12
|
## 0.12.12
|
||||||
|
|
||||||
- Fix high CPU usage when `intel_gpu_top` returns an error. (#1203)
|
- Fix high CPU usage when `intel_gpu_top` returns an error. (#1203)
|
||||||
|
|||||||
@@ -757,20 +757,12 @@ start_service() {
|
|||||||
procd_set_param user beszel
|
procd_set_param user beszel
|
||||||
procd_set_param pidfile /var/run/beszel-agent.pid
|
procd_set_param pidfile /var/run/beszel-agent.pid
|
||||||
procd_set_param env PORT="$PORT" KEY="$KEY" TOKEN="$TOKEN" HUB_URL="$HUB_URL"
|
procd_set_param env PORT="$PORT" KEY="$KEY" TOKEN="$TOKEN" HUB_URL="$HUB_URL"
|
||||||
|
procd_set_param respawn
|
||||||
procd_set_param stdout 1
|
procd_set_param stdout 1
|
||||||
procd_set_param stderr 1
|
procd_set_param stderr 1
|
||||||
procd_close_instance
|
procd_close_instance
|
||||||
}
|
}
|
||||||
|
|
||||||
stop_service() {
|
|
||||||
killall beszel-agent
|
|
||||||
}
|
|
||||||
|
|
||||||
restart_service() {
|
|
||||||
stop
|
|
||||||
start
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extra command to trigger agent update
|
# Extra command to trigger agent update
|
||||||
EXTRA_COMMANDS="update restart"
|
EXTRA_COMMANDS="update restart"
|
||||||
EXTRA_HELP=" update Update the Beszel agent
|
EXTRA_HELP=" update Update the Beszel agent
|
||||||
|
|||||||
Reference in New Issue
Block a user