Compare commits

...

39 Commits

Author SHA1 Message Date
henrygd
29b182fd7b 0.12.11 release :) 2025-09-24 18:08:54 -04:00
henrygd
fc78b959aa update colors for gpu power chart 2025-09-24 18:01:51 -04:00
henrygd
b8b3604aec update language files 2025-09-24 17:41:11 -04:00
henrygd
e45606fdec New Croatian translations by nikola.smis on Crowdin 2025-09-24 17:40:22 -04:00
aroxu
640afd82ad New Korean translations 2025-09-24 17:31:04 -04:00
henrygd
d025e51c67 make sure agent connection title works in grid layout 2025-09-24 17:15:17 -04:00
henrygd
f70c30345a fix sticky header z-index 2025-09-24 17:07:38 -04:00
henrygd
63bdac83a1 hide interfaces chart legend if interfaces.length > 15 2025-09-24 16:45:43 -04:00
Sven van Ginkel
65897a8df6 add cali to the default nics skip list (#1195) 2025-09-24 16:29:11 -04:00
henrygd
0dc9b3e273 add pattern matching and blacklist functionality to NICS env var. (#1190) 2025-09-24 16:27:37 -04:00
Sven van Ginkel
c1c0d8d672 Fix hub executable (#1193) 2025-09-24 15:13:24 -04:00
henrygd
1811ab64be add migration to fix bad cached mem values (#1196) 2025-09-24 15:07:11 -04:00
henrygd
5578520054 add title to agent connection type in all systems table 2025-09-24 14:18:20 -04:00
henrygd
7b128d09ac Update Intel GPU collector to parse plain text (-l) instead of JSON output (#1150) 2025-09-24 13:24:48 -04:00
henrygd
d295507c0b adjust calculation of cached memory (#1187, #1196) 2025-09-24 13:23:59 -04:00
henrygd
79fbbb7ad0 add ghcr.io image configuration for beszel-agent-intel in gh workflow 2025-09-23 20:16:19 -04:00
henrygd
e7325b23c4 simplify filter bar component 2025-09-23 20:15:42 -04:00
henrygd
c5eba6547a comments 2025-09-22 20:48:37 -04:00
henrygd
82e7c04b25 0.12.10 release :) 2025-09-22 18:32:30 -04:00
henrygd
a9ce16cfdd update language files 2025-09-22 18:28:39 -04:00
henrygd
2af8b6057f new Polish translations 2025-09-22 18:18:39 -04:00
henrygd
3fae4360a8 update changelog 2025-09-22 18:14:36 -04:00
henrygd
10073d85e1 update go deps 2025-09-22 18:13:50 -04:00
henrygd
e240ced018 add support for henrygd/beszel-agent-intel in docker workflow 2025-09-22 17:47:32 -04:00
henrygd
ae1e17f5ed add dockerfile for henrygd/beszel-agent-intel 2025-09-22 17:41:44 -04:00
henrygd
3abb7c213b initial support for one intel gpu with intel_gpu_top 2025-09-22 16:36:10 -04:00
henrygd
240e75f025 add sorted style to home table header buttons 2025-09-21 19:23:34 -04:00
henrygd
ea984844ff update changelog 2025-09-21 17:56:35 -04:00
henrygd
0d157b5857 display agent connection type in hub (ssh, websocket) 2025-09-21 17:49:22 -04:00
henrygd
d0b6e725c8 fix positioning of bandwidth chart button 2025-09-19 12:08:41 -04:00
henrygd
ffe7f8547a fix: update temperature and byte formatting functions to use loose equality checks (#1180) 2025-09-19 11:51:27 -04:00
henrygd
37817b0f15 add --auto-update flag to hub install script 2025-09-18 17:51:22 -04:00
henrygd
a66ac418ae install: remove additional service restart for openwrt 2025-09-18 14:05:19 -04:00
henrygd
2ee2f53267 fix: resolve mipsle architecture detection for install script (#1176)
- Add proper endianness detection using ELF header inspection
- Prevent mipsle devices from downloading incorrect mips binaries
- Maintain backward compatibility for all other architectures
2025-09-18 13:48:24 -04:00
henrygd
e5c766c00b refactoring
- network interface delta
- string concatenation
2025-09-17 21:36:05 -04:00
henrygd
da43ba10e1 add aria-label to button in NetworkSheet for improved accessibility 2025-09-17 16:23:02 -04:00
henrygd
fca13004bd release 0.12.9 2025-09-17 16:06:20 -04:00
henrygd
376a86829c fix divide by zero error (#1175) 2025-09-17 16:00:50 -04:00
henrygd
ef48613f3f improve style of chart sheet button on mobile
- also update changelog
2025-09-17 15:13:10 -04:00
59 changed files with 1903 additions and 352 deletions

View File

@@ -33,6 +33,14 @@ jobs:
registry: docker.io registry: docker.io
username_secret: DOCKERHUB_USERNAME username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
- image: henrygd/beszel-agent-intel
context: ./
dockerfile: ./internal/dockerfile_agent_intel
platforms: linux/amd64
registry: docker.io
username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN
- image: ghcr.io/${{ github.repository }}/beszel - image: ghcr.io/${{ github.repository }}/beszel
context: ./ context: ./
@@ -56,6 +64,14 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
- image: ghcr.io/${{ github.repository }}/beszel-agent-intel
context: ./
dockerfile: ./internal/dockerfile_agent_intel
platforms: linux/amd64
registry: ghcr.io
username: ${{ github.actor }}
password_secret: GITHUB_TOKEN
permissions: permissions:
contents: read contents: read
packages: write packages: write

View File

@@ -9,19 +9,21 @@ import (
"time" "time"
"github.com/henrygd/beszel/agent/health" "github.com/henrygd/beszel/agent/health"
"github.com/henrygd/beszel/internal/entities/system"
) )
// ConnectionManager manages the connection state and events for the agent. // ConnectionManager manages the connection state and events for the agent.
// It handles both WebSocket and SSH connections, automatically switching between // It handles both WebSocket and SSH connections, automatically switching between
// them based on availability and managing reconnection attempts. // them based on availability and managing reconnection attempts.
type ConnectionManager struct { type ConnectionManager struct {
agent *Agent // Reference to the parent agent agent *Agent // Reference to the parent agent
State ConnectionState // Current connection state State ConnectionState // Current connection state
eventChan chan ConnectionEvent // Channel for connection events eventChan chan ConnectionEvent // Channel for connection events
wsClient *WebSocketClient // WebSocket client for hub communication wsClient *WebSocketClient // WebSocket client for hub communication
serverOptions ServerOptions // Configuration for SSH server serverOptions ServerOptions // Configuration for SSH server
wsTicker *time.Ticker // Ticker for WebSocket connection attempts wsTicker *time.Ticker // Ticker for WebSocket connection attempts
isConnecting bool // Prevents multiple simultaneous reconnection attempts isConnecting bool // Prevents multiple simultaneous reconnection attempts
ConnectionType system.ConnectionType
} }
// ConnectionState represents the current connection state of the agent. // ConnectionState represents the current connection state of the agent.
@@ -144,15 +146,18 @@ func (c *ConnectionManager) handleStateChange(newState ConnectionState) {
switch newState { switch newState {
case WebSocketConnected: case WebSocketConnected:
slog.Info("WebSocket connected", "host", c.wsClient.hubURL.Host) slog.Info("WebSocket connected", "host", c.wsClient.hubURL.Host)
c.ConnectionType = system.ConnectionTypeWebSocket
c.stopWsTicker() c.stopWsTicker()
_ = c.agent.StopServer() _ = c.agent.StopServer()
c.isConnecting = false c.isConnecting = false
case SSHConnected: case SSHConnected:
// stop new ws connection attempts // stop new ws connection attempts
slog.Info("SSH connection established") slog.Info("SSH connection established")
c.ConnectionType = system.ConnectionTypeSSH
c.stopWsTicker() c.stopWsTicker()
c.isConnecting = false c.isConnecting = false
case Disconnected: case Disconnected:
c.ConnectionType = system.ConnectionTypeNone
if c.isConnecting { if c.isConnecting {
// Already handling reconnection, avoid duplicate attempts // Already handling reconnection, avoid duplicate attempts
return return

View File

@@ -27,13 +27,10 @@ const (
nvidiaSmiInterval string = "4" // in seconds nvidiaSmiInterval string = "4" // in seconds
tegraStatsInterval string = "3700" // in milliseconds tegraStatsInterval string = "3700" // in milliseconds
rocmSmiInterval time.Duration = 4300 * time.Millisecond rocmSmiInterval time.Duration = 4300 * time.Millisecond
// Command retry and timeout constants // Command retry and timeout constants
retryWaitTime time.Duration = 5 * time.Second retryWaitTime time.Duration = 5 * time.Second
maxFailureRetries int = 5 maxFailureRetries int = 5
cmdBufferSize uint16 = 10 * 1024
// Unit Conversions // Unit Conversions
mebibytesInAMegabyte float64 = 1.024 // nvidia-smi reports memory in MiB mebibytesInAMegabyte float64 = 1.024 // nvidia-smi reports memory in MiB
milliwattsInAWatt float64 = 1000.0 // tegrastats reports power in mW milliwattsInAWatt float64 = 1000.0 // tegrastats reports power in mW
@@ -42,10 +39,11 @@ const (
// GPUManager manages data collection for GPUs (either Nvidia or AMD) // GPUManager manages data collection for GPUs (either Nvidia or AMD)
type GPUManager struct { type GPUManager struct {
sync.Mutex sync.Mutex
nvidiaSmi bool nvidiaSmi bool
rocmSmi bool rocmSmi bool
tegrastats bool tegrastats bool
GpuDataMap map[string]*system.GPUData intelGpuStats bool
GpuDataMap map[string]*system.GPUData
} }
// RocmSmiJson represents the JSON structure of rocm-smi output // RocmSmiJson represents the JSON structure of rocm-smi output
@@ -66,6 +64,7 @@ type gpuCollector struct {
cmdArgs []string cmdArgs []string
parse func([]byte) bool // returns true if valid data was found parse func([]byte) bool // returns true if valid data was found
buf []byte buf []byte
bufSize uint16
} }
var errNoValidData = fmt.Errorf("no valid GPU data found") // Error for missing data var errNoValidData = fmt.Errorf("no valid GPU data found") // Error for missing data
@@ -99,7 +98,7 @@ func (c *gpuCollector) collect() error {
scanner := bufio.NewScanner(stdout) scanner := bufio.NewScanner(stdout)
if c.buf == nil { if c.buf == nil {
c.buf = make([]byte, 0, cmdBufferSize) c.buf = make([]byte, 0, c.bufSize)
} }
scanner.Buffer(c.buf, bufio.MaxScanTokenSize) scanner.Buffer(c.buf, bufio.MaxScanTokenSize)
@@ -244,20 +243,31 @@ func (gm *GPUManager) GetCurrentData() map[string]system.GPUData {
// copy / reset the data // copy / reset the data
gpuData := make(map[string]system.GPUData, len(gm.GpuDataMap)) gpuData := make(map[string]system.GPUData, len(gm.GpuDataMap))
for id, gpu := range gm.GpuDataMap { for id, gpu := range gm.GpuDataMap {
gpuAvg := *gpu
gpuAvg.Temperature = twoDecimals(gpu.Temperature)
gpuAvg.MemoryUsed = twoDecimals(gpu.MemoryUsed)
gpuAvg.MemoryTotal = twoDecimals(gpu.MemoryTotal)
// avoid division by zero // avoid division by zero
if gpu.Count > 0 { count := max(gpu.Count, 1)
gpuAvg.Usage = twoDecimals(gpu.Usage / gpu.Count)
gpuAvg.Power = twoDecimals(gpu.Power / gpu.Count) // average the data
gpuAvg := *gpu
gpuAvg.Temperature = twoDecimals(gpu.Temperature)
gpuAvg.Power = twoDecimals(gpu.Power / count)
// intel gpu stats doesn't provide usage, memory used, or memory total
if gpu.Engines != nil {
maxEngineUsage := 0.0
for name, engine := range gpu.Engines {
gpuAvg.Engines[name] = twoDecimals(engine / count)
maxEngineUsage = max(maxEngineUsage, engine/count)
}
gpuAvg.Usage = twoDecimals(maxEngineUsage)
} else {
gpuAvg.Usage = twoDecimals(gpu.Usage / count)
gpuAvg.MemoryUsed = twoDecimals(gpu.MemoryUsed)
gpuAvg.MemoryTotal = twoDecimals(gpu.MemoryTotal)
} }
// reset accumulators in the original // reset accumulators in the original gpu data for next collection
gpu.Usage, gpu.Power, gpu.Count = 0, 0, 0 gpu.Usage, gpu.Power, gpu.Count = gpuAvg.Usage, gpuAvg.Power, 1
gpu.Engines = gpuAvg.Engines
// append id to the name if there are multiple GPUs with the same name // append id to the name if there are multiple GPUs with the same name
if nameCounts[gpu.Name] > 1 { if nameCounts[gpu.Name] > 1 {
@@ -284,18 +294,37 @@ func (gm *GPUManager) detectGPUs() error {
gm.tegrastats = true gm.tegrastats = true
gm.nvidiaSmi = false gm.nvidiaSmi = false
} }
if gm.nvidiaSmi || gm.rocmSmi || gm.tegrastats { if _, err := exec.LookPath(intelGpuStatsCmd); err == nil {
gm.intelGpuStats = true
}
if gm.nvidiaSmi || gm.rocmSmi || gm.tegrastats || gm.intelGpuStats {
return nil return nil
} }
return fmt.Errorf("no GPU found - install nvidia-smi, rocm-smi, or tegrastats") return fmt.Errorf("no GPU found - install nvidia-smi, rocm-smi, tegrastats, or intel_gpu_top")
} }
// startCollector starts the appropriate GPU data collector based on the command // startCollector starts the appropriate GPU data collector based on the command
func (gm *GPUManager) startCollector(command string) { func (gm *GPUManager) startCollector(command string) {
collector := gpuCollector{ collector := gpuCollector{
name: command, name: command,
bufSize: 10 * 1024,
} }
switch command { switch command {
case intelGpuStatsCmd:
go func() {
failures := 0
for {
if err := gm.collectIntelStats(); err != nil {
failures++
if failures > maxFailureRetries {
break
}
slog.Warn("Error collecting Intel GPU data; see https://beszel.dev/guide/gpu", "err", err)
time.Sleep(retryWaitTime)
continue
}
}
}()
case nvidiaSmiCmd: case nvidiaSmiCmd:
collector.cmdArgs = []string{ collector.cmdArgs = []string{
"-l", nvidiaSmiInterval, "-l", nvidiaSmiInterval,
@@ -344,6 +373,9 @@ func NewGPUManager() (*GPUManager, error) {
if gm.tegrastats { if gm.tegrastats {
gm.startCollector(tegraStatsCmd) gm.startCollector(tegraStatsCmd)
} }
if gm.intelGpuStats {
gm.startCollector(intelGpuStatsCmd)
}
return &gm, nil return &gm, nil
} }

179
agent/gpu_intel.go Normal file
View File

@@ -0,0 +1,179 @@
package agent
import (
"bufio"
"io"
"os/exec"
"strconv"
"strings"
"github.com/henrygd/beszel/internal/entities/system"
)
const (
intelGpuStatsCmd string = "intel_gpu_top"
intelGpuStatsInterval string = "3300" // in milliseconds
)
type intelGpuStats struct {
PowerGPU float64
Engines map[string]float64
}
// updateIntelFromStats updates aggregated GPU data from a single intelGpuStats sample
func (gm *GPUManager) updateIntelFromStats(sample *intelGpuStats) bool {
gm.Lock()
defer gm.Unlock()
// only one gpu for now - cmd doesn't provide all by default
gpuData, ok := gm.GpuDataMap["0"]
if !ok {
gpuData = &system.GPUData{Name: "GPU", Engines: make(map[string]float64)}
gm.GpuDataMap["0"] = gpuData
}
gpuData.Power += sample.PowerGPU
if gpuData.Engines == nil {
gpuData.Engines = make(map[string]float64, len(sample.Engines))
}
for name, engine := range sample.Engines {
gpuData.Engines[name] += engine
}
gpuData.Count++
return true
}
// collectIntelStats executes intel_gpu_top in text mode (-l) and parses the output
func (gm *GPUManager) collectIntelStats() error {
cmd := exec.Command(intelGpuStatsCmd, "-s", intelGpuStatsInterval, "-l")
// Avoid blocking if intel_gpu_top writes to stderr
cmd.Stderr = io.Discard
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
if err := cmd.Start(); err != nil {
return err
}
// Ensure we always reap the child to avoid zombies on any return path.
defer func() {
// Best-effort close of the pipe (unblock the child if it writes)
_ = stdout.Close()
if cmd.ProcessState == nil || !cmd.ProcessState.Exited() {
_ = cmd.Process.Kill()
}
_ = cmd.Wait()
}()
scanner := bufio.NewScanner(stdout)
var header1 string
var header2 string
var engineNames []string
var friendlyNames []string
var preEngineCols int
var powerIndex int
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
// first header line
if header1 == "" {
header1 = line
continue
}
// second header line
if header2 == "" {
engineNames, friendlyNames, powerIndex, preEngineCols = gm.parseIntelHeaders(header1, line)
header1, header2 = "x", "x" // don't need these anymore
continue
}
// Data row
sample := gm.parseIntelData(line, engineNames, friendlyNames, powerIndex, preEngineCols)
gm.updateIntelFromStats(&sample)
}
if err := scanner.Err(); err != nil {
return err
}
return nil
}
func (gm *GPUManager) parseIntelHeaders(header1 string, header2 string) (engineNames []string, friendlyNames []string, powerIndex int, preEngineCols int) {
// Build indexes
h1 := strings.Fields(header1)
h2 := strings.Fields(header2)
powerIndex = -1 // Initialize to -1, will be set to actual index if found
// Collect engine names from header1
for _, col := range h1 {
key := strings.TrimRightFunc(col, func(r rune) bool { return r >= '0' && r <= '9' })
var friendly string
switch key {
case "RCS":
friendly = "Render/3D"
case "BCS":
friendly = "Blitter"
case "VCS":
friendly = "Video"
case "VECS":
friendly = "VideoEnhance"
case "CCS":
friendly = "Compute"
default:
continue
}
engineNames = append(engineNames, key)
friendlyNames = append(friendlyNames, friendly)
}
// find power gpu index among pre-engine columns
if n := len(engineNames); n > 0 {
preEngineCols = max(len(h2)-3*n, 0)
limit := min(len(h2), preEngineCols)
for i := range limit {
if strings.EqualFold(h2[i], "gpu") {
powerIndex = i
break
}
}
}
return engineNames, friendlyNames, powerIndex, preEngineCols
}
func (gm *GPUManager) parseIntelData(line string, engineNames []string, friendlyNames []string, powerIndex int, preEngineCols int) (sample intelGpuStats) {
fields := strings.Fields(line)
if len(fields) == 0 {
return sample
}
// Make sure row has enough columns for engines
if need := preEngineCols + 3*len(engineNames); len(fields) < need {
return sample
}
if powerIndex >= 0 && powerIndex < len(fields) {
if v, perr := strconv.ParseFloat(fields[powerIndex], 64); perr == nil {
sample.PowerGPU = v
}
}
if len(engineNames) > 0 {
sample.Engines = make(map[string]float64, len(engineNames))
for k := range engineNames {
base := preEngineCols + 3*k
if base < len(fields) {
busy := 0.0
if v, e := strconv.ParseFloat(fields[base], 64); e == nil {
busy = v
}
cur := sample.Engines[friendlyNames[k]]
sample.Engines[friendlyNames[k]] = cur + busy
} else {
sample.Engines[friendlyNames[k]] = 0
}
}
}
return sample
}

View File

@@ -379,12 +379,12 @@ func TestGetCurrentData(t *testing.T) {
assert.InDelta(t, 60.0, result["1"].Power, 0.01) assert.InDelta(t, 60.0, result["1"].Power, 0.01)
// Verify that accumulators in the original map are reset // Verify that accumulators in the original map are reset
assert.Equal(t, float64(0), gm.GpuDataMap["0"].Count, "GPU 0 Count should be reset") assert.EqualValues(t, float64(1), gm.GpuDataMap["0"].Count, "GPU 0 Count should be reset")
assert.Equal(t, float64(0), gm.GpuDataMap["0"].Usage, "GPU 0 Usage should be reset") assert.EqualValues(t, float64(50.0), gm.GpuDataMap["0"].Usage, "GPU 0 Usage should be reset")
assert.Equal(t, float64(0), gm.GpuDataMap["0"].Power, "GPU 0 Power should be reset") assert.Equal(t, float64(100.0), gm.GpuDataMap["0"].Power, "GPU 0 Power should be reset")
assert.Equal(t, float64(0), gm.GpuDataMap["1"].Count, "GPU 1 Count should be reset") assert.Equal(t, float64(1), gm.GpuDataMap["1"].Count, "GPU 1 Count should be reset")
assert.Equal(t, float64(0), gm.GpuDataMap["1"].Usage, "GPU 1 Usage should be reset") assert.Equal(t, float64(30), gm.GpuDataMap["1"].Usage, "GPU 1 Usage should be reset")
assert.Equal(t, float64(0), gm.GpuDataMap["1"].Power, "GPU 1 Power should be reset") assert.Equal(t, float64(60), gm.GpuDataMap["1"].Power, "GPU 1 Power should be reset")
}) })
t.Run("handles zero count without panicking", func(t *testing.T) { t.Run("handles zero count without panicking", func(t *testing.T) {
@@ -409,7 +409,7 @@ func TestGetCurrentData(t *testing.T) {
assert.Equal(t, 0.0, result["0"].Power) assert.Equal(t, 0.0, result["0"].Power)
// Verify reset count // Verify reset count
assert.Equal(t, float64(0), gm.GpuDataMap["0"].Count) assert.EqualValues(t, 1, gm.GpuDataMap["0"].Count)
}) })
} }
@@ -756,11 +756,11 @@ func TestAccumulation(t *testing.T) {
continue continue
} }
assert.InDelta(t, expected.temperature, gpu.Temperature, 0.01, "Temperature should match") assert.EqualValues(t, expected.temperature, gpu.Temperature, "Temperature should match")
assert.InDelta(t, expected.memoryUsed, gpu.MemoryUsed, 0.01, "Memory used should match") assert.EqualValues(t, expected.memoryUsed, gpu.MemoryUsed, "Memory used should match")
assert.InDelta(t, expected.memoryTotal, gpu.MemoryTotal, 0.01, "Memory total should match") assert.EqualValues(t, expected.memoryTotal, gpu.MemoryTotal, "Memory total should match")
assert.InDelta(t, expected.usage, gpu.Usage, 0.01, "Usage should match") assert.EqualValues(t, expected.usage, gpu.Usage, "Usage should match")
assert.InDelta(t, expected.power, gpu.Power, 0.01, "Power should match") assert.EqualValues(t, expected.power, gpu.Power, "Power should match")
assert.Equal(t, expected.count, gpu.Count, "Count should match") assert.Equal(t, expected.count, gpu.Count, "Count should match")
} }
@@ -773,22 +773,313 @@ func TestAccumulation(t *testing.T) {
continue continue
} }
assert.InDelta(t, expected.temperature, gpu.Temperature, 0.01, "Temperature in GetCurrentData should match") assert.EqualValues(t, expected.temperature, gpu.Temperature, "Temperature in GetCurrentData should match")
assert.InDelta(t, expected.avgUsage, gpu.Usage, 0.01, "Average usage in GetCurrentData should match") assert.EqualValues(t, expected.avgUsage, gpu.Usage, "Average usage in GetCurrentData should match")
assert.InDelta(t, expected.avgPower, gpu.Power, 0.01, "Average power in GetCurrentData should match") assert.EqualValues(t, expected.avgPower, gpu.Power, "Average power in GetCurrentData should match")
} }
// Verify that accumulators in the original map are reset // Verify that accumulators in the original map are reset
for id := range tt.expectedValues { for id, expected := range tt.expectedValues {
gpu, exists := gm.GpuDataMap[id] gpu, exists := gm.GpuDataMap[id]
assert.True(t, exists, "GPU with ID %s should still exist after GetCurrentData", id) assert.True(t, exists, "GPU with ID %s should still exist after GetCurrentData", id)
if !exists { if !exists {
continue continue
} }
assert.Equal(t, float64(0), gpu.Count, "Count should be reset for GPU ID %s", id) assert.EqualValues(t, 1, gpu.Count, "Count should be reset for GPU ID %s", id)
assert.Equal(t, float64(0), gpu.Usage, "Usage should be reset for GPU ID %s", id) assert.EqualValues(t, expected.avgUsage, gpu.Usage, "Usage should be reset for GPU ID %s", id)
assert.Equal(t, float64(0), gpu.Power, "Power should be reset for GPU ID %s", id) assert.EqualValues(t, expected.avgPower, gpu.Power, "Power should be reset for GPU ID %s", id)
} }
}) })
} }
} }
func TestIntelUpdateFromStats(t *testing.T) {
gm := &GPUManager{
GpuDataMap: make(map[string]*system.GPUData),
}
// First sample with power and two engines
sample1 := intelGpuStats{
PowerGPU: 10.5,
Engines: map[string]float64{
"Render/3D": 20.0,
"Video": 5.0,
},
}
ok := gm.updateIntelFromStats(&sample1)
assert.True(t, ok)
gpu := gm.GpuDataMap["0"]
require.NotNil(t, gpu)
assert.Equal(t, "GPU", gpu.Name)
assert.EqualValues(t, 10.5, gpu.Power)
assert.EqualValues(t, 20.0, gpu.Engines["Render/3D"])
assert.EqualValues(t, 5.0, gpu.Engines["Video"])
assert.Equal(t, float64(1), gpu.Count)
// Second sample with zero power (should not add) and additional engine busy
sample2 := intelGpuStats{
PowerGPU: 0.0,
Engines: map[string]float64{
"Render/3D": 10.0,
"Video": 2.5,
"Blitter": 1.0,
},
}
// zero power should not increment power accumulator
ok = gm.updateIntelFromStats(&sample2)
assert.True(t, ok)
gpu = gm.GpuDataMap["0"]
require.NotNil(t, gpu)
assert.EqualValues(t, 10.5, gpu.Power)
assert.EqualValues(t, 30.0, gpu.Engines["Render/3D"]) // 20 + 10
assert.EqualValues(t, 7.5, gpu.Engines["Video"]) // 5 + 2.5
assert.EqualValues(t, 1.0, gpu.Engines["Blitter"])
assert.Equal(t, float64(2), gpu.Count)
}
func TestIntelCollectorStreaming(t *testing.T) {
// Save and override PATH
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
dir := t.TempDir()
os.Setenv("PATH", dir)
// Create a fake intel_gpu_top that prints -l format with two samples and exits
scriptPath := filepath.Join(dir, "intel_gpu_top")
script := `#!/bin/sh
echo "Freq MHz IRQ RC6 Power W IMC MiB/s RCS BCS VCS"
echo " req act /s % gpu pkg rd wr % se wa % se wa % se wa"
echo "373 373 224 45 1.50 4.13 2554 714 12.34 0 0 0.00 0 0 5.00 0 0"
echo "226 223 338 58 2.00 2.69 1820 965 0.00 0 0 0.00 0 0 0.00 0 0"`
if err := os.WriteFile(scriptPath, []byte(script), 0755); err != nil {
t.Fatal(err)
}
gm := &GPUManager{
GpuDataMap: make(map[string]*system.GPUData),
}
// Run the collector once; it should read two samples and return
if err := gm.collectIntelStats(); err != nil {
t.Fatalf("collectIntelStats error: %v", err)
}
gpu := gm.GpuDataMap["0"]
require.NotNil(t, gpu)
// Power should be sum of samples: 1.5 + 2.0 = 3.5
assert.EqualValues(t, 3.5, gpu.Power)
// Engines aggregated
assert.EqualValues(t, 12.34, gpu.Engines["Render/3D"])
assert.EqualValues(t, 5.0, gpu.Engines["Video"])
assert.EqualValues(t, 0.0, gpu.Engines["Blitter"]) // BCS is zero in both samples
// Count should be 2 samples
assert.Equal(t, float64(2), gpu.Count)
}
func TestParseIntelHeaders(t *testing.T) {
tests := []struct {
name string
header1 string
header2 string
wantEngineNames []string
wantFriendlyNames []string
wantPowerIndex int
wantPreEngineCols int
}{
{
name: "basic headers with RCS BCS VCS",
header1: "Freq MHz IRQ RC6 Power W IMC MiB/s RCS BCS VCS",
header2: " req act /s % gpu pkg rd wr % se wa % se wa % se wa",
wantEngineNames: []string{"RCS", "BCS", "VCS"},
wantFriendlyNames: []string{"Render/3D", "Blitter", "Video"},
wantPowerIndex: 4, // "gpu" is at index 4
wantPreEngineCols: 8, // 17 total cols - 3*3 = 8
},
{
name: "headers with only RCS",
header1: "Freq MHz IRQ RC6 Power W IMC MiB/s RCS",
header2: " req act /s % gpu pkg rd wr % se wa",
wantEngineNames: []string{"RCS"},
wantFriendlyNames: []string{"Render/3D"},
wantPowerIndex: 4,
wantPreEngineCols: 8, // 11 total - 3*1 = 8
},
{
name: "headers with VECS and CCS",
header1: "Freq MHz IRQ RC6 Power W IMC MiB/s VECS CCS",
header2: " req act /s % gpu pkg rd wr % se wa % se wa",
wantEngineNames: []string{"VECS", "CCS"},
wantFriendlyNames: []string{"VideoEnhance", "Compute"},
wantPowerIndex: 4,
wantPreEngineCols: 8, // 14 total - 3*2 = 8
},
{
name: "no engines",
header1: "Freq MHz IRQ RC6 Power W IMC MiB/s",
header2: " req act /s % gpu pkg rd wr",
wantEngineNames: nil, // no engines found, slices remain nil
wantFriendlyNames: nil,
wantPowerIndex: -1, // no engines, so no search
wantPreEngineCols: 0,
},
{
name: "power index not found",
header1: "Freq MHz IRQ RC6 Power W IMC MiB/s RCS",
header2: " req act /s % pkg cpu rd wr % se wa", // no "gpu"
wantEngineNames: []string{"RCS"},
wantFriendlyNames: []string{"Render/3D"},
wantPowerIndex: -1, // "gpu" not found
wantPreEngineCols: 8, // 11 total - 3*1 = 8
},
{
name: "empty headers",
header1: "",
header2: "",
wantEngineNames: nil, // empty input, slices remain nil
wantFriendlyNames: nil,
wantPowerIndex: -1,
wantPreEngineCols: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gm := &GPUManager{}
engineNames, friendlyNames, powerIndex, preEngineCols := gm.parseIntelHeaders(tt.header1, tt.header2)
assert.Equal(t, tt.wantEngineNames, engineNames)
assert.Equal(t, tt.wantFriendlyNames, friendlyNames)
assert.Equal(t, tt.wantPowerIndex, powerIndex)
assert.Equal(t, tt.wantPreEngineCols, preEngineCols)
})
}
}
func TestParseIntelData(t *testing.T) {
tests := []struct {
name string
line string
engineNames []string
friendlyNames []string
powerIndex int
preEngineCols int
wantPowerGPU float64
wantEngines map[string]float64
}{
{
name: "basic data with power and engines",
line: "373 373 224 45 1.50 4.13 2554 714 12.34 0 0 0.00 0 0 5.00 0 0",
engineNames: []string{"RCS", "BCS", "VCS"},
friendlyNames: []string{"Render/3D", "Blitter", "Video"},
powerIndex: 4,
preEngineCols: 8,
wantPowerGPU: 1.50,
wantEngines: map[string]float64{
"Render/3D": 12.34,
"Blitter": 0.00,
"Video": 5.00,
},
},
{
name: "data with zero power",
line: "226 223 338 58 0.00 2.69 1820 965 0.00 0 0 0.00 0 0 0.00 0 0",
engineNames: []string{"RCS", "BCS", "VCS"},
friendlyNames: []string{"Render/3D", "Blitter", "Video"},
powerIndex: 4,
preEngineCols: 8,
wantPowerGPU: 0.00,
wantEngines: map[string]float64{
"Render/3D": 0.00,
"Blitter": 0.00,
"Video": 0.00,
},
},
{
name: "data with no power index",
line: "373 373 224 45 1.50 4.13 2554 714 12.34 0 0 0.00 0 0 5.00 0 0",
engineNames: []string{"RCS", "BCS", "VCS"},
friendlyNames: []string{"Render/3D", "Blitter", "Video"},
powerIndex: -1,
preEngineCols: 8,
wantPowerGPU: 0.0, // no power parsed
wantEngines: map[string]float64{
"Render/3D": 12.34,
"Blitter": 0.00,
"Video": 5.00,
},
},
{
name: "data with insufficient columns",
line: "373 373 224 45 1.50", // too few columns
engineNames: []string{"RCS", "BCS", "VCS"},
friendlyNames: []string{"Render/3D", "Blitter", "Video"},
powerIndex: 4,
preEngineCols: 8,
wantPowerGPU: 0.0,
wantEngines: nil, // empty sample returned
},
{
name: "empty line",
line: "",
engineNames: []string{"RCS"},
friendlyNames: []string{"Render/3D"},
powerIndex: 4,
preEngineCols: 8,
wantPowerGPU: 0.0,
wantEngines: nil,
},
{
name: "data with invalid power value",
line: "373 373 224 45 N/A 4.13 2554 714 12.34 0 0 0.00 0 0 5.00 0 0",
engineNames: []string{"RCS", "BCS", "VCS"},
friendlyNames: []string{"Render/3D", "Blitter", "Video"},
powerIndex: 4,
preEngineCols: 8,
wantPowerGPU: 0.0, // N/A can't be parsed
wantEngines: map[string]float64{
"Render/3D": 12.34,
"Blitter": 0.00,
"Video": 5.00,
},
},
{
name: "data with invalid engine value",
line: "373 373 224 45 1.50 4.13 2554 714 N/A 0 0 0.00 0 0 5.00 0 0",
engineNames: []string{"RCS", "BCS", "VCS"},
friendlyNames: []string{"Render/3D", "Blitter", "Video"},
powerIndex: 4,
preEngineCols: 8,
wantPowerGPU: 1.50,
wantEngines: map[string]float64{
"Render/3D": 0.0, // N/A becomes 0
"Blitter": 0.00,
"Video": 5.00,
},
},
{
name: "data with no engines",
line: "373 373 224 45 1.50 4.13 2554 714",
engineNames: []string{},
friendlyNames: []string{},
powerIndex: 4,
preEngineCols: 8,
wantPowerGPU: 1.50,
wantEngines: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gm := &GPUManager{}
sample := gm.parseIntelData(tt.line, tt.engineNames, tt.friendlyNames, tt.powerIndex, tt.preEngineCols)
assert.Equal(t, tt.wantPowerGPU, sample.PowerGPU)
assert.Equal(t, tt.wantEngines, sample.Engines)
})
}
}

View File

@@ -3,13 +3,80 @@ package agent
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"path"
"strings" "strings"
"time" "time"
"github.com/henrygd/beszel/agent/deltatracker"
"github.com/henrygd/beszel/internal/entities/system" "github.com/henrygd/beszel/internal/entities/system"
psutilNet "github.com/shirou/gopsutil/v4/net" psutilNet "github.com/shirou/gopsutil/v4/net"
) )
var netInterfaceDeltaTracker = deltatracker.NewDeltaTracker[string, uint64]()
// NicConfig controls inclusion/exclusion of network interfaces via the NICS env var
//
// Behavior mirrors SensorConfig's matching logic:
// - Leading '-' means blacklist mode; otherwise whitelist mode
// - Supports '*' wildcards using path.Match
// - In whitelist mode with an empty list, no NICs are selected
// - In blacklist mode with an empty list, all NICs are selected
type NicConfig struct {
nics map[string]struct{}
isBlacklist bool
hasWildcards bool
}
func newNicConfig(nicsEnvVal string) *NicConfig {
cfg := &NicConfig{
nics: make(map[string]struct{}),
}
if strings.HasPrefix(nicsEnvVal, "-") {
cfg.isBlacklist = true
nicsEnvVal = nicsEnvVal[1:]
}
for nic := range strings.SplitSeq(nicsEnvVal, ",") {
nic = strings.TrimSpace(nic)
if nic != "" {
cfg.nics[nic] = struct{}{}
if strings.Contains(nic, "*") {
cfg.hasWildcards = true
}
}
}
return cfg
}
// isValidNic determines if a NIC should be included based on NicConfig rules
func isValidNic(nicName string, cfg *NicConfig) bool {
// Empty list behavior differs by mode: blacklist: allow all; whitelist: allow none
if len(cfg.nics) == 0 {
return cfg.isBlacklist
}
// Exact match: return true if whitelist, false if blacklist
if _, exactMatch := cfg.nics[nicName]; exactMatch {
return !cfg.isBlacklist
}
// If no wildcards, return true if blacklist, false if whitelist
if !cfg.hasWildcards {
return cfg.isBlacklist
}
// Check for wildcard patterns
for pattern := range cfg.nics {
if !strings.Contains(pattern, "*") {
continue
}
if match, _ := path.Match(pattern, nicName); match {
return !cfg.isBlacklist
}
}
return cfg.isBlacklist
}
func (a *Agent) updateNetworkStats(systemStats *system.Stats) { func (a *Agent) updateNetworkStats(systemStats *system.Stats) {
// network stats // network stats
if len(a.netInterfaces) == 0 { if len(a.netInterfaces) == 0 {
@@ -40,10 +107,14 @@ func (a *Agent) updateNetworkStats(systemStats *system.Stats) {
totalBytesRecv += v.BytesRecv totalBytesRecv += v.BytesRecv
// track deltas for each network interface // track deltas for each network interface
netInterfaceDeltaTracker.Set(fmt.Sprintf("%sdown", v.Name), v.BytesRecv) var upDelta, downDelta uint64
netInterfaceDeltaTracker.Set(fmt.Sprintf("%sup", v.Name), v.BytesSent) upKey, downKey := fmt.Sprintf("%sup", v.Name), fmt.Sprintf("%sdown", v.Name)
upDelta := netInterfaceDeltaTracker.Delta(fmt.Sprintf("%sup", v.Name)) * 1000 / msElapsed netInterfaceDeltaTracker.Set(upKey, v.BytesSent)
downDelta := netInterfaceDeltaTracker.Delta(fmt.Sprintf("%sdown", v.Name)) * 1000 / msElapsed netInterfaceDeltaTracker.Set(downKey, v.BytesRecv)
if msElapsed > 0 {
upDelta = netInterfaceDeltaTracker.Delta(upKey) * 1000 / msElapsed
downDelta = netInterfaceDeltaTracker.Delta(downKey) * 1000 / msElapsed
}
// add interface to systemStats // add interface to systemStats
systemStats.NetworkInterfaces[v.Name] = [4]uint64{upDelta, downDelta, v.BytesSent, v.BytesRecv} systemStats.NetworkInterfaces[v.Name] = [4]uint64{upDelta, downDelta, v.BytesSent, v.BytesRecv}
} }
@@ -82,14 +153,11 @@ func (a *Agent) initializeNetIoStats() {
// reset valid network interfaces // reset valid network interfaces
a.netInterfaces = make(map[string]struct{}, 0) a.netInterfaces = make(map[string]struct{}, 0)
// map of network interface names passed in via NICS env var // parse NICS env var for whitelist / blacklist
var nicsMap map[string]struct{} nicsEnvVal, nicsEnvExists := GetEnv("NICS")
nics, nicsEnvExists := GetEnv("NICS") var nicCfg *NicConfig
if nicsEnvExists { if nicsEnvExists {
nicsMap = make(map[string]struct{}, 0) nicCfg = newNicConfig(nicsEnvVal)
for nic := range strings.SplitSeq(nics, ",") {
nicsMap[nic] = struct{}{}
}
} }
// reset network I/O stats // reset network I/O stats
@@ -100,17 +168,11 @@ func (a *Agent) initializeNetIoStats() {
if netIO, err := psutilNet.IOCounters(true); err == nil { if netIO, err := psutilNet.IOCounters(true); err == nil {
a.netIoStats.Time = time.Now() a.netIoStats.Time = time.Now()
for _, v := range netIO { for _, v := range netIO {
switch { if nicsEnvExists && !isValidNic(v.Name, nicCfg) {
// skip if nics exists and the interface is not in the list continue
case nicsEnvExists: }
if _, nameInNics := nicsMap[v.Name]; !nameInNics { if a.skipNetworkInterface(v) {
continue continue
}
// otherwise run the interface name through the skipNetworkInterface function
default:
if a.skipNetworkInterface(v) {
continue
}
} }
slog.Info("Detected network interface", "name", v.Name, "sent", v.BytesSent, "recv", v.BytesRecv) slog.Info("Detected network interface", "name", v.Name, "sent", v.BytesSent, "recv", v.BytesRecv)
a.netIoStats.BytesSent += v.BytesSent a.netIoStats.BytesSent += v.BytesSent
@@ -128,6 +190,7 @@ func (a *Agent) skipNetworkInterface(v psutilNet.IOCountersStat) bool {
strings.HasPrefix(v.Name, "br-"), strings.HasPrefix(v.Name, "br-"),
strings.HasPrefix(v.Name, "veth"), strings.HasPrefix(v.Name, "veth"),
strings.HasPrefix(v.Name, "bond"), strings.HasPrefix(v.Name, "bond"),
strings.HasPrefix(v.Name, "cali"),
v.BytesRecv == 0, v.BytesRecv == 0,
v.BytesSent == 0: v.BytesSent == 0:
return true return true

259
agent/network_test.go Normal file
View File

@@ -0,0 +1,259 @@
//go:build testing
package agent
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIsValidNic(t *testing.T) {
tests := []struct {
name string
nicName string
config *NicConfig
expectedValid bool
}{
{
name: "Whitelist - NIC in list",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: false,
},
expectedValid: true,
},
{
name: "Whitelist - NIC not in list",
nicName: "wlan0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: false,
},
expectedValid: false,
},
{
name: "Blacklist - NIC in list",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: true,
},
expectedValid: false,
},
{
name: "Blacklist - NIC not in list",
nicName: "wlan0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: true,
},
expectedValid: true,
},
{
name: "Whitelist with wildcard - matching pattern",
nicName: "eth1",
config: &NicConfig{
nics: map[string]struct{}{"eth*": {}},
isBlacklist: false,
hasWildcards: true,
},
expectedValid: true,
},
{
name: "Whitelist with wildcard - non-matching pattern",
nicName: "wlan0",
config: &NicConfig{
nics: map[string]struct{}{"eth*": {}},
isBlacklist: false,
hasWildcards: true,
},
expectedValid: false,
},
{
name: "Blacklist with wildcard - matching pattern",
nicName: "eth1",
config: &NicConfig{
nics: map[string]struct{}{"eth*": {}},
isBlacklist: true,
hasWildcards: true,
},
expectedValid: false,
},
{
name: "Blacklist with wildcard - non-matching pattern",
nicName: "wlan0",
config: &NicConfig{
nics: map[string]struct{}{"eth*": {}},
isBlacklist: true,
hasWildcards: true,
},
expectedValid: true,
},
{
name: "Empty whitelist config - no NICs allowed",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{},
isBlacklist: false,
},
expectedValid: false,
},
{
name: "Empty blacklist config - all NICs allowed",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{},
isBlacklist: true,
},
expectedValid: true,
},
{
name: "Multiple patterns - exact match",
nicName: "eth0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan*": {}},
isBlacklist: false,
},
expectedValid: true,
},
{
name: "Multiple patterns - wildcard match",
nicName: "wlan1",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan*": {}},
isBlacklist: false,
hasWildcards: true,
},
expectedValid: true,
},
{
name: "Multiple patterns - no match",
nicName: "bond0",
config: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan*": {}},
isBlacklist: false,
hasWildcards: true,
},
expectedValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isValidNic(tt.nicName, tt.config)
assert.Equal(t, tt.expectedValid, result)
})
}
}
func TestNewNicConfig(t *testing.T) {
tests := []struct {
name string
nicsEnvVal string
expectedCfg *NicConfig
}{
{
name: "Empty string",
nicsEnvVal: "",
expectedCfg: &NicConfig{
nics: map[string]struct{}{},
isBlacklist: false,
hasWildcards: false,
},
},
{
name: "Single NIC whitelist",
nicsEnvVal: "eth0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}},
isBlacklist: false,
hasWildcards: false,
},
},
{
name: "Multiple NICs whitelist",
nicsEnvVal: "eth0,wlan0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan0": {}},
isBlacklist: false,
hasWildcards: false,
},
},
{
name: "Blacklist mode",
nicsEnvVal: "-eth0,wlan0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan0": {}},
isBlacklist: true,
hasWildcards: false,
},
},
{
name: "With wildcards",
nicsEnvVal: "eth*,wlan0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth*": {}, "wlan0": {}},
isBlacklist: false,
hasWildcards: true,
},
},
{
name: "Blacklist with wildcards",
nicsEnvVal: "-eth*,wlan0",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth*": {}, "wlan0": {}},
isBlacklist: true,
hasWildcards: true,
},
},
{
name: "With whitespace",
nicsEnvVal: "eth0, wlan0 , eth1",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "wlan0": {}, "eth1": {}},
isBlacklist: false,
hasWildcards: false,
},
},
{
name: "Only wildcards",
nicsEnvVal: "eth*,wlan*",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth*": {}, "wlan*": {}},
isBlacklist: false,
hasWildcards: true,
},
},
{
name: "Leading dash only",
nicsEnvVal: "-",
expectedCfg: &NicConfig{
nics: map[string]struct{}{},
isBlacklist: true,
hasWildcards: false,
},
},
{
name: "Mixed exact and wildcard",
nicsEnvVal: "eth0,br-*",
expectedCfg: &NicConfig{
nics: map[string]struct{}{"eth0": {}, "br-*": {}},
isBlacklist: false,
hasWildcards: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := newNicConfig(tt.nicsEnvVal)
require.NotNil(t, cfg)
assert.Equal(t, tt.expectedCfg.isBlacklist, cfg.isBlacklist)
assert.Equal(t, tt.expectedCfg.hasWildcards, cfg.hasWildcards)
assert.Equal(t, tt.expectedCfg.nics, cfg.nics)
})
}
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/henrygd/beszel" "github.com/henrygd/beszel"
"github.com/henrygd/beszel/agent/battery" "github.com/henrygd/beszel/agent/battery"
"github.com/henrygd/beszel/agent/deltatracker"
"github.com/henrygd/beszel/internal/entities/system" "github.com/henrygd/beszel/internal/entities/system"
"github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/cpu"
@@ -21,8 +20,6 @@ import (
"github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/mem"
) )
var netInterfaceDeltaTracker = deltatracker.NewDeltaTracker[string, uint64]()
// Sets initial / non-changing values about the host system // Sets initial / non-changing values about the host system
func (a *Agent) initializeSystemInfo() { func (a *Agent) initializeSystemInfo() {
a.systemInfo.AgentVersion = beszel.Version a.systemInfo.AgentVersion = beszel.Version
@@ -34,7 +31,7 @@ func (a *Agent) initializeSystemInfo() {
a.systemInfo.KernelVersion = version a.systemInfo.KernelVersion = version
a.systemInfo.Os = system.Darwin a.systemInfo.Os = system.Darwin
} else if strings.Contains(platform, "indows") { } else if strings.Contains(platform, "indows") {
a.systemInfo.KernelVersion = strings.Replace(platform, "Microsoft ", "", 1) + " " + version a.systemInfo.KernelVersion = fmt.Sprintf("%s %s", strings.Replace(platform, "Microsoft ", "", 1), version)
a.systemInfo.Os = system.Windows a.systemInfo.Os = system.Windows
} else if platform == "freebsd" { } else if platform == "freebsd" {
a.systemInfo.Os = system.Freebsd a.systemInfo.Os = system.Freebsd
@@ -103,14 +100,19 @@ func (a *Agent) getSystemStats() system.Stats {
systemStats.Swap = bytesToGigabytes(v.SwapTotal) systemStats.Swap = bytesToGigabytes(v.SwapTotal)
systemStats.SwapUsed = bytesToGigabytes(v.SwapTotal - v.SwapFree - v.SwapCached) systemStats.SwapUsed = bytesToGigabytes(v.SwapTotal - v.SwapFree - v.SwapCached)
// cache + buffers value for default mem calculation // cache + buffers value for default mem calculation
cacheBuff := v.Total - v.Free - v.Used // note: gopsutil automatically adds SReclaimable to v.Cached
// htop memory calculation overrides cacheBuff := v.Cached + v.Buffers - v.Shared
// htop memory calculation overrides (likely outdated as of mid 2025)
if a.memCalc == "htop" { if a.memCalc == "htop" {
// note: gopsutil automatically adds SReclaimable to v.Cached // cacheBuff = v.Cached + v.Buffers - v.Shared
cacheBuff = v.Cached + v.Buffers - v.Shared
v.Used = v.Total - (v.Free + cacheBuff) v.Used = v.Total - (v.Free + cacheBuff)
v.UsedPercent = float64(v.Used) / float64(v.Total) * 100.0 v.UsedPercent = float64(v.Used) / float64(v.Total) * 100.0
} }
// if a.memCalc == "legacy" {
// v.Used = v.Total - v.Free - v.Buffers - v.Cached
// cacheBuff = v.Total - v.Free - v.Used
// v.UsedPercent = float64(v.Used) / float64(v.Total) * 100.0
// }
// subtract ZFS ARC size from used memory and add as its own category // subtract ZFS ARC size from used memory and add as its own category
if a.zfs { if a.zfs {
if arcSize, _ := getARCSize(); arcSize > 0 && arcSize < v.Used { if arcSize, _ := getARCSize(); arcSize > 0 && arcSize < v.Used {
@@ -215,6 +217,7 @@ func (a *Agent) getSystemStats() system.Stats {
} }
// update base system info // update base system info
a.systemInfo.ConnectionType = a.connectionManager.ConnectionType
a.systemInfo.Cpu = systemStats.Cpu a.systemInfo.Cpu = systemStats.Cpu
a.systemInfo.LoadAvg = systemStats.LoadAvg a.systemInfo.LoadAvg = systemStats.LoadAvg
// TODO: remove these in future release in favor of load avg array // TODO: remove these in future release in favor of load avg array

View File

@@ -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.12.8" Version = "0.12.11"
// AppName is the name of the application. // AppName is the name of the application.
AppName = "beszel" AppName = "beszel"
) )

38
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/henrygd/beszel
go 1.25.1 go 1.25.1
// lock shoutrrr to specific version to allow review before updating // lock shoutrrr to specific version to allow review before updating
replace github.com/nicholas-fedor/shoutrrr => github.com/nicholas-fedor/shoutrrr v0.8.8 replace github.com/nicholas-fedor/shoutrrr => github.com/nicholas-fedor/shoutrrr v0.9.1
require ( require (
github.com/blang/semver v3.5.1+incompatible github.com/blang/semver v3.5.1+incompatible
@@ -12,16 +12,16 @@ require (
github.com/gliderlabs/ssh v0.3.8 github.com/gliderlabs/ssh v0.3.8
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lxzan/gws v1.8.9 github.com/lxzan/gws v1.8.9
github.com/nicholas-fedor/shoutrrr v0.8.17 github.com/nicholas-fedor/shoutrrr v0.9.1
github.com/pocketbase/dbx v1.11.0 github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/pocketbase v0.29.3 github.com/pocketbase/pocketbase v0.30.0
github.com/shirou/gopsutil/v4 v4.25.6 github.com/shirou/gopsutil/v4 v4.25.8
github.com/spf13/cast v1.9.2 github.com/spf13/cast v1.10.0
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.7 github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.0 github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.41.0 golang.org/x/crypto v0.42.0
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b golang.org/x/exp v0.0.0-20250911091902-df9299821621
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -33,9 +33,9 @@ require (
github.com/dolthub/maphash v0.1.0 // indirect github.com/dolthub/maphash v0.1.0 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.8.4 // indirect github.com/ebitengine/purego v0.9.0 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/ganigeorgiev/fexpr v0.5.0 // indirect github.com/ganigeorgiev/fexpr v0.5.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
@@ -43,7 +43,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d // indirect github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
@@ -54,12 +54,12 @@ require (
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/image v0.30.0 // indirect golang.org/x/image v0.31.0 // indirect
golang.org/x/net v0.43.0 // indirect golang.org/x/net v0.44.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect
golang.org/x/sync v0.16.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.35.0 // indirect golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.28.0 // indirect golang.org/x/text v0.29.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
howett.net/plist v1.0.1 // indirect howett.net/plist v1.0.1 // indirect
modernc.org/libc v1.66.3 // indirect modernc.org/libc v1.66.3 // indirect

128
go.sum
View File

@@ -1,5 +1,7 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
@@ -21,22 +23,22 @@ github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCO
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk= github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@@ -52,14 +54,14 @@ github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
@@ -67,8 +69,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d h1:vFzYZc8yji+9DmNRhpEbs8VBK4CgV/DPfGzeVJSSp/8= github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
github.com/lufia/plan9stats v0.0.0-20250821153705-5981dea3221d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM= github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM=
github.com/lxzan/gws v1.8.9/go.mod h1:d9yHaR1eDTBHagQC6KY7ycUOaz5KWeqQtP3xu7aMK8Y= github.com/lxzan/gws v1.8.9/go.mod h1:d9yHaR1eDTBHagQC6KY7ycUOaz5KWeqQtP3xu7aMK8Y=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
@@ -77,19 +79,19 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nicholas-fedor/shoutrrr v0.8.8 h1:F/oyoatWK5cbHPPgkjRZrA0262TP7KWuUQz9KskRtR8= github.com/nicholas-fedor/shoutrrr v0.9.1 h1:SEBhM6P1favzILO0f55CY3P9JwvM9RZ7B1ZMCl+Injs=
github.com/nicholas-fedor/shoutrrr v0.8.8/go.mod h1:T30Y+eoZFEjDk4HtOItcHQioZSOe3Z6a6aNfSz6jc5c= github.com/nicholas-fedor/shoutrrr v0.9.1/go.mod h1:khue5m8LYyMzdPWuJxDTJeT89l9gjwjA+a+r0e8qxxk=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
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.29.3 h1:Mj8o5awsbVJIdIoTuQNhfC2oL/c4aImQ3RyfFZlzFVg= github.com/pocketbase/pocketbase v0.30.0 h1:7v9O3hBYyHyptnnFjdP8tEJIuyHEfjhG6PC4gjf5eoE=
github.com/pocketbase/pocketbase v0.29.3/go.mod h1:oGpT67LObxCFK4V2fSL7J9YnPbBnnshOpJ5v3zcneww= github.com/pocketbase/pocketbase v0.30.0/go.mod h1:gZIwampw4VqMcEdGHwBZgSa54xWIDgVJb4uINUMXLmA=
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=
@@ -97,19 +99,19 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI=
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
@@ -120,42 +122,44 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -165,18 +169,20 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= modernc.org/cc/v4 v4.26.4 h1:jPhG8oNjtTYuP2FA4YefTJ/wioNUGALmGuEWt7SUR6s=
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= modernc.org/fileutil v1.3.28 h1:Vp156KUA2nPu9F1NEv036x9UGOjg2qsi5QlWTjZmtMk=
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/fileutil v1.3.28/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
modernc.org/libc v1.66.9 h1:YkHp7E1EWrN2iyNav7JE/nHasmshPvlGkon1VxGqOw0=
modernc.org/libc v1.66.9/go.mod h1:aVdcY7udcawRqauu0HukYYxtBSizV+R80n/6aQe9D5k=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=

View File

@@ -0,0 +1,25 @@
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
WORKDIR /app
COPY ../go.mod ../go.sum ./
RUN go mod download
# Copy source files
COPY . ./
# Build
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
# --------------------------
# Final image
# Note: must cap_add: [CAP_PERFMON] and mount /dev/dri/ as volume
# --------------------------
FROM alpine:edge
COPY --from=builder /agent /agent
RUN apk add --no-cache -X https://dl-cdn.alpinelinux.org/alpine/edge/testing igt-gpu-tools
ENTRYPOINT ["/agent"]

View File

@@ -45,13 +45,14 @@ type Stats struct {
} }
type GPUData struct { type GPUData struct {
Name string `json:"n" cbor:"0,keyasint"` Name string `json:"n" cbor:"0,keyasint"`
Temperature float64 `json:"-"` Temperature float64 `json:"-"`
MemoryUsed float64 `json:"mu,omitempty" cbor:"1,keyasint,omitempty"` MemoryUsed float64 `json:"mu,omitempty,omitzero" cbor:"1,keyasint,omitempty,omitzero"`
MemoryTotal float64 `json:"mt,omitempty" cbor:"2,keyasint,omitempty"` MemoryTotal float64 `json:"mt,omitempty,omitzero" cbor:"2,keyasint,omitempty,omitzero"`
Usage float64 `json:"u" cbor:"3,keyasint"` Usage float64 `json:"u" cbor:"3,keyasint,omitempty"`
Power float64 `json:"p,omitempty" cbor:"4,keyasint,omitempty"` Power float64 `json:"p,omitempty" cbor:"4,keyasint,omitempty"`
Count float64 `json:"-"` Count float64 `json:"-"`
Engines map[string]float64 `json:"e,omitempty" cbor:"5,keyasint,omitempty"`
} }
type FsStats struct { type FsStats struct {
@@ -84,6 +85,14 @@ const (
Freebsd Freebsd
) )
type ConnectionType = uint8
const (
ConnectionTypeNone ConnectionType = iota
ConnectionTypeSSH
ConnectionTypeWebSocket
)
type Info struct { type Info struct {
Hostname string `json:"h" cbor:"0,keyasint"` Hostname string `json:"h" cbor:"0,keyasint"`
KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"` KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"`
@@ -105,7 +114,8 @@ type Info struct {
LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"` LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"`
BandwidthBytes uint64 `json:"bb" cbor:"18,keyasint"` BandwidthBytes uint64 `json:"bb" cbor:"18,keyasint"`
// TODO: remove load fields in future release in favor of load avg array // TODO: remove load fields in future release in favor of load avg array
LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"` LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"`
ConnectionType ConnectionType `json:"ct,omitempty" cbor:"20,keyasint,omitempty,omitzero"`
} }
// Final data structure to return to the hub // Final data structure to return to the hub

View File

@@ -22,6 +22,12 @@ func Update(cmd *cobra.Command, _ []string) {
// Check if china-mirrors flag is set // Check if china-mirrors flag is set
useMirror, _ := cmd.Flags().GetBool("china-mirrors") useMirror, _ := cmd.Flags().GetBool("china-mirrors")
// Get the executable path before update
exePath, err := os.Executable()
if err != nil {
log.Fatal(err)
}
updated, err := ghupdate.Update(ghupdate.Config{ updated, err := ghupdate.Update(ghupdate.Config{
ArchiveExecutable: "beszel", ArchiveExecutable: "beszel",
DataDir: dataDir, DataDir: dataDir,
@@ -35,11 +41,8 @@ func Update(cmd *cobra.Command, _ []string) {
} }
// make sure the file is executable // make sure the file is executable
exePath, err := os.Executable() if err := os.Chmod(exePath, 0755); err != nil {
if err == nil { fmt.Printf("Warning: failed to set executable permissions: %v\n", err)
if err := os.Chmod(exePath, 0755); err != nil {
fmt.Printf("Warning: failed to set executable permissions: %v\n", err)
}
} }
// Try to restart the service if it's running // Try to restart the service if it's running

View File

@@ -0,0 +1,50 @@
package migrations
import (
"github.com/henrygd/beszel/internal/entities/system"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
// This can be deleted after Nov 2025 or so
func init() {
m.Register(func(app core.App) error {
app.RunInTransaction(func(txApp core.App) error {
var systemIds []string
txApp.DB().NewQuery("SELECT id FROM systems").Column(&systemIds)
for _, systemId := range systemIds {
var statRecordIds []string
txApp.DB().NewQuery("SELECT id FROM system_stats WHERE system = {:system} AND created > {:created}").Bind(map[string]any{"system": systemId, "created": "2025-09-21"}).Column(&statRecordIds)
for _, statRecordId := range statRecordIds {
statRecord, err := txApp.FindRecordById("system_stats", statRecordId)
if err != nil {
return err
}
var systemStats system.Stats
err = statRecord.UnmarshalJSONField("stats", &systemStats)
if err != nil {
return err
}
// if mem buff cache is less than total mem, we don't need to fix it
if systemStats.MemBuffCache < systemStats.Mem {
continue
}
systemStats.MemBuffCache = 0
statRecord.Set("stats", systemStats)
err = txApp.SaveNoValidate(statRecord)
if err != nil {
return err
}
}
}
return nil
})
return nil
}, func(app core.App) error {
return nil
})
}

View File

@@ -284,6 +284,16 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
gpu.Usage += value.Usage gpu.Usage += value.Usage
gpu.Power += value.Power gpu.Power += value.Power
gpu.Count += value.Count gpu.Count += value.Count
if value.Engines != nil {
if gpu.Engines == nil {
gpu.Engines = make(map[string]float64, len(value.Engines))
}
for engineKey, engineValue := range value.Engines {
gpu.Engines[engineKey] += engineValue
}
}
sum.GPUData[id] = gpu sum.GPUData[id] = gpu
} }
} }
@@ -353,6 +363,13 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
gpu.Usage = twoDecimals(gpu.Usage / count) gpu.Usage = twoDecimals(gpu.Usage / count)
gpu.Power = twoDecimals(gpu.Power / count) gpu.Power = twoDecimals(gpu.Power / count)
gpu.Count = twoDecimals(gpu.Count / count) gpu.Count = twoDecimals(gpu.Count / count)
if gpu.Engines != nil {
for engineKey := range gpu.Engines {
gpu.Engines[engineKey] = twoDecimals(gpu.Engines[engineKey] / count)
}
}
sum.GPUData[id] = gpu sum.GPUData[id] = gpu
} }
} }

View File

@@ -1,12 +1,12 @@
{ {
"name": "beszel", "name": "beszel",
"version": "0.12.8", "version": "0.12.11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "beszel", "name": "beszel",
"version": "0.12.8", "version": "0.12.11",
"dependencies": { "dependencies": {
"@henrygd/queue": "^1.0.7", "@henrygd/queue": "^1.0.7",
"@henrygd/semaphore": "^0.0.2", "@henrygd/semaphore": "^0.0.2",

View File

@@ -1,7 +1,7 @@
{ {
"name": "beszel", "name": "beszel",
"private": true, "private": true,
"version": "0.12.8", "version": "0.12.11",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",

View File

@@ -40,7 +40,7 @@ export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData
} }
const keys = Object.keys(powerSums).sort((a, b) => powerSums[b] - powerSums[a]) const keys = Object.keys(powerSums).sort((a, b) => powerSums[b] - powerSums[a])
for (const key of keys) { for (const key of keys) {
newChartData.colors[key] = `hsl(${((keys.indexOf(key) * 360) / keys.length) % 360}, 60%, 55%)` newChartData.colors[key] = `hsl(${(226 + (keys.indexOf(key) * 360) / keys.length) % 360}, 60%, 55%)`
} }
return newChartData return newChartData
}, [chartData]) }, [chartData])

View File

@@ -115,11 +115,11 @@ export function useNetworkInterfaces(interfaces: SystemStats["ni"]) {
data: (index = 3) => { data: (index = 3) => {
return sortedKeys.map((key) => ({ return sortedKeys.map((key) => ({
label: key, label: key,
dataKey: (stats: SystemStatsRecord) => stats.stats?.ni?.[key]?.[index], dataKey: ({ stats }: SystemStatsRecord) => stats?.ni?.[key]?.[index],
color: `hsl(${220 + (((sortedKeys.indexOf(key) * 360) / sortedKeys.length) % 360)}, 70%, 50%)`, color: `hsl(${220 + (((sortedKeys.indexOf(key) * 360) / sortedKeys.length) % 360)}, 70%, 50%)`,
opacity: 0.3, opacity: 0.3,
})) }))
}, },
} }
} }

View File

@@ -3,7 +3,15 @@ import { Plural, Trans, useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router" import { getPagePath } from "@nanostores/router"
import { timeTicks } from "d3-time" import { timeTicks } from "d3-time"
import { ClockArrowUp, CpuIcon, GlobeIcon, LayoutGridIcon, MonitorIcon, XIcon } from "lucide-react" import {
ChevronRightSquareIcon,
ClockArrowUp,
CpuIcon,
GlobeIcon,
LayoutGridIcon,
MonitorIcon,
XIcon,
} from "lucide-react"
import { subscribeKeys } from "nanostores" import { subscribeKeys } from "nanostores"
import React, { type JSX, memo, useCallback, useEffect, useMemo, useRef, useState } from "react" import React, { type JSX, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import AreaChartDefault from "@/components/charts/area-chart" import AreaChartDefault from "@/components/charts/area-chart"
@@ -16,7 +24,7 @@ import MemChart from "@/components/charts/mem-chart"
import SwapChart from "@/components/charts/swap-chart" import SwapChart from "@/components/charts/swap-chart"
import TemperatureChart from "@/components/charts/temperature-chart" import TemperatureChart from "@/components/charts/temperature-chart"
import { getPbTimestamp, pb } from "@/lib/api" import { getPbTimestamp, pb } from "@/lib/api"
import { ChartType, 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 {
$allSystemsByName, $allSystemsByName,
@@ -47,12 +55,13 @@ import { $router, navigate } from "../router"
import Spinner from "../spinner" import Spinner from "../spinner"
import { Button } from "../ui/button" import { Button } from "../ui/button"
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card" import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { AppleIcon, ChartAverage, ChartMax, FreeBsdIcon, Rows, TuxIcon, WindowsIcon } from "../ui/icons" import { AppleIcon, ChartAverage, ChartMax, FreeBsdIcon, Rows, TuxIcon, WebSocketIcon, WindowsIcon } from "../ui/icons"
import { Input } from "../ui/input" import { Input } from "../ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
import { Separator } from "../ui/separator" import { Separator } from "../ui/separator"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
import NetworkSheet from "./system/network-sheet" import NetworkSheet from "./system/network-sheet"
import LineChartDefault from "../charts/line-chart"
type ChartTimeData = { type ChartTimeData = {
time: number time: number
@@ -130,7 +139,7 @@ async function getStats<T extends SystemStatsRecord | ContainerStatsRecord>(
function dockerOrPodman(str: string, system: SystemRecord) { function dockerOrPodman(str: string, system: SystemRecord) {
if (system.info.p) { if (system.info.p) {
str = str.replace("docker", "podman").replace("Docker", "Podman") return str.replace("docker", "podman").replace("Docker", "Podman")
} }
return str return str
} }
@@ -390,6 +399,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
const lastGpuVals = Object.values(systemStats.at(-1)?.stats.g ?? {}) const lastGpuVals = Object.values(systemStats.at(-1)?.stats.g ?? {})
const hasGpuData = lastGpuVals.length > 0 const hasGpuData = lastGpuVals.length > 0
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined) const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined)
const hasGpuEnginesData = lastGpuVals.some((gpu) => gpu.e !== undefined)
let translatedStatus: string = system.status let translatedStatus: string = system.status
if (system.status === SystemStatus.Up) { if (system.status === SystemStatus.Up) {
@@ -407,25 +417,44 @@ export default memo(function SystemDetail({ name }: { name: string }) {
<div> <div>
<h1 className="text-[1.6rem] font-semibold mb-1.5">{system.name}</h1> <h1 className="text-[1.6rem] font-semibold mb-1.5">{system.name}</h1>
<div className="flex flex-wrap items-center gap-3 gap-y-2 text-sm opacity-90"> <div className="flex flex-wrap items-center gap-3 gap-y-2 text-sm opacity-90">
<div className="capitalize flex gap-2 items-center"> <TooltipProvider>
<span className={cn("relative flex h-3 w-3")}> <Tooltip>
{system.status === SystemStatus.Up && ( <TooltipTrigger asChild>
<span <div className="capitalize flex gap-2 items-center">
className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" <span className={cn("relative flex h-3 w-3")}>
style={{ animationDuration: "1.5s" }} {system.status === SystemStatus.Up && (
></span> <span
className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
style={{ animationDuration: "1.5s" }}
></span>
)}
<span
className={cn("relative inline-flex rounded-full h-3 w-3", {
"bg-green-500": system.status === SystemStatus.Up,
"bg-red-500": system.status === SystemStatus.Down,
"bg-primary/40": system.status === SystemStatus.Paused,
"bg-yellow-500": system.status === SystemStatus.Pending,
})}
></span>
</span>
{translatedStatus}
</div>
</TooltipTrigger>
{system.info.ct && (
<TooltipContent>
<div className="flex gap-1 items-center">
{system.info.ct === ConnectionType.WebSocket ? (
<WebSocketIcon className="size-4" />
) : (
<ChevronRightSquareIcon className="size-4" strokeWidth={2} />
)}
{connectionTypeLabels[system.info.ct as ConnectionType]}
</div>
</TooltipContent>
)} )}
<span </Tooltip>
className={cn("relative inline-flex rounded-full h-3 w-3", { </TooltipProvider>
"bg-green-500": system.status === SystemStatus.Up,
"bg-red-500": system.status === SystemStatus.Down,
"bg-primary/40": system.status === SystemStatus.Paused,
"bg-yellow-500": system.status === SystemStatus.Pending,
})}
></span>
</span>
{translatedStatus}
</div>
{systemInfo.map(({ value, label, Icon, hide }) => { {systemInfo.map(({ value, label, Icon, hide }) => {
if (hide || !value) { if (hide || !value) {
return null return null
@@ -730,7 +759,6 @@ export default memo(function SystemDetail({ name }: { name: string }) {
/> />
</ChartCard> </ChartCard>
)} )}
{/* GPU power draw chart */} {/* GPU power draw chart */}
{hasGpuPowerData && ( {hasGpuPowerData && (
<ChartCard <ChartCard
@@ -744,14 +772,26 @@ export default memo(function SystemDetail({ name }: { name: string }) {
)} )}
</div> </div>
{/* GPU charts */} {/* Non-power GPU charts */}
{hasGpuData && ( {hasGpuData && (
<div className="grid xl:grid-cols-2 gap-4"> <div className="grid xl:grid-cols-2 gap-4">
{hasGpuEnginesData && (
<ChartCard
legend={true}
empty={dataEmpty}
grid={grid}
title={t`GPU Engines`}
description={t`Average utilization of GPU engines`}
>
<GpuEnginesChart chartData={chartData} />
</ChartCard>
)}
{Object.keys(systemStats.at(-1)?.stats.g ?? {}).map((id) => { {Object.keys(systemStats.at(-1)?.stats.g ?? {}).map((id) => {
const gpu = systemStats.at(-1)?.stats.g?.[id] as GPUData const gpu = systemStats.at(-1)?.stats.g?.[id] as GPUData
return ( return (
<div key={id} className="contents"> <div key={id} className="contents">
<ChartCard <ChartCard
className="!col-span-1"
empty={dataEmpty} empty={dataEmpty}
grid={grid} grid={grid}
title={`${gpu.n} ${t`Usage`}`} title={`${gpu.n} ${t`Usage`}`}
@@ -771,33 +811,36 @@ export default memo(function SystemDetail({ name }: { name: string }) {
contentFormatter={({ value }) => `${decimalString(value)}%`} contentFormatter={({ value }) => `${decimalString(value)}%`}
/> />
</ChartCard> </ChartCard>
<ChartCard
empty={dataEmpty} {(gpu.mt ?? 0) > 0 && (
grid={grid} <ChartCard
title={`${gpu.n} VRAM`} empty={dataEmpty}
description={t`Precise utilization at the recorded time`} grid={grid}
> title={`${gpu.n} VRAM`}
<AreaChartDefault description={t`Precise utilization at the recorded time`}
chartData={chartData} >
dataPoints={[ <AreaChartDefault
{ chartData={chartData}
label: t`Usage`, dataPoints={[
dataKey: ({ stats }) => stats?.g?.[id]?.mu ?? 0, {
color: 2, label: t`Usage`,
opacity: 0.25, dataKey: ({ stats }) => stats?.g?.[id]?.mu ?? 0,
}, color: 2,
]} opacity: 0.25,
max={gpu.mt} },
tickFormatter={(val) => { ]}
const { value, unit } = formatBytes(val, false, Unit.Bytes, true) max={gpu.mt}
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}` tickFormatter={(val) => {
}} const { value, unit } = formatBytes(val, false, Unit.Bytes, true)
contentFormatter={({ value }) => { return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
const { value: convertedValue, unit } = formatBytes(value, false, Unit.Bytes, true) }}
return `${decimalString(convertedValue)} ${unit}` contentFormatter={({ value }) => {
}} const { value: convertedValue, unit } = formatBytes(value, false, Unit.Bytes, true)
/> return `${decimalString(convertedValue)} ${unit}`
</ChartCard> }}
/>
</ChartCard>
)}
</div> </div>
) )
})} })}
@@ -869,27 +912,47 @@ export default memo(function SystemDetail({ name }: { name: string }) {
) )
}) })
function GpuEnginesChart({ chartData }: { chartData: ChartData }) {
const dataPoints = []
const engines = Object.keys(chartData.systemStats?.at(-1)?.stats.g?.[0]?.e ?? {}).sort()
for (const engine of engines) {
dataPoints.push({
label: engine,
dataKey: ({ stats }: SystemStatsRecord) => stats?.g?.[0]?.e?.[engine] ?? 0,
color: `hsl(${140 + (((engines.indexOf(engine) * 360) / engines.length) % 360)}, 65%, 52%)`,
opacity: 0.35,
})
}
return (
<LineChartDefault
legend={true}
chartData={chartData}
dataPoints={dataPoints}
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
contentFormatter={({ value }) => `${decimalString(value)}%`}
/>
)
}
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) { function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
const containerFilter = useStore(store) const containerFilter = useStore(store)
const { t } = useLingui() const { t } = useLingui()
const inputRef = useRef<HTMLInputElement>(null)
const debouncedStoreSet = useMemo(() => debounce((value: string) => store.set(value), 150), [store]) const debouncedStoreSet = useMemo(() => debounce((value: string) => store.set(value), 80), [store])
const handleChange = useCallback( const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { (e: React.ChangeEvent<HTMLInputElement>) => debouncedStoreSet(e.target.value),
const value = e.target.value
if (inputRef.current) {
inputRef.current.value = value
}
debouncedStoreSet(value)
},
[debouncedStoreSet] [debouncedStoreSet]
) )
return ( return (
<> <>
<Input placeholder={t`Filter...`} className="ps-4 pe-8 w-full sm:w-44" onChange={handleChange} ref={inputRef} /> <Input
placeholder={t`Filter...`}
className="ps-4 pe-8 w-full sm:w-44"
onChange={handleChange}
value={containerFilter}
/>
{containerFilter && ( {containerFilter && (
<Button <Button
type="button" type="button"
@@ -897,12 +960,7 @@ function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilt
size="icon" size="icon"
aria-label="Clear" aria-label="Clear"
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100" className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
onClick={() => { onClick={() => store.set("")}
if (inputRef.current) {
inputRef.current.value = ""
}
store.set("")
}}
> >
<XIcon className="h-4 w-4" /> <XIcon className="h-4 w-4" />
</Button> </Button>
@@ -960,9 +1018,7 @@ export function ChartCard({
<CardHeader className="pb-5 pt-4 gap-1 relative max-sm:py-3 max-sm:px-4"> <CardHeader className="pb-5 pt-4 gap-1 relative max-sm:py-3 max-sm:px-4">
<CardTitle className="text-xl sm:text-2xl">{title}</CardTitle> <CardTitle className="text-xl sm:text-2xl">{title}</CardTitle>
<CardDescription>{description}</CardDescription> <CardDescription>{description}</CardDescription>
{cornerEl && ( {cornerEl && <div className="py-1 grid sm:justify-end sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>}
<div className="relative py-1 grid sm:justify-end sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>
)}
</CardHeader> </CardHeader>
<div className={cn("ps-0 w-[calc(100%-1.5em)] relative group", legend ? "h-54 md:h-56" : "h-48 md:h-52")}> <div className={cn("ps-0 w-[calc(100%-1.5em)] relative group", legend ? "h-54 md:h-56" : "h-48 md:h-52")}>
{ {

View File

@@ -7,6 +7,7 @@ import ChartTimeSelect from "@/components/charts/chart-time-select"
import { useNetworkInterfaces } from "@/components/charts/hooks" import { useNetworkInterfaces } from "@/components/charts/hooks"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet" import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { DialogTitle } from "@/components/ui/dialog"
import { $userSettings } from "@/lib/stores" import { $userSettings } from "@/lib/stores"
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils" import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types" import type { ChartData } from "@/types"
@@ -26,7 +27,7 @@ export default memo(function NetworkSheet({
const [netInterfacesOpen, setNetInterfacesOpen] = useState(false) const [netInterfacesOpen, setNetInterfacesOpen] = useState(false)
const userSettings = useStore($userSettings) const userSettings = useStore($userSettings)
const netInterfaces = useNetworkInterfaces(chartData.systemStats.at(-1)?.stats?.ni ?? {}) const netInterfaces = useNetworkInterfaces(chartData.systemStats.at(-1)?.stats?.ni ?? {})
const showNetLegend = netInterfaces.length > 0 const showNetLegend = netInterfaces.length > 0 && netInterfaces.length < 15
const hasOpened = useRef(false) const hasOpened = useRef(false)
if (netInterfacesOpen && !hasOpened.current) { if (netInterfacesOpen && !hasOpened.current) {
@@ -39,14 +40,20 @@ export default memo(function NetworkSheet({
return ( return (
<Sheet open={netInterfacesOpen} onOpenChange={setNetInterfacesOpen}> <Sheet open={netInterfacesOpen} onOpenChange={setNetInterfacesOpen}>
<DialogTitle className="sr-only">{t`Network traffic of public interfaces`}</DialogTitle>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button variant="outline" size="icon" className="shrink-0"> <Button
title={t`View more`}
variant="outline"
size="icon"
className="shrink-0 max-sm:absolute max-sm:top-3 max-sm:end-3"
>
<MoreHorizontalIcon /> <MoreHorizontalIcon />
</Button> </Button>
</SheetTrigger> </SheetTrigger>
{hasOpened.current && ( {hasOpened.current && (
<SheetContent className="overflow-auto w-200 !max-w-full p-4 sm:p-6"> <SheetContent aria-describedby={undefined} className="overflow-auto w-200 !max-w-full p-4 sm:p-6">
<ChartTimeSelect className="w-[calc(100%-1.5em)]" /> <ChartTimeSelect className="w-[calc(100%-2em)]" />
<ChartCard <ChartCard
empty={dataEmpty} empty={dataEmpty}
grid={grid} grid={grid}

View File

@@ -6,6 +6,7 @@ import type { CellContext, ColumnDef, HeaderContext } from "@tanstack/react-tabl
import type { ClassValue } from "clsx" import type { ClassValue } from "clsx"
import { import {
ArrowUpDownIcon, ArrowUpDownIcon,
ChevronRightSquareIcon,
CopyIcon, CopyIcon,
CpuIcon, CpuIcon,
HardDriveIcon, HardDriveIcon,
@@ -20,7 +21,7 @@ import {
} from "lucide-react" } from "lucide-react"
import { memo, useMemo, useRef, useState } from "react" import { memo, useMemo, useRef, useState } from "react"
import { isReadOnlyUser, pb } from "@/lib/api" import { isReadOnlyUser, pb } from "@/lib/api"
import { MeterState, SystemStatus } from "@/lib/enums" import { ConnectionType, connectionTypeLabels, MeterState, SystemStatus } from "@/lib/enums"
import { $longestSystemNameLen, $userSettings } from "@/lib/stores" import { $longestSystemNameLen, $userSettings } from "@/lib/stores"
import { import {
cn, cn,
@@ -54,7 +55,7 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "../ui/dropdown-menu" } from "../ui/dropdown-menu"
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons" import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon, WebSocketIcon } from "../ui/icons"
const STATUS_COLORS = { const STATUS_COLORS = {
[SystemStatus.Up]: "bg-green-500", [SystemStatus.Up]: "bg-green-500",
@@ -271,18 +272,31 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
return null return null
} }
const system = info.row.original const system = info.row.original
const color = {
"text-green-500": version === globalThis.BESZEL.HUB_VERSION,
"text-yellow-500": version !== globalThis.BESZEL.HUB_VERSION,
"text-red-500": system.status !== SystemStatus.Up,
}
return ( return (
<span className={cn("flex gap-1.5 items-center md:pe-5 tabular-nums", viewMode === "table" && "ps-0.5")}> <Link
<IndicatorDot href={getPagePath($router, "system", { name: system.name })}
system={system} className={cn(
className={ "flex gap-1.5 items-center md:pe-5 tabular-nums relative z-10",
(system.status !== SystemStatus.Up && STATUS_COLORS[SystemStatus.Paused]) || viewMode === "table" && "ps-0.5"
(version === globalThis.BESZEL.HUB_VERSION && STATUS_COLORS[SystemStatus.Up]) || )}
STATUS_COLORS[SystemStatus.Pending] tabIndex={-1}
} title={connectionTypeLabels[system.info.ct as ConnectionType]}
/> role="none"
>
{system.info.ct === ConnectionType.WebSocket && (
<WebSocketIcon className={cn("size-3 pointer-events-none", color)} />
)}
{system.info.ct === ConnectionType.SSH && (
<ChevronRightSquareIcon className={cn("size-3 pointer-events-none", color)} />
)}
{!system.info.ct && <IndicatorDot system={system} className={cn(color, "bg-current mx-0.5")} />}
<span className="truncate max-w-14">{info.getValue() as string}</span> <span className="truncate max-w-14">{info.getValue() as string}</span>
</span> </Link>
) )
}, },
}, },
@@ -305,10 +319,11 @@ function sortableHeader(context: HeaderContext<SystemRecord, unknown>) {
const { column } = context const { column } = context
// @ts-expect-error // @ts-expect-error
const { Icon, hideSort, name }: { Icon: React.ElementType; name: () => string; hideSort: boolean } = column.columnDef const { Icon, hideSort, name }: { Icon: React.ElementType; name: () => string; hideSort: boolean } = column.columnDef
const isSorted = column.getIsSorted()
return ( return (
<Button <Button
variant="ghost" variant="ghost"
className="h-9 px-3 flex" className={cn("h-9 px-3 flex duration-50", isSorted && "bg-accent/70 light:bg-accent text-accent-foreground/90")}
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")} onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
> >
{Icon && <Icon className="me-2 size-4" />} {Icon && <Icon className="me-2 size-4" />}

View File

@@ -337,7 +337,7 @@ const AllSystemsTable = memo(
{/* add header height to table size */} {/* add header height to table size */}
<div style={{ height: `${virtualizer.getTotalSize() + 50}px`, paddingTop, paddingBottom }}> <div style={{ height: `${virtualizer.getTotalSize() + 50}px`, paddingTop, paddingBottom }}>
<table className="text-sm w-full h-full"> <table className="text-sm w-full h-full">
<SystemsTableHead table={table} colLength={colLength} /> <SystemsTableHead table={table} />
<TableBody onMouseEnter={preloadSystemDetail}> <TableBody onMouseEnter={preloadSystemDetail}>
{rows.length ? ( {rows.length ? (
virtualRows.map((virtualRow) => { virtualRows.map((virtualRow) => {
@@ -367,26 +367,23 @@ const AllSystemsTable = memo(
} }
) )
function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>; colLength: number }) { function SystemsTableHead({ table }: { table: TableType<SystemRecord> }) {
const { i18n } = useLingui() const { t } = useLingui()
return (
return useMemo(() => { <TableHeader className="sticky top-0 z-50 w-full border-b-2">
return ( {table.getHeaderGroups().map((headerGroup) => (
<TableHeader className="sticky top-0 z-20 w-full border-b-2"> <tr key={headerGroup.id}>
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => {
<tr key={headerGroup.id}> return (
{headerGroup.headers.map((header) => { <TableHead className="px-1.5" key={header.id}>
return ( {flexRender(header.column.columnDef.header, header.getContext())}
<TableHead className="px-1.5" key={header.id}> </TableHead>
{flexRender(header.column.columnDef.header, header.getContext())} )
</TableHead> })}
) </tr>
})} ))}
</tr> </TableHeader>
))} )
</TableHeader>
)
}, [i18n.locale, colLength])
} }
const SystemTableRow = memo( const SystemTableRow = memo(

View File

@@ -130,3 +130,12 @@ export function HourglassIcon(props: SVGProps<SVGSVGElement>) {
</svg> </svg>
) )
} }
export function WebSocketIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 256 193" {...props} fill="currentColor">
<title>WebSocket</title>
<path d="M192 145h32V68l-36-35-22 22 26 27zm32 16H113l-26-27 11-11 22 22h45l-44-45 11-11 44 44V88l-21-22 11-11-55-55H0l32 32h65l24 23-34 34-24-23V48H32v31l55 55-23 22 36 36h156z" />
</svg>
)
}

View File

@@ -53,3 +53,11 @@ export enum HourFormat {
"12h" = "12h", "12h" = "12h",
"24h" = "24h", "24h" = "24h",
} }
/** Connection type */
export enum ConnectionType {
SSH = 1,
WebSocket,
}
export const connectionTypeLabels = ["", "SSH", "WebSocket"] as const

View File

@@ -179,8 +179,8 @@ export function formatTemperature(celsius: number, unit?: Unit): { value: number
if (!unit) { if (!unit) {
unit = $userSettings.get().unitTemp || Unit.Celsius unit = $userSettings.get().unitTemp || Unit.Celsius
} }
// need loose equality check due to form data being strings // biome-ignore lint/suspicious/noDoubleEquals: need loose equality check due to form data being strings
if (unit === Unit.Fahrenheit) { if (unit == Unit.Fahrenheit) {
return { return {
value: celsius * 1.8 + 32, value: celsius * 1.8 + 32,
unit: "°F", unit: "°F",
@@ -202,8 +202,8 @@ export function formatBytes(
// Convert MB to bytes if isMegabytes is true // Convert MB to bytes if isMegabytes is true
if (isMegabytes) size *= 1024 * 1024 if (isMegabytes) size *= 1024 * 1024
// need loose equality check due to form data being strings // biome-ignore lint/suspicious/noDoubleEquals: need loose equality check due to form data being strings
if (unit === Unit.Bits) { if (unit == Unit.Bits) {
const bits = size * 8 const bits = size * 8
const suffix = perSecond ? "ps" : "" const suffix = perSecond ? "ps" : ""
if (bits < 1000) return { value: bits, unit: `b${suffix}` } if (bits < 1000) return { value: bits, unit: `b${suffix}` }

View File

@@ -173,6 +173,10 @@ msgstr "متوسط استخدام وحدة المعالجة المركزية ع
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "متوسط ​​استخدام {0}" msgstr "متوسط ​​استخدام {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "متوسط استغلال محركات GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "ممتلئة"
msgid "General" msgid "General"
msgstr "عام" msgstr "عام"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "محركات GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "استهلاك طاقة وحدة معالجة الرسوميات" msgstr "استهلاك طاقة وحدة معالجة الرسوميات"
@@ -706,6 +714,7 @@ msgstr "حركة مرور الشبكة لحاويات الدوكر"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "حركة مرور الشبكة للواجهات العامة" msgstr "حركة مرور الشبكة للواجهات العامة"
@@ -1178,6 +1187,10 @@ msgstr "القيمة"
msgid "View" msgid "View"
msgstr "عرض" msgstr "عرض"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "عرض المزيد"
#: 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 "عرض أحدث 200 تنبيه." msgstr "عرض أحدث 200 تنبيه."

View File

@@ -173,6 +173,10 @@ msgstr "Средно използване на процесора на цяла
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Средно използване на {0}" msgstr "Средно използване на {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Средно използване на GPU двигатели"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Пълна"
msgid "General" msgid "General"
msgstr "Общо" msgstr "Общо"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU двигатели"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Консумация на ток от графична карта" msgstr "Консумация на ток от графична карта"
@@ -706,6 +714,7 @@ msgstr "Мрежов трафик на docker контейнери"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Мрежов трафик на публични интерфейси" msgstr "Мрежов трафик на публични интерфейси"
@@ -1178,6 +1187,10 @@ msgstr "Стойност"
msgid "View" msgid "View"
msgstr "Изглед" msgstr "Изглед"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Виж повече"
#: 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 "Прегледайте последните си 200 сигнала." msgstr "Прегледайте последните си 200 сигнала."

View File

@@ -173,6 +173,10 @@ msgstr "Průměrné využití CPU v celém systému"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Průměrné využití {0}" msgstr "Průměrné využití {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Průměrné využití GPU engine"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Plná"
msgid "General" msgid "General"
msgstr "Obecné" msgstr "Obecné"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU enginy"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Spotřeba energie GPU" msgstr "Spotřeba energie GPU"
@@ -706,6 +714,7 @@ msgstr "Síťový provoz kontejnerů docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Síťový provoz veřejných rozhraní" msgstr "Síťový provoz veřejných rozhraní"
@@ -1178,6 +1187,10 @@ msgstr "Hodnota"
msgid "View" msgid "View"
msgstr "Zobrazení" msgstr "Zobrazení"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Zobrazit více"
#: 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 "Zobrazit vašich 200 nejnovějších upozornění." msgstr "Zobrazit vašich 200 nejnovějších upozornění."

View File

@@ -173,6 +173,10 @@ msgstr "Gennemsnitlig systembaseret CPU-udnyttelse"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Gennemsnitlig udnyttelse af {0}" msgstr "Gennemsnitlig udnyttelse af {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Gennemsnitlig udnyttelse af GPU-motorer"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Fuldt opladt"
msgid "General" msgid "General"
msgstr "Generelt" msgstr "Generelt"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU-motorer"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Gpu Strøm Træk" msgstr "Gpu Strøm Træk"
@@ -706,6 +714,7 @@ msgstr "Netværkstrafik af dockercontainere"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Netværkstrafik af offentlige grænseflader" msgstr "Netværkstrafik af offentlige grænseflader"
@@ -1178,6 +1187,10 @@ msgstr ""
msgid "View" msgid "View"
msgstr "Vis" msgstr "Vis"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Se mere"
#: 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 ""

View File

@@ -173,6 +173,10 @@ msgstr "Durchschnittliche systemweite CPU-Auslastung"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Durchschnittliche Auslastung von {0}" msgstr "Durchschnittliche Auslastung von {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Durchschnittliche Auslastung der GPU-Engines"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Voll"
msgid "General" msgid "General"
msgstr "Allgemein" msgstr "Allgemein"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU-Engines"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU-Leistungsaufnahme" msgstr "GPU-Leistungsaufnahme"
@@ -706,6 +714,7 @@ msgstr "Netzwerkverkehr der Docker-Container"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Netzwerkverkehr der öffentlichen Schnittstellen" msgstr "Netzwerkverkehr der öffentlichen Schnittstellen"
@@ -1178,6 +1187,10 @@ msgstr "Wert"
msgid "View" msgid "View"
msgstr "Ansicht" msgstr "Ansicht"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Mehr anzeigen"
#: 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 "Sieh dir die neusten 200 Alarme an." msgstr "Sieh dir die neusten 200 Alarme an."

View File

@@ -168,6 +168,10 @@ msgstr "Average system-wide CPU utilization"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Average utilization of {0}" msgstr "Average utilization of {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Average utilization of GPU engines"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -571,6 +575,10 @@ msgstr "Full"
msgid "General" msgid "General"
msgstr "General" msgstr "General"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU Engines"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU Power Draw" msgstr "GPU Power Draw"
@@ -701,6 +709,7 @@ msgstr "Network traffic of docker containers"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Network traffic of public interfaces" msgstr "Network traffic of public interfaces"
@@ -1173,6 +1182,10 @@ msgstr "Value"
msgid "View" msgid "View"
msgstr "View" msgstr "View"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "View more"
#: 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 "View your 200 most recent alerts." msgstr "View your 200 most recent alerts."

View File

@@ -173,6 +173,10 @@ msgstr "Utilización promedio de CPU del sistema"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Uso promedio de {0}" msgstr "Uso promedio de {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Utilización promedio de motores GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Llena"
msgid "General" msgid "General"
msgstr "General" msgstr "General"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "Motores GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Consumo de energía de la GPU" msgstr "Consumo de energía de la GPU"
@@ -706,6 +714,7 @@ msgstr "Tráfico de red de los contenedores de Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Tráfico de red de interfaces públicas" msgstr "Tráfico de red de interfaces públicas"
@@ -1178,6 +1187,10 @@ msgstr "Valor"
msgid "View" msgid "View"
msgstr "Vista" msgstr "Vista"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Ver más"
#: 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 "Ver sus 200 alertas más recientes." msgstr "Ver sus 200 alertas más recientes."

View File

@@ -173,6 +173,10 @@ msgstr "میانگین استفاده از CPU در کل سیستم"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "میانگین استفاده از {0}" msgstr "میانگین استفاده از {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "میانگین استفاده از موتورهای GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "پر"
msgid "General" msgid "General"
msgstr "عمومی" msgstr "عمومی"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "موتورهای GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "مصرف برق پردازنده گرافیکی" msgstr "مصرف برق پردازنده گرافیکی"
@@ -706,6 +714,7 @@ msgstr "ترافیک شبکه کانتینرهای داکر"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "ترافیک شبکه رابط‌های عمومی" msgstr "ترافیک شبکه رابط‌های عمومی"
@@ -1178,6 +1187,10 @@ msgstr "مقدار"
msgid "View" msgid "View"
msgstr "مشاهده" msgstr "مشاهده"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "مشاهده بیشتر"
#: 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 "۲۰۰ هشدار اخیر خود را مشاهده کنید."

View File

@@ -173,6 +173,10 @@ msgstr "Utilisation moyenne du CPU à l'échelle du système"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Utilisation moyenne de {0}" msgstr "Utilisation moyenne de {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Utilisation moyenne des moteurs GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Pleine"
msgid "General" msgid "General"
msgstr "Général" msgstr "Général"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "Moteurs GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Consommation du GPU" msgstr "Consommation du GPU"
@@ -706,6 +714,7 @@ msgstr "Trafic réseau des conteneurs Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Trafic réseau des interfaces publiques" msgstr "Trafic réseau des interfaces publiques"
@@ -1178,6 +1187,10 @@ msgstr ""
msgid "View" msgid "View"
msgstr "Vue" msgstr "Vue"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Voir plus"
#: 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 ""

View File

@@ -8,15 +8,15 @@ msgstr ""
"Language: hr\n" "Language: hr\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-23 12:43\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Croatian\n" "Language-Team: Croatian\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: hr\n" "X-Crowdin-Language: hr\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400) #. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -46,7 +46,7 @@ msgstr "1 sat"
#. 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 minut"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 week" msgid "1 week"
@@ -59,7 +59,7 @@ msgstr "12 sati"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "15 min" msgid "15 min"
msgstr "" msgstr "15 minuta"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "24 hours" msgid "24 hours"
@@ -83,7 +83,7 @@ msgstr "Akcije"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Active" msgid "Active"
msgstr "" msgstr "Aktivan"
#: src/components/routes/home.tsx #: src/components/routes/home.tsx
msgid "Active Alerts" msgid "Active Alerts"
@@ -141,7 +141,7 @@ msgstr "Jeste li sigurni da želite izbrisati {name}?"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?" msgid "Are you sure?"
msgstr "" msgstr "Jeste li sigurni?"
#: src/components/copy-to-clipboard.tsx #: src/components/copy-to-clipboard.tsx
msgid "Automatic copy requires a secure context." msgid "Automatic copy requires a secure context."
@@ -173,6 +173,10 @@ msgstr "Prosječna iskorištenost procesora na cijelom sustavu"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "" msgstr ""
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Prosječna iskorištenost GPU motora"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -309,7 +313,7 @@ msgstr "Kopiraj docker run"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Environment variables" msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "" msgstr "Kopiraj env"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
@@ -357,7 +361,7 @@ msgstr "Napravite račun"
#. Context: date created #. Context: date created
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Created" msgid "Created"
msgstr "" msgstr "Kreiran"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Critical (%)" msgid "Critical (%)"
@@ -455,12 +459,12 @@ msgstr "Preuzmi"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr "Trajanje"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "" msgstr "Uredi"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
@@ -510,7 +514,7 @@ msgstr "Postojeći sistemi koji nisu definirani u <0>config.yml</0> će biti izb
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "" msgstr "Izvezi"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Export configuration" msgid "Export configuration"
@@ -545,11 +549,11 @@ msgstr "Ažuriranje upozorenja nije uspjelo"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filtriraj..."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr "Otisak prsta"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -576,6 +580,10 @@ msgstr "Puna"
msgid "General" msgid "General"
msgstr "Općenito" msgstr "Općenito"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU motori"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "" msgstr ""
@@ -639,7 +647,7 @@ msgstr ""
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr "Prosječno opterećenje"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
@@ -706,6 +714,7 @@ msgstr "Mrežni promet Docker spremnika"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Mrežni promet javnih sučelja" msgstr "Mrežni promet javnih sučelja"
@@ -720,7 +729,7 @@ msgstr "Nema rezultata."
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "No results." msgid "No results."
msgstr "" msgstr "Nema rezultata."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
@@ -767,7 +776,7 @@ msgstr "Stranica"
#. placeholder {1}: table.getPageCount() #. placeholder {1}: table.getPageCount()
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Page {0} of {1}" msgid "Page {0} of {1}"
msgstr "" msgstr "Stranica {0} od {1}"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Pages / Settings" msgid "Pages / Settings"
@@ -784,7 +793,7 @@ msgstr "Lozinka mora imati najmanje 8 znakova."
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Password must be less than 72 bytes." msgid "Password must be less than 72 bytes."
msgstr "" msgstr "Lozinka mora biti kraća od 72 bajta."
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Password reset request received" msgid "Password reset request received"
@@ -800,7 +809,7 @@ msgstr "Pauzirano"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "" msgstr "Pauzirano ({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."
@@ -1172,12 +1181,16 @@ msgstr "Korisnici"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Value" msgid "Value"
msgstr "" msgstr "Vrijednost"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "View" msgid "View"
msgstr "Prikaz" msgstr "Prikaz"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Prikaži više"
#: 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 ""
@@ -1224,7 +1237,7 @@ msgstr "Piši"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "YAML Config" msgid "YAML Config"
msgstr "YAML Config" msgstr "YAML konfiguracija"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "YAML Configuration" msgid "YAML Configuration"

View File

@@ -173,6 +173,10 @@ msgstr "Rendszerszintű CPU átlagos kihasználtság"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "{0} átlagos kihasználtsága" msgstr "{0} átlagos kihasználtsága"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "GPU motorok átlagos kihasználtsága"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Tele"
msgid "General" msgid "General"
msgstr "Általános" msgstr "Általános"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU motorok"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU áramfelvétele" msgstr "GPU áramfelvétele"
@@ -706,6 +714,7 @@ msgstr "Docker konténerek hálózati forgalma"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Nyilvános interfészek hálózati forgalma" msgstr "Nyilvános interfészek hálózati forgalma"
@@ -1178,6 +1187,10 @@ msgstr ""
msgid "View" msgid "View"
msgstr "Nézet" msgstr "Nézet"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Továbbiak megjelenítése"
#: 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 ""

View File

@@ -173,6 +173,10 @@ msgstr "Meðal nýting örgjörva yfir allt kerfið"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Meðal notkun af {0}" msgstr "Meðal notkun af {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Full"
msgid "General" msgid "General"
msgstr "Almennt" msgstr "Almennt"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Skjákorts rafmagnsnotkun" msgstr "Skjákorts rafmagnsnotkun"
@@ -706,6 +714,7 @@ msgstr "Net traffík docker kerfa"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "" msgstr ""
@@ -1178,6 +1187,10 @@ msgstr ""
msgid "View" msgid "View"
msgstr "Skoða" msgstr "Skoða"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr ""
#: 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 ""

View File

@@ -173,6 +173,10 @@ msgstr "Utilizzo medio della CPU a livello di sistema"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Utilizzo medio di {0}" msgstr "Utilizzo medio di {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Utilizzo medio dei motori GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Piena"
msgid "General" msgid "General"
msgstr "Generale" msgstr "Generale"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "Motori GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Consumo della GPU" msgstr "Consumo della GPU"
@@ -706,6 +714,7 @@ msgstr "Traffico di rete dei container Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Traffico di rete delle interfacce pubbliche" msgstr "Traffico di rete delle interfacce pubbliche"
@@ -1178,6 +1187,10 @@ msgstr "Valore"
msgid "View" msgid "View"
msgstr "Vista" msgstr "Vista"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Visualizza altro"
#: 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 "Visualizza i tuoi 200 avvisi più recenti." msgstr "Visualizza i tuoi 200 avvisi più recenti."

View File

@@ -173,6 +173,10 @@ msgstr "システム全体の平均CPU使用率"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "{0}の平均使用率" msgstr "{0}の平均使用率"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "GPUエンジンの平均使用率"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "満充電"
msgid "General" msgid "General"
msgstr "一般" msgstr "一般"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPUエンジン"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPUの消費電力" msgstr "GPUの消費電力"
@@ -706,6 +714,7 @@ msgstr "Dockerコンテナのネットワークトラフィック"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "パブリックインターフェースのネットワークトラフィック" msgstr "パブリックインターフェースのネットワークトラフィック"
@@ -1178,6 +1187,10 @@ msgstr "値"
msgid "View" msgid "View"
msgstr "表示" msgstr "表示"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "もっと見る"
#: 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 "直近200件のアラートを表示します。" msgstr "直近200件のアラートを表示します。"

View File

@@ -8,15 +8,15 @@ msgstr ""
"Language: ko\n" "Language: ko\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-31 15:44\n" "PO-Revision-Date: 2025-09-23 02:45\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Korean\n" "Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: ko\n" "X-Crowdin-Language: ko\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400) #. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -173,6 +173,10 @@ msgstr "시스템 전체의 평균 CPU 사용량"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "평균 {0} 사용량" msgstr "평균 {0} 사용량"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "GPU 엔진 평균 사용량"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -487,7 +491,7 @@ msgstr "이메일 주소 입력..."
#: src/components/login/otp-forms.tsx #: src/components/login/otp-forms.tsx
msgid "Enter your one-time password." msgid "Enter your one-time password."
msgstr "일회용 비밀번호를 입력하세요." msgstr "OTP를 입력하세요."
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
@@ -576,6 +580,10 @@ msgstr "가득"
msgid "General" msgid "General"
msgstr "일반" msgstr "일반"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU 엔진들"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU 전원 사용량" msgstr "GPU 전원 사용량"
@@ -706,6 +714,7 @@ msgstr "Docker 컨테이너의 네트워크 트래픽"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "공용 인터페이스의 네트워크 트래픽" msgstr "공용 인터페이스의 네트워크 트래픽"
@@ -743,7 +752,7 @@ msgstr "매 시작 시, 데이터베이스가 파일에 정의된 시스템과
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "One-time password" msgid "One-time password"
msgstr "일회용 비밀번호" msgstr "OTP"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -865,7 +874,7 @@ msgstr "수신됨"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "일회용 비밀번호 요청" msgstr "OTP 요청"
#: src/components/login/otp-forms.tsx #: src/components/login/otp-forms.tsx
msgid "Request OTP" msgid "Request OTP"
@@ -1074,11 +1083,11 @@ msgstr "토큰과 지문은 허브에 대한 WebSocket 연결을 인증하는
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "각 인터페이스별 총 수신 데이터량" msgstr "각 인터페이스별 총합 다운로드 데이터량"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "각 인터페이스별 총 발신 데이터량" msgstr "각 인터페이스별 총합 업로드 데이터량"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
@@ -1178,6 +1187,10 @@ msgstr "값"
msgid "View" msgid "View"
msgstr "보기" msgstr "보기"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "더 보기"
#: 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 "최근 200개의 알림을 봅니다." msgstr "최근 200개의 알림을 봅니다."

View File

@@ -173,6 +173,10 @@ msgstr "Gemiddeld systeembrede CPU-gebruik"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Gemiddeld gebruik van {0}" msgstr "Gemiddeld gebruik van {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Gemiddeld gebruik van GPU-engines"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Vol"
msgid "General" msgid "General"
msgstr "Algemeen" msgstr "Algemeen"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU-engines"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU stroomverbruik" msgstr "GPU stroomverbruik"
@@ -706,6 +714,7 @@ msgstr "Netwerkverkeer van docker containers"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Netwerkverkeer van publieke interfaces" msgstr "Netwerkverkeer van publieke interfaces"
@@ -1178,6 +1187,10 @@ msgstr "Waarde"
msgid "View" msgid "View"
msgstr "Weergave" msgstr "Weergave"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Meer weergeven"
#: 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 "Bekijk je 200 meest recente meldingen." msgstr "Bekijk je 200 meest recente meldingen."

View File

@@ -173,6 +173,10 @@ msgstr "Gjennomsnittlig CPU-utnyttelse for hele systemet"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Gjennomsnittlig utnyttelse av {0}" msgstr "Gjennomsnittlig utnyttelse av {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Gjennomsnittlig utnyttelse av GPU-motorer"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Full"
msgid "General" msgid "General"
msgstr "Generelt" msgstr "Generelt"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU-motorer"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU Effektforbruk" msgstr "GPU Effektforbruk"
@@ -706,6 +714,7 @@ msgstr "Nettverkstrafikk av docker-konteinere"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Nettverkstrafikk av eksterne nettverksgrensesnitt" msgstr "Nettverkstrafikk av eksterne nettverksgrensesnitt"
@@ -1178,6 +1187,10 @@ msgstr ""
msgid "View" msgid "View"
msgstr "Visning" msgstr "Visning"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
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 ""

View File

@@ -8,15 +8,15 @@ msgstr ""
"Language: pl\n" "Language: pl\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-09-03 18:54\n" "PO-Revision-Date: 2025-09-18 15:36\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Polish\n" "Language-Team: Polish\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: pl\n" "X-Crowdin-Language: pl\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400) #. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -173,6 +173,10 @@ msgstr "Średnie wykorzystanie procesora w całym systemie"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Średnie użycie {0}" msgstr "Średnie użycie {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Średnie wykorzystanie silników GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -451,7 +455,7 @@ msgstr "Nie działa ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Download" msgid "Download"
msgstr "Pobierz" msgstr "Pobieranie"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
@@ -576,6 +580,10 @@ msgstr "Pełna"
msgid "General" msgid "General"
msgstr "Ogólne" msgstr "Ogólne"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "Silniki GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Moc GPU" msgstr "Moc GPU"
@@ -706,6 +714,7 @@ msgstr "Ruch sieciowy kontenerów Docker."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Ruch sieciowy interfejsów publicznych" msgstr "Ruch sieciowy interfejsów publicznych"
@@ -857,7 +866,7 @@ msgstr "Klucz publiczny"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Read" msgid "Read"
msgstr "Czytaj" msgstr "Odczyt"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Received" msgid "Received"
@@ -1143,7 +1152,7 @@ msgstr "Działa ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Wyślij" msgstr "Wysyłanie"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
@@ -1178,6 +1187,10 @@ msgstr "Wartość"
msgid "View" msgid "View"
msgstr "Widok" msgstr "Widok"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Zobacz więcej"
#: 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 "Wyświetl 200 ostatnich alertów." msgstr "Wyświetl 200 ostatnich alertów."

View File

@@ -173,6 +173,10 @@ msgstr "Utilização média de CPU em todo o sistema"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Utilização média de {0}" msgstr "Utilização média de {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
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"
@@ -576,6 +580,10 @@ msgstr "Cheia"
msgid "General" msgid "General"
msgstr "Geral" msgstr "Geral"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "Motores GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Consumo de Energia da GPU" msgstr "Consumo de Energia da GPU"
@@ -706,6 +714,7 @@ msgstr "Tráfego de rede dos contêineres Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Tráfego de rede das interfaces públicas" msgstr "Tráfego de rede das interfaces públicas"
@@ -1178,6 +1187,10 @@ msgstr "Valor"
msgid "View" msgid "View"
msgstr "Visual" msgstr "Visual"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Ver mais"
#: 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 "Veja os seus 200 alertas mais recentes." msgstr "Veja os seus 200 alertas mais recentes."

View File

@@ -173,6 +173,10 @@ msgstr "Среднее использование CPU по всей систем
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Среднее использование {0}" msgstr "Среднее использование {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Средняя загрузка GPU движков"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Полная"
msgid "General" msgid "General"
msgstr "Общие" msgstr "Общие"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU движки"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Потребляемая мощность GPU" msgstr "Потребляемая мощность GPU"
@@ -706,6 +714,7 @@ msgstr "Сетевой трафик контейнеров Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Сетевой трафик публичных интерфейсов" msgstr "Сетевой трафик публичных интерфейсов"
@@ -1178,6 +1187,10 @@ msgstr "Значение"
msgid "View" msgid "View"
msgstr "Вид" msgstr "Вид"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Показать больше"
#: 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 "Просмотреть 200 последних оповещений." msgstr "Просмотреть 200 последних оповещений."

View File

@@ -173,6 +173,10 @@ msgstr "Povprečna CPU izkoriščenost v celotnem sistemu"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Povprečna poraba {0}" msgstr "Povprečna poraba {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Povprečna uporaba GPU motorjev"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Polna"
msgid "General" msgid "General"
msgstr "Splošno" msgstr "Splošno"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU motorji"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU poraba moči" msgstr "GPU poraba moči"
@@ -706,6 +714,7 @@ msgstr "Omrežni promet docker kontejnerjev"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Omrežni promet javnih vmesnikov" msgstr "Omrežni promet javnih vmesnikov"
@@ -1178,6 +1187,10 @@ msgstr ""
msgid "View" msgid "View"
msgstr "Pogled" msgstr "Pogled"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Prikaži več"
#: 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 ""

View File

@@ -173,6 +173,10 @@ msgstr "Genomsnittlig systemomfattande CPU-användning"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Genomsnittlig användning av {0}" msgstr "Genomsnittlig användning av {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Genomsnittlig användning av GPU-motorer"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Full"
msgid "General" msgid "General"
msgstr "Allmänt" msgstr "Allmänt"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU-motorer"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU-strömförbrukning" msgstr "GPU-strömförbrukning"
@@ -706,6 +714,7 @@ msgstr "Nätverkstrafik för dockercontainrar"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Nätverkstrafik för publika gränssnitt" msgstr "Nätverkstrafik för publika gränssnitt"
@@ -1178,6 +1187,10 @@ msgstr ""
msgid "View" msgid "View"
msgstr "Visa" msgstr "Visa"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Visa 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 ""

View File

@@ -173,6 +173,10 @@ msgstr "Sistem genelinde ortalama CPU kullanımı"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "{0} ortalama kullanımı" msgstr "{0} ortalama kullanımı"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "GPU motorlarının ortalama kullanımı"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Dolu"
msgid "General" msgid "General"
msgstr "Genel" msgstr "Genel"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU motorları"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU Güç Çekimi" msgstr "GPU Güç Çekimi"
@@ -706,6 +714,7 @@ msgstr "Docker konteynerlerinin ağ trafiği"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Genel arayüzlerin ağ trafiği" msgstr "Genel arayüzlerin ağ trafiği"
@@ -1178,6 +1187,10 @@ msgstr "Değer"
msgid "View" msgid "View"
msgstr "Görüntüle" msgstr "Görüntüle"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Daha fazla göster"
#: 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 "En son 200 uyarınızı görüntüleyin." msgstr "En son 200 uyarınızı görüntüleyin."

View File

@@ -173,6 +173,10 @@ msgstr "Середнє використання CPU по всій системі
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Середнє використання {0}" msgstr "Середнє використання {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Середнє використання рушіїв GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Повна"
msgid "General" msgid "General"
msgstr "Загальні" msgstr "Загальні"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "Рушії GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Енергоспоживання GPU" msgstr "Енергоспоживання GPU"
@@ -706,6 +714,7 @@ msgstr "Мережевий трафік контейнерів Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Мережевий трафік публічних інтерфейсів" msgstr "Мережевий трафік публічних інтерфейсів"
@@ -1178,6 +1187,10 @@ msgstr "Значення"
msgid "View" msgid "View"
msgstr "Вигляд" msgstr "Вигляд"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Переглянути більше"
#: 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 "Переглянути 200 останніх сповіщень." msgstr "Переглянути 200 останніх сповіщень."

View File

@@ -173,6 +173,10 @@ msgstr "Sử dụng CPU trung bình toàn hệ thống"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "Mức sử dụng trung bình của {0}" msgstr "Mức sử dụng trung bình của {0}"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "Mức sử dụng trung bình của động cơ GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "Đầy pin"
msgid "General" msgid "General"
msgstr "Chung" msgstr "Chung"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "Động cơ GPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Mức tiêu thụ điện của GPU" msgstr "Mức tiêu thụ điện của GPU"
@@ -706,6 +714,7 @@ msgstr "Lưu lượng mạng của các container Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Lưu lượng mạng của các giao diện công cộng" msgstr "Lưu lượng mạng của các giao diện công cộng"
@@ -1178,6 +1187,10 @@ msgstr "Giá trị"
msgid "View" msgid "View"
msgstr "Xem" msgstr "Xem"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "Xem thêm"
#: 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 "Xem 200 cảnh báo gần đây nhất của bạn." msgstr "Xem 200 cảnh báo gần đây nhất của bạn."

View File

@@ -173,6 +173,10 @@ msgstr "系统范围内的平均 CPU 使用率"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "{0} 平均利用率" msgstr "{0} 平均利用率"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "GPU 引擎的平均利用率"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "满电"
msgid "General" msgid "General"
msgstr "常规" msgstr "常规"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU 引擎"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU 功耗" msgstr "GPU 功耗"
@@ -706,6 +714,7 @@ msgstr "Docker 容器的网络流量"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "公共接口的网络流量" msgstr "公共接口的网络流量"
@@ -1178,6 +1187,10 @@ msgstr "值"
msgid "View" msgid "View"
msgstr "视图" msgstr "视图"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "查看更多"
#: 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 "查看您最近的200个警报。" msgstr "查看您最近的200个警报。"

View File

@@ -173,6 +173,10 @@ msgstr "系統的平均 CPU 使用率"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "{0} 的平均使用率" msgstr "{0} 的平均使用率"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "GPU 引擎的平均利用率"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "滿電"
msgid "General" msgid "General"
msgstr "一般" msgstr "一般"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU 引擎"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU 功耗" msgstr "GPU 功耗"
@@ -706,6 +714,7 @@ msgstr "Docker 容器的網絡流量"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "公共接口的網絡流量" msgstr "公共接口的網絡流量"
@@ -1178,6 +1187,10 @@ msgstr "值"
msgid "View" msgid "View"
msgstr "檢視" msgstr "檢視"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "查看更多"
#: 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 "檢視最近 200 則警報。" msgstr "檢視最近 200 則警報。"

View File

@@ -173,6 +173,10 @@ msgstr "系統的平均 CPU 使用率"
msgid "Average utilization of {0}" msgid "Average utilization of {0}"
msgstr "{0} 的平均使用率" msgstr "{0} 的平均使用率"
#: src/components/routes/system.tsx
msgid "Average utilization of GPU engines"
msgstr "GPU 引擎的平均利用率"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
@@ -576,6 +580,10 @@ msgstr "滿電"
msgid "General" msgid "General"
msgstr "一般" msgstr "一般"
#: src/components/routes/system.tsx
msgid "GPU Engines"
msgstr "GPU 引擎"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU 功耗" msgstr "GPU 功耗"
@@ -706,6 +714,7 @@ msgstr "Docker 容器的網路流量"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "公開介面的網路流量" msgstr "公開介面的網路流量"
@@ -1178,6 +1187,10 @@ msgstr "值"
msgid "View" msgid "View"
msgstr "檢視" msgstr "檢視"
#: src/components/routes/system/network-sheet.tsx
msgid "View more"
msgstr "查看更多"
#: 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 "檢視最近 200 則警報。" msgstr "檢視最近 200 則警報。"

View File

@@ -1,5 +1,5 @@
import type { RecordModel } from "pocketbase" import type { RecordModel } from "pocketbase"
import type { Unit, Os, BatteryState, HourFormat } from "./lib/enums" import type { Unit, Os, BatteryState, HourFormat, ConnectionType } from "@/lib/enums"
// global window properties // global window properties
declare global { declare global {
@@ -75,6 +75,8 @@ export interface SystemInfo {
dt?: number dt?: number
/** operating system */ /** operating system */
os?: Os os?: Os
/** connection type */
ct?: ConnectionType
} }
export interface SystemStats { export interface SystemStats {
@@ -156,6 +158,8 @@ export interface GPUData {
u: number u: number
/** power (w) */ /** power (w) */
p?: number p?: number
/** engines */
e?: Record<string, number>
} }
export interface ExtraFsStats { export interface ExtraFsStats {

View File

@@ -1,3 +1,29 @@
## 0.12.11
- Adjust calculation of cached memory (fixes #1187, #1196)
- Add pattern matching and blacklist functionality to `NICS` env var. (#1190)
- Update Intel GPU collector to parse plain text (`-l`) instead of JSON output (#1150)
## 0.12.10
Note that the default memory calculation changed in this release, which may cause a difference in memory usage compared to previous versions.
- Add initial support for Intel GPUs (#1150, #755)
- Show connection type (WebSocket / SSH) in hub UI.
- Fix temperature unit and bytes / bits settings. (#1180)
- Add `henrygd/beszel-agent-intel` image for Intel GPUs (experimental).
- Update Go dependencies. Shoutrrr now supports notifications for Signal and WeChat Work (WeCom).
## 0.12.9
- Fix divide by zero error introduced in 0.12.8 :) (#1175)
## 0.12.8 ## 0.12.8
- Add per-interface network traffic charts. (#926) - Add per-interface network traffic charts. (#926)
@@ -14,6 +40,8 @@
- Add FreeBSD support for agent install script and update command. - Add FreeBSD support for agent install script and update command.
- Fix status alerts not being resolved when system comes up. (#1052)
## 0.12.7 ## 0.12.7
- Make LibreHardwareMonitor opt-in with `LHM=true` environment variable. (#1130) - Make LibreHardwareMonitor opt-in with `LHM=true` environment variable. (#1130)

View File

@@ -161,6 +161,53 @@ run_rc_command "$1"
EOF EOF
} }
# Detect system architecture
detect_architecture() {
local arch=$(uname -m)
if [ "$arch" = "mips" ]; then
detect_mips_endianness
return $?
fi
case "$arch" in
x86_64)
arch="amd64"
;;
armv6l|armv7l)
arch="arm"
;;
aarch64)
arch="arm64"
;;
esac
echo "$arch"
}
# Detect MIPS endianness using ELF header
detect_mips_endianness() {
local bins="/bin/sh /bin/ls /usr/bin/env"
local bin_to_check endian
for bin_to_check in $bins; do
if [ -f "$bin_to_check" ]; then
# The 6th byte in ELF header: 01 = little, 02 = big
endian=$(hexdump -n 1 -s 5 -e '1/1 "%02x"' "$bin_to_check" 2>/dev/null)
if [ "$endian" = "01" ]; then
echo "mipsle"
return
elif [ "$endian" = "02" ]; then
echo "mips"
return
fi
fi
done
# Final fallback
echo "mips"
}
# Default values # Default values
PORT=45876 PORT=45876
UNINSTALL=false UNINSTALL=false
@@ -556,7 +603,7 @@ fi
echo "Downloading and installing the agent..." echo "Downloading and installing the agent..."
OS=$(uname -s | sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/') OS=$(uname -s | sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/')
ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/armv6l/arm/' -e 's/armv7l/arm/' -e 's/aarch64/arm64/') ARCH=$(detect_architecture)
FILE_NAME="beszel-agent_${OS}_${ARCH}.tar.gz" FILE_NAME="beszel-agent_${OS}_${ARCH}.tar.gz"
# Determine version to install # Determine version to install
@@ -738,9 +785,7 @@ EXTRA_HELP=" update Update the Beszel agent
restart Restart the Beszel agent" restart Restart the Beszel agent"
update() { update() {
if $BIN_PATH update | grep -q "Update completed successfully"; then $BIN_PATH update
/etc/init.d/beszel-agent restart
fi
} }
EOF EOF

View File

@@ -16,6 +16,7 @@ fi
version=0.0.1 version=0.0.1
PORT=8090 # Default port PORT=8090 # Default port
GITHUB_PROXY_URL="https://ghfast.top/" # Default proxy URL GITHUB_PROXY_URL="https://ghfast.top/" # Default proxy URL
AUTO_UPDATE_FLAG="false" # default to no auto-updates, "true" means enable
# Function to ensure the proxy URL ends with a / # Function to ensure the proxy URL ends with a /
ensure_trailing_slash() { ensure_trailing_slash() {
@@ -32,26 +33,42 @@ ensure_trailing_slash() {
# Ensure the proxy URL ends with a / # Ensure the proxy URL ends with a /
GITHUB_PROXY_URL=$(ensure_trailing_slash "$GITHUB_PROXY_URL") GITHUB_PROXY_URL=$(ensure_trailing_slash "$GITHUB_PROXY_URL")
# Read command line options # Parse command line arguments
while getopts ":uhp:c:" opt; do while [ $# -gt 0 ]; do
case $opt in case "$1" in
u) UNINSTALL="true" ;; -u)
h) UNINSTALL="true"
printf "Beszel Hub installation script\n\n" shift
printf "Usage: ./install-hub.sh [options]\n\n" ;;
printf "Options: \n" -h|--help)
printf " -u : Uninstall the Beszel Hub\n" printf "Beszel Hub installation script\n\n"
printf " -p <port> : Specify a port number (default: 8090)\n" printf "Usage: ./install-hub.sh [options]\n\n"
printf " -c <url> : Use a custom GitHub mirror URL (e.g., https://ghfast.top/)\n" printf "Options: \n"
echo " -h : Display this help message" printf " -u : Uninstall the Beszel Hub\n"
exit 0 printf " -p <port> : Specify a port number (default: 8090)\n"
;; printf " -c <url> : Use a custom GitHub mirror URL (e.g., https://ghfast.top/)\n"
p) PORT=$OPTARG ;; printf " --auto-update : Enable automatic daily updates (disabled by default)\n"
c) GITHUB_PROXY_URL=$(ensure_trailing_slash "$OPTARG") ;; printf " -h, --help : Display this help message\n"
\?) exit 0
echo "Invalid option: -$OPTARG" ;;
exit 1 -p)
;; shift
PORT="$1"
shift
;;
-c)
shift
GITHUB_PROXY_URL=$(ensure_trailing_slash "$1")
shift
;;
--auto-update)
AUTO_UPDATE_FLAG="true"
shift
;;
*)
echo "Invalid option: $1" >&2
exit 1
;;
esac esac
done done
@@ -63,7 +80,14 @@ if [ "$UNINSTALL" = "true" ]; then
# Remove the systemd service file # Remove the systemd service file
echo "Removing the systemd service file..." echo "Removing the systemd service file..."
rm /etc/systemd/system/beszel-hub.service rm -f /etc/systemd/system/beszel-hub.service
# Remove the update timer and service if they exist
echo "Removing the daily update service and timer..."
systemctl stop beszel-hub-update.timer 2>/dev/null
systemctl disable beszel-hub-update.timer 2>/dev/null
rm -f /etc/systemd/system/beszel-hub-update.service
rm -f /etc/systemd/system/beszel-hub-update.timer
# Reload the systemd daemon # Reload the systemd daemon
echo "Reloading the systemd daemon..." echo "Reloading the systemd daemon..."
@@ -75,7 +99,7 @@ if [ "$UNINSTALL" = "true" ]; then
# Remove the dedicated user # Remove the dedicated user
echo "Removing the dedicated user..." echo "Removing the dedicated user..."
userdel beszel userdel beszel 2>/dev/null
echo "The Beszel Hub has been uninstalled successfully!" echo "The Beszel Hub has been uninstalled successfully!"
exit 0 exit 0
@@ -151,4 +175,39 @@ if [ "$(systemctl is-active beszel-hub.service)" != "active" ]; then
exit 1 exit 1
fi fi
# Enable auto-update if flag is set to true
if [ "$AUTO_UPDATE_FLAG" = "true" ]; then
echo "Setting up daily automatic updates for beszel-hub..."
# Create systemd service for the daily update
cat >/etc/systemd/system/beszel-hub-update.service <<EOF
[Unit]
Description=Update beszel-hub if needed
Wants=beszel-hub.service
[Service]
Type=oneshot
ExecStart=/opt/beszel/beszel update
EOF
# Create systemd timer for the daily update
cat >/etc/systemd/system/beszel-hub-update.timer <<EOF
[Unit]
Description=Run beszel-hub update daily
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=4h
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now beszel-hub-update.timer
printf "\nDaily updates have been enabled.\n"
fi
echo "The Beszel Hub has been installed and configured successfully! It is now accessible on port $PORT." echo "The Beszel Hub has been installed and configured successfully! It is now accessible on port $PORT."