Compare commits

..

48 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
henrygd
49976c6f61 fix nvidia agent dockerfile after project reorganization 2025-09-17 14:10:02 -04:00
henrygd
d68f1f0985 0.12.8 release :) 2025-09-17 14:02:17 -04:00
henrygd
273a090200 update translations 2025-09-17 14:01:20 -04:00
henrygd
59057a2ba4 add check for status alerts which are not properly resolved (#1052) 2025-09-17 13:31:49 -04:00
henrygd
1b9e781d45 refactor deltatracker
- embed mutex
- add example function
2025-09-16 22:09:46 -04:00
henrygd
4e0ca7c2ba formatting (biome) 2025-09-15 18:04:13 -04:00
henrygd
a9e7bcd37f add per-interface and cumulative network traffic charts (#926)
Co-authored-by: Sven van Ginkel <svenvanginkel@icloud.com>
2025-09-15 17:59:21 -04:00
henrygd
4635f24fb2 fix entre arg in makefile dev server 2025-09-15 17:26:07 -04:00
henrygd
3e73399b87 fix battery detection on newer macs (#1170) 2025-09-15 12:02:50 -04:00
103 changed files with 5380 additions and 1897 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

1
.gitignore vendored
View File

@@ -20,4 +20,3 @@ __debug_*
agent/lhm/obj agent/lhm/obj
agent/lhm/bin agent/lhm/bin
dockerfile_agent_dev dockerfile_agent_dev
.vite

View File

@@ -22,23 +22,23 @@ import (
) )
type Agent struct { type Agent struct {
sync.Mutex // Used to lock agent while collecting data sync.Mutex // Used to lock agent while collecting data
debug bool // true if LOG_LEVEL is set to debug debug bool // true if LOG_LEVEL is set to debug
zfs bool // true if system has arcstats zfs bool // true if system has arcstats
memCalc string // Memory calculation formula memCalc string // Memory calculation formula
fsNames []string // List of filesystem device names being monitored fsNames []string // List of filesystem device names being monitored
fsStats map[string]*system.FsStats // Keeps track of disk stats for each filesystem fsStats map[string]*system.FsStats // Keeps track of disk stats for each filesystem
netInterfaces map[string]struct{} // Stores all valid network interfaces netInterfaces map[string]struct{} // Stores all valid network interfaces
netIoStats map[string]system.NetIoStats // Keeps track of per-interface bandwidth usage netIoStats system.NetIoStats // Keeps track of bandwidth usage
dockerManager *dockerManager // Manages Docker API requests dockerManager *dockerManager // Manages Docker API requests
sensorConfig *SensorConfig // Sensors config sensorConfig *SensorConfig // Sensors config
systemInfo system.Info // Host system info systemInfo system.Info // Host system info
gpuManager *GPUManager // Manages GPU data gpuManager *GPUManager // Manages GPU data
cache *SessionCache // Cache for system stats based on primary session ID cache *SessionCache // Cache for system stats based on primary session ID
connectionManager *ConnectionManager // Channel to signal connection events connectionManager *ConnectionManager // Channel to signal connection events
server *ssh.Server // SSH server server *ssh.Server // SSH server
dataDir string // Directory for persisting data dataDir string // Directory for persisting data
keys []gossh.PublicKey // SSH public keys keys []gossh.PublicKey // SSH public keys
} }
// NewAgent creates a new agent with the given data directory for persisting data. // NewAgent creates a new agent with the given data directory for persisting data.

View File

@@ -20,9 +20,8 @@ func HasReadableBattery() bool {
} }
haveCheckedBattery = true haveCheckedBattery = true
bat, err := battery.Get(0) bat, err := battery.Get(0)
if err == nil && bat != nil { systemHasBattery = err == nil && bat != nil && bat.Design != 0 && bat.Full != 0
systemHasBattery = true if !systemHasBattery {
} else {
slog.Debug("No battery found", "err", err) slog.Debug("No battery found", "err", err)
} }
return systemHasBattery return systemHasBattery

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

@@ -0,0 +1,81 @@
// Package deltatracker provides a tracker for calculating differences in numeric values over time.
package deltatracker
import (
"sync"
"golang.org/x/exp/constraints"
)
// Numeric is a constraint that permits any integer or floating-point type.
type Numeric interface {
constraints.Integer | constraints.Float
}
// DeltaTracker is a generic, thread-safe tracker for calculating differences
// in numeric values over time.
// K is the key type (e.g., int, string).
// V is the value type (e.g., int, int64, float32, float64).
type DeltaTracker[K comparable, V Numeric] struct {
sync.RWMutex
current map[K]V
previous map[K]V
}
// NewDeltaTracker creates a new generic tracker.
func NewDeltaTracker[K comparable, V Numeric]() *DeltaTracker[K, V] {
return &DeltaTracker[K, V]{
current: make(map[K]V),
previous: make(map[K]V),
}
}
// Set records the current value for a given ID.
func (t *DeltaTracker[K, V]) Set(id K, value V) {
t.Lock()
defer t.Unlock()
t.current[id] = value
}
// Deltas returns a map of all calculated deltas for the current interval.
func (t *DeltaTracker[K, V]) Deltas() map[K]V {
t.RLock()
defer t.RUnlock()
deltas := make(map[K]V)
for id, currentVal := range t.current {
if previousVal, ok := t.previous[id]; ok {
deltas[id] = currentVal - previousVal
} else {
deltas[id] = 0
}
}
return deltas
}
// Delta returns the delta for a single key.
// Returns 0 if the key doesn't exist or has no previous value.
func (t *DeltaTracker[K, V]) Delta(id K) V {
t.RLock()
defer t.RUnlock()
currentVal, currentOk := t.current[id]
if !currentOk {
return 0
}
previousVal, previousOk := t.previous[id]
if !previousOk {
return 0
}
return currentVal - previousVal
}
// Cycle prepares the tracker for the next interval.
func (t *DeltaTracker[K, V]) Cycle() {
t.Lock()
defer t.Unlock()
t.previous = t.current
t.current = make(map[K]V)
}

View File

@@ -0,0 +1,217 @@
package deltatracker
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func ExampleDeltaTracker() {
tracker := NewDeltaTracker[string, int]()
tracker.Set("key1", 10)
tracker.Set("key2", 20)
tracker.Cycle()
tracker.Set("key1", 15)
tracker.Set("key2", 30)
fmt.Println(tracker.Delta("key1"))
fmt.Println(tracker.Delta("key2"))
fmt.Println(tracker.Deltas())
// Output: 5
// 10
// map[key1:5 key2:10]
}
func TestNewDeltaTracker(t *testing.T) {
tracker := NewDeltaTracker[string, int]()
assert.NotNil(t, tracker)
assert.Empty(t, tracker.current)
assert.Empty(t, tracker.previous)
}
func TestSet(t *testing.T) {
tracker := NewDeltaTracker[string, int]()
tracker.Set("key1", 10)
tracker.RLock()
defer tracker.RUnlock()
assert.Equal(t, 10, tracker.current["key1"])
}
func TestDeltas(t *testing.T) {
tracker := NewDeltaTracker[string, int]()
// Test with no previous values
tracker.Set("key1", 10)
tracker.Set("key2", 20)
deltas := tracker.Deltas()
assert.Equal(t, 0, deltas["key1"])
assert.Equal(t, 0, deltas["key2"])
// Cycle to move current to previous
tracker.Cycle()
// Set new values and check deltas
tracker.Set("key1", 15) // Delta should be 5 (15-10)
tracker.Set("key2", 25) // Delta should be 5 (25-20)
tracker.Set("key3", 30) // New key, delta should be 0
deltas = tracker.Deltas()
assert.Equal(t, 5, deltas["key1"])
assert.Equal(t, 5, deltas["key2"])
assert.Equal(t, 0, deltas["key3"])
}
func TestCycle(t *testing.T) {
tracker := NewDeltaTracker[string, int]()
tracker.Set("key1", 10)
tracker.Set("key2", 20)
// Verify current has values
tracker.RLock()
assert.Equal(t, 10, tracker.current["key1"])
assert.Equal(t, 20, tracker.current["key2"])
assert.Empty(t, tracker.previous)
tracker.RUnlock()
tracker.Cycle()
// After cycle, previous should have the old current values
// and current should be empty
tracker.RLock()
assert.Empty(t, tracker.current)
assert.Equal(t, 10, tracker.previous["key1"])
assert.Equal(t, 20, tracker.previous["key2"])
tracker.RUnlock()
}
func TestCompleteWorkflow(t *testing.T) {
tracker := NewDeltaTracker[string, int]()
// First interval
tracker.Set("server1", 100)
tracker.Set("server2", 200)
// Get deltas for first interval (should be zero)
firstDeltas := tracker.Deltas()
assert.Equal(t, 0, firstDeltas["server1"])
assert.Equal(t, 0, firstDeltas["server2"])
// Cycle to next interval
tracker.Cycle()
// Second interval
tracker.Set("server1", 150) // Delta: 50
tracker.Set("server2", 180) // Delta: -20
tracker.Set("server3", 300) // New server, delta: 300
secondDeltas := tracker.Deltas()
assert.Equal(t, 50, secondDeltas["server1"])
assert.Equal(t, -20, secondDeltas["server2"])
assert.Equal(t, 0, secondDeltas["server3"])
}
func TestDeltaTrackerWithDifferentTypes(t *testing.T) {
// Test with int64
intTracker := NewDeltaTracker[string, int64]()
intTracker.Set("pid1", 1000)
intTracker.Cycle()
intTracker.Set("pid1", 1200)
intDeltas := intTracker.Deltas()
assert.Equal(t, int64(200), intDeltas["pid1"])
// Test with float64
floatTracker := NewDeltaTracker[string, float64]()
floatTracker.Set("cpu1", 1.5)
floatTracker.Cycle()
floatTracker.Set("cpu1", 2.7)
floatDeltas := floatTracker.Deltas()
assert.InDelta(t, 1.2, floatDeltas["cpu1"], 0.0001)
// Test with int keys
pidTracker := NewDeltaTracker[int, int64]()
pidTracker.Set(101, 20000)
pidTracker.Cycle()
pidTracker.Set(101, 22500)
pidDeltas := pidTracker.Deltas()
assert.Equal(t, int64(2500), pidDeltas[101])
}
func TestDelta(t *testing.T) {
tracker := NewDeltaTracker[string, int]()
// Test getting delta for non-existent key
result := tracker.Delta("nonexistent")
assert.Equal(t, 0, result)
// Test getting delta for key with no previous value
tracker.Set("key1", 10)
result = tracker.Delta("key1")
assert.Equal(t, 0, result)
// Cycle to move current to previous
tracker.Cycle()
// Test getting delta for key with previous value
tracker.Set("key1", 15)
result = tracker.Delta("key1")
assert.Equal(t, 5, result)
// Test getting delta for key that exists in previous but not current
result = tracker.Delta("key1")
assert.Equal(t, 5, result) // Should still return 5
// Test getting delta for key that exists in current but not previous
tracker.Set("key2", 20)
result = tracker.Delta("key2")
assert.Equal(t, 0, result)
}
func TestDeltaWithDifferentTypes(t *testing.T) {
// Test with int64
intTracker := NewDeltaTracker[string, int64]()
intTracker.Set("pid1", 1000)
intTracker.Cycle()
intTracker.Set("pid1", 1200)
result := intTracker.Delta("pid1")
assert.Equal(t, int64(200), result)
// Test with float64
floatTracker := NewDeltaTracker[string, float64]()
floatTracker.Set("cpu1", 1.5)
floatTracker.Cycle()
floatTracker.Set("cpu1", 2.7)
floatResult := floatTracker.Delta("cpu1")
assert.InDelta(t, 1.2, floatResult, 0.0001)
// Test with int keys
pidTracker := NewDeltaTracker[int, int64]()
pidTracker.Set(101, 20000)
pidTracker.Cycle()
pidTracker.Set(101, 22500)
pidResult := pidTracker.Delta(101)
assert.Equal(t, int64(2500), pidResult)
}
func TestDeltaConcurrentAccess(t *testing.T) {
tracker := NewDeltaTracker[string, int]()
// Set initial values
tracker.Set("key1", 10)
tracker.Set("key2", 20)
tracker.Cycle()
// Set new values
tracker.Set("key1", 15)
tracker.Set("key2", 25)
// Test concurrent access safety
result1 := tracker.Delta("key1")
result2 := tracker.Delta("key2")
assert.Equal(t, 5, result1)
assert.Equal(t, 5, result2)
}

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

@@ -1,58 +1,184 @@
package agent package agent
import ( import (
"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) {
// network stats
if len(a.netInterfaces) == 0 {
// if no network interfaces, initialize again
// this is a fix if agent started before network is online (#466)
// maybe refactor this in the future to not cache interface names at all so we
// don't miss an interface that's been added after agent started in any circumstance
a.initializeNetIoStats()
}
if systemStats.NetworkInterfaces == nil {
systemStats.NetworkInterfaces = make(map[string][4]uint64, 0)
}
if netIO, err := psutilNet.IOCounters(true); err == nil {
msElapsed := uint64(time.Since(a.netIoStats.Time).Milliseconds())
a.netIoStats.Time = time.Now()
totalBytesSent := uint64(0)
totalBytesRecv := uint64(0)
netInterfaceDeltaTracker.Cycle()
// sum all bytes sent and received
for _, v := range netIO {
// skip if not in valid network interfaces list
if _, exists := a.netInterfaces[v.Name]; !exists {
continue
}
totalBytesSent += v.BytesSent
totalBytesRecv += v.BytesRecv
// track deltas for each network interface
var upDelta, downDelta uint64
upKey, downKey := fmt.Sprintf("%sup", v.Name), fmt.Sprintf("%sdown", v.Name)
netInterfaceDeltaTracker.Set(upKey, v.BytesSent)
netInterfaceDeltaTracker.Set(downKey, v.BytesRecv)
if msElapsed > 0 {
upDelta = netInterfaceDeltaTracker.Delta(upKey) * 1000 / msElapsed
downDelta = netInterfaceDeltaTracker.Delta(downKey) * 1000 / msElapsed
}
// add interface to systemStats
systemStats.NetworkInterfaces[v.Name] = [4]uint64{upDelta, downDelta, v.BytesSent, v.BytesRecv}
}
// add to systemStats
var bytesSentPerSecond, bytesRecvPerSecond uint64
if msElapsed > 0 {
bytesSentPerSecond = (totalBytesSent - a.netIoStats.BytesSent) * 1000 / msElapsed
bytesRecvPerSecond = (totalBytesRecv - a.netIoStats.BytesRecv) * 1000 / msElapsed
}
networkSentPs := bytesToMegabytes(float64(bytesSentPerSecond))
networkRecvPs := bytesToMegabytes(float64(bytesRecvPerSecond))
// add check for issue (#150) where sent is a massive number
if networkSentPs > 10_000 || networkRecvPs > 10_000 {
slog.Warn("Invalid net stats. Resetting.", "sent", networkSentPs, "recv", networkRecvPs)
for _, v := range netIO {
if _, exists := a.netInterfaces[v.Name]; !exists {
continue
}
slog.Info(v.Name, "recv", v.BytesRecv, "sent", v.BytesSent)
}
// reset network I/O stats
a.initializeNetIoStats()
} else {
systemStats.NetworkSent = networkSentPs
systemStats.NetworkRecv = networkRecvPs
systemStats.Bandwidth[0], systemStats.Bandwidth[1] = bytesSentPerSecond, bytesRecvPerSecond
// update netIoStats
a.netIoStats.BytesSent = totalBytesSent
a.netIoStats.BytesRecv = totalBytesRecv
}
}
}
func (a *Agent) initializeNetIoStats() { 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)
// reset network I/O stats per interface
a.netIoStats = make(map[string]system.NetIoStats, 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
a.netIoStats.BytesSent = 0
a.netIoStats.BytesRecv = 0
// get intial network I/O stats // get intial network I/O stats
if netIO, err := psutilNet.IOCounters(true); err == nil { if netIO, err := psutilNet.IOCounters(true); err == nil {
now := 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.BytesRecv += v.BytesRecv
// store as a valid network interface // store as a valid network interface
a.netInterfaces[v.Name] = struct{}{} a.netInterfaces[v.Name] = struct{}{}
// initialize per-interface stats
a.netIoStats[v.Name] = system.NetIoStats{
BytesRecv: v.BytesRecv,
BytesSent: v.BytesSent,
Time: now,
Name: v.Name,
}
} }
} }
} }
@@ -64,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

@@ -18,7 +18,6 @@ import (
"github.com/shirou/gopsutil/v4/host" "github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/load" "github.com/shirou/gopsutil/v4/load"
"github.com/shirou/gopsutil/v4/mem" "github.com/shirou/gopsutil/v4/mem"
psutilNet "github.com/shirou/gopsutil/v4/net"
) )
// Sets initial / non-changing values about the host system // Sets initial / non-changing values about the host system
@@ -32,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
@@ -70,7 +69,7 @@ func (a *Agent) initializeSystemInfo() {
// Returns current info, stats about the host system // Returns current info, stats about the host system
func (a *Agent) getSystemStats() system.Stats { func (a *Agent) getSystemStats() system.Stats {
systemStats := system.Stats{} var systemStats system.Stats
// battery // battery
if battery.HasReadableBattery() { if battery.HasReadableBattery() {
@@ -101,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 {
@@ -173,87 +177,7 @@ func (a *Agent) getSystemStats() system.Stats {
} }
// network stats // network stats
if len(a.netInterfaces) == 0 { a.updateNetworkStats(&systemStats)
// if no network interfaces, initialize again
// this is a fix if agent started before network is online (#466)
a.initializeNetIoStats()
}
if netIO, err := psutilNet.IOCounters(true); err == nil {
now := time.Now()
// pre-allocate maps with known capacity
interfaceCount := len(a.netInterfaces)
if systemStats.NetworkInterfaces == nil || len(systemStats.NetworkInterfaces) != interfaceCount {
systemStats.NetworkInterfaces = make(map[string]system.NetworkInterfaceStats, interfaceCount)
}
var totalSent, totalRecv float64
// single pass through interfaces
for _, v := range netIO {
// skip if not in valid network interfaces list
if _, exists := a.netInterfaces[v.Name]; !exists {
continue
}
// get previous stats for this interface
prevStats, exists := a.netIoStats[v.Name]
var networkSentPs, networkRecvPs float64
if exists {
secondsElapsed := time.Since(prevStats.Time).Seconds()
if secondsElapsed > 0 {
// direct calculation to MB/s, avoiding intermediate bytes/sec
networkSentPs = bytesToMegabytes(float64(v.BytesSent-prevStats.BytesSent) / secondsElapsed)
networkRecvPs = bytesToMegabytes(float64(v.BytesRecv-prevStats.BytesRecv) / secondsElapsed)
}
}
// accumulate totals
totalSent += networkSentPs
totalRecv += networkRecvPs
// store per-interface stats
systemStats.NetworkInterfaces[v.Name] = system.NetworkInterfaceStats{
NetworkSent: networkSentPs,
NetworkRecv: networkRecvPs,
TotalBytesSent: v.BytesSent,
TotalBytesRecv: v.BytesRecv,
}
// update previous stats (reuse existing struct if possible)
if prevStats.Name == v.Name {
prevStats.BytesRecv = v.BytesRecv
prevStats.BytesSent = v.BytesSent
prevStats.PacketsSent = v.PacketsSent
prevStats.PacketsRecv = v.PacketsRecv
prevStats.Time = now
a.netIoStats[v.Name] = prevStats
} else {
a.netIoStats[v.Name] = system.NetIoStats{
BytesRecv: v.BytesRecv,
BytesSent: v.BytesSent,
PacketsSent: v.PacketsSent,
PacketsRecv: v.PacketsRecv,
Time: now,
Name: v.Name,
}
}
}
// add check for issue (#150) where sent is a massive number
if totalSent > 10_000 || totalRecv > 10_000 {
slog.Warn("Invalid net stats. Resetting.", "sent", totalSent, "recv", totalRecv)
// reset network I/O stats
a.initializeNetIoStats()
} else {
systemStats.NetworkSent = totalSent
systemStats.NetworkRecv = totalRecv
}
}
// connection counts
a.updateConnectionCounts(&systemStats)
// temperatures // temperatures
// TODO: maybe refactor to methods on systemStats // TODO: maybe refactor to methods on systemStats
@@ -293,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
@@ -302,109 +227,14 @@ func (a *Agent) getSystemStats() system.Stats {
a.systemInfo.MemPct = systemStats.MemPct a.systemInfo.MemPct = systemStats.MemPct
a.systemInfo.DiskPct = systemStats.DiskPct a.systemInfo.DiskPct = systemStats.DiskPct
a.systemInfo.Uptime, _ = host.Uptime() a.systemInfo.Uptime, _ = host.Uptime()
// TODO: in future release, remove MB bandwidth values in favor of bytes
// Sum all per-interface network sent/recv and assign to systemInfo a.systemInfo.Bandwidth = twoDecimals(systemStats.NetworkSent + systemStats.NetworkRecv)
var totalSent, totalRecv float64 a.systemInfo.BandwidthBytes = systemStats.Bandwidth[0] + systemStats.Bandwidth[1]
for _, iface := range systemStats.NetworkInterfaces {
totalSent += iface.NetworkSent
totalRecv += iface.NetworkRecv
}
a.systemInfo.NetworkSent = twoDecimals(totalSent)
a.systemInfo.NetworkRecv = twoDecimals(totalRecv)
slog.Debug("sysinfo", "data", a.systemInfo) slog.Debug("sysinfo", "data", a.systemInfo)
return systemStats return systemStats
} }
func (a *Agent) updateConnectionCounts(systemStats *system.Stats) {
// Get IPv4 connections
connectionsIPv4, err := psutilNet.Connections("inet")
if err != nil {
slog.Debug("Failed to get IPv4 connection stats", "err", err)
return
}
// Get IPv6 connections
connectionsIPv6, err := psutilNet.Connections("inet6")
if err != nil {
slog.Debug("Failed to get IPv6 connection stats", "err", err)
// Continue with IPv4 only if IPv6 fails
}
// Initialize Nets map if needed
if systemStats.Nets == nil {
systemStats.Nets = make(map[string]float64)
}
// Count IPv4 connection states
connStatsIPv4 := map[string]int{
"established": 0,
"listen": 0,
"time_wait": 0,
"close_wait": 0,
"syn_recv": 0,
}
for _, conn := range connectionsIPv4 {
// Only count TCP connections (Type 1 = SOCK_STREAM)
if conn.Type == 1 {
switch strings.ToUpper(conn.Status) {
case "ESTABLISHED":
connStatsIPv4["established"]++
case "LISTEN":
connStatsIPv4["listen"]++
case "TIME_WAIT":
connStatsIPv4["time_wait"]++
case "CLOSE_WAIT":
connStatsIPv4["close_wait"]++
case "SYN_RECV":
connStatsIPv4["syn_recv"]++
}
}
}
// Count IPv6 connection states
connStatsIPv6 := map[string]int{
"established": 0,
"listen": 0,
"time_wait": 0,
"close_wait": 0,
"syn_recv": 0,
}
for _, conn := range connectionsIPv6 {
// Only count TCP connections (Type 1 = SOCK_STREAM)
if conn.Type == 1 {
switch strings.ToUpper(conn.Status) {
case "ESTABLISHED":
connStatsIPv6["established"]++
case "LISTEN":
connStatsIPv6["listen"]++
case "TIME_WAIT":
connStatsIPv6["time_wait"]++
case "CLOSE_WAIT":
connStatsIPv6["close_wait"]++
case "SYN_RECV":
connStatsIPv6["syn_recv"]++
}
}
}
// Add IPv4 connection counts to Nets
systemStats.Nets["conn_established"] = float64(connStatsIPv4["established"])
systemStats.Nets["conn_listen"] = float64(connStatsIPv4["listen"])
systemStats.Nets["conn_timewait"] = float64(connStatsIPv4["time_wait"])
systemStats.Nets["conn_closewait"] = float64(connStatsIPv4["close_wait"])
systemStats.Nets["conn_synrecv"] = float64(connStatsIPv4["syn_recv"])
// Add IPv6 connection counts to Nets
systemStats.Nets["conn6_established"] = float64(connStatsIPv6["established"])
systemStats.Nets["conn6_listen"] = float64(connStatsIPv6["listen"])
systemStats.Nets["conn6_timewait"] = float64(connStatsIPv6["time_wait"])
systemStats.Nets["conn6_closewait"] = float64(connStatsIPv6["close_wait"])
systemStats.Nets["conn6_synrecv"] = float64(connStatsIPv6["syn_recv"])
}
// Returns the size of the ZFS ARC memory cache in bytes // Returns the size of the ZFS ARC memory cache in bytes
func getARCSize() (uint64, error) { func getARCSize() (uint64, error) {
file, err := os.Open("/proc/spl/kstat/zfs/arcstats") file, err := os.Open("/proc/spl/kstat/zfs/arcstats")

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.7" 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

@@ -25,7 +25,12 @@ type alertInfo struct {
// startWorker is a long-running goroutine that processes alert tasks // startWorker is a long-running goroutine that processes alert tasks
// every x seconds. It must be running to process status alerts. // every x seconds. It must be running to process status alerts.
func (am *AlertManager) startWorker() { func (am *AlertManager) startWorker() {
tick := time.Tick(15 * time.Second) processPendingAlerts := time.Tick(15 * time.Second)
// check for status alerts that are not resolved when system comes up
// (can be removed if we figure out core bug in #1052)
checkStatusAlerts := time.Tick(561 * time.Second)
for { for {
select { select {
case <-am.stopChan: case <-am.stopChan:
@@ -41,7 +46,9 @@ func (am *AlertManager) startWorker() {
case "cancel": case "cancel":
am.pendingAlerts.Delete(task.alertRecord.Id) am.pendingAlerts.Delete(task.alertRecord.Id)
} }
case <-tick: case <-checkStatusAlerts:
resolveStatusAlerts(am.hub)
case <-processPendingAlerts:
// Check for expired alerts every tick // Check for expired alerts every tick
now := time.Now() now := time.Now()
for key, value := range am.pendingAlerts.Range { for key, value := range am.pendingAlerts.Range {
@@ -170,3 +177,35 @@ func (am *AlertManager) sendStatusAlert(alertStatus string, systemName string, a
LinkText: "View " + systemName, LinkText: "View " + systemName,
}) })
} }
// resolveStatusAlerts resolves any status alerts that weren't resolved
// when system came up (https://github.com/henrygd/beszel/issues/1052)
func resolveStatusAlerts(app core.App) error {
db := app.DB()
// Find all active status alerts where the system is actually up
var alertIds []string
err := db.NewQuery(`
SELECT a.id
FROM alerts a
JOIN systems s ON a.system = s.id
WHERE a.name = 'Status'
AND a.triggered = true
AND s.status = 'up'
`).Column(&alertIds)
if err != nil {
return err
}
// resolve all matching alert records
for _, alertId := range alertIds {
alert, err := app.FindRecordById("alerts", alertId)
if err != nil {
return err
}
alert.Set("triggered", false)
err = app.Save(alert)
if err != nil {
return err
}
}
return nil
}

View File

@@ -38,7 +38,7 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
case "Memory": case "Memory":
val = data.Info.MemPct val = data.Info.MemPct
case "Bandwidth": case "Bandwidth":
val = data.Info.NetworkSent + data.Info.NetworkRecv val = data.Info.Bandwidth
unit = " MB/s" unit = " MB/s"
case "Disk": case "Disk":
maxUsedPct := data.Info.DiskPct maxUsedPct := data.Info.DiskPct

View File

@@ -13,6 +13,7 @@ import (
"testing/synctest" "testing/synctest"
"time" "time"
"github.com/henrygd/beszel/internal/alerts"
beszelTests "github.com/henrygd/beszel/internal/tests" beszelTests "github.com/henrygd/beszel/internal/tests"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
@@ -369,33 +370,9 @@ func TestUserAlertsApi(t *testing.T) {
} }
} }
func getHubWithUser(t *testing.T) (*beszelTests.TestHub, *core.Record) {
hub, err := beszelTests.NewTestHub(t.TempDir())
assert.NoError(t, err)
hub.StartHub()
// Manually initialize the system manager to bind event hooks
err = hub.GetSystemManager().Initialize()
assert.NoError(t, err)
// Create a test user
user, err := beszelTests.CreateUser(hub, "test@example.com", "password")
assert.NoError(t, err)
// Create user settings for the test user (required for alert notifications)
userSettingsData := map[string]any{
"user": user.Id,
"settings": `{"emails":[test@example.com],"webhooks":[]}`,
}
_, err = beszelTests.CreateRecord(hub, "user_settings", userSettingsData)
assert.NoError(t, err)
return hub, user
}
func TestStatusAlerts(t *testing.T) { func TestStatusAlerts(t *testing.T) {
synctest.Test(t, func(t *testing.T) { synctest.Test(t, func(t *testing.T) {
hub, user := getHubWithUser(t) hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup() defer hub.Cleanup()
systems, err := beszelTests.CreateSystems(hub, 4, user.Id, "paused") systems, err := beszelTests.CreateSystems(hub, 4, user.Id, "paused")
@@ -476,7 +453,7 @@ func TestStatusAlerts(t *testing.T) {
func TestAlertsHistory(t *testing.T) { func TestAlertsHistory(t *testing.T) {
synctest.Test(t, func(t *testing.T) { synctest.Test(t, func(t *testing.T) {
hub, user := getHubWithUser(t) hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup() defer hub.Cleanup()
// Create systems and alerts // Create systems and alerts
@@ -602,3 +579,102 @@ func TestAlertsHistory(t *testing.T) {
assert.EqualValues(t, 2, totalHistoryCount, "Should have 2 total alert history records") assert.EqualValues(t, 2, totalHistoryCount, "Should have 2 total alert history records")
}) })
} }
func TestResolveStatusAlerts(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create a systemUp
systemUp, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "test-system",
"users": []string{user.Id},
"host": "127.0.0.1",
"status": "up",
})
assert.NoError(t, err)
systemDown, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "test-system-2",
"users": []string{user.Id},
"host": "127.0.0.2",
"status": "up",
})
assert.NoError(t, err)
// Create a status alertUp for the system
alertUp, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "Status",
"system": systemUp.Id,
"user": user.Id,
"min": 1,
})
assert.NoError(t, err)
alertDown, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "Status",
"system": systemDown.Id,
"user": user.Id,
"min": 1,
})
assert.NoError(t, err)
// Verify alert is not triggered initially
assert.False(t, alertUp.GetBool("triggered"), "Alert should not be triggered initially")
// Set the system to 'up' (this should not trigger the alert)
systemUp.Set("status", "up")
err = hub.SaveNoValidate(systemUp)
assert.NoError(t, err)
systemDown.Set("status", "down")
err = hub.SaveNoValidate(systemDown)
assert.NoError(t, err)
// Wait a moment for any processing
time.Sleep(10 * time.Millisecond)
// Verify alertUp is still not triggered after setting system to up
alertUp, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": alertUp.Id})
assert.NoError(t, err)
assert.False(t, alertUp.GetBool("triggered"), "Alert should not be triggered when system is up")
// Manually set both alerts triggered to true
alertUp.Set("triggered", true)
err = hub.SaveNoValidate(alertUp)
assert.NoError(t, err)
alertDown.Set("triggered", true)
err = hub.SaveNoValidate(alertDown)
assert.NoError(t, err)
// Verify we have exactly one alert with triggered true
triggeredCount, err := hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
assert.NoError(t, err)
assert.EqualValues(t, 2, triggeredCount, "Should have exactly two alerts with triggered true")
// Verify the specific alertUp is triggered
alertUp, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": alertUp.Id})
assert.NoError(t, err)
assert.True(t, alertUp.GetBool("triggered"), "Alert should be triggered")
// Verify we have two unresolved alert history records
alertHistoryCount, err := hub.CountRecords("alerts_history", dbx.HashExp{"resolved": ""})
assert.NoError(t, err)
assert.EqualValues(t, 2, alertHistoryCount, "Should have exactly two unresolved alert history records")
err = alerts.ResolveStatusAlerts(hub)
assert.NoError(t, err)
// Verify alertUp is not triggered after resolving
alertUp, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": alertUp.Id})
assert.NoError(t, err)
assert.False(t, alertUp.GetBool("triggered"), "Alert should not be triggered after resolving")
// Verify alertDown is still triggered
alertDown, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": alertDown.Id})
assert.NoError(t, err)
assert.True(t, alertDown.GetBool("triggered"), "Alert should still be triggered after resolving")
// Verify we have one unresolved alert history record
alertHistoryCount, err = hub.CountRecords("alerts_history", dbx.HashExp{"resolved": ""})
assert.NoError(t, err)
assert.EqualValues(t, 1, alertHistoryCount, "Should have exactly one unresolved alert history record")
}

View File

@@ -1,3 +1,6 @@
//go:build testing
// +build testing
package alerts package alerts
import ( import (
@@ -53,3 +56,7 @@ func (am *AlertManager) ForceExpirePendingAlerts() {
return true return true
}) })
} }
func ResolveStatusAlerts(app core.App) error {
return resolveStatusAlerts(app)
}

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

@@ -2,15 +2,18 @@ FROM --platform=$BUILDPLATFORM golang:alpine AS builder
WORKDIR /app WORKDIR /app
COPY go.mod go.sum ./
# RUN go mod download COPY ../go.mod ../go.sum ./
COPY *.go ./ RUN go mod download
COPY cmd ./cmd
COPY internal ./internal # Copy source files
COPY . ./
# Build # Build
ARG TARGETOS TARGETARCH ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./cmd/agent RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/cmd/agent
RUN rm -rf /tmp/*
# -------------------------- # --------------------------
# Final image: GPU-enabled agent with nvidia-smi # Final image: GPU-enabled agent with nvidia-smi
@@ -18,4 +21,7 @@ RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-
FROM nvidia/cuda:12.2.2-base-ubuntu22.04 FROM nvidia/cuda:12.2.2-base-ubuntu22.04
COPY --from=builder /agent /agent COPY --from=builder /agent /agent
# this is so we don't need to create the /tmp directory in the scratch container
COPY --from=builder /tmp /tmp
ENTRYPOINT ["/agent"] ENTRYPOINT ["/agent"]

View File

@@ -8,57 +8,51 @@ import (
"github.com/henrygd/beszel/internal/entities/container" "github.com/henrygd/beszel/internal/entities/container"
) )
type NetworkInterfaceStats struct {
NetworkSent float64 `json:"ns"`
NetworkRecv float64 `json:"nr"`
MaxNetworkSent float64 `json:"nsm,omitempty"`
MaxNetworkRecv float64 `json:"nrm,omitempty"`
TotalBytesSent uint64 `json:"tbs,omitempty"` // Total bytes sent since boot
TotalBytesRecv uint64 `json:"tbr,omitempty"` // Total bytes received since boot
}
type Stats struct { type Stats struct {
Cpu float64 `json:"cpu" cbor:"0,keyasint"` Cpu float64 `json:"cpu" cbor:"0,keyasint"`
MaxCpu float64 `json:"cpum,omitempty" cbor:"1,keyasint,omitempty"` MaxCpu float64 `json:"cpum,omitempty" cbor:"1,keyasint,omitempty"`
Mem float64 `json:"m" cbor:"2,keyasint"` Mem float64 `json:"m" cbor:"2,keyasint"`
MemUsed float64 `json:"mu" cbor:"3,keyasint"` MemUsed float64 `json:"mu" cbor:"3,keyasint"`
MemPct float64 `json:"mp" cbor:"4,keyasint"` MemPct float64 `json:"mp" cbor:"4,keyasint"`
MemBuffCache float64 `json:"mb" cbor:"5,keyasint"` MemBuffCache float64 `json:"mb" cbor:"5,keyasint"`
MemZfsArc float64 `json:"mz,omitempty" cbor:"6,keyasint,omitempty"` // ZFS ARC memory MemZfsArc float64 `json:"mz,omitempty" cbor:"6,keyasint,omitempty"` // ZFS ARC memory
Swap float64 `json:"s,omitempty" cbor:"7,keyasint,omitempty"` Swap float64 `json:"s,omitempty" cbor:"7,keyasint,omitempty"`
SwapUsed float64 `json:"su,omitempty" cbor:"8,keyasint,omitempty"` SwapUsed float64 `json:"su,omitempty" cbor:"8,keyasint,omitempty"`
DiskTotal float64 `json:"d" cbor:"9,keyasint"` DiskTotal float64 `json:"d" cbor:"9,keyasint"`
DiskUsed float64 `json:"du" cbor:"10,keyasint"` DiskUsed float64 `json:"du" cbor:"10,keyasint"`
DiskPct float64 `json:"dp" cbor:"11,keyasint"` DiskPct float64 `json:"dp" cbor:"11,keyasint"`
DiskReadPs float64 `json:"dr" cbor:"12,keyasint"` DiskReadPs float64 `json:"dr" cbor:"12,keyasint"`
DiskWritePs float64 `json:"dw" cbor:"13,keyasint"` DiskWritePs float64 `json:"dw" cbor:"13,keyasint"`
MaxDiskReadPs float64 `json:"drm,omitempty" cbor:"14,keyasint,omitempty"` MaxDiskReadPs float64 `json:"drm,omitempty" cbor:"14,keyasint,omitempty"`
MaxDiskWritePs float64 `json:"dwm,omitempty" cbor:"15,keyasint,omitempty"` MaxDiskWritePs float64 `json:"dwm,omitempty" cbor:"15,keyasint,omitempty"`
NetworkInterfaces map[string]NetworkInterfaceStats `json:"ni" cbor:"16,omitempty"` // Per-interface network stats NetworkSent float64 `json:"ns" cbor:"16,keyasint"`
NetworkSent float64 `json:"ns" cbor:"17,keyasint"` // Total network sent (MB/s) NetworkRecv float64 `json:"nr" cbor:"17,keyasint"`
NetworkRecv float64 `json:"nr" cbor:"18,keyasint"` // Total network recv (MB/s) MaxNetworkSent float64 `json:"nsm,omitempty" cbor:"18,keyasint,omitempty"`
MaxNetworkSent float64 `json:"nsm,omitempty" cbor:"19,keyasint,omitempty"` MaxNetworkRecv float64 `json:"nrm,omitempty" cbor:"19,keyasint,omitempty"`
MaxNetworkRecv float64 `json:"nrm,omitempty" cbor:"20,keyasint,omitempty"` Temperatures map[string]float64 `json:"t,omitempty" cbor:"20,keyasint,omitempty"`
Temperatures map[string]float64 `json:"t,omitempty" cbor:"21,keyasint,omitempty"` ExtraFs map[string]*FsStats `json:"efs,omitempty" cbor:"21,keyasint,omitempty"`
ExtraFs map[string]*FsStats `json:"efs,omitempty" cbor:"22,keyasint,omitempty"` GPUData map[string]GPUData `json:"g,omitempty" cbor:"22,keyasint,omitempty"`
GPUData map[string]GPUData `json:"g,omitempty" cbor:"23,keyasint,omitempty"` LoadAvg1 float64 `json:"l1,omitempty" cbor:"23,keyasint,omitempty"`
LoadAvg1 float64 `json:"l1,omitempty" cbor:"24,keyasint,omitempty"` LoadAvg5 float64 `json:"l5,omitempty" cbor:"24,keyasint,omitempty"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"25,keyasint,omitempty"` LoadAvg15 float64 `json:"l15,omitempty" cbor:"25,keyasint,omitempty"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"26,keyasint,omitempty"` Bandwidth [2]uint64 `json:"b,omitzero" cbor:"26,keyasint,omitzero"` // [sent bytes, recv bytes]
LoadAvg [3]float64 `json:"la,omitempty" cbor:"27,keyasint"` // [1min, 5min, 15min] MaxBandwidth [2]uint64 `json:"bm,omitzero" cbor:"27,keyasint,omitzero"` // [sent bytes, recv bytes]
Battery [2]uint8 `json:"bat,omitzero" cbor:"28,keyasint,omitzero"` // [percent, charge state] // TODO: remove other load fields in future release in favor of load avg array
MaxMem float64 `json:"mm,omitempty" cbor:"29,keyasint,omitempty"` LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"`
Nets map[string]float64 `json:"nets,omitempty" cbor:"30,keyasint,omitempty"` // Network connection statistics Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current]
MaxMem float64 `json:"mm,omitempty" cbor:"30,keyasint,omitempty"`
NetworkInterfaces map[string][4]uint64 `json:"ni,omitempty" cbor:"31,keyasint,omitempty"` // [upload bytes, download bytes, total upload, total download]
} }
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 {
@@ -76,12 +70,10 @@ type FsStats struct {
} }
type NetIoStats struct { type NetIoStats struct {
BytesRecv uint64 BytesRecv uint64
BytesSent uint64 BytesSent uint64
PacketsSent uint64 Time time.Time
PacketsRecv uint64 Name string
Time time.Time
Name string
} }
type Os = uint8 type Os = uint8
@@ -93,27 +85,37 @@ 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"`
Cores int `json:"c" cbor:"2,keyasint"` Cores int `json:"c" cbor:"2,keyasint"`
Threads int `json:"t,omitempty" cbor:"3,keyasint,omitempty"` Threads int `json:"t,omitempty" cbor:"3,keyasint,omitempty"`
CpuModel string `json:"m" cbor:"4,keyasint"` CpuModel string `json:"m" cbor:"4,keyasint"`
Uptime uint64 `json:"u" cbor:"5,keyasint"` Uptime uint64 `json:"u" cbor:"5,keyasint"`
Cpu float64 `json:"cpu" cbor:"6,keyasint"` Cpu float64 `json:"cpu" cbor:"6,keyasint"`
MemPct float64 `json:"mp" cbor:"7,keyasint"` MemPct float64 `json:"mp" cbor:"7,keyasint"`
DiskPct float64 `json:"dp" cbor:"8,keyasint"` DiskPct float64 `json:"dp" cbor:"8,keyasint"`
NetworkSent float64 `json:"ns" cbor:"9,keyasint"` // Per-interface total (MB/s) Bandwidth float64 `json:"b" cbor:"9,keyasint"`
NetworkRecv float64 `json:"nr" cbor:"10,keyasint"` // Per-interface total (MB/s) AgentVersion string `json:"v" cbor:"10,keyasint"`
AgentVersion string `json:"v" cbor:"11,keyasint"` Podman bool `json:"p,omitempty" cbor:"11,keyasint,omitempty"`
Podman bool `json:"p,omitempty" cbor:"12,keyasint,omitempty"` GpuPct float64 `json:"g,omitempty" cbor:"12,keyasint,omitempty"`
GpuPct float64 `json:"g,omitempty" cbor:"13,keyasint,omitempty"` DashboardTemp float64 `json:"dt,omitempty" cbor:"13,keyasint,omitempty"`
DashboardTemp float64 `json:"dt,omitempty" cbor:"14,keyasint,omitempty"` Os Os `json:"os" cbor:"14,keyasint"`
Os Os `json:"os" cbor:"15,keyasint"` LoadAvg1 float64 `json:"l1,omitempty" cbor:"15,keyasint,omitempty"`
LoadAvg1 float64 `json:"l1,omitempty" cbor:"16,keyasint,omitempty"` LoadAvg5 float64 `json:"l5,omitempty" cbor:"16,keyasint,omitempty"`
LoadAvg5 float64 `json:"l5,omitempty" cbor:"17,keyasint,omitempty"` LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"`
LoadAvg15 float64 `json:"l15,omitempty" cbor:"18,keyasint,omitempty"` BandwidthBytes uint64 `json:"bb" cbor:"18,keyasint"`
LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"` // [1min, 5min, 15min] // TODO: remove load fields in future release in favor of load avg array
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

@@ -206,51 +206,15 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.DiskPct += stats.DiskPct sum.DiskPct += stats.DiskPct
sum.DiskReadPs += stats.DiskReadPs sum.DiskReadPs += stats.DiskReadPs
sum.DiskWritePs += stats.DiskWritePs sum.DiskWritePs += stats.DiskWritePs
sum.LoadAvg1 += stats.LoadAvg1
sum.LoadAvg5 += stats.LoadAvg5
sum.LoadAvg15 += stats.LoadAvg15
sum.NetworkSent += stats.NetworkSent sum.NetworkSent += stats.NetworkSent
sum.NetworkRecv += stats.NetworkRecv sum.NetworkRecv += stats.NetworkRecv
sum.LoadAvg[0] += stats.LoadAvg[0] sum.LoadAvg[0] += stats.LoadAvg[0]
sum.LoadAvg[1] += stats.LoadAvg[1] sum.LoadAvg[1] += stats.LoadAvg[1]
sum.LoadAvg[2] += stats.LoadAvg[2] sum.LoadAvg[2] += stats.LoadAvg[2]
sum.Bandwidth[0] += stats.Bandwidth[0]
sum.Bandwidth[1] += stats.Bandwidth[1]
batterySum += int(stats.Battery[0]) batterySum += int(stats.Battery[0])
sum.Battery[1] = stats.Battery[1] sum.Battery[1] = stats.Battery[1]
if stats.NetworkInterfaces != nil {
if sum.NetworkInterfaces == nil {
sum.NetworkInterfaces = make(map[string]system.NetworkInterfaceStats, len(stats.NetworkInterfaces))
}
for key, value := range stats.NetworkInterfaces {
if _, ok := sum.NetworkInterfaces[key]; !ok {
sum.NetworkInterfaces[key] = system.NetworkInterfaceStats{}
}
ni := sum.NetworkInterfaces[key]
ni.NetworkSent += value.NetworkSent
ni.NetworkRecv += value.NetworkRecv
ni.MaxNetworkSent += value.MaxNetworkSent
ni.MaxNetworkRecv += value.MaxNetworkRecv
// For cumulative totals, use the maximum value (most recent)
if value.TotalBytesSent > ni.TotalBytesSent {
ni.TotalBytesSent = value.TotalBytesSent
}
if value.TotalBytesRecv > ni.TotalBytesRecv {
ni.TotalBytesRecv = value.TotalBytesRecv
}
sum.NetworkInterfaces[key] = ni
}
}
// Handle network connection stats - use the latest values (most recent sample)
if stats.Nets != nil {
if sum.Nets == nil {
sum.Nets = make(map[string]float64)
}
for key, value := range stats.Nets {
sum.Nets[key] = value
}
}
// Set peak values // Set peak values
sum.MaxCpu = max(sum.MaxCpu, stats.MaxCpu, stats.Cpu) sum.MaxCpu = max(sum.MaxCpu, stats.MaxCpu, stats.Cpu)
sum.MaxMem = max(sum.MaxMem, stats.MaxMem, stats.MemUsed) sum.MaxMem = max(sum.MaxMem, stats.MaxMem, stats.MemUsed)
@@ -258,6 +222,21 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.MaxNetworkRecv = max(sum.MaxNetworkRecv, stats.MaxNetworkRecv, stats.NetworkRecv) sum.MaxNetworkRecv = max(sum.MaxNetworkRecv, stats.MaxNetworkRecv, stats.NetworkRecv)
sum.MaxDiskReadPs = max(sum.MaxDiskReadPs, stats.MaxDiskReadPs, stats.DiskReadPs) sum.MaxDiskReadPs = max(sum.MaxDiskReadPs, stats.MaxDiskReadPs, stats.DiskReadPs)
sum.MaxDiskWritePs = max(sum.MaxDiskWritePs, stats.MaxDiskWritePs, stats.DiskWritePs) sum.MaxDiskWritePs = max(sum.MaxDiskWritePs, stats.MaxDiskWritePs, stats.DiskWritePs)
sum.MaxBandwidth[0] = max(sum.MaxBandwidth[0], stats.MaxBandwidth[0], stats.Bandwidth[0])
sum.MaxBandwidth[1] = max(sum.MaxBandwidth[1], stats.MaxBandwidth[1], stats.Bandwidth[1])
// Accumulate network interfaces
if sum.NetworkInterfaces == nil {
sum.NetworkInterfaces = make(map[string][4]uint64, len(stats.NetworkInterfaces))
}
for key, value := range stats.NetworkInterfaces {
sum.NetworkInterfaces[key] = [4]uint64{
sum.NetworkInterfaces[key][0] + value[0],
sum.NetworkInterfaces[key][1] + value[1],
max(sum.NetworkInterfaces[key][2], value[2]),
max(sum.NetworkInterfaces[key][3], value[3]),
}
}
// Accumulate temperatures // Accumulate temperatures
if stats.Temperatures != nil { if stats.Temperatures != nil {
@@ -305,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
} }
} }
@@ -325,26 +314,27 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.DiskPct = twoDecimals(sum.DiskPct / count) sum.DiskPct = twoDecimals(sum.DiskPct / count)
sum.DiskReadPs = twoDecimals(sum.DiskReadPs / count) sum.DiskReadPs = twoDecimals(sum.DiskReadPs / count)
sum.DiskWritePs = twoDecimals(sum.DiskWritePs / count) sum.DiskWritePs = twoDecimals(sum.DiskWritePs / count)
sum.LoadAvg1 = twoDecimals(sum.LoadAvg1 / count)
sum.LoadAvg5 = twoDecimals(sum.LoadAvg5 / count)
sum.LoadAvg15 = twoDecimals(sum.LoadAvg15 / count)
sum.NetworkSent = twoDecimals(sum.NetworkSent / count) sum.NetworkSent = twoDecimals(sum.NetworkSent / count)
sum.NetworkRecv = twoDecimals(sum.NetworkRecv / count) sum.NetworkRecv = twoDecimals(sum.NetworkRecv / count)
sum.LoadAvg[0] = twoDecimals(sum.LoadAvg[0] / count) sum.LoadAvg[0] = twoDecimals(sum.LoadAvg[0] / count)
sum.LoadAvg[1] = twoDecimals(sum.LoadAvg[1] / count) sum.LoadAvg[1] = twoDecimals(sum.LoadAvg[1] / count)
sum.LoadAvg[2] = twoDecimals(sum.LoadAvg[2] / count) sum.LoadAvg[2] = twoDecimals(sum.LoadAvg[2] / count)
sum.Bandwidth[0] = sum.Bandwidth[0] / uint64(count)
sum.Bandwidth[1] = sum.Bandwidth[1] / uint64(count)
sum.Battery[0] = uint8(batterySum / int(count)) sum.Battery[0] = uint8(batterySum / int(count))
// Average network interfaces
if sum.NetworkInterfaces != nil { if sum.NetworkInterfaces != nil {
for key := range sum.NetworkInterfaces { for key := range sum.NetworkInterfaces {
ni := sum.NetworkInterfaces[key] sum.NetworkInterfaces[key] = [4]uint64{
ni.NetworkSent = twoDecimals(ni.NetworkSent / count) sum.NetworkInterfaces[key][0] / uint64(count),
ni.NetworkRecv = twoDecimals(ni.NetworkRecv / count) sum.NetworkInterfaces[key][1] / uint64(count),
ni.MaxNetworkSent = twoDecimals(max(ni.MaxNetworkSent, ni.NetworkSent)) sum.NetworkInterfaces[key][2],
ni.MaxNetworkRecv = twoDecimals(max(ni.MaxNetworkRecv, ni.NetworkRecv)) sum.NetworkInterfaces[key][3],
sum.NetworkInterfaces[key] = ni }
} }
} }
// Average temperatures // Average temperatures
if sum.Temperatures != nil && tempCount > 0 { if sum.Temperatures != nil && tempCount > 0 {
for key := range sum.Temperatures { for key := range sum.Temperatures {
@@ -373,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
} }
} }
@@ -409,15 +406,19 @@ func (rm *RecordManager) AverageContainerStats(db dbx.Builder, records RecordIds
} }
sums[stat.Name].Cpu += stat.Cpu sums[stat.Name].Cpu += stat.Cpu
sums[stat.Name].Mem += stat.Mem sums[stat.Name].Mem += stat.Mem
sums[stat.Name].NetworkSent += stat.NetworkSent
sums[stat.Name].NetworkRecv += stat.NetworkRecv
} }
} }
result := make([]container.Stats, 0, len(sums)) result := make([]container.Stats, 0, len(sums))
for _, value := range sums { for _, value := range sums {
result = append(result, container.Stats{ result = append(result, container.Stats{
Name: value.Name, Name: value.Name,
Cpu: twoDecimals(value.Cpu / count), Cpu: twoDecimals(value.Cpu / count),
Mem: twoDecimals(value.Mem / count), Mem: twoDecimals(value.Mem / count),
NetworkSent: twoDecimals(value.NetworkSent / count),
NetworkRecv: twoDecimals(value.NetworkRecv / count),
}) })
} }
return result return result

View File

@@ -175,7 +175,7 @@ func TestDeleteOldSystemStats(t *testing.T) {
} }
// Run deletion // Run deletion
err = records.TestDeleteOldSystemStats(hub) err = records.DeleteOldSystemStats(hub)
require.NoError(t, err) require.NoError(t, err)
// Verify results // Verify results
@@ -268,7 +268,7 @@ func TestDeleteOldAlertsHistory(t *testing.T) {
assert.Equal(t, int64(tc.alertCount), countBefore, "Initial count should match") assert.Equal(t, int64(tc.alertCount), countBefore, "Initial count should match")
// Run deletion // Run deletion
err = records.TestDeleteOldAlertsHistory(hub, tc.countToKeep, tc.countBeforeDeletion) err = records.DeleteOldAlertsHistory(hub, tc.countToKeep, tc.countBeforeDeletion)
require.NoError(t, err) require.NoError(t, err)
// Count after deletion // Count after deletion
@@ -332,7 +332,7 @@ func TestDeleteOldAlertsHistoryEdgeCases(t *testing.T) {
} }
// Should not error and should not delete anything // Should not error and should not delete anything
err = records.TestDeleteOldAlertsHistory(hub, 10, 20) err = records.DeleteOldAlertsHistory(hub, 10, 20)
require.NoError(t, err) require.NoError(t, err)
count, err := hub.CountRecords("alerts_history") count, err := hub.CountRecords("alerts_history")
@@ -346,7 +346,7 @@ func TestDeleteOldAlertsHistoryEdgeCases(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Should not error with empty table // Should not error with empty table
err = records.TestDeleteOldAlertsHistory(hub, 10, 20) err = records.DeleteOldAlertsHistory(hub, 10, 20)
require.NoError(t, err) require.NoError(t, err)
}) })
} }
@@ -376,7 +376,7 @@ func TestTwoDecimals(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
result := records.TestTwoDecimals(tc.input) result := records.TwoDecimals(tc.input)
assert.InDelta(t, tc.expected, result, 0.02, "twoDecimals(%f) should equal %f", tc.input, tc.expected) assert.InDelta(t, tc.expected, result, 0.02, "twoDecimals(%f) should equal %f", tc.input, tc.expected)
} }
} }

View File

@@ -7,17 +7,17 @@ import (
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
) )
// TestDeleteOldSystemStats exposes deleteOldSystemStats for testing // DeleteOldSystemStats exposes deleteOldSystemStats for testing
func TestDeleteOldSystemStats(app core.App) error { func DeleteOldSystemStats(app core.App) error {
return deleteOldSystemStats(app) return deleteOldSystemStats(app)
} }
// TestDeleteOldAlertsHistory exposes deleteOldAlertsHistory for testing // DeleteOldAlertsHistory exposes deleteOldAlertsHistory for testing
func TestDeleteOldAlertsHistory(app core.App, countToKeep, countBeforeDeletion int) error { func DeleteOldAlertsHistory(app core.App, countToKeep, countBeforeDeletion int) error {
return deleteOldAlertsHistory(app, countToKeep, countBeforeDeletion) return deleteOldAlertsHistory(app, countToKeep, countBeforeDeletion)
} }
// TestTwoDecimals exposes twoDecimals for testing // TwoDecimals exposes twoDecimals for testing
func TestTwoDecimals(value float64) float64 { func TwoDecimals(value float64) float64 {
return twoDecimals(value) return twoDecimals(value)
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "beszel", "name": "beszel",
"private": true, "private": true,
"version": "0.12.7", "version": "0.12.11",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host", "dev": "vite --host",
@@ -76,4 +76,4 @@
"optionalDependencies": { "optionalDependencies": {
"@esbuild/linux-arm64": "^0.21.5" "@esbuild/linux-arm64": "^0.21.5"
} }
} }

View File

@@ -1,5 +1,9 @@
import { Trans } from "@lingui/react/macro"
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router"
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
import { memo, useEffect, useRef, useState } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import {
Dialog, Dialog,
@@ -10,34 +14,30 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog" } from "@/components/ui/dialog"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { isReadOnlyUser, pb } from "@/lib/api"
import { SystemStatus } from "@/lib/enums"
import { $publicKey } from "@/lib/stores" import { $publicKey } from "@/lib/stores"
import { cn, generateToken, tokenMap, useBrowserStorage } from "@/lib/utils" import { cn, generateToken, tokenMap, useBrowserStorage } from "@/lib/utils"
import { pb, isReadOnlyUser } from "@/lib/api" import type { SystemRecord } from "@/types"
import { useStore } from "@nanostores/react"
import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
import { memo, useEffect, useRef, useState } from "react"
import { $router, basePath, Link, navigate } from "./router"
import { SystemRecord } from "@/types"
import { SystemStatus } from "@/lib/enums"
import { AppleIcon, DockerIcon, FreeBsdIcon, TuxIcon, WindowsIcon } from "./ui/icons"
import { InputCopy } from "./ui/input-copy"
import { getPagePath } from "@nanostores/router"
import { import {
copyDockerCompose, copyDockerCompose,
copyDockerRun, copyDockerRun,
copyLinuxCommand, copyLinuxCommand,
copyWindowsCommand, copyWindowsCommand,
DropdownItem, type DropdownItem,
InstallDropdown, InstallDropdown,
} from "./install-dropdowns" } from "./install-dropdowns"
import { $router, basePath, Link, navigate } from "./router"
import { DropdownMenu, DropdownMenuTrigger } from "./ui/dropdown-menu" import { DropdownMenu, DropdownMenuTrigger } from "./ui/dropdown-menu"
import { AppleIcon, DockerIcon, FreeBsdIcon, TuxIcon, WindowsIcon } from "./ui/icons"
import { InputCopy } from "./ui/input-copy"
export function AddSystemButton({ className }: { className?: string }) { export function AddSystemButton({ className }: { className?: string }) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
let opened = useRef(false) const opened = useRef(false)
if (open) { if (open) {
opened.current = true opened.current = true
} }

View File

@@ -1,11 +1,11 @@
import { ColumnDef } from "@tanstack/react-table"
import { AlertsHistoryRecord } from "@/types"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { formatShortDate, toFixedFloat, formatDuration, cn } from "@/lib/utils"
import { alertInfo } from "@/lib/alerts"
import { Trans } from "@lingui/react/macro"
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
import type { ColumnDef } from "@tanstack/react-table"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { alertInfo } from "@/lib/alerts"
import { cn, formatDuration, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { AlertsHistoryRecord } from "@/types"
export const alertsHistoryColumns: ColumnDef<AlertsHistoryRecord>[] = [ export const alertsHistoryColumns: ColumnDef<AlertsHistoryRecord>[] = [
{ {
@@ -38,7 +38,7 @@ export const alertsHistoryColumns: ColumnDef<AlertsHistoryRecord>[] = [
</Button> </Button>
), ),
cell: ({ getValue, row }) => { cell: ({ getValue, row }) => {
let name = getValue() as string const name = getValue() as string
const info = alertInfo[row.original.name] const info = alertInfo[row.original.name]
const Icon = info?.icon const Icon = info?.icon

View File

@@ -1,13 +1,13 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { memo, useMemo, useState } from "react"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { $alerts } from "@/lib/stores"
import { BellIcon } from "lucide-react" import { BellIcon } from "lucide-react"
import { cn } from "@/lib/utils" import { memo, useMemo, useState } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { SystemRecord } from "@/types"
import { AlertDialogContent } from "./alerts-sheet"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet" import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { $alerts } from "@/lib/stores"
import { cn } from "@/lib/utils"
import type { SystemRecord } from "@/types"
import { AlertDialogContent } from "./alerts-sheet"
export default memo(function AlertsButton({ system }: { system: SystemRecord }) { export default memo(function AlertsButton({ system }: { system: SystemRecord }) {
const [opened, setOpened] = useState(false) const [opened, setOpened] = useState(false)

View File

@@ -1,21 +1,20 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { Trans, Plural } from "@lingui/react/macro" import { Plural, Trans } from "@lingui/react/macro"
import { $alerts, $systems } from "@/lib/stores"
import { cn, debounce } from "@/lib/utils"
import { alertInfo } from "@/lib/alerts"
import { Switch } from "@/components/ui/switch"
import { AlertInfo, AlertRecord, SystemRecord } from "@/types"
import { lazy, memo, Suspense, useMemo, useState } from "react"
import { toast } from "@/components/ui/use-toast"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router" import { getPagePath } from "@nanostores/router"
import { Checkbox } from "@/components/ui/checkbox" import { GlobeIcon, ServerIcon } from "lucide-react"
import { DialogTitle, DialogDescription } from "@/components/ui/dialog" import { lazy, memo, Suspense, useMemo, useState } from "react"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { ServerIcon, GlobeIcon } from "lucide-react"
import { $router, Link } from "@/components/router" import { $router, Link } from "@/components/router"
import { DialogHeader } from "@/components/ui/dialog" import { Checkbox } from "@/components/ui/checkbox"
import { DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Switch } from "@/components/ui/switch"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { toast } from "@/components/ui/use-toast"
import { alertInfo } from "@/lib/alerts"
import { pb } from "@/lib/api" import { pb } from "@/lib/api"
import { $alerts, $systems } from "@/lib/stores"
import { cn, debounce } from "@/lib/utils"
import type { AlertInfo, AlertRecord, SystemRecord } from "@/types"
const Slider = lazy(() => import("@/components/ui/slider")) const Slider = lazy(() => import("@/components/ui/slider"))
@@ -172,7 +171,7 @@ export function AlertContent({
const [checked, setChecked] = useState(global ? false : !!alert) const [checked, setChecked] = useState(global ? false : !!alert)
const [min, setMin] = useState(alert?.min || 10) const [min, setMin] = useState(alert?.min || 10)
const [value, setValue] = useState(alert?.value || (singleDescription ? 0 : alertData.start ?? 80)) const [value, setValue] = useState(alert?.value || (singleDescription ? 0 : (alertData.start ?? 80)))
const Icon = alertData.icon const Icon = alertData.icon

View File

@@ -1,9 +1,16 @@
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { cn, formatShortDate, chartMargin } from "@/lib/utils"
import { useYAxisWidth } from "./hooks"
import { ChartData, SystemStatsRecord } from "@/types"
import { useMemo } from "react" import { useMemo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
xAxis,
} from "@/components/ui/chart"
import { chartMargin, cn, formatShortDate } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { useYAxisWidth } from "./hooks"
export type DataPoint = { export type DataPoint = {
label: string label: string
@@ -20,6 +27,8 @@ export default function AreaChartDefault({
contentFormatter, contentFormatter,
dataPoints, dataPoints,
domain, domain,
legend,
itemSorter,
}: // logRender = false, }: // logRender = false,
{ {
chartData: ChartData chartData: ChartData
@@ -29,10 +38,13 @@ export default function AreaChartDefault({
contentFormatter: ({ value, payload }: { value: number; payload: SystemStatsRecord }) => string contentFormatter: ({ value, payload }: { value: number; payload: SystemStatsRecord }) => string
dataPoints?: DataPoint[] dataPoints?: DataPoint[]
domain?: [number, number] domain?: [number, number]
legend?: boolean
itemSorter?: (a: any, b: any) => number
// logRender?: boolean // logRender?: boolean
}) { }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
// biome-ignore lint/correctness/useExhaustiveDependencies: ignore
return useMemo(() => { return useMemo(() => {
if (chartData.systemStats.length === 0) { if (chartData.systemStats.length === 0) {
return null return null
@@ -63,6 +75,8 @@ export default function AreaChartDefault({
<ChartTooltip <ChartTooltip
animationEasing="ease-out" animationEasing="ease-out"
animationDuration={150} animationDuration={150}
// @ts-expect-error
itemSorter={itemSorter}
content={ content={
<ChartTooltipContent <ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
@@ -70,11 +84,14 @@ export default function AreaChartDefault({
/> />
} }
/> />
{dataPoints?.map((dataPoint, i) => { {dataPoints?.map((dataPoint) => {
const color = `var(--chart-${dataPoint.color})` let { color } = dataPoint
if (typeof color === "number") {
color = `var(--chart-${color})`
}
return ( return (
<Area <Area
key={i} key={dataPoint.label}
dataKey={dataPoint.dataKey} dataKey={dataPoint.dataKey}
name={dataPoint.label} name={dataPoint.label}
type="monotoneX" type="monotoneX"
@@ -85,7 +102,7 @@ export default function AreaChartDefault({
/> />
) )
})} })}
{/* <ChartLegend content={<ChartLegendContent />} /> */} {legend && <ChartLegend content={<ChartLegendContent />} />}
</AreaChart> </AreaChart>
</ChartContainer> </ChartContainer>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { useStore } from "@nanostores/react"
import { HistoryIcon } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { $chartTime } from "@/lib/stores" import { $chartTime } from "@/lib/stores"
import { chartTimeData, cn } from "@/lib/utils" import { chartTimeData, cn } from "@/lib/utils"
import { ChartTimes } from "@/types" import type { ChartTimes } from "@/types"
import { useStore } from "@nanostores/react"
import { HistoryIcon } from "lucide-react"
export default function ChartTimeSelect({ className }: { className?: string }) { export default function ChartTimeSelect({ className }: { className?: string }) {
const chartTime = useStore($chartTime) const chartTime = useStore($chartTime)

View File

@@ -1,124 +0,0 @@
import { memo } from "react"
import { useLingui } from "@lingui/react/macro"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
xAxis,
} from "@/components/ui/chart"
import { cn, formatShortDate, chartMargin } from "@/lib/utils"
import { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks"
export default memo(function ConnectionChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { t } = useLingui()
if (chartData.systemStats.length === 0) {
return null
}
const dataKeys = [
{
name: t`IPv4 Established`,
dataKey: "stats.nets.conn_established",
color: "hsl(220, 70%, 50%)", // Blue
},
{
name: t`IPv4 Listen`,
dataKey: "stats.nets.conn_listen",
color: "hsl(142, 70%, 45%)", // Green
},
{
name: t`IPv4 Time Wait`,
dataKey: "stats.nets.conn_timewait",
color: "hsl(48, 96%, 53%)", // Yellow
},
{
name: t`IPv4 Close Wait`,
dataKey: "stats.nets.conn_closewait",
color: "hsl(271, 81%, 56%)", // Purple
},
{
name: t`IPv4 Syn Recv`,
dataKey: "stats.nets.conn_synrecv",
color: "hsl(9, 78%, 56%)", // Red
},
{
name: t`IPv6 Established`,
dataKey: "stats.nets.conn6_established",
color: "hsl(220, 70%, 65%)", // Light Blue
},
{
name: t`IPv6 Listen`,
dataKey: "stats.nets.conn6_listen",
color: "hsl(142, 70%, 60%)", // Light Green
},
{
name: t`IPv6 Time Wait`,
dataKey: "stats.nets.conn6_timewait",
color: "hsl(48, 96%, 68%)", // Light Yellow
},
{
name: t`IPv6 Close Wait`,
dataKey: "stats.nets.conn6_closewait",
color: "hsl(271, 81%, 71%)", // Light Purple
},
{
name: t`IPv6 Syn Recv`,
dataKey: "stats.nets.conn6_synrecv",
color: "hsl(9, 78%, 71%)", // Light Red
},
]
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<AreaChart accessibilityLayer data={chartData.systemStats} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
width={yAxisWidth}
tickFormatter={(value) => updateYAxisWidth(value.toString())}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }) => value.toString()}
/>
}
/>
<ChartLegend content={<ChartLegendContent />} />
{dataKeys.map((key, i) => (
<Area
key={i}
dataKey={key.dataKey}
name={key.name}
type="monotoneX"
fill={key.color}
fillOpacity={0.3}
stroke={key.color}
strokeOpacity={1}
isAnimationActive={false}
/>
))}
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,13 +1,13 @@
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { memo, useMemo } from "react"
import { cn, formatShortDate, chartMargin, toFixedFloat, formatBytes, decimalString } from "@/lib/utils"
// import Spinner from '../spinner' // import Spinner from '../spinner'
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { memo, useMemo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { ChartType, Unit } from "@/lib/enums"
import { $containerFilter, $userSettings } from "@/lib/stores" import { $containerFilter, $userSettings } from "@/lib/stores"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types" import type { ChartData } from "@/types"
import { Separator } from "../ui/separator" import { Separator } from "../ui/separator"
import { ChartType, Unit } from "@/lib/enums"
import { useYAxisWidth } from "./hooks" import { useYAxisWidth } from "./hooks"
export default memo(function ContainerChart({ export default memo(function ContainerChart({

View File

@@ -1,10 +1,10 @@
import { useLingui } from "@lingui/react/macro"
import { memo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts" import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart" import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
import { ChartData } from "@/types"
import { memo } from "react"
import { useLingui } from "@lingui/react/macro"
import { Unit } from "@/lib/enums" import { Unit } from "@/lib/enums"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks" import { useYAxisWidth } from "./hooks"
export default memo(function DiskChart({ export default memo(function DiskChart({

View File

@@ -1,5 +1,5 @@
import { memo, useMemo } from "react"
import { CartesianGrid, Line, LineChart, YAxis } from "recharts" import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
import { import {
ChartContainer, ChartContainer,
ChartLegend, ChartLegend,
@@ -8,9 +8,8 @@ import {
ChartTooltipContent, ChartTooltipContent,
xAxis, xAxis,
} from "@/components/ui/chart" } from "@/components/ui/chart"
import { cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils" import { chartMargin, cn, decimalString, formatShortDate, toFixedFloat } from "@/lib/utils"
import { ChartData } from "@/types" import type { ChartData } from "@/types"
import { memo, useMemo } from "react"
import { useYAxisWidth } from "./hooks" import { useYAxisWidth } from "./hooks"
export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData }) { export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData }) {
@@ -27,10 +26,10 @@ export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData
colors: Record<string, string> colors: Record<string, string>
} }
const powerSums = {} as Record<string, number> const powerSums = {} as Record<string, number>
for (let data of chartData.systemStats) { for (const data of chartData.systemStats) {
let newData = { created: data.created } as Record<string, number | string> const newData = { created: data.created } as Record<string, number | string>
for (let gpu of Object.values(data.stats?.g ?? {})) { for (const gpu of Object.values(data.stats?.g ?? {})) {
if (gpu.p) { if (gpu.p) {
const name = gpu.n const name = gpu.n
newData[name] = gpu.p newData[name] = gpu.p
@@ -40,8 +39,8 @@ export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData
newChartData.data.push(newData) newChartData.data.push(newData)
} }
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 (let 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])
@@ -67,7 +66,7 @@ export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData
width={yAxisWidth} width={yAxisWidth}
tickFormatter={(value) => { tickFormatter={(value) => {
const val = toFixedFloat(value, 2) const val = toFixedFloat(value, 2)
return updateYAxisWidth(val + "W") return updateYAxisWidth(`${val}W`)
}} }}
tickLine={false} tickLine={false}
axisLine={false} axisLine={false}
@@ -76,12 +75,12 @@ export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData
<ChartTooltip <ChartTooltip
animationEasing="ease-out" animationEasing="ease-out"
animationDuration={150} animationDuration={150}
// @ts-ignore // @ts-expect-error
itemSorter={(a, b) => b.value - a.value} itemSorter={(a, b) => b.value - a.value}
content={ content={
<ChartTooltipContent <ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={(item) => decimalString(item.value) + "W"} contentFormatter={(item) => `${decimalString(item.value)}W`}
// indicator="line" // indicator="line"
/> />
} }

View File

@@ -1,6 +1,6 @@
import { useMemo, useState } from "react" import { useMemo, useState } from "react"
import { ChartConfig } from "@/components/ui/chart" import type { ChartConfig } from "@/components/ui/chart"
import { ChartData } from "@/types" import type { ChartData, SystemStats, SystemStatsRecord } from "@/types"
/** Chart configurations for CPU, memory, and network usage charts */ /** Chart configurations for CPU, memory, and network usage charts */
export interface ContainerChartConfigs { export interface ContainerChartConfigs {
@@ -105,3 +105,21 @@ export function useYAxisWidth() {
} }
return { yAxisWidth, updateYAxisWidth } return { yAxisWidth, updateYAxisWidth }
} }
// Assures consistent colors for network interfaces
export function useNetworkInterfaces(interfaces: SystemStats["ni"]) {
const keys = Object.keys(interfaces ?? {})
const sortedKeys = keys.sort((a, b) => (interfaces?.[b]?.[3] ?? 0) - (interfaces?.[a]?.[3] ?? 0))
return {
length: sortedKeys.length,
data: (index = 3) => {
return sortedKeys.map((key) => ({
label: key,
dataKey: ({ stats }: SystemStatsRecord) => stats?.ni?.[key]?.[index],
color: `hsl(${220 + (((sortedKeys.indexOf(key) * 360) / sortedKeys.length) % 360)}, 70%, 50%)`,
opacity: 0.3,
}))
},
}
}

View File

@@ -0,0 +1,110 @@
import { useMemo } from "react"
import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
xAxis,
} from "@/components/ui/chart"
import { chartMargin, cn, formatShortDate } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { useYAxisWidth } from "./hooks"
export type DataPoint = {
label: string
dataKey: (data: SystemStatsRecord) => number | undefined
color: number | string
}
export default function LineChartDefault({
chartData,
max,
maxToggled,
tickFormatter,
contentFormatter,
dataPoints,
domain,
legend,
itemSorter,
}: // logRender = false,
{
chartData: ChartData
max?: number
maxToggled?: boolean
tickFormatter: (value: number, index: number) => string
contentFormatter: ({ value, payload }: { value: number; payload: SystemStatsRecord }) => string
dataPoints?: DataPoint[]
domain?: [number, number]
legend?: boolean
itemSorter?: (a: any, b: any) => number
// logRender?: boolean
}) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
// biome-ignore lint/correctness/useExhaustiveDependencies: ignore
return useMemo(() => {
if (chartData.systemStats.length === 0) {
return null
}
// if (logRender) {
// console.log("Rendered at", new Date())
// }
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<LineChart accessibilityLayer data={chartData.systemStats} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
width={yAxisWidth}
domain={domain ?? [0, max ?? "auto"]}
tickFormatter={(value, index) => updateYAxisWidth(tickFormatter(value, index))}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
// @ts-expect-error
itemSorter={itemSorter}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={contentFormatter}
/>
}
/>
{dataPoints?.map((dataPoint) => {
let { color } = dataPoint
if (typeof color === "number") {
color = `var(--chart-${color})`
}
return (
<Line
key={dataPoint.label}
dataKey={dataPoint.dataKey}
name={dataPoint.label}
type="monotoneX"
dot={false}
strokeWidth={1.5}
stroke={color}
isAnimationActive={false}
/>
)
})}
{legend && <ChartLegend content={<ChartLegendContent />} />}
</LineChart>
</ChartContainer>
</div>
)
}, [chartData.systemStats.at(-1), yAxisWidth, maxToggled])
}

View File

@@ -1,5 +1,6 @@
import { t } from "@lingui/core/macro"
import { memo } from "react"
import { CartesianGrid, Line, LineChart, YAxis } from "recharts" import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
import { import {
ChartContainer, ChartContainer,
ChartLegend, ChartLegend,
@@ -8,10 +9,8 @@ import {
ChartTooltipContent, ChartTooltipContent,
xAxis, xAxis,
} from "@/components/ui/chart" } from "@/components/ui/chart"
import { cn, formatShortDate, toFixedFloat, decimalString, chartMargin } from "@/lib/utils" import { chartMargin, cn, decimalString, formatShortDate, toFixedFloat } from "@/lib/utils"
import { ChartData, SystemStats } from "@/types" import type { ChartData, SystemStats } from "@/types"
import { memo } from "react"
import { t } from "@lingui/core/macro"
import { useYAxisWidth } from "./hooks" import { useYAxisWidth } from "./hooks"
export default memo(function LoadAverageChart({ chartData }: { chartData: ChartData }) { export default memo(function LoadAverageChart({ chartData }: { chartData: ChartData }) {
@@ -60,7 +59,7 @@ export default memo(function LoadAverageChart({ chartData }: { chartData: ChartD
<ChartTooltip <ChartTooltip
animationEasing="ease-out" animationEasing="ease-out"
animationDuration={150} animationDuration={150}
// @ts-ignore // @ts-expect-error
// itemSorter={(a, b) => b.value - a.value} // itemSorter={(a, b) => b.value - a.value}
content={ content={
<ChartTooltipContent <ChartTooltipContent

View File

@@ -1,10 +1,10 @@
import { useLingui } from "@lingui/react/macro"
import { memo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts" import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart" import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { cn, decimalString, formatShortDate, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
import { memo } from "react"
import { ChartData } from "@/types"
import { useLingui } from "@lingui/react/macro"
import { Unit } from "@/lib/enums" import { Unit } from "@/lib/enums"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks" import { useYAxisWidth } from "./hooks"
export default memo(function MemChart({ chartData, showMax }: { chartData: ChartData; showMax: boolean }) { export default memo(function MemChart({ chartData, showMax }: { chartData: ChartData; showMax: boolean }) {
@@ -53,7 +53,7 @@ export default memo(function MemChart({ chartData, showMax }: { chartData: Chart
animationDuration={150} animationDuration={150}
content={ content={
<ChartTooltipContent <ChartTooltipContent
// @ts-ignore // @ts-expect-error
itemSorter={(a, b) => a.order - b.order} itemSorter={(a, b) => a.order - b.order}
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }) => { contentFormatter={({ value }) => {

View File

@@ -1,164 +0,0 @@
import { memo, useMemo } from "react"
import { useLingui } from "@lingui/react/macro"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
xAxis,
ChartLegend,
ChartLegendContent,
} from "@/components/ui/chart"
import { cn, formatShortDate, chartMargin, formatBytes, toFixedFloat, decimalString } from "@/lib/utils"
import { ChartData } from "@/types"
import { useStore } from "@nanostores/react"
import { $networkInterfaceFilter, $userSettings } from "@/lib/stores"
import { Unit } from "@/lib/enums"
import { useYAxisWidth } from "./hooks"
const getNestedValue = (path: string, max = false, data: any): number | null => {
// path format is like "eth0.ns" or "eth0.nr"
// need to access data.stats.ni[interface][property]
const parts = path.split(".")
if (parts.length !== 2) return null
const [interfaceName, property] = parts
const propertyKey = property + (max ? "m" : "")
return data?.stats?.ni?.[interfaceName]?.[propertyKey] ?? null
}
export default memo(function NetworkInterfaceChart({
chartData,
maxToggled = false,
max,
}: {
chartData: ChartData
maxToggled?: boolean
max?: number
}) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { i18n } = useLingui()
const networkInterfaceFilter = useStore($networkInterfaceFilter)
const userSettings = useStore($userSettings)
const { chartTime } = chartData
const showMax = chartTime !== "1h" && maxToggled
// Get network interface names from the latest stats
const networkInterfaces = useMemo(() => {
if (chartData.systemStats.length === 0) return []
const latestStats = chartData.systemStats[chartData.systemStats.length - 1]
const allInterfaces = Object.keys(latestStats.stats.ni || {})
// Filter interfaces based on filter value
if (networkInterfaceFilter) {
return allInterfaces.filter((iface) => iface.toLowerCase().includes(networkInterfaceFilter.toLowerCase()))
}
return allInterfaces
}, [chartData.systemStats, networkInterfaceFilter])
const dataKeys = useMemo(() => {
// Generate colors for each interface - each interface gets a unique hue
// and sent/received use different shades of that hue
const interfaceColors = networkInterfaces.map((iface, index) => {
const hue = ((index * 360) / Math.max(networkInterfaces.length, 1)) % 360
return {
interface: iface,
sentColor: `hsl(${hue}, 70%, 45%)`, // Darker shade for sent
receivedColor: `hsl(${hue}, 70%, 65%)`, // Lighter shade for received
}
})
return interfaceColors.flatMap(({ interface: iface, sentColor, receivedColor }) => [
{
name: `${iface} Sent`,
dataKey: `${iface}.ns`,
color: sentColor,
type: "sent" as const,
interface: iface,
},
{
name: `${iface} Received`,
dataKey: `${iface}.nr`,
color: receivedColor,
type: "received" as const,
interface: iface,
},
])
}, [networkInterfaces, i18n.locale])
const colors = dataKeys.map((key) => key.name)
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<AreaChart accessibilityLayer data={chartData.systemStats} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
width={yAxisWidth}
tickFormatter={(value) => {
const { value: formattedValue, unit } = formatBytes(value, true, userSettings.unitNet ?? Unit.Bits, true)
const rounded = toFixedFloat(formattedValue, formattedValue >= 10 ? 1 : 2)
return updateYAxisWidth(`${rounded} ${unit}`)
}}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_: any, data: any) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }: any) => {
const { value: formattedValue, unit } = formatBytes(
value,
true,
userSettings.unitNet ?? Unit.Bits,
true
)
return (
<span className="flex">
{decimalString(formattedValue, formattedValue >= 10 ? 1 : 2)} {unit}
</span>
)
}}
/>
}
/>
{dataKeys.map((key, i) => {
const filtered =
networkInterfaceFilter && !key.interface.toLowerCase().includes(networkInterfaceFilter.toLowerCase())
let fillOpacity = filtered ? 0.05 : 0.4
let strokeOpacity = filtered ? 0.1 : 1
return (
<Area
key={i}
dataKey={getNestedValue.bind(null, key.dataKey, showMax)}
name={key.name}
type="monotoneX"
fill={key.color}
fillOpacity={fillOpacity}
stroke={key.color}
strokeOpacity={strokeOpacity}
activeDot={{ opacity: filtered ? 0 : 1 }}
isAnimationActive={false}
/>
)
})}
{colors.length < 12 && <ChartLegend content={<ChartLegendContent />} />}
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,12 +1,11 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { useStore } from "@nanostores/react"
import { memo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts" import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart" import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
import { ChartData } from "@/types"
import { memo } from "react"
import { $userSettings } from "@/lib/stores" import { $userSettings } from "@/lib/stores"
import { useStore } from "@nanostores/react" import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks" import { useYAxisWidth } from "./hooks"
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) { export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {

View File

@@ -1,5 +1,6 @@
import { useStore } from "@nanostores/react"
import { memo, useMemo } from "react"
import { CartesianGrid, Line, LineChart, YAxis } from "recharts" import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
import { import {
ChartContainer, ChartContainer,
ChartLegend, ChartLegend,
@@ -8,11 +9,9 @@ import {
ChartTooltipContent, ChartTooltipContent,
xAxis, xAxis,
} from "@/components/ui/chart" } from "@/components/ui/chart"
import { cn, formatShortDate, toFixedFloat, chartMargin, formatTemperature, decimalString } from "@/lib/utils"
import { ChartData } from "@/types"
import { memo, useMemo } from "react"
import { $temperatureFilter, $userSettings } from "@/lib/stores" import { $temperatureFilter, $userSettings } from "@/lib/stores"
import { useStore } from "@nanostores/react" import { chartMargin, cn, decimalString, formatShortDate, formatTemperature, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks" import { useYAxisWidth } from "./hooks"
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) { export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
@@ -31,18 +30,18 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
colors: Record<string, string> colors: Record<string, string>
} }
const tempSums = {} as Record<string, number> const tempSums = {} as Record<string, number>
for (let data of chartData.systemStats) { for (const data of chartData.systemStats) {
let newData = { created: data.created } as Record<string, number | string> const newData = { created: data.created } as Record<string, number | string>
let keys = Object.keys(data.stats?.t ?? {}) const keys = Object.keys(data.stats?.t ?? {})
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
let key = keys[i] const key = keys[i]
newData[key] = data.stats.t![key] newData[key] = data.stats.t![key]
tempSums[key] = (tempSums[key] ?? 0) + newData[key] tempSums[key] = (tempSums[key] ?? 0) + newData[key]
} }
newChartData.data.push(newData) newChartData.data.push(newData)
} }
const keys = Object.keys(tempSums).sort((a, b) => tempSums[b] - tempSums[a]) const keys = Object.keys(tempSums).sort((a, b) => tempSums[b] - tempSums[a])
for (let 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(${((keys.indexOf(key) * 360) / keys.length) % 360}, 60%, 55%)`
} }
return newChartData return newChartData
@@ -78,7 +77,7 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
<ChartTooltip <ChartTooltip
animationEasing="ease-out" animationEasing="ease-out"
animationDuration={150} animationDuration={150}
// @ts-ignore // @ts-expect-error
itemSorter={(a, b) => b.value - a.value} itemSorter={(a, b) => b.value - a.value}
content={ content={
<ChartTooltipContent <ChartTooltipContent
@@ -93,7 +92,7 @@ export default memo(function TemperatureChart({ chartData }: { chartData: ChartD
/> />
{colors.map((key) => { {colors.map((key) => {
const filtered = filter && !key.toLowerCase().includes(filter.toLowerCase()) const filtered = filter && !key.toLowerCase().includes(filter.toLowerCase())
let strokeOpacity = filtered ? 0.1 : 1 const strokeOpacity = filtered ? 0.1 : 1
return ( return (
<Line <Line
key={key} key={key}

View File

@@ -1,159 +0,0 @@
import { memo, useMemo } from "react"
import { useLingui } from "@lingui/react/macro"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
xAxis,
} from "@/components/ui/chart"
import { cn, formatShortDate, chartMargin, formatBytes, toFixedFloat, decimalString } from "@/lib/utils"
import { ChartData } from "@/types"
import { useStore } from "@nanostores/react"
import { $userSettings } from "@/lib/stores"
import { Unit } from "@/lib/enums"
import { useYAxisWidth } from "./hooks"
const getPerInterfaceBandwidth = (data: any): Record<string, { sent: number; recv: number }> | null => {
const networkInterfaces = data?.stats?.ni
if (!networkInterfaces) {
return null
}
const interfaceData: Record<string, { sent: number; recv: number }> = {}
let hasData = false
Object.entries(networkInterfaces).forEach(([name, iface]: [string, any]) => {
if (iface.tbs || iface.tbr) {
interfaceData[name] = {
sent: iface.tbs || 0,
recv: iface.tbr || 0,
}
hasData = true
}
})
return hasData ? interfaceData : null
}
export default memo(function TotalBandwidthChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { i18n } = useLingui()
const userSettings = useStore($userSettings)
// Transform data to include per-interface bandwidth
const { transformedData, interfaceNames } = useMemo(() => {
const allInterfaces = new Set<string>()
// First pass: collect all interface names
chartData.systemStats.forEach((dataPoint) => {
const interfaceData = getPerInterfaceBandwidth(dataPoint)
if (interfaceData) {
Object.keys(interfaceData).forEach((name) => allInterfaces.add(name))
}
})
const interfaceNames = Array.from(allInterfaces).sort()
// Second pass: transform data with per-interface values
const transformedData = chartData.systemStats.map((dataPoint) => {
const interfaceData = getPerInterfaceBandwidth(dataPoint)
const result: any = { ...dataPoint }
interfaceNames.forEach((interfaceName) => {
const data = interfaceData?.[interfaceName]
result[`${interfaceName}_sent`] = data?.sent || 0
result[`${interfaceName}_recv`] = data?.recv || 0
})
return result
})
return { transformedData, interfaceNames }
}, [chartData.systemStats])
// Generate dynamic data keys for each interface using same color scheme as NetworkInterfaceChart
const dataKeys = useMemo(() => {
const keys: Array<{ name: string; dataKey: string; color: string }> = []
interfaceNames.forEach((interfaceName, index) => {
// Use the same color calculation as NetworkInterfaceChart
const hue = ((index * 360) / Math.max(interfaceNames.length, 1)) % 360
keys.push({
name: `${interfaceName} Sent`,
dataKey: `${interfaceName}_sent`,
color: `hsl(${hue}, 70%, 45%)`, // Darker shade for sent (same as NetworkInterfaceChart)
})
keys.push({
name: `${interfaceName} Received`,
dataKey: `${interfaceName}_recv`,
color: `hsl(${hue}, 70%, 65%)`, // Lighter shade for received (same as NetworkInterfaceChart)
})
})
return keys
}, [interfaceNames])
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<AreaChart accessibilityLayer data={transformedData} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
width={yAxisWidth}
tickFormatter={(value) => {
const { value: formattedValue, unit } = formatBytes(value, false, userSettings.unitNet ?? Unit.Bytes)
const rounded = toFixedFloat(formattedValue, formattedValue >= 10 ? 1 : 2)
return updateYAxisWidth(`${rounded} ${unit}`)
}}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_: any, data: any) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }: any) => {
const { value: formattedValue, unit } = formatBytes(value, false, userSettings.unitNet ?? Unit.Bytes)
return (
<span className="flex">
{decimalString(formattedValue, formattedValue >= 10 ? 1 : 2)} {unit}
</span>
)
}}
/>
}
/>
<ChartLegend content={<ChartLegendContent />} />
{dataKeys.map((key, i) => (
<Area
key={i}
dataKey={key.dataKey}
name={key.name}
type="monotoneX"
fill={key.color}
fillOpacity={0.3}
stroke={key.color}
strokeOpacity={1}
isAnimationActive={false}
/>
))}
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,3 +1,7 @@
import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
import { getPagePath } from "@nanostores/router"
import { DialogDescription } from "@radix-ui/react-dialog"
import { import {
AlertOctagonIcon, AlertOctagonIcon,
BookIcon, BookIcon,
@@ -10,7 +14,7 @@ import {
SettingsIcon, SettingsIcon,
UsersIcon, UsersIcon,
} from "lucide-react" } from "lucide-react"
import { memo, useEffect, useMemo } from "react"
import { import {
CommandDialog, CommandDialog,
CommandEmpty, CommandEmpty,
@@ -21,15 +25,10 @@ import {
CommandSeparator, CommandSeparator,
CommandShortcut, CommandShortcut,
} from "@/components/ui/command" } from "@/components/ui/command"
import { memo, useEffect, useMemo } from "react" import { isAdmin } from "@/lib/api"
import { $systems } from "@/lib/stores" import { $systems } from "@/lib/stores"
import { getHostDisplayValue, listen } from "@/lib/utils" import { getHostDisplayValue, listen } from "@/lib/utils"
import { $router, basePath, navigate, prependBasePath } from "./router" import { $router, basePath, navigate, prependBasePath } from "./router"
import { Trans } from "@lingui/react/macro"
import { t } from "@lingui/core/macro"
import { getPagePath } from "@nanostores/router"
import { DialogDescription } from "@radix-ui/react-dialog"
import { isAdmin } from "@/lib/api"
export default memo(function CommandPalette({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) { export default memo(function CommandPalette({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) {
useEffect(() => { useEffect(() => {

View File

@@ -1,8 +1,8 @@
import { Trans } from "@lingui/react/macro"; import { Trans } from "@lingui/react/macro"
import { useEffect, useMemo, useRef } from "react" import { useEffect, useMemo, useRef } from "react"
import { $copyContent } from "@/lib/stores"
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./ui/dialog"
import { Textarea } from "./ui/textarea" import { Textarea } from "./ui/textarea"
import { $copyContent } from "@/lib/stores"
export default function CopyToClipboard({ content }: { content: string }) { export default function CopyToClipboard({ content }: { content: string }) {
return ( return (

View File

@@ -1,7 +1,7 @@
import { memo } from "react"
import { DropdownMenuContent, DropdownMenuItem } from "./ui/dropdown-menu"
import { copyToClipboard, getHubURL } from "@/lib/utils"
import { i18n } from "@lingui/core" import { i18n } from "@lingui/core"
import { memo } from "react"
import { copyToClipboard, getHubURL } from "@/lib/utils"
import { DropdownMenuContent, DropdownMenuItem } from "./ui/dropdown-menu"
// const isbeta = beszel.hub_version.includes("beta") // const isbeta = beszel.hub_version.includes("beta")
// const imagetag = isbeta ? ":edge" : "" // const imagetag = isbeta ? ":edge" : ""

View File

@@ -1,11 +1,10 @@
import { useLingui } from "@lingui/react/macro"
import { LanguagesIcon } from "lucide-react" import { LanguagesIcon } from "lucide-react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { dynamicActivate } from "@/lib/i18n"
import languages from "@/lib/languages" import languages from "@/lib/languages"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { useLingui } from "@lingui/react/macro"
import { dynamicActivate } from "@/lib/i18n"
export function LangToggle() { export function LangToggle() {
const { i18n } = useLingui() const { i18n } = useLingui()

View File

@@ -1,19 +1,19 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro" import { Trans } from "@lingui/react/macro"
import { cn } from "@/lib/utils" import { getPagePath } from "@nanostores/router"
import { KeyIcon, LoaderCircle, LockIcon, LogInIcon, MailIcon } from "lucide-react"
import type { AuthMethodsList, AuthProviderInfo, OAuth2AuthConfig } from "pocketbase"
import { useCallback, useEffect, useState } from "react"
import * as v from "valibot"
import { buttonVariants } from "@/components/ui/button" import { buttonVariants } from "@/components/ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { KeyIcon, LoaderCircle, LockIcon, LogInIcon, MailIcon } from "lucide-react"
import { $authenticated } from "@/lib/stores"
import * as v from "valibot"
import { toast } from "../ui/use-toast"
import { Dialog, DialogContent, DialogTrigger, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { useCallback, useEffect, useState } from "react"
import { AuthMethodsList, AuthProviderInfo, OAuth2AuthConfig } from "pocketbase"
import { $router, Link, prependBasePath } from "../router"
import { getPagePath } from "@nanostores/router"
import { pb } from "@/lib/api" import { pb } from "@/lib/api"
import { $authenticated } from "@/lib/stores"
import { cn } from "@/lib/utils"
import { $router, Link, prependBasePath } from "../router"
import { toast } from "../ui/use-toast"
import { OtpInputForm } from "./otp-forms" import { OtpInputForm } from "./otp-forms"
const honeypot = v.literal("") const honeypot = v.literal("")
@@ -83,9 +83,9 @@ export function UserAuthForm({
const result = v.safeParse(Schema, data) const result = v.safeParse(Schema, data)
if (!result.success) { if (!result.success) {
console.log(result) console.log(result)
let errors = {} const errors = {}
for (const issue of result.issues) { for (const issue of result.issues) {
// @ts-ignore // @ts-expect-error
errors[issue.path[0].key] = issue.message errors[issue.path[0].key] = issue.message
} }
setErrors(errors) setErrors(errors)
@@ -96,7 +96,7 @@ export function UserAuthForm({
if (isFirstRun) { if (isFirstRun) {
// check that passwords match // check that passwords match
if (password !== passwordConfirm) { if (password !== passwordConfirm) {
let msg = "Passwords do not match" const msg = "Passwords do not match"
setErrors({ passwordConfirm: msg }) setErrors({ passwordConfirm: msg })
return return
} }

View File

@@ -1,15 +1,14 @@
import { Trans } from "@lingui/react/macro"
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
import { LoaderCircle, MailIcon, SendHorizonalIcon } from "lucide-react" import { LoaderCircle, MailIcon, SendHorizonalIcon } from "lucide-react"
import { useCallback, useState } from "react"
import { pb } from "@/lib/api"
import { cn } from "@/lib/utils"
import { buttonVariants } from "../ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog"
import { Input } from "../ui/input" import { Input } from "../ui/input"
import { Label } from "../ui/label" import { Label } from "../ui/label"
import { useCallback, useState } from "react"
import { toast } from "../ui/use-toast" import { toast } from "../ui/use-toast"
import { buttonVariants } from "../ui/button"
import { cn } from "@/lib/utils"
import { Dialog, DialogHeader } from "../ui/dialog"
import { DialogContent, DialogTrigger, DialogTitle } from "../ui/dialog"
import { pb } from "@/lib/api"
const showLoginFaliedToast = () => { const showLoginFaliedToast = () => {
toast({ toast({

View File

@@ -1,14 +1,14 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { UserAuthForm } from "@/components/login/auth-form"
import { Logo } from "../logo"
import { useEffect, useMemo, useState } from "react"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import ForgotPassword from "./forgot-pass-form" import type { AuthMethodsList } from "pocketbase"
import { $router } from "../router" import { useEffect, useMemo, useState } from "react"
import { AuthMethodsList } from "pocketbase" import { UserAuthForm } from "@/components/login/auth-form"
import { useTheme } from "../theme-provider"
import { pb } from "@/lib/api" import { pb } from "@/lib/api"
import { Logo } from "../logo"
import { ModeToggle } from "../mode-toggle" import { ModeToggle } from "../mode-toggle"
import { $router } from "../router"
import { useTheme } from "../theme-provider"
import ForgotPassword from "./forgot-pass-form"
import { OtpRequestForm } from "./otp-forms" import { OtpRequestForm } from "./otp-forms"
export default function () { export default function () {
@@ -53,7 +53,7 @@ export default function () {
<div className="min-h-svh grid items-center py-12"> <div className="min-h-svh grid items-center py-12">
<div <div
className="grid gap-5 w-full px-4 mx-auto" className="grid gap-5 w-full px-4 mx-auto"
// @ts-ignore // @ts-expect-error
style={{ maxWidth: "21.5em", "--border": theme == "light" ? "hsl(30, 8%, 70%)" : "hsl(220, 3%, 25%)" }} style={{ maxWidth: "21.5em", "--border": theme == "light" ? "hsl(30, 8%, 70%)" : "hsl(220, 3%, 25%)" }}
> >
<div className="absolute top-3 right-3"> <div className="absolute top-3 right-3">

View File

@@ -1,15 +1,15 @@
import { Trans } from "@lingui/react/macro"
import { LoaderCircle, MailIcon, SendHorizonalIcon } from "lucide-react"
import { useCallback, useState } from "react" import { useCallback, useState } from "react"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/otp"
import { pb } from "@/lib/api" import { pb } from "@/lib/api"
import { $authenticated } from "@/lib/stores" import { $authenticated } from "@/lib/stores"
import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/otp"
import { Trans } from "@lingui/react/macro"
import { showLoginFaliedToast } from "./auth-form"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { MailIcon, LoaderCircle, SendHorizonalIcon } from "lucide-react" import { $router } from "../router"
import { Label } from "../ui/label"
import { buttonVariants } from "../ui/button" import { buttonVariants } from "../ui/button"
import { Input } from "../ui/input" import { Input } from "../ui/input"
import { $router } from "../router" import { Label } from "../ui/label"
import { showLoginFaliedToast } from "./auth-form"
export function OtpInputForm({ otpId, mfaId }: { otpId: string; mfaId: string }) { export function OtpInputForm({ otpId, mfaId }: { otpId: string; mfaId: string }) {
const [value, setValue] = useState("") const [value, setValue] = useState("")

View File

@@ -1,8 +1,7 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { MoonStarIcon, SunIcon } from "lucide-react" import { MoonStarIcon, SunIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useTheme } from "@/components/theme-provider" import { useTheme } from "@/components/theme-provider"
import { Button } from "@/components/ui/button"
export function ModeToggle() { export function ModeToggle() {
const { theme, setTheme } = useTheme() const { theme, setTheme } = useTheme()

View File

@@ -1,6 +1,5 @@
import { Trans } from "@lingui/react/macro" import { Trans } from "@lingui/react/macro"
import { useState, lazy, Suspense } from "react" import { getPagePath } from "@nanostores/router"
import { Button, buttonVariants } from "@/components/ui/button"
import { import {
DatabaseBackupIcon, DatabaseBackupIcon,
LogOutIcon, LogOutIcon,
@@ -11,23 +10,24 @@ import {
UserIcon, UserIcon,
UsersIcon, UsersIcon,
} from "lucide-react" } from "lucide-react"
import { $router, basePath, Link, prependBasePath } from "./router" import { lazy, Suspense, useState } from "react"
import { LangToggle } from "./lang-toggle" import { Button, buttonVariants } from "@/components/ui/button"
import { ModeToggle } from "./mode-toggle"
import { Logo } from "./logo"
import { cn, runOnce } from "@/lib/utils"
import { isReadOnlyUser, isAdmin, logOut, pb } from "@/lib/api"
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuGroup, DropdownMenuGroup,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu"
import { isAdmin, isReadOnlyUser, logOut, pb } from "@/lib/api"
import { cn, runOnce } from "@/lib/utils"
import { AddSystemButton } from "./add-system" import { AddSystemButton } from "./add-system"
import { getPagePath } from "@nanostores/router" import { LangToggle } from "./lang-toggle"
import { Logo } from "./logo"
import { ModeToggle } from "./mode-toggle"
import { $router, basePath, Link, prependBasePath } from "./router"
const CommandPalette = lazy(() => import("./command-palette")) const CommandPalette = lazy(() => import("./command-palette"))

View File

@@ -23,7 +23,7 @@ export const prependBasePath = (path: string) => (basePath + path).replaceAll("/
// prepend base path to routes // prepend base path to routes
for (const route in routes) { for (const route in routes) {
// @ts-ignore need as const above to get nanostores to parse types properly // @ts-expect-error need as const above to get nanostores to parse types properly
routes[route] = prependBasePath(routes[route]) routes[route] = prependBasePath(routes[route])
} }

View File

@@ -3,13 +3,13 @@ import { Trans, useLingui } from "@lingui/react/macro"
import { redirectPage } from "@nanostores/router" import { redirectPage } from "@nanostores/router"
import { import {
CopyIcon, CopyIcon,
ExternalLinkIcon,
FingerprintIcon, FingerprintIcon,
KeyIcon, KeyIcon,
MoreHorizontalIcon, MoreHorizontalIcon,
RotateCwIcon, RotateCwIcon,
ServerIcon, ServerIcon,
Trash2Icon, Trash2Icon,
ExternalLinkIcon,
} from "lucide-react" } from "lucide-react"
import { memo, useEffect, useMemo, useState } from "react" import { memo, useEffect, useMemo, useState } from "react"
import { import {

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,
@@ -24,7 +32,6 @@ import {
$containerFilter, $containerFilter,
$direction, $direction,
$maxValues, $maxValues,
$networkInterfaceFilter,
$systems, $systems,
$temperatureFilter, $temperatureFilter,
$userSettings, $userSettings,
@@ -48,14 +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 ConnectionChart from "../charts/connection-chart" import NetworkSheet from "./system/network-sheet"
import NetworkInterfaceChart from "../charts/network-interface-chart" import LineChartDefault from "../charts/line-chart"
import TotalBandwidthChart from "../charts/total-bandwidth-chart"
type ChartTimeData = { type ChartTimeData = {
time: number time: number
@@ -133,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
} }
@@ -151,7 +157,6 @@ export default memo(function SystemDetail({ name }: { name: string }) {
const netCardRef = useRef<HTMLDivElement>(null) const netCardRef = useRef<HTMLDivElement>(null)
const persistChartTime = useRef(false) const persistChartTime = useRef(false)
const [containerFilterBar, setContainerFilterBar] = useState(null as null | JSX.Element) const [containerFilterBar, setContainerFilterBar] = useState(null as null | JSX.Element)
const [networkInterfaceFilterBar, setNetworkInterfaceFilterBar] = useState(null as null | JSX.Element)
const [bottomSpacing, setBottomSpacing] = useState(0) const [bottomSpacing, setBottomSpacing] = useState(0)
const [chartLoading, setChartLoading] = useState(true) const [chartLoading, setChartLoading] = useState(true)
const isLongerChart = chartTime !== "1h" const isLongerChart = chartTime !== "1h"
@@ -168,9 +173,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
setSystemStats([]) setSystemStats([])
setContainerData([]) setContainerData([])
setContainerFilterBar(null) setContainerFilterBar(null)
setNetworkInterfaceFilterBar(null)
$containerFilter.set("") $containerFilter.set("")
$networkInterfaceFilter.set("")
} }
}, [name]) }, [name])
@@ -267,19 +270,6 @@ export default memo(function SystemDetail({ name }: { name: string }) {
}) })
}, [system, chartTime]) }, [system, chartTime])
// Set up network interface filter bar
useEffect(() => {
if (systemStats.length > 0) {
const latestStats = systemStats[systemStats.length - 1]
const networkInterfaces = Object.keys(latestStats.stats.ns || {})
if (networkInterfaces.length > 0) {
!networkInterfaceFilterBar && setNetworkInterfaceFilterBar(<FilterBar store={$networkInterfaceFilter} />)
} else if (networkInterfaceFilterBar) {
setNetworkInterfaceFilterBar(null)
}
}
}, [systemStats, networkInterfaceFilterBar])
// values for system info bar // values for system info bar
const systemInfo = useMemo(() => { const systemInfo = useMemo(() => {
if (!system.info) { if (!system.info) {
@@ -409,7 +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 latestNetworkStats = systemStats.at(-1)?.stats.ni 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) {
@@ -427,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
@@ -576,7 +585,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
empty={dataEmpty} empty={dataEmpty}
grid={grid} grid={grid}
title={t`Disk I/O`} title={t`Disk I/O`}
description={t`Disk read and write throughput`} description={t`Throughput of root filesystem`}
cornerEl={maxValSelect} cornerEl={maxValSelect}
> >
<AreaChartDefault <AreaChartDefault
@@ -585,13 +594,13 @@ export default memo(function SystemDetail({ name }: { name: string }) {
dataPoints={[ dataPoints={[
{ {
label: t({ message: "Write", comment: "Disk write" }), label: t({ message: "Write", comment: "Disk write" }),
dataKey: ({ stats }) => (showMax ? stats?.dwm : stats?.dw), dataKey: ({ stats }: SystemStatsRecord) => (showMax ? stats?.dwm : stats?.dw),
color: 3, color: 3,
opacity: 0.3, opacity: 0.3,
}, },
{ {
label: t({ message: "Read", comment: "Disk read" }), label: t({ message: "Read", comment: "Disk read" }),
dataKey: ({ stats }) => (showMax ? stats?.drm : stats?.dr), dataKey: ({ stats }: SystemStatsRecord) => (showMax ? stats?.drm : stats?.dr),
color: 1, color: 1,
opacity: 0.3, opacity: 0.3,
}, },
@@ -607,44 +616,58 @@ export default memo(function SystemDetail({ name }: { name: string }) {
/> />
</ChartCard> </ChartCard>
{/* Network interface charts */} <ChartCard
{Object.keys(latestNetworkStats ?? {}).length > 0 && ( empty={dataEmpty}
<ChartCard grid={grid}
empty={dataEmpty} title={t`Bandwidth`}
grid={grid} cornerEl={
title={t`Network Interfaces`} <div className="flex gap-2">
description={t`Network traffic per interface`} {maxValSelect}
cornerEl={networkInterfaceFilterBar} <NetworkSheet chartData={chartData} dataEmpty={dataEmpty} grid={grid} maxValues={maxValues} />
> </div>
{/* @ts-ignore */} }
<NetworkInterfaceChart chartData={chartData} /> description={t`Network traffic of public interfaces`}
</ChartCard> >
)} <AreaChartDefault
chartData={chartData}
{/* Per-Interface Cumulative Bandwidth chart */} maxToggled={maxValues}
{Object.keys(latestNetworkStats ?? {}).length > 0 && ( dataPoints={[
<ChartCard {
empty={dataEmpty} label: t`Sent`,
grid={grid} // use bytes if available, otherwise multiply old MB (can remove in future)
title={t`Cumulative Bandwidth`} dataKey(data: SystemStatsRecord) {
description={t`Total bytes sent and received per network interface since boot`} if (showMax) {
> return data?.stats?.bm?.[0] ?? (data?.stats?.nsm ?? 0) * 1024 * 1024
{/* @ts-ignore */} }
<TotalBandwidthChart chartData={chartData} /> return data?.stats?.b?.[0] ?? data?.stats?.ns * 1024 * 1024
</ChartCard> },
)} color: 5,
opacity: 0.2,
{/* TCP Connection States chart */} },
{systemStats.at(-1)?.stats.nets && Object.keys(systemStats.at(-1)?.stats.nets ?? {}).length > 0 && ( {
<ChartCard label: t`Received`,
empty={dataEmpty} dataKey(data: SystemStatsRecord) {
grid={grid} if (showMax) {
title={t`TCP Connection States`} return data?.stats?.bm?.[1] ?? (data?.stats?.nrm ?? 0) * 1024 * 1024
description={t`TCP connection states for IPv4 and IPv6`} }
> return data?.stats?.b?.[1] ?? data?.stats?.nr * 1024 * 1024
<ConnectionChart chartData={chartData} /> },
</ChartCard> color: 2,
)} opacity: 0.2,
},
]
// try to place the lesser number in front for better visibility
.sort(() => (systemStats.at(-1)?.stats.b?.[1] ?? 0) - (systemStats.at(-1)?.stats.b?.[0] ?? 0))}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, true, userSettings.unitNet, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={(data) => {
const { value, unit } = formatBytes(data.value, true, userSettings.unitNet, false)
return `${decimalString(value, value >= 100 ? 1 : 2)} ${unit}`
}}
/>
</ChartCard>
{containerFilterBar && containerData.length > 0 && ( {containerFilterBar && containerData.length > 0 && (
<div <div
@@ -688,6 +711,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
grid={grid} grid={grid}
title={t`Load Average`} title={t`Load Average`}
description={t`System load averages over time`} description={t`System load averages over time`}
legend={true}
> >
<LoadAverageChart chartData={chartData} /> <LoadAverageChart chartData={chartData} />
</ChartCard> </ChartCard>
@@ -701,6 +725,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
title={t`Temperature`} title={t`Temperature`}
description={t`Temperatures of system sensors`} description={t`Temperatures of system sensors`}
cornerEl={<FilterBar store={$temperatureFilter} />} cornerEl={<FilterBar store={$temperatureFilter} />}
legend={Object.keys(systemStats.at(-1)?.stats.t ?? {}).length < 12}
> >
<TemperatureChart chartData={chartData} /> <TemperatureChart chartData={chartData} />
</ChartCard> </ChartCard>
@@ -734,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
@@ -748,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`}`}
@@ -775,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>
) )
})} })}
@@ -873,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" 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"
@@ -901,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>
@@ -919,7 +973,7 @@ const SelectAvgMax = memo(({ max }: { max: boolean }) => {
const Icon = max ? ChartMax : ChartAverage const Icon = max ? ChartMax : ChartAverage
return ( return (
<Select value={max ? "max" : "avg"} onValueChange={(e) => $maxValues.set(e === "max")}> <Select value={max ? "max" : "avg"} onValueChange={(e) => $maxValues.set(e === "max")}>
<SelectTrigger className="relative ps-10 pe-5"> <SelectTrigger className="relative ps-10 pe-5 w-full sm:w-44">
<Icon className="h-4 w-4 absolute start-4 top-1/2 -translate-y-1/2 opacity-85" /> <Icon className="h-4 w-4 absolute start-4 top-1/2 -translate-y-1/2 opacity-85" />
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
@@ -935,13 +989,15 @@ const SelectAvgMax = memo(({ max }: { max: boolean }) => {
) )
}) })
function ChartCard({ export function ChartCard({
title, title,
description, description,
children, children,
grid, grid,
empty, empty,
cornerEl, cornerEl,
legend,
className,
}: { }: {
title: string title: string
description: string description: string
@@ -949,17 +1005,22 @@ function ChartCard({
grid?: boolean grid?: boolean
empty?: boolean empty?: boolean
cornerEl?: JSX.Element | null cornerEl?: JSX.Element | null
legend?: boolean
className?: string
}) { }) {
const { isIntersecting, ref } = useIntersectionObserver() const { isIntersecting, ref } = useIntersectionObserver()
return ( return (
<Card className={cn("pb-2 sm:pb-4 odd:last-of-type:col-span-full", { "col-span-full": !grid })} ref={ref}> <Card
className={cn("pb-2 sm:pb-4 odd:last-of-type:col-span-full min-h-full", { "col-span-full": !grid }, className)}
ref={ref}
>
<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 && <div className="relative py-1 block sm:w-44 sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>} {cornerEl && <div className="py-1 grid sm:justify-end sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>}
</CardHeader> </CardHeader>
<div className="ps-0 w-[calc(100%-1.5em)] h-48 md:h-52 relative group"> <div className={cn("ps-0 w-[calc(100%-1.5em)] relative group", legend ? "h-54 md:h-56" : "h-48 md:h-52")}>
{ {
<Spinner <Spinner
msg={empty ? t`Waiting for enough records to display` : undefined} msg={empty ? t`Waiting for enough records to display` : undefined}

View File

@@ -0,0 +1,156 @@
import { t } from "@lingui/core/macro"
import { useStore } from "@nanostores/react"
import { MoreHorizontalIcon } from "lucide-react"
import { memo, useRef, useState } from "react"
import AreaChartDefault from "@/components/charts/area-chart"
import ChartTimeSelect from "@/components/charts/chart-time-select"
import { useNetworkInterfaces } from "@/components/charts/hooks"
import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { DialogTitle } from "@/components/ui/dialog"
import { $userSettings } from "@/lib/stores"
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { ChartCard } from "../system"
export default memo(function NetworkSheet({
chartData,
dataEmpty,
grid,
maxValues,
}: {
chartData: ChartData
dataEmpty: boolean
grid: boolean
maxValues: boolean
}) {
const [netInterfacesOpen, setNetInterfacesOpen] = useState(false)
const userSettings = useStore($userSettings)
const netInterfaces = useNetworkInterfaces(chartData.systemStats.at(-1)?.stats?.ni ?? {})
const showNetLegend = netInterfaces.length > 0 && netInterfaces.length < 15
const hasOpened = useRef(false)
if (netInterfacesOpen && !hasOpened.current) {
hasOpened.current = true
}
if (!netInterfaces.length) {
return null
}
return (
<Sheet open={netInterfacesOpen} onOpenChange={setNetInterfacesOpen}>
<DialogTitle className="sr-only">{t`Network traffic of public interfaces`}</DialogTitle>
<SheetTrigger asChild>
<Button
title={t`View more`}
variant="outline"
size="icon"
className="shrink-0 max-sm:absolute max-sm:top-3 max-sm:end-3"
>
<MoreHorizontalIcon />
</Button>
</SheetTrigger>
{hasOpened.current && (
<SheetContent aria-describedby={undefined} className="overflow-auto w-200 !max-w-full p-4 sm:p-6">
<ChartTimeSelect className="w-[calc(100%-2em)]" />
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Download`}
description={t`Network traffic of public interfaces`}
legend={showNetLegend}
className="min-h-auto"
>
<AreaChartDefault
chartData={chartData}
maxToggled={maxValues}
itemSorter={(a, b) => b.value - a.value}
dataPoints={netInterfaces.data(1)}
legend={showNetLegend}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, true, userSettings.unitNet, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitNet, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
/>
</ChartCard>
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Upload`}
description={t`Network traffic of public interfaces`}
legend={showNetLegend}
className="min-h-auto"
>
<AreaChartDefault
chartData={chartData}
maxToggled={maxValues}
itemSorter={(a, b) => b.value - a.value}
legend={showNetLegend}
dataPoints={netInterfaces.data(0)}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, true, userSettings.unitNet, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitNet, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
/>
</ChartCard>
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Cumulative Download`}
description={t`Total data received for each interface`}
legend={showNetLegend}
className="min-h-auto"
>
<AreaChartDefault
chartData={chartData}
legend={showNetLegend}
dataPoints={netInterfaces.data(3)}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, false, userSettings.unitNet, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value, false, userSettings.unitNet, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
/>
</ChartCard>
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Cumulative Upload`}
description={t`Total data sent for each interface`}
legend={showNetLegend}
className="min-h-auto"
>
<AreaChartDefault
chartData={chartData}
legend={showNetLegend}
dataPoints={netInterfaces.data(2)}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, false, userSettings.unitNet, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value, false, userSettings.unitNet, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
/>
</ChartCard>
</SheetContent>
)}
</Sheet>
)
})

View File

@@ -1,5 +1,5 @@
import { cn } from "@/lib/utils"
import { LoaderCircleIcon } from "lucide-react" import { LoaderCircleIcon } from "lucide-react"
import { cn } from "@/lib/utils"
export default function ({ msg, className }: { msg?: string; className?: string }) { export default function ({ msg, className }: { msg?: string; className?: string }) {
return ( return (

View File

@@ -1,8 +1,12 @@
import { SystemRecord } from "@/types" import { t } from "@lingui/core/macro"
import { CellContext, ColumnDef, HeaderContext } from "@tanstack/react-table" import { Trans, useLingui } from "@lingui/react/macro"
import { ClassValue } from "clsx" import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router"
import type { CellContext, ColumnDef, HeaderContext } from "@tanstack/react-table"
import type { ClassValue } from "clsx"
import { import {
ArrowUpDownIcon, ArrowUpDownIcon,
ChevronRightSquareIcon,
CopyIcon, CopyIcon,
CpuIcon, CpuIcon,
HardDriveIcon, HardDriveIcon,
@@ -15,7 +19,10 @@ import {
Trash2Icon, Trash2Icon,
WifiIcon, WifiIcon,
} from "lucide-react" } from "lucide-react"
import { Button } from "../ui/button" import { memo, useMemo, useRef, useState } from "react"
import { isReadOnlyUser, pb } from "@/lib/api"
import { ConnectionType, connectionTypeLabels, MeterState, SystemStatus } from "@/lib/enums"
import { $longestSystemNameLen, $userSettings } from "@/lib/stores"
import { import {
cn, cn,
copyToClipboard, copyToClipboard,
@@ -25,24 +32,12 @@ import {
getMeterState, getMeterState,
parseSemVer, parseSemVer,
} from "@/lib/utils" } from "@/lib/utils"
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons" import type { SystemRecord } from "@/types"
import { useStore } from "@nanostores/react"
import { $longestSystemNameLen, $userSettings } from "@/lib/stores"
import { Trans, useLingui } from "@lingui/react/macro"
import { useMemo, useRef, useState } from "react"
import { memo } from "react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu"
import AlertButton from "../alerts/alert-button"
import { Dialog } from "../ui/dialog"
import { SystemDialog } from "../add-system" import { SystemDialog } from "../add-system"
import { AlertDialog } from "../ui/alert-dialog" import AlertButton from "../alerts/alert-button"
import { $router, Link } from "../router"
import { import {
AlertDialog,
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
AlertDialogContent, AlertDialogContent,
@@ -51,12 +46,16 @@ import {
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
} from "../ui/alert-dialog" } from "../ui/alert-dialog"
import { buttonVariants } from "../ui/button" import { Button, buttonVariants } from "../ui/button"
import { t } from "@lingui/core/macro" import { Dialog } from "../ui/dialog"
import { MeterState, SystemStatus } from "@/lib/enums" import {
import { $router, Link } from "../router" DropdownMenu,
import { getPagePath } from "@nanostores/router" DropdownMenuContent,
import { isReadOnlyUser, pb } from "@/lib/api" DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu"
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",
@@ -216,32 +215,22 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
}, },
}, },
{ {
accessorFn: (row) => (row.info.ns || 0) + (row.info.nr || 0), accessorFn: ({ info }) => info.bb || (info.b || 0) * 1024 * 1024,
id: "net", id: "net",
name: () => t`Net`, name: () => t`Net`,
size: 0, size: 0,
Icon: EthernetIcon, Icon: EthernetIcon,
header: sortableHeader, header: sortableHeader,
sortDescFirst: true,
sortingFn: (rowA, rowB) => {
const a = (rowA.original.info.ns || 0) + (rowA.original.info.nr || 0)
const b = (rowB.original.info.ns || 0) + (rowB.original.info.nr || 0)
return a - b
},
cell(info) { cell(info) {
const system = info.row.original const sys = info.row.original
const sent = system.info.ns || 0
const received = system.info.nr || 0
const userSettings = useStore($userSettings, { keys: ["unitNet"] }) const userSettings = useStore($userSettings, { keys: ["unitNet"] })
if (system.status === SystemStatus.Paused) { if (sys.status === SystemStatus.Paused) {
return null return null
} }
const sentFmt = formatBytes(sent, true, userSettings.unitNet, true) const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false)
const receivedFmt = formatBytes(received, true, userSettings.unitNet, true)
return ( return (
<span className={cn("tabular-nums whitespace-nowrap", { "ps-1": viewMode === "table" })}> <span className="tabular-nums whitespace-nowrap">
<span className="text-green-600"></span> {Math.round(sentFmt.value)} {sentFmt.unit}{" "} {decimalString(value, value >= 100 ? 1 : 2)} {unit}
<span className="text-blue-600"></span> {Math.round(receivedFmt.value)} {receivedFmt.unit}
</span> </span>
) )
}, },
@@ -283,24 +272,37 @@ 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>
) )
}, },
}, },
{ {
id: "actions", id: "actions",
// @ts-ignore // @ts-expect-error
name: () => t({ message: "Actions", comment: "Table column" }), name: () => t({ message: "Actions", comment: "Table column" }),
size: 50, size: 50,
cell: ({ row }) => ( cell: ({ row }) => (
@@ -315,12 +317,13 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
function sortableHeader(context: HeaderContext<SystemRecord, unknown>) { function sortableHeader(context: HeaderContext<SystemRecord, unknown>) {
const { column } = context const { column } = context
// @ts-ignore // @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" />}
@@ -363,7 +366,7 @@ export function IndicatorDot({ system, className }: { system: SystemRecord; clas
export const ActionsButton = memo(({ system }: { system: SystemRecord }) => { export const ActionsButton = memo(({ system }: { system: SystemRecord }) => {
const [deleteOpen, setDeleteOpen] = useState(false) const [deleteOpen, setDeleteOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false) const [editOpen, setEditOpen] = useState(false)
let editOpened = useRef(false) const editOpened = useRef(false)
const { t } = useLingui() const { t } = useLingui()
const { id, status, host, name } = system const { id, status, host, name } = system

View File

@@ -1,17 +1,31 @@
import { Trans, useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router"
import { import {
ColumnDef, type ColumnDef,
ColumnFiltersState, type ColumnFiltersState,
getFilteredRowModel,
SortingState,
getSortedRowModel,
flexRender, flexRender,
VisibilityState,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel,
getSortedRowModel,
type Row,
type SortingState,
type Table as TableType,
useReactTable, useReactTable,
Row, type VisibilityState,
Table as TableType,
} from "@tanstack/react-table" } from "@tanstack/react-table"
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { useVirtualizer, type VirtualItem } from "@tanstack/react-virtual"
import {
ArrowDownIcon,
ArrowUpDownIcon,
ArrowUpIcon,
EyeIcon,
FilterIcon,
LayoutGridIcon,
LayoutListIcon,
Settings2Icon,
} from "lucide-react"
import { memo, useEffect, useMemo, useRef, useState } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import {
DropdownMenu, DropdownMenu,
@@ -24,30 +38,16 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } from "@/components/ui/dropdown-menu"
import { SystemRecord } from "@/types"
import {
ArrowUpDownIcon,
LayoutGridIcon,
LayoutListIcon,
ArrowDownIcon,
ArrowUpIcon,
Settings2Icon,
EyeIcon,
FilterIcon,
} from "lucide-react"
import { memo, useEffect, useMemo, useRef, useState } from "react"
import { $pausedSystems, $downSystems, $upSystems, $systems } from "@/lib/stores"
import { useStore } from "@nanostores/react"
import { cn, runOnce, useBrowserStorage } from "@/lib/utils"
import { $router, Link } from "../router"
import { useLingui, Trans } from "@lingui/react/macro"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { getPagePath } from "@nanostores/router" import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
import AlertButton from "../alerts/alert-button"
import { SystemStatus } from "@/lib/enums" import { SystemStatus } from "@/lib/enums"
import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual" import { $downSystems, $pausedSystems, $systems, $upSystems } from "@/lib/stores"
import { cn, runOnce, useBrowserStorage } from "@/lib/utils"
import type { SystemRecord } from "@/types"
import AlertButton from "../alerts/alert-button"
import { $router, Link } from "../router"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
type ViewMode = "table" | "grid" type ViewMode = "table" | "grid"
type StatusFilter = "all" | SystemRecord["status"] type StatusFilter = "all" | SystemRecord["status"]
@@ -309,128 +309,121 @@ export default function SystemsTable() {
) )
} }
const AllSystemsTable = memo(function ({ const AllSystemsTable = memo(
table, ({ table, rows, colLength }: { table: TableType<SystemRecord>; rows: Row<SystemRecord>[]; colLength: number }) => {
rows, // The virtualizer will need a reference to the scrollable container element
colLength, const scrollRef = useRef<HTMLDivElement>(null)
}: {
table: TableType<SystemRecord>
rows: Row<SystemRecord>[]
colLength: number
}) {
// The virtualizer will need a reference to the scrollable container element
const scrollRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({ const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
count: rows.length, count: rows.length,
estimateSize: () => (rows.length > 10 ? 56 : 60), estimateSize: () => (rows.length > 10 ? 56 : 60),
getScrollElement: () => scrollRef.current, getScrollElement: () => scrollRef.current,
overscan: 5, overscan: 5,
}) })
const virtualRows = virtualizer.getVirtualItems() const virtualRows = virtualizer.getVirtualItems()
const paddingTop = Math.max(0, virtualRows[0]?.start ?? 0 - virtualizer.options.scrollMargin) const paddingTop = Math.max(0, virtualRows[0]?.start ?? 0 - virtualizer.options.scrollMargin)
const paddingBottom = Math.max(0, virtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0)) const paddingBottom = Math.max(0, virtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0))
return (
<div
className={cn(
"h-min max-h-[calc(100dvh-17rem)] max-w-full relative overflow-auto border rounded-md",
// don't set min height if there are less than 2 rows, do set if we need to display the empty state
(!rows.length || rows.length > 2) && "min-h-50"
)}
ref={scrollRef}
>
{/* add header height to table size */}
<div style={{ height: `${virtualizer.getTotalSize() + 50}px`, paddingTop, paddingBottom }}>
<table className="text-sm w-full h-full">
<SystemsTableHead table={table} colLength={colLength} />
<TableBody onMouseEnter={preloadSystemDetail}>
{rows.length ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index] as Row<SystemRecord>
return (
<SystemTableRow
key={row.id}
row={row}
virtualRow={virtualRow}
length={rows.length}
colLength={colLength}
/>
)
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
<Trans>No systems found.</Trans>
</TableCell>
</TableRow>
)}
</TableBody>
</table>
</div>
</div>
)
})
function SystemsTableHead({ table, colLength }: { table: TableType<SystemRecord>; colLength: number }) {
const { i18n } = useLingui()
return useMemo(() => {
return ( return (
<TableHeader className="sticky top-0 z-20 w-full border-b-2"> <div
{table.getHeaderGroups().map((headerGroup) => ( className={cn(
<tr key={headerGroup.id}> "h-min max-h-[calc(100dvh-17rem)] max-w-full relative overflow-auto border rounded-md",
{headerGroup.headers.map((header) => { // don't set min height if there are less than 2 rows, do set if we need to display the empty state
return ( (!rows.length || rows.length > 2) && "min-h-50"
<TableHead className="px-1.5" key={header.id}> )}
{flexRender(header.column.columnDef.header, header.getContext())} ref={scrollRef}
</TableHead> >
) {/* add header height to table size */}
})} <div style={{ height: `${virtualizer.getTotalSize() + 50}px`, paddingTop, paddingBottom }}>
</tr> <table className="text-sm w-full h-full">
))} <SystemsTableHead table={table} />
</TableHeader> <TableBody onMouseEnter={preloadSystemDetail}>
{rows.length ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index] as Row<SystemRecord>
return (
<SystemTableRow
key={row.id}
row={row}
virtualRow={virtualRow}
length={rows.length}
colLength={colLength}
/>
)
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
<Trans>No systems found.</Trans>
</TableCell>
</TableRow>
)}
</TableBody>
</table>
</div>
</div>
) )
}, [i18n.locale, colLength]) }
)
function SystemsTableHead({ table }: { table: TableType<SystemRecord> }) {
const { t } = useLingui()
return (
<TableHeader className="sticky top-0 z-50 w-full border-b-2">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead className="px-1.5" key={header.id}>
{flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
)
})}
</tr>
))}
</TableHeader>
)
} }
const SystemTableRow = memo(function ({ const SystemTableRow = memo(
row, ({
virtualRow, row,
colLength, virtualRow,
}: { colLength,
row: Row<SystemRecord> }: {
virtualRow: VirtualItem row: Row<SystemRecord>
length: number virtualRow: VirtualItem
colLength: number length: number
}) { colLength: number
const system = row.original }) => {
const { t } = useLingui() const system = row.original
return useMemo(() => { const { t } = useLingui()
return ( return useMemo(() => {
<TableRow return (
// data-state={row.getIsSelected() && "selected"} <TableRow
className={cn("cursor-pointer transition-opacity relative safari:transform-3d", { // data-state={row.getIsSelected() && "selected"}
"opacity-50": system.status === SystemStatus.Paused, className={cn("cursor-pointer transition-opacity relative safari:transform-3d", {
})} "opacity-50": system.status === SystemStatus.Paused,
> })}
{row.getVisibleCells().map((cell) => ( >
<TableCell {row.getVisibleCells().map((cell) => (
key={cell.id} <TableCell
style={{ key={cell.id}
width: cell.column.getSize(), style={{
height: virtualRow.size, width: cell.column.getSize(),
}} height: virtualRow.size,
className="py-0" }}
> className="py-0"
{flexRender(cell.column.columnDef.cell, cell.getContext())} >
</TableCell> {flexRender(cell.column.columnDef.cell, cell.getContext())}
))} </TableCell>
</TableRow> ))}
) </TableRow>
}, [system, system.status, colLength, t]) )
}) }, [system, system.status, colLength, t])
}
)
const SystemCard = memo( const SystemCard = memo(
({ row, table, colLength }: { row: Row<SystemRecord>; table: TableType<SystemRecord>; colLength: number }) => { ({ row, table, colLength }: { row: Row<SystemRecord>; table: TableType<SystemRecord>; colLength: number }) => {
@@ -471,7 +464,7 @@ const SystemCard = memo(
if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null if (!column.getIsVisible() || column.id === "system" || column.id === "actions") return null
const cell = row.getAllCells().find((cell) => cell.column.id === column.id) const cell = row.getAllCells().find((cell) => cell.column.id === column.id)
if (!cell) return null if (!cell) return null
// @ts-ignore // @ts-expect-error
const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown> const { Icon, name } = column.columnDef as ColumnDef<SystemRecord, unknown>
return ( return (
<> <>

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

@@ -55,9 +55,6 @@ listenKeys($userSettings, ["chartTime"], ({ chartTime }) => $chartTime.set(chart
/** Container chart filter */ /** Container chart filter */
export const $containerFilter = atom("") export const $containerFilter = atom("")
/** Network interface chart filter */
export const $networkInterfaceFilter = atom("")
/** Temperature chart filter */ /** Temperature chart filter */
export const $temperatureFilter = atom("") export const $temperatureFilter = atom("")

View File

@@ -1,6 +1,7 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { type ClassValue, clsx } from "clsx" import { type ClassValue, clsx } from "clsx"
import { timeDay, timeHour } from "d3-time" import { timeDay, timeHour } from "d3-time"
import { listenKeys } from "nanostores"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { twMerge } from "tailwind-merge" import { twMerge } from "tailwind-merge"
import { prependBasePath } from "@/components/router" import { prependBasePath } from "@/components/router"
@@ -8,7 +9,6 @@ import { toast } from "@/components/ui/use-toast"
import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types" import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types"
import { HourFormat, MeterState, Unit } from "./enums" import { HourFormat, MeterState, Unit } from "./enums"
import { $copyContent, $userSettings } from "./stores" import { $copyContent, $userSettings } from "./stores"
import { listenKeys } from "nanostores"
export const FAVICON_DEFAULT = "favicon.svg" export const FAVICON_DEFAULT = "favicon.svg"
export const FAVICON_GREEN = "favicon-green.svg" export const FAVICON_GREEN = "favicon-green.svg"
@@ -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}` }
@@ -283,22 +283,6 @@ export const getHubURL = () => BESZEL?.HUB_URL || window.location.origin
/** Map of system IDs to their corresponding tokens (used to avoid fetching in add-system dialog) */ /** Map of system IDs to their corresponding tokens (used to avoid fetching in add-system dialog) */
export const tokenMap = new Map<SystemRecord["id"], FingerprintRecord["token"]>() export const tokenMap = new Map<SystemRecord["id"], FingerprintRecord["token"]>()
/**
* Formats a network speed value (in MB/s) to the most readable unit (B/s, KB/s, MB/s, GB/s, TB/s).
* @param valueMBps The value in MB/s
* @returns A string with the value and the appropriate unit
*/
export function formatSpeed(valueMBps: number): string {
const bitsPerSec = valueMBps * 8_000_000
if (bitsPerSec >= 1_000_000_000) {
return (bitsPerSec / 1_000_000_000).toFixed(2) + ' Gbit/s'
} else if (bitsPerSec >= 1_000_000) {
return (bitsPerSec / 1_000_000).toFixed(2) + ' Mbit/s'
} else {
return (bitsPerSec / 1_000).toFixed(2) + ' kbit/s'
}
}
/** Calculate duration between two dates and format as human-readable string */ /** Calculate duration between two dates and format as human-readable string */
export function formatDuration( export function formatDuration(
createdDate: string | null | undefined, createdDate: string | null | undefined,

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"
@@ -363,6 +367,14 @@ msgstr "أنشئت"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "حرج (%)" msgstr "حرج (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "التنزيل التراكمي"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "الرفع التراكمي"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "معطل"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "معطل ({downSystemsLength})" msgstr "معطل ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "تنزيل"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "المدة" msgstr "المدة"
@@ -452,6 +468,7 @@ msgstr "تعديل"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "البريد الإشباكي" msgstr "البريد الإشباكي"
@@ -472,6 +489,10 @@ msgstr "أدخل عنوان البريد الإشباكي لإعادة تعيي
msgid "Enter email address..." msgid "Enter email address..."
msgstr "أدخل عنوان البريد الإشباكي..." msgstr "أدخل عنوان البريد الإشباكي..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "أدخل كلمة المرور لمرة واحدة الخاصة بك."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "لمدة <0>{min}</0> {min, plural, one {دقيقة} other {دقائق}}
msgid "Forgot password?" msgid "Forgot password?"
msgstr "هل نسيت كلمة المرور؟" msgstr "هل نسيت كلمة المرور؟"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "أمر FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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 "استهلاك طاقة وحدة معالجة الرسوميات"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "إدارة تفضيلات العرض والإشعارات." msgstr "إدارة تفضيلات العرض والإشعارات."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "تعليمات الإعداد اليدوي" msgstr "تعليمات الإعداد اليدوي"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "حركة مرور الشبكة لحاويات الدوكر" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "حركة مرور الشبكة للواجهات العامة" msgstr "حركة مرور الشبكة للواجهات العامة"
@@ -715,6 +750,10 @@ msgstr "دعم OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "في كل إعادة تشغيل، سيتم تحديث الأنظمة في قاعدة البيانات لتتطابق مع الأنظمة المعرفة في الملف." msgstr "في كل إعادة تشغيل، سيتم تحديث الأنظمة في قاعدة البيانات لتتطابق مع الأنظمة المعرفة في الملف."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "كلمة مرور لمرة واحدة"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "قراءة"
msgid "Received" msgid "Received"
msgstr "تم الاستلام" msgstr "تم الاستلام"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "طلب كلمة مرور لمرة واحدة"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "طلب OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "إعادة تعيين كلمة المرور" msgstr "إعادة تعيين كلمة المرور"
@@ -888,10 +935,6 @@ msgstr "تم الإرسال"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "تعيين عتبات النسبة المئوية لألوان العداد." msgstr "تعيين عتبات النسبة المئوية لألوان العداد."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "يحدد النطاق الزمني الافتراضي للرسوم البيانية عند عرض النظام."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "معدل نقل {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "معدل نقل نظام الملفات الجذر" msgstr "معدل نقل نظام الملفات الجذر"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "تنسيق الوقت"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "إلى البريد الإشباكي" msgstr "إلى البريد الإشباكي"
@@ -1034,6 +1081,14 @@ msgstr "تسمح الرموز المميزة للوكلاء بالاتصال و
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور." msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "إجمالي البيانات المستلمة لكل واجهة"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة دقيقة واحدة عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة دقيقة واحدة عتبة معينة"
@@ -1048,7 +1103,7 @@ msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
msgstr "يتم التفعيل عندما <EFBFBD><EFBFBD>تجاوز أي مستشعر عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز أي مستشعر عتبة معينة"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
@@ -1095,6 +1150,10 @@ msgstr "قيد التشغيل"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "قيد التشغيل ({upSystemsLength})" msgstr "قيد التشغيل ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "رفع"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "مدة التشغيل" msgstr "مدة التشغيل"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Създаден"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Критично (%)" msgstr "Критично (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Кумулативно изтегляне"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Кумулативно качване"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Офлайн"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Офлайн ({downSystemsLength})" msgstr "Офлайн ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Изтегляне"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Продължителност" msgstr "Продължителност"
@@ -452,6 +468,7 @@ msgstr "Редактирай"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Имейл" msgstr "Имейл"
@@ -472,6 +489,10 @@ msgstr "Въведи имейл адрес за да нулираш парола
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Въведи имейл адрес..." msgstr "Въведи имейл адрес..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Въведете Вашата еднократна парола."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "За <0>{min}</0> {min, plural, one {минута} other {минути}}
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Забравена парола?" msgstr "Забравена парола?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD команда"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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 "Консумация на ток от графична карта"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Управление на предпочитанията за показване и уведомяване." msgstr "Управление на предпочитанията за показване и уведомяване."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Инструкции за ръчна настройка" msgstr "Инструкции за ръчна настройка"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Мрежов трафик на docker контейнери" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Мрежов трафик на публични интерфейси" msgstr "Мрежов трафик на публични интерфейси"
@@ -715,6 +750,10 @@ msgstr "Поддръжка на OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "На всеки рестарт, системите в датабазата ще бъдат обновени да съвпадат със системите зададени във файла." msgstr "На всеки рестарт, системите в датабазата ще бъдат обновени да съвпадат със системите зададени във файла."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Еднократна парола"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Прочети"
msgid "Received" msgid "Received"
msgstr "Получени" msgstr "Получени"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Заявка за еднократна парола"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Заявка OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Нулиране на парола" msgstr "Нулиране на парола"
@@ -888,10 +935,6 @@ msgstr "Изпратени"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Задайте процентни прагове за цветовете на измервателните уреди." msgstr "Задайте процентни прагове за цветовете на измервателните уреди."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Задава диапазона за време за диаграмите, когато се разглежда система."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Пропускателна способност на {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Пропускателна способност на root файловата система" msgstr "Пропускателна способност на root файловата система"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Формат на времето"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "До имейл(ите)" msgstr "До имейл(ите)"
@@ -1034,6 +1081,14 @@ msgstr "Токените позволяват на агентите да се с
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токените и пръстовите отпечатъци се използват за удостоверяване на WebSocket връзките към концентратора." msgstr "Токените и пръстовите отпечатъци се използват за удостоверяване на WebSocket връзките към концентратора."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Общо получени данни за всеки интерфейс"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "Задейства се, когато употребата на паметта за 1 минута надвиши зададен праг" msgstr "Задейства се, когато употребата на паметта за 1 минута надвиши зададен праг"
@@ -1095,6 +1150,10 @@ msgstr "Нагоре"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Нагоре ({upSystemsLength})" msgstr "Нагоре ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Качване"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Време на работа" msgstr "Време на работа"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Vytvořeno"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritické (%)" msgstr "Kritické (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativní stažení"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativní odeslání"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Nefunkční"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Nefunkční ({downSystemsLength})" msgstr "Nefunkční ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Stažení"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Doba trvání" msgstr "Doba trvání"
@@ -452,6 +468,7 @@ msgstr "Upravit"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -472,6 +489,10 @@ msgstr "Zadejte e-mailovou adresu pro obnovu hesla"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Zadejte e-mailovou adresu..." msgstr "Zadejte e-mailovou adresu..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Zadejte Vaše jednorázové heslo."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Za <0>{min}</0> {min, plural, one {minutu} few {minuty} other {minut}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Zapomněli jste heslo?" msgstr "Zapomněli jste heslo?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD příkaz"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Správa nastavení zobrazení a oznámení." msgstr "Správa nastavení zobrazení a oznámení."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Pokyny k manuálnímu nastavení" msgstr "Pokyny k manuálnímu nastavení"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Síťový provoz kontejnerů docker" 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
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í"
@@ -715,6 +750,10 @@ msgstr "Podpora OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Při každém restartu budou systémy v databázi aktualizovány tak, aby odpovídaly systémům definovaným v souboru." msgstr "Při každém restartu budou systémy v databázi aktualizovány tak, aby odpovídaly systémům definovaným v souboru."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Jednorázové heslo"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Číst"
msgid "Received" msgid "Received"
msgstr "Přijato" msgstr "Přijato"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Požádat o jednorázové heslo"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Požádat OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Obnovit heslo" msgstr "Obnovit heslo"
@@ -888,10 +935,6 @@ msgstr "Odeslat"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Nastavte procentuální prahové hodnoty pro barvy měřičů." msgstr "Nastavte procentuální prahové hodnoty pro barvy měřičů."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Nastaví výchozí časový rozsah grafů, když je systém zobrazen."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Propustnost {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Propustnost kořenového souborového systému" msgstr "Propustnost kořenového souborového systému"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Formát času"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Na email(y)" msgstr "Na email(y)"
@@ -1034,6 +1081,14 @@ msgstr "Tokeny umožňují agentům připojení a registraci. Otisky jsou stabil
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeny a otisky slouží k ověření připojení WebSocket k uzlu." msgstr "Tokeny a otisky slouží k ověření připojení WebSocket k uzlu."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Celkový přijatý objem dat pro každé rozhraní"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Celkový odeslaný objem dat pro každé rozhraní"
#: 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"
msgstr "Spustí se, když využití paměti během 1 minuty překročí prahovou hodnotu" msgstr "Spustí se, když využití paměti během 1 minuty překročí prahovou hodnotu"
@@ -1095,6 +1150,10 @@ msgstr "Funkční"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Funkční ({upSystemsLength})" msgstr "Funkční ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Odeslání"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Doba provozu" msgstr "Doba provozu"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr ""
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritisk (%)" msgstr "Kritisk (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativ download"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativ upload"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Nede"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Download"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
@@ -452,6 +468,7 @@ msgstr "Rediger"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "E-mail" msgstr "E-mail"
@@ -472,6 +489,10 @@ msgstr "Indtast e-mailadresse for at nulstille adgangskoden"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Indtast e-mailadresse..." msgstr "Indtast e-mailadresse..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Indtast din engangsadgangskode."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "For <0>{min}</0> {min, plural, one {minut} other {minutter}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Glemt adgangskode?" msgstr "Glemt adgangskode?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD kommando"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Administrer display og notifikationsindstillinger." msgstr "Administrer display og notifikationsindstillinger."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Manuel opsætningsvejledning" msgstr "Manuel opsætningsvejledning"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Netværkstrafik af dockercontainere" 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
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"
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDC understøttelse"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ved hver genstart vil systemer i databasen blive opdateret til at matche de systemer, der er defineret i filen." msgstr "Ved hver genstart vil systemer i databasen blive opdateret til at matche de systemer, der er defineret i filen."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Engangsadgangskode"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Læs"
msgid "Received" msgid "Received"
msgstr "Modtaget" msgstr "Modtaget"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Anmod om engangsadgangskode"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Anmod OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Nulstil adgangskode" msgstr "Nulstil adgangskode"
@@ -888,10 +935,6 @@ msgstr "Sendt"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Indstil procentvise tærskler for målerfarver." msgstr "Indstil procentvise tærskler for målerfarver."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Sætter standardtidsintervallet for diagrammer når et system vises."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Gennemløb af {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Gennemløb af rodfilsystemet" msgstr "Gennemløb af rodfilsystemet"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Tidsformat"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Til email(s)" msgstr "Til email(s)"
@@ -1034,6 +1081,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Samlet modtaget data for hver interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Samlet sendt data for hver interface"
#: 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"
msgstr "" msgstr ""
@@ -1095,6 +1150,10 @@ msgstr "Oppe"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Upload"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Oppetid" msgstr "Oppetid"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Erstellt"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritisch (%)" msgstr "Kritisch (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativer Download"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativer Upload"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Offline"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Offline ({downSystemsLength})" msgstr "Offline ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Download"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Dauer" msgstr "Dauer"
@@ -452,6 +468,7 @@ msgstr "Bearbeiten"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "E-Mail" msgstr "E-Mail"
@@ -472,6 +489,10 @@ msgstr "E-Mail-Adresse eingeben, um das Passwort zurückzusetzen"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "E-Mail-Adresse eingeben..." msgstr "E-Mail-Adresse eingeben..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Geben Sie Ihr Einmalpasswort ein."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Für <0>{min}</0> {min, plural, one {Minute} other {Minuten}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Passwort vergessen?" msgstr "Passwort vergessen?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD Befehl"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Anzeige- und Benachrichtigungseinstellungen verwalten." msgstr "Anzeige- und Benachrichtigungseinstellungen verwalten."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Anleitung zur manuellen Einrichtung" msgstr "Anleitung zur manuellen Einrichtung"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Netzwerkverkehr der Docker-Container" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Netzwerkverkehr der öffentlichen Schnittstellen" msgstr "Netzwerkverkehr der öffentlichen Schnittstellen"
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDC-Unterstützung"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Bei jedem Neustart werden die Systeme in der Datenbank aktualisiert, um den in der Datei definierten Systemen zu entsprechen." msgstr "Bei jedem Neustart werden die Systeme in der Datenbank aktualisiert, um den in der Datei definierten Systemen zu entsprechen."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Einmalpasswort"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Lesen"
msgid "Received" msgid "Received"
msgstr "Empfangen" msgstr "Empfangen"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Einmalpasswort anfordern"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP anfordern"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Passwort zurücksetzen" msgstr "Passwort zurücksetzen"
@@ -888,10 +935,6 @@ msgstr "Gesendet"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Prozentuale Schwellenwerte für Zählerfarben festlegen." msgstr "Prozentuale Schwellenwerte für Zählerfarben festlegen."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Legt den Standardzeitraum für Diagramme fest, wenn ein System angezeigt wird."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Durchsatz von {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Durchsatz des Root-Dateisystems" msgstr "Durchsatz des Root-Dateisystems"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Zeitformat"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "An E-Mail(s)" msgstr "An E-Mail(s)"
@@ -1034,6 +1081,14 @@ msgstr "Tokens ermöglichen es Agents, sich zu verbinden und zu registrieren. Fi
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren." msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Gesamtdatenmenge für jede Schnittstelle empfangen"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Gesamtdatenmenge für jede Schnittstelle gesendet"
#: 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"
msgstr "Löst aus, wenn der Lastdurchschnitt der letzten Minute einen Schwellenwert überschreitet" msgstr "Löst aus, wenn der Lastdurchschnitt der letzten Minute einen Schwellenwert überschreitet"
@@ -1095,6 +1150,10 @@ msgstr "aktiv"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "aktiv ({upSystemsLength})" msgstr "aktiv ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Upload"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Betriebszeit" msgstr "Betriebszeit"
@@ -1128,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"
@@ -358,6 +362,14 @@ msgstr "Created"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Critical (%)" msgstr "Critical (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Cumulative Download"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Cumulative Upload"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -436,6 +448,10 @@ msgstr "Down"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Down ({downSystemsLength})" msgstr "Down ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Download"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Duration" msgstr "Duration"
@@ -447,6 +463,7 @@ msgstr "Edit"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -467,6 +484,10 @@ msgstr "Enter email address to reset password"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Enter email address..." msgstr "Enter email address..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Enter your one-time password."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -537,6 +558,12 @@ msgstr "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Forgot password?" msgstr "Forgot password?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD command"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -548,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"
@@ -640,6 +671,7 @@ msgid "Manage display and notification preferences."
msgstr "Manage display and notification preferences." msgstr "Manage display and notification preferences."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Manual setup instructions" msgstr "Manual setup instructions"
@@ -675,6 +707,9 @@ msgid "Network traffic of docker containers"
msgstr "Network traffic of docker containers" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Network traffic of public interfaces" msgstr "Network traffic of public interfaces"
@@ -710,6 +745,10 @@ msgstr "OAuth 2 / OIDC support"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "On each restart, systems in the database will be updated to match the systems defined in the file." msgstr "On each restart, systems in the database will be updated to match the systems defined in the file."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "One-time password"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -828,6 +867,14 @@ msgstr "Read"
msgid "Received" msgid "Received"
msgstr "Received" msgstr "Received"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Request a one-time password"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Request OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Reset Password" msgstr "Reset Password"
@@ -883,10 +930,6 @@ msgstr "Sent"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Set percentage thresholds for meter colors." msgstr "Set percentage thresholds for meter colors."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Sets the default time range for charts when a system is viewed."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -997,6 +1040,10 @@ msgstr "Throughput of {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Throughput of root filesystem" msgstr "Throughput of root filesystem"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Time format"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "To email(s)" msgstr "To email(s)"
@@ -1029,6 +1076,14 @@ msgstr "Tokens allow agents to connect and register. Fingerprints are stable ide
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Total data received for each interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Total data sent for each interface"
#: 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"
msgstr "Triggers when 1 minute load average exceeds a threshold" msgstr "Triggers when 1 minute load average exceeds a threshold"
@@ -1090,6 +1145,10 @@ msgstr "Up"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Up ({upSystemsLength})" msgstr "Up ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Upload"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Uptime" msgstr "Uptime"
@@ -1123,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"
@@ -363,6 +367,14 @@ msgstr "Creado"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Crítico (%)" msgstr "Crítico (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Descarga acumulativa"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Carga acumulativa"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Abajo"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Abajo ({downSystemsLength})" msgstr "Abajo ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Descargar"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Duración" msgstr "Duración"
@@ -452,6 +468,7 @@ msgstr "Editar"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Correo electrónico" msgstr "Correo electrónico"
@@ -472,6 +489,10 @@ msgstr "Ingrese la dirección de correo electrónico para restablecer la contras
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Ingrese dirección de correo..." msgstr "Ingrese dirección de correo..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Ingrese su contraseña de un solo uso."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "¿Olvidó su contraseña?" msgstr "¿Olvidó su contraseña?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Administrar preferencias de visualización y notificaciones." msgstr "Administrar preferencias de visualización y notificaciones."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Instrucciones manuales de configuración" msgstr "Instrucciones manuales de configuración"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Tráfico de red de los contenedores de Docker" 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
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"
@@ -715,6 +750,10 @@ msgstr "Soporte para OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "En cada reinicio, los sistemas en la base de datos se actualizarán para coincidir con los sistemas definidos en el archivo." msgstr "En cada reinicio, los sistemas en la base de datos se actualizarán para coincidir con los sistemas definidos en el archivo."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Contraseña de un solo uso"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Lectura"
msgid "Received" msgid "Received"
msgstr "Recibido" msgstr "Recibido"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Solicitar contraseña de un solo uso"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Solicitar OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Restablecer Contraseña" msgstr "Restablecer Contraseña"
@@ -888,10 +935,6 @@ msgstr "Enviado"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Establecer umbrales de porcentaje para los colores de los medidores." msgstr "Establecer umbrales de porcentaje para los colores de los medidores."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Establece el rango de tiempo predeterminado para los gráficos cuando se visualiza un sistema."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Rendimiento de {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Rendimiento del sistema de archivos raíz" msgstr "Rendimiento del sistema de archivos raíz"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Formato de hora"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "A correo(s)" msgstr "A correo(s)"
@@ -1034,6 +1081,14 @@ msgstr "Los tokens permiten que los agentes se conecten y registren. Las huellas
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub." msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Datos totales recibidos por cada interfaz"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Datos totales enviados por cada interfaz"
#: 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"
msgstr "Se activa cuando la carga media de 1 minuto supera un umbral" msgstr "Se activa cuando la carga media de 1 minuto supera un umbral"
@@ -1095,6 +1150,10 @@ msgstr "Activo"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Activo ({upSystemsLength})" msgstr "Activo ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Cargar"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Tiempo de actividad" msgstr "Tiempo de actividad"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "ایجاد شده"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "بحرانی (%)" msgstr "بحرانی (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "دانلود تجمعی"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "آپلود تجمعی"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "قطع"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "قطع ({downSystemsLength})" msgstr "قطع ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "دانلود"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "مدت زمان" msgstr "مدت زمان"
@@ -452,6 +468,7 @@ msgstr "ویرایش"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "ایمیل" msgstr "ایمیل"
@@ -472,6 +489,10 @@ msgstr "آدرس ایمیل را برای بازنشانی رمز عبور وا
msgid "Enter email address..." msgid "Enter email address..."
msgstr "آدرس ایمیل را وارد کنید..." msgstr "آدرس ایمیل را وارد کنید..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "رمز عبور یک‌بار مصرف خود را وارد کنید."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "برای <0>{min}</0> {min, plural, one {دقیقه} other {دقیقه}}
msgid "Forgot password?" msgid "Forgot password?"
msgstr "رمز عبور را فراموش کرده‌اید؟" msgstr "رمز عبور را فراموش کرده‌اید؟"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "دستور FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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 "مصرف برق پردازنده گرافیکی"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "مدیریت تنظیمات نمایش و اعلان‌ها." msgstr "مدیریت تنظیمات نمایش و اعلان‌ها."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "دستورالعمل‌های راه‌اندازی دستی" msgstr "دستورالعمل‌های راه‌اندازی دستی"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "ترافیک شبکه کانتینرهای داکر" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "ترافیک شبکه رابط‌های عمومی" msgstr "ترافیک شبکه رابط‌های عمومی"
@@ -715,6 +750,10 @@ msgstr "پشتیبانی از OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "در هر بار راه‌اندازی مجدد، سیستم‌های موجود در پایگاه داده با سیستم‌های تعریف شده در فایل مطابقت داده می‌شوند." msgstr "در هر بار راه‌اندازی مجدد، سیستم‌های موجود در پایگاه داده با سیستم‌های تعریف شده در فایل مطابقت داده می‌شوند."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "رمز عبور یک‌بار مصرف"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "خواندن"
msgid "Received" msgid "Received"
msgstr "دریافت شد" msgstr "دریافت شد"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "درخواست رمز عبور یک‌بار مصرف"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "درخواست OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "بازنشانی رمز عبور" msgstr "بازنشانی رمز عبور"
@@ -888,10 +935,6 @@ msgstr "ارسال شد"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "آستانه های درصدی را برای رنگ های متر تنظیم کنید." msgstr "آستانه های درصدی را برای رنگ های متر تنظیم کنید."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "بازه زمانی پیش‌فرض برای نمودارها هنگام مشاهده یک سیستم را تعیین می‌کند."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "توان عملیاتی {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "توان عملیاتی سیستم فایل ریشه" msgstr "توان عملیاتی سیستم فایل ریشه"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "فرمت زمان"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "به ایمیل(ها)" msgstr "به ایمیل(ها)"
@@ -1034,6 +1081,14 @@ msgstr "توکن‌ها به عامل‌ها اجازه اتصال و ثبت‌
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند." msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "داده‌های کل دریافت شده برای هر رابط"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "هنگامی که میانگین بار ۱ دقیقه‌ای از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که میانگین بار ۱ دقیقه‌ای از یک آستانه فراتر رود، فعال می‌شود"
@@ -1095,6 +1150,10 @@ msgstr "فعال"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "فعال ({upSystemsLength})" msgstr "فعال ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "آپلود"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "آپتایم" msgstr "آپتایم"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr ""
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Critique (%)" msgstr "Critique (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Téléchargement cumulatif"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Téléversement cumulatif"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Injoignable"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Télécharger"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
@@ -452,6 +468,7 @@ msgstr "Éditer"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -472,6 +489,10 @@ msgstr "Entrez l'adresse email pour réinitialiser le mot de passe"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Entrez l'adresse email..." msgstr "Entrez l'adresse email..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Entrez votre mot de passe à usage unique."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Pour <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Mot de passe oublié ?" msgstr "Mot de passe oublié ?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Commande FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Gérer les préférences d'affichage et de notification." msgstr "Gérer les préférences d'affichage et de notification."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Guide pour une installation manuelle" msgstr "Guide pour une installation manuelle"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Trafic réseau des conteneurs Docker" 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
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"
@@ -715,6 +750,10 @@ msgstr "Support OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "À chaque redémarrage, les systèmes dans la base de données seront mis à jour pour correspondre aux systèmes définis dans le fichier." msgstr "À chaque redémarrage, les systèmes dans la base de données seront mis à jour pour correspondre aux systèmes définis dans le fichier."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Mot de passe à usage unique"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Lecture"
msgid "Received" msgid "Received"
msgstr "Reçu" msgstr "Reçu"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Demander un mot de passe à usage unique"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Demander OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Réinitialiser le mot de passe" msgstr "Réinitialiser le mot de passe"
@@ -888,10 +935,6 @@ msgstr "Envoyé"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Définir des seuils de pourcentage pour les couleurs des compteurs." msgstr "Définir des seuils de pourcentage pour les couleurs des compteurs."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Définit la plage de temps par défaut pour les graphiques lorsqu'un système est consulté."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Débit de {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Débit du système de fichiers racine" msgstr "Débit du système de fichiers racine"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Format d'heure"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Aux email(s)" msgstr "Aux email(s)"
@@ -1034,6 +1081,14 @@ msgstr "Les tokens permettent aux agents de se connecter et de s'enregistrer. Le
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub." msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Données totales reçues pour chaque interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Données totales envoyées pour chaque interface"
#: 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"
msgstr "" msgstr ""
@@ -1095,6 +1150,10 @@ msgstr "Joignable"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Téléverser"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Temps de fonctionnement" msgstr "Temps de fonctionnement"
@@ -1128,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,12 +361,20 @@ 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 (%)"
msgstr "Kritično (%)" msgstr "Kritično (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativno preuzimanje"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativno otpremanje"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,17 +453,22 @@ msgstr ""
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -472,6 +489,10 @@ msgstr "Unesite email adresu za resetiranje lozinke"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Unesite email adresu..." msgstr "Unesite email adresu..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Unesite Vašu jednokratnu lozinku."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -493,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"
@@ -528,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}}"
@@ -542,6 +563,12 @@ msgstr "Za <0>{min}</0> {min, plural, one {minutu} other {minute}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Zaboravljena lozinka?" msgstr "Zaboravljena lozinka?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD naredba"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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 ""
@@ -616,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Upravljajte postavkama prikaza i obavijesti." msgstr "Upravljajte postavkama prikaza i obavijesti."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr ""
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Mrežni promet Docker spremnika" 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
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"
@@ -694,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
@@ -715,6 +750,10 @@ msgstr "Podrška za OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Prilikom svakog ponovnog pokretanja, sustavi u bazi podataka biti će ažurirani kako bi odgovarali sustavima definiranim u datoteci." msgstr "Prilikom svakog ponovnog pokretanja, sustavi u bazi podataka biti će ažurirani kako bi odgovarali sustavima definiranim u datoteci."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Jednokratna lozinka"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -737,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"
@@ -754,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"
@@ -770,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."
@@ -833,6 +872,14 @@ msgstr "Pročitaj"
msgid "Received" msgid "Received"
msgstr "Primljeno" msgstr "Primljeno"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Zatraži jednokratnu lozinku"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Zatraži OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Resetiraj Lozinku" msgstr "Resetiraj Lozinku"
@@ -888,10 +935,6 @@ msgstr "Poslano"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Postavite pragove postotka za boje mjerača." msgstr "Postavite pragove postotka za boje mjerača."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Postavlja zadani vremenski raspon za grafikone kada se sustav gleda."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Protok {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Protok root datotečnog sustava" msgstr "Protok root datotečnog sustava"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Format vremena"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Primaoci e-pošte" msgstr "Primaoci e-pošte"
@@ -1034,6 +1081,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Ukupni podaci primljeni za svako sučelje"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Ukupni podaci poslani za svako sučelje"
#: 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"
msgstr "" msgstr ""
@@ -1095,6 +1150,10 @@ msgstr ""
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Otpremi"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Vrijeme rada" msgstr "Vrijeme rada"
@@ -1122,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 ""
@@ -1174,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"
@@ -363,6 +367,14 @@ msgstr ""
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritikus (%)" msgstr "Kritikus (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulatív letöltés"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulatív feltöltés"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr ""
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Letöltés"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
@@ -452,6 +468,7 @@ msgstr ""
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -472,6 +489,10 @@ msgstr "E-mail cím megadása a jelszó visszaállításához"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Adja meg az e-mail címet..." msgstr "Adja meg az e-mail címet..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Adja meg az egyszeri jelszavát."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "A <0>{min}</0> {min, plural, one {perc} other {percek}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Elfelejtette a jelszavát?" msgstr "Elfelejtette a jelszavát?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD parancs"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "A megjelenítési és értesítési beállítások kezelése." msgstr "A megjelenítési és értesítési beállítások kezelése."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr ""
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Docker konténerek hálózati forgalma" 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
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"
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDC támogatás"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Minden újraindításkor az adatbázisban lévő rendszerek frissítésre kerülnek, hogy megfeleljenek a fájlban meghatározott rendszereknek." msgstr "Minden újraindításkor az adatbázisban lévő rendszerek frissítésre kerülnek, hogy megfeleljenek a fájlban meghatározott rendszereknek."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Egyszeri jelszó"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Olvasás"
msgid "Received" msgid "Received"
msgstr "Fogadott" msgstr "Fogadott"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Egyszeri jelszó kérése"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP kérése"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Jelszó visszaállítása" msgstr "Jelszó visszaállítása"
@@ -888,10 +935,6 @@ msgstr "Elküldve"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Százalékos küszöbértékek beállítása a mérőszínekhez." msgstr "Százalékos küszöbértékek beállítása a mérőszínekhez."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Beállítja az alapértelmezett időtartamot a diagramokhoz, amikor egy rendszert néznek."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "A {extraFsName} átviteli teljesítménye"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "A gyökér fájlrendszer átviteli teljesítménye" msgstr "A gyökér fájlrendszer átviteli teljesítménye"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Időformátum"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "E-mailben" msgstr "E-mailben"
@@ -1034,6 +1081,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Összes fogadott adat minden interfészenként"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Összes elküldött adat minden interfészenként"
#: 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"
msgstr "" msgstr ""
@@ -1095,6 +1150,10 @@ msgstr ""
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Feltöltés"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Üzemidő" msgstr "Üzemidő"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr ""
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritískt (%)" msgstr "Kritískt (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr ""
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr ""
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr ""
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
@@ -452,6 +468,7 @@ msgstr ""
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Netfang" msgstr "Netfang"
@@ -472,6 +489,10 @@ msgstr "Settu netfang til að endursetja lykilorð"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Settu inn Netfang..." msgstr "Settu inn Netfang..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr ""
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr ""
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Gleymt lykilorð?" msgstr "Gleymt lykilorð?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr ""
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "" msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr ""
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Net traffík docker kerfa" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "" msgstr ""
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDC stuðningur"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "" msgstr ""
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr ""
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Lesa"
msgid "Received" msgid "Received"
msgstr "Móttekið" msgstr "Móttekið"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr ""
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr ""
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Endurstilla lykilorð" msgstr "Endurstilla lykilorð"
@@ -888,10 +935,6 @@ msgstr "Sent"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Stilltu prósentuþröskuld fyrir mælaliti." msgstr "Stilltu prósentuþröskuld fyrir mælaliti."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr ""
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "" msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr ""
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Til tölvupósta" msgstr "Til tölvupósta"
@@ -1034,6 +1081,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "" msgstr ""
@@ -1095,6 +1150,10 @@ msgstr ""
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "" msgstr ""
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Creato"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Critico (%)" msgstr "Critico (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Download cumulativo"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Upload cumulativo"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Offline"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Offline ({downSystemsLength})" msgstr "Offline ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Scarica"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Durata" msgstr "Durata"
@@ -452,6 +468,7 @@ msgstr "Modifica"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -472,6 +489,10 @@ msgstr "Inserisci l'indirizzo email per reimpostare la password"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Inserisci l'indirizzo email..." msgstr "Inserisci l'indirizzo email..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Inserisci la tua password monouso."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Per <0>{min}</0> {min, plural, one {minuto} other {minuti}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Password dimenticata?" msgstr "Password dimenticata?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Gestisci le preferenze di visualizzazione e notifica." msgstr "Gestisci le preferenze di visualizzazione e notifica."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Istruzioni di configurazione manuale" msgstr "Istruzioni di configurazione manuale"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Traffico di rete dei container Docker" 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
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"
@@ -715,6 +750,10 @@ msgstr "Supporto OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ad ogni riavvio, i sistemi nel database verranno aggiornati per corrispondere ai sistemi definiti nel file." msgstr "Ad ogni riavvio, i sistemi nel database verranno aggiornati per corrispondere ai sistemi definiti nel file."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Password monouso"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Lettura"
msgid "Received" msgid "Received"
msgstr "Ricevuto" msgstr "Ricevuto"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Richiedi una password monouso"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Richiedi OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Reimposta Password" msgstr "Reimposta Password"
@@ -888,10 +935,6 @@ msgstr "Inviato"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Imposta le soglie percentuali per i colori dei contatori." msgstr "Imposta le soglie percentuali per i colori dei contatori."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Imposta l'intervallo di tempo predefinito per i grafici quando viene visualizzato un sistema."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Throughput di {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Throughput del filesystem root" msgstr "Throughput del filesystem root"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Formato orario"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "A email(s)" msgstr "A email(s)"
@@ -1034,6 +1081,14 @@ msgstr "I token consentono agli agenti di connettersi e registrarsi. Le impronte
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub." msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Dati totali ricevuti per ogni interfaccia"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Dati totali inviati per ogni interfaccia"
#: 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"
msgstr "Si attiva quando la media di carico di 1 minuto supera una soglia" msgstr "Si attiva quando la media di carico di 1 minuto supera una soglia"
@@ -1095,6 +1150,10 @@ msgstr "Attivo"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Attivo ({upSystemsLength})" msgstr "Attivo ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Carica"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Tempo di attività" msgstr "Tempo di attività"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "作成日"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "致命的 (%)" msgstr "致命的 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "累積ダウンロード"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "累積アップロード"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "停止"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "停止 ({downSystemsLength})" msgstr "停止 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "ダウンロード"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "期間" msgstr "期間"
@@ -452,6 +468,7 @@ msgstr "編集"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "メール" msgstr "メール"
@@ -472,6 +489,10 @@ msgstr "パスワードをリセットするためにメールアドレスを入
msgid "Enter email address..." msgid "Enter email address..."
msgstr "メールアドレスを入力..." msgstr "メールアドレスを入力..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "ワンタイムパスワードを入力してください。"
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "<0>{min}</0> {min, plural, one {分} other {分}}の間"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "パスワードをお忘れですか?" msgstr "パスワードをお忘れですか?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD コマンド"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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の消費電力"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "表示と通知の設定を管理します。" msgstr "表示と通知の設定を管理します。"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "手動セットアップの手順" msgstr "手動セットアップの手順"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Dockerコンテナのネットワークトラフィック" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "パブリックインターフェースのネットワークトラフィック" msgstr "パブリックインターフェースのネットワークトラフィック"
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDCサポート"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "再起動のたびに、データベース内のシステムはファイルに定義されたシステムに一致するように更新されます。" msgstr "再起動のたびに、データベース内のシステムはファイルに定義されたシステムに一致するように更新されます。"
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "ワンタイムパスワード"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "読み取り"
msgid "Received" msgid "Received"
msgstr "受信" msgstr "受信"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "ワンタイムパスワードをリクエスト"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP をリクエスト"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "パスワードをリセット" msgstr "パスワードをリセット"
@@ -888,10 +935,6 @@ msgstr "送信"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "メーターの色を変更するしきい値(パーセンテージ)を設定します。" msgstr "メーターの色を変更するしきい値(パーセンテージ)を設定します。"
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "システムを表示する際のチャートのデフォルトの時間範囲を設定します。"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "{extraFsName}のスループット"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "ルートファイルシステムのスループット" msgstr "ルートファイルシステムのスループット"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "時間形式"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "宛先メールアドレス" msgstr "宛先メールアドレス"
@@ -1034,6 +1081,14 @@ msgstr "トークンはエージェントの接続と登録を可能にします
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。" msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "各インターフェースの総受信データ量"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "1分間の負荷平均がしきい値を超えたときにトリガーされます" msgstr "1分間の負荷平均がしきい値を超えたときにトリガーされます"
@@ -1095,6 +1150,10 @@ msgstr "正常"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "正常 ({upSystemsLength})" msgstr "正常 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "アップロード"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "稼働時間" msgstr "稼働時間"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "생성됨"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "위험 (%)" msgstr "위험 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "누적 다운로드"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "누적 업로드"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "오프라인"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "오프라인 ({downSystemsLength})" msgstr "오프라인 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "다운로드"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "기간" msgstr "기간"
@@ -452,6 +468,7 @@ msgstr "수정"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "이메일" msgstr "이메일"
@@ -472,6 +489,10 @@ msgstr "비밀번호를 재설정하려면 이메일 주소를 입력하세요"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "이메일 주소 입력..." msgstr "이메일 주소 입력..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "<0>{min}</0> {min, plural, one {분} other {분}} 동안"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "비밀번호를 잊으셨나요?" msgstr "비밀번호를 잊으셨나요?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD 명령어"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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 전원 사용량"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "디스플레이 및 알림 설정" msgstr "디스플레이 및 알림 설정"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "수동 설정 방법" msgstr "수동 설정 방법"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Docker 컨테이너의 네트워크 트래픽" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "공용 인터페이스의 네트워크 트래픽" msgstr "공용 인터페이스의 네트워크 트래픽"
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDC 지원"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "매 시작 시, 데이터베이스가 파일에 정의된 시스템과 일치하도록 업데이트됩니다." msgstr "매 시작 시, 데이터베이스가 파일에 정의된 시스템과 일치하도록 업데이트됩니다."
#: src/components/login/auth-form.tsx
msgid "One-time password"
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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "읽기"
msgid "Received" msgid "Received"
msgstr "수신됨" msgstr "수신됨"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "OTP 요청"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP 요청"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "비밀번호 재설정" msgstr "비밀번호 재설정"
@@ -888,10 +935,6 @@ msgstr "보냄"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "그래프 미터 색상의 백분율 임계값을 설정합니다." msgstr "그래프 미터 색상의 백분율 임계값을 설정합니다."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "시스템을 볼 때 차트의 기본 시간 범위를 설정합니다."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "{extraFsName}의 처리량"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "루트 파일 시스템의 처리량" msgstr "루트 파일 시스템의 처리량"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "시간 형식"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "받는사람(들)" msgstr "받는사람(들)"
@@ -1034,6 +1081,14 @@ msgstr "토큰은 에이전트가 연결하고 등록할 수 있도록 합니다
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "토큰과 지문은 허브에 대한 WebSocket 연결을 인증하는 데 사용됩니다." msgstr "토큰과 지문은 허브에 대한 WebSocket 연결을 인증하는 데 사용됩니다."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "각 인터페이스별 총합 다운로드 데이터량"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "1분 부하 평균이 임계값을 초과하면 트리거됩니다." msgstr "1분 부하 평균이 임계값을 초과하면 트리거됩니다."
@@ -1095,6 +1150,10 @@ msgstr "온라인"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "온라인 ({upSystemsLength})" msgstr "온라인 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "업로드"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "가동 시간" msgstr "가동 시간"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Aangemaakt"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritiek (%)" msgstr "Kritiek (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Cumulatieve download"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Cumulatieve upload"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Offline"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Offline ({downSystemsLength})" msgstr "Offline ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Downloaden"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Duur" msgstr "Duur"
@@ -452,6 +468,7 @@ msgstr "Bewerken"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "E-mail" msgstr "E-mail"
@@ -472,6 +489,10 @@ msgstr "Voer een e-mailadres in om het wachtwoord opnieuw in te stellen"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Voer een e-mailadres in..." msgstr "Voer een e-mailadres in..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Voer uw eenmalig wachtwoord in."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Voor <0>{min}</0> {min, plural, one {minuut} other {minuten}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Wachtwoord vergeten?" msgstr "Wachtwoord vergeten?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD commando"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Weergave- en notificatievoorkeuren beheren." msgstr "Weergave- en notificatievoorkeuren beheren."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Handmatige installatie-instructies" msgstr "Handmatige installatie-instructies"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Netwerkverkeer van docker containers" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Netwerkverkeer van publieke interfaces" msgstr "Netwerkverkeer van publieke interfaces"
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDC ondersteuning"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Bij elke herstart zullen systemen in de database worden bijgewerkt om overeen te komen met de systemen die in het bestand zijn gedefinieerd." msgstr "Bij elke herstart zullen systemen in de database worden bijgewerkt om overeen te komen met de systemen die in het bestand zijn gedefinieerd."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Eenmalig wachtwoord"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Lezen"
msgid "Received" msgid "Received"
msgstr "Ontvangen" msgstr "Ontvangen"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Eenmalig wachtwoord aanvragen"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP aanvragen"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Wachtwoord resetten" msgstr "Wachtwoord resetten"
@@ -888,10 +935,6 @@ msgstr "Verzonden"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Stel percentagedrempels in voor meterkleuren." msgstr "Stel percentagedrempels in voor meterkleuren."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Stelt het standaard tijdsbereik voor grafieken in wanneer een systeem wordt bekeken."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Doorvoer van {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Doorvoer van het root bestandssysteem" msgstr "Doorvoer van het root bestandssysteem"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Tijdnotatie"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Naar e-mail(s)" msgstr "Naar e-mail(s)"
@@ -1034,6 +1081,14 @@ msgstr "Tokens staan agenten toe om verbinding te maken met en te registreren. V
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens en vingerafdrukken worden gebruikt om WebSocket verbindingen te verifiëren naar de hub." msgstr "Tokens en vingerafdrukken worden gebruikt om WebSocket verbindingen te verifiëren naar de hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Totaal ontvangen gegevens per interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Totaal verzonden gegevens per interface"
#: 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"
msgstr "Triggert wanneer de gemiddelde belasting een drempelwaarde overschrijdt" msgstr "Triggert wanneer de gemiddelde belasting een drempelwaarde overschrijdt"
@@ -1095,6 +1150,10 @@ msgstr "Online"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Online ({upSystemsLength})" msgstr "Online ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Uploaden"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Actief" msgstr "Actief"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr ""
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritisk (%)" msgstr "Kritisk (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativ nedlasting"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativ opplasting"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Nede"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Last ned"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
@@ -452,6 +468,7 @@ msgstr "Rediger"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "E-post" msgstr "E-post"
@@ -472,6 +489,10 @@ msgstr "Skriv inn e-postadresse for å nullstille passordet"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Skriv inn e-postadresse..." msgstr "Skriv inn e-postadresse..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Skriv inn ditt engangspassord."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "I <0>{min}</0> {min, plural, one {minutt} other {minutter}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Glemt passord?" msgstr "Glemt passord?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD kommando"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Endre visnings- og varslingsinnstillinger." msgstr "Endre visnings- og varslingsinnstillinger."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Instruks for Manuell Installasjon" msgstr "Instruks for Manuell Installasjon"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Nettverkstrafikk av docker-konteinere" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Nettverkstrafikk av eksterne nettverksgrensesnitt" msgstr "Nettverkstrafikk av eksterne nettverksgrensesnitt"
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDC-støtte"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ved hver omstart vil systemer i databasen bli oppdatert til å matche systemene definert i fila." msgstr "Ved hver omstart vil systemer i databasen bli oppdatert til å matche systemene definert i fila."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Engangspassord"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Lesing"
msgid "Received" msgid "Received"
msgstr "Mottatt" msgstr "Mottatt"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Be om engangspassord"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Be om OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Nullstill Passord" msgstr "Nullstill Passord"
@@ -888,10 +935,6 @@ msgstr "Sendt"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Angi prosentvise terskler for målerfarger." msgstr "Angi prosentvise terskler for målerfarger."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Angir standard tidsperiode for diagrammer når et system vises."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Gjennomstrømning av {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Gjennomstrømning av rot-filsystemet" msgstr "Gjennomstrømning av rot-filsystemet"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Tidsformat"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Til e-postadresse(r)" msgstr "Til e-postadresse(r)"
@@ -1034,6 +1081,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Totalt mottatt data for hvert grensesnitt"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Totalt sendt data for hvert grensesnitt"
#: 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"
msgstr "" msgstr ""
@@ -1095,6 +1150,10 @@ msgstr "Oppe"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Last opp"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Oppetid" msgstr "Oppetid"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Utworzono"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Krytyczny (%)" msgstr "Krytyczny (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Pobieranie skumulowane"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Wysyłanie skumulowane"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Nie działa"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Nie działa ({downSystemsLength})" msgstr "Nie działa ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Pobieranie"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Czas trwania" msgstr "Czas trwania"
@@ -452,6 +468,7 @@ msgstr "Edytuj"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "E-mail" msgstr "E-mail"
@@ -472,6 +489,10 @@ msgstr "Wprowadź adres e-mail, aby zresetować hasło"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Wprowadź adres e-mail..." msgstr "Wprowadź adres e-mail..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Wprowadź swoje jednorazowe hasło."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Na <0>{min}</0> {min, plural, one {minutę} other {minut}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Zapomniałeś hasła?" msgstr "Zapomniałeś hasła?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Polecenie FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Zarządzaj preferencjami wyświetlania i powiadomień." msgstr "Zarządzaj preferencjami wyświetlania i powiadomień."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Instrukcja ręcznej konfiguracji" msgstr "Instrukcja ręcznej konfiguracji"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Ruch sieciowy kontenerów Docker." 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Ruch sieciowy interfejsów publicznych" msgstr "Ruch sieciowy interfejsów publicznych"
@@ -715,6 +750,10 @@ msgstr "Wsparcie OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Przy każdym ponownym uruchomieniu systemy w bazie danych będą aktualizowane, aby odpowiadały systemom zdefiniowanym w pliku." msgstr "Przy każdym ponownym uruchomieniu systemy w bazie danych będą aktualizowane, aby odpowiadały systemom zdefiniowanym w pliku."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Hasło jednorazowe"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -827,12 +866,20 @@ 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"
msgstr "Otrzymane" msgstr "Otrzymane"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Zażądaj jednorazowego hasła"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Zażądaj OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Resetuj hasło" msgstr "Resetuj hasło"
@@ -888,10 +935,6 @@ msgstr "Wysłane"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Ustaw progi procentowe dla kolorów mierników." msgstr "Ustaw progi procentowe dla kolorów mierników."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Ustawia domyślny zakres czasowy dla wykresów, gdy system jest wyświetlony."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Przepustowość {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Przepustowość głównego systemu plików" msgstr "Przepustowość głównego systemu plików"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Format czasu"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Do e-mail(ów)" msgstr "Do e-mail(ów)"
@@ -1034,6 +1081,14 @@ msgstr "Tokeny umożliwiają agentom łączenie się i rejestrację. Odciski pal
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeny i odciski palców (fingerprinty) służą do uwierzytelniania połączeń WebSocket z hubem." msgstr "Tokeny i odciski palców (fingerprinty) służą do uwierzytelniania połączeń WebSocket z hubem."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Całkowita ilość danych odebranych dla każdego interfejsu"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Całkowita ilość danych wysłanych dla każdego interfejsu"
#: 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"
msgstr "Uruchamia się, gdy 1-minutowe średnie obciążenie systemu przekroczy ustawiony próg" msgstr "Uruchamia się, gdy 1-minutowe średnie obciążenie systemu przekroczy ustawiony próg"
@@ -1095,6 +1150,10 @@ msgstr "Działa"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Działa ({upSystemsLength})" msgstr "Działa ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Wysyłanie"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Czas pracy" msgstr "Czas pracy"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Criado"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Crítico (%)" msgstr "Crítico (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Download cumulativo"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Upload cumulativo"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "“Desligado”"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Inativo ({downSystemsLength})" msgstr "Inativo ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Transferir"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Duração" msgstr "Duração"
@@ -452,6 +468,7 @@ msgstr "Editar"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -472,6 +489,10 @@ msgstr "Digite o endereço de email para redefinir a senha"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Digite o endereço de email..." msgstr "Digite o endereço de email..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Insira a sua senha de uso único."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Esqueceu a senha?" msgstr "Esqueceu a senha?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Gerenciar preferências de exibição e notificação." msgstr "Gerenciar preferências de exibição e notificação."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Instruções de configuração manual" msgstr "Instruções de configuração manual"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Tráfego de rede dos contêineres Docker" 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
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"
@@ -715,6 +750,10 @@ msgstr "Suporte a OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "A cada reinício, os sistemas no banco de dados serão atualizados para corresponder aos sistemas definidos no arquivo." msgstr "A cada reinício, os sistemas no banco de dados serão atualizados para corresponder aos sistemas definidos no arquivo."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Senha de uso único"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Ler"
msgid "Received" msgid "Received"
msgstr "Recebido" msgstr "Recebido"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Solicitar senha de uso único"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Solicitar OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Redefinir Senha" msgstr "Redefinir Senha"
@@ -888,10 +935,6 @@ msgstr "Enviado"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Defina os limiares de porcentagem para as cores do medidor." msgstr "Defina os limiares de porcentagem para as cores do medidor."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Define o intervalo de tempo padrão para gráficos quando um sistema é visualizado."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Taxa de transferência de {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Taxa de transferência do sistema de arquivos raiz" msgstr "Taxa de transferência do sistema de arquivos raiz"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Formato de hora"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Para email(s)" msgstr "Para email(s)"
@@ -1034,6 +1081,14 @@ msgstr "Os tokens permitem que os agentes se conectem e registrem. As impressõe
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens e impressões digitais são usados para autenticar conexões WebSocket ao hub." msgstr "Tokens e impressões digitais são usados para autenticar conexões WebSocket ao hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Dados totais recebidos para cada interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Dados totais enviados para cada interface"
#: 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"
msgstr "Dispara quando a média de carga de 1 minuto excede um limite" msgstr "Dispara quando a média de carga de 1 minuto excede um limite"
@@ -1095,6 +1150,10 @@ msgstr "“Ligado”"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Ativo ({upSystemsLength})" msgstr "Ativo ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Carregar"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Tempo de Atividade" msgstr "Tempo de Atividade"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Создано"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Критический (%)" msgstr "Критический (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Совокупная загрузка"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Совокупная выгрузка"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Не в сети"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Скачать"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Длительность" msgstr "Длительность"
@@ -452,6 +468,7 @@ msgstr "Редактировать"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Электронная почта" msgstr "Электронная почта"
@@ -472,6 +489,10 @@ msgstr "Введите адрес электронной почты для сб
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Введите адрес электронной почты..." msgstr "Введите адрес электронной почты..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Введите ваш одноразовый пароль."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "На <0>{min}</0> {min, plural, one {минуту} other {минут}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Забыли пароль?" msgstr "Забыли пароль?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Команда FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Управляйте предпочтениями отображения и уведомлений." msgstr "Управляйте предпочтениями отображения и уведомлений."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Инструкции по ручной настройке" msgstr "Инструкции по ручной настройке"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Сетевой трафик контейнеров Docker" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Сетевой трафик публичных интерфейсов" msgstr "Сетевой трафик публичных интерфейсов"
@@ -715,6 +750,10 @@ msgstr "Поддержка OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "При каждом перезапуске системы в базе данных будут обновлены в соответствии с системами, определенными в файле." msgstr "При каждом перезапуске системы в базе данных будут обновлены в соответствии с системами, определенными в файле."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Одноразовый пароль"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Чтение"
msgid "Received" msgid "Received"
msgstr "Получено" msgstr "Получено"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Запросить одноразовый пароль"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Запросить OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Сбросить пароль" msgstr "Сбросить пароль"
@@ -888,10 +935,6 @@ msgstr "Отправлено"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Установите процентные пороги для цветов счетчиков." msgstr "Установите процентные пороги для цветов счетчиков."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Устанавливает диапазон времени по умолчанию для графиков при просмотре системы."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Пропускная способность {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Пропускная способность корневой файловой системы" msgstr "Пропускная способность корневой файловой системы"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Формат времени"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "На электронную почту" msgstr "На электронную почту"
@@ -1034,6 +1081,14 @@ msgstr "Токены позволяют агентам подключаться
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токены и отпечатки используются для аутентификации соединений WebSocket с хабом." msgstr "Токены и отпечатки используются для аутентификации соединений WebSocket с хабом."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Общий объем полученных данных для каждого интерфейса"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "Срабатывает, когда средняя загрузка за 1 минуту превышает порог" msgstr "Срабатывает, когда средняя загрузка за 1 минуту превышает порог"
@@ -1095,6 +1150,10 @@ msgstr "В сети"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Загрузить"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Время работы" msgstr "Время работы"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr ""
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritično (%)" msgstr "Kritično (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativno prenos"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativno nalaganje"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr ""
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Prenesi"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
@@ -452,6 +468,7 @@ msgstr ""
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "E-pošta" msgstr "E-pošta"
@@ -472,6 +489,10 @@ msgstr "Vnesite e-poštni naslov za ponastavitev gesla"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Vnesite e-poštni naslov..." msgstr "Vnesite e-poštni naslov..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Vnesite svoje enkratno geslo."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Za <0>{min}</0> {min, plural, one {minuto} other {minut}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Pozabljeno geslo?" msgstr "Pozabljeno geslo?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Ukaz FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Upravljajte nastavitve prikaza in obvestil." msgstr "Upravljajte nastavitve prikaza in obvestil."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr ""
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Omrežni promet docker kontejnerjev" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Omrežni promet javnih vmesnikov" msgstr "Omrežni promet javnih vmesnikov"
@@ -715,6 +750,10 @@ msgstr "Podpora za OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ob vsakem ponovnem zagonu bodo sistemi v zbirki podatkov posodobljeni, da se bodo ujemali s sistemi, definiranimi v datoteki." msgstr "Ob vsakem ponovnem zagonu bodo sistemi v zbirki podatkov posodobljeni, da se bodo ujemali s sistemi, definiranimi v datoteki."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Enkratno geslo"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Preberano"
msgid "Received" msgid "Received"
msgstr "Prejeto" msgstr "Prejeto"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Zahtevaj enkratno geslo"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Zahtevaj OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Ponastavi geslo" msgstr "Ponastavi geslo"
@@ -888,10 +935,6 @@ msgstr "Poslano"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Nastavite odstotne pragove za barve merilnikov." msgstr "Nastavite odstotne pragove za barve merilnikov."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Nastavi privzeti časovni obseg za grafikone, ko si ogledujete sistem."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Prepustnost {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Prepustnost korenskega datotečnega sistema" msgstr "Prepustnost korenskega datotečnega sistema"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Oblika časa"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "E-pošta za" msgstr "E-pošta za"
@@ -1034,6 +1081,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Skupni prejeti podatki za vsak vmesnik"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Skupni poslani podatki za vsak vmesnik"
#: 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"
msgstr "" msgstr ""
@@ -1095,6 +1150,10 @@ msgstr ""
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Naloži"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Čas delovanja" msgstr "Čas delovanja"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Skapad"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritisk (%)" msgstr "Kritisk (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativ nedladdning"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativ uppladdning"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr ""
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Ladda ner"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr ""
@@ -452,6 +468,7 @@ msgstr ""
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "E-post" msgstr "E-post"
@@ -472,6 +489,10 @@ msgstr "Ange e-postadress för att återställa lösenord"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Ange e-postadress..." msgstr "Ange e-postadress..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Ange ditt engångslösenord."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Under <0>{min}</0> {min, plural, one {minut} other {minuter}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Glömt lösenordet?" msgstr "Glömt lösenordet?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD kommando"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Hantera visnings- och aviseringsinställningar." msgstr "Hantera visnings- och aviseringsinställningar."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr ""
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Nätverkstrafik för dockercontainrar" 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
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"
@@ -715,6 +750,10 @@ msgstr "Stöd för OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Vid varje omstart kommer systemen i databasen att uppdateras för att matcha systemen som definieras i filen." msgstr "Vid varje omstart kommer systemen i databasen att uppdateras för att matcha systemen som definieras i filen."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Engångslösenord"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Läs"
msgid "Received" msgid "Received"
msgstr "Mottaget" msgstr "Mottaget"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Begär engångslösenord"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Begär OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Återställ lösenord" msgstr "Återställ lösenord"
@@ -888,10 +935,6 @@ msgstr "Skickat"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Ställ in procentuella tröskelvärden för mätarfärger." msgstr "Ställ in procentuella tröskelvärden för mätarfärger."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Anger standardtidsintervallet för diagram när ett system visas."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Genomströmning av {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Genomströmning av rotfilsystemet" msgstr "Genomströmning av rotfilsystemet"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Tidsformat"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Till e-postadress(er)" msgstr "Till e-postadress(er)"
@@ -1034,6 +1081,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Totalt mottagen data för varje gränssnitt"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Totalt skickad data för varje gränssnitt"
#: 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"
msgstr "" msgstr ""
@@ -1095,6 +1150,10 @@ msgstr ""
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Ladda upp"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Drifttid" msgstr "Drifttid"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Oluşturuldu"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Kritik (%)" msgstr "Kritik (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kümülatif İndirme"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kümülatif Yükleme"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Kapalı"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Kapalı ({downSystemsLength})" msgstr "Kapalı ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "İndir"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Süre" msgstr "Süre"
@@ -452,6 +468,7 @@ msgstr "Düzenle"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "E-posta" msgstr "E-posta"
@@ -472,6 +489,10 @@ msgstr "Şifreyi sıfırlamak için e-posta adresini girin"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "E-posta adresini girin..." msgstr "E-posta adresini girin..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Tek kullanımlık şifrenizi girin."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "<0>{min}</0> {min, plural, one {dakika} other {dakika}} için"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Şifrenizi mi unuttunuz?" msgstr "Şifrenizi mi unuttunuz?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD komutu"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Görüntüleme ve bildirim tercihlerini yönetin." msgstr "Görüntüleme ve bildirim tercihlerini yönetin."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Manuel kurulum talimatları" msgstr "Manuel kurulum talimatları"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Docker konteynerlerinin ağ trafiği" 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
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"
@@ -715,6 +750,10 @@ msgstr "OAuth 2 / OIDC desteği"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Her yeniden başlatmada, veritabanındaki sistemler dosyada tanımlanan sistemlerle eşleşecek şekilde güncellenecektir." msgstr "Her yeniden başlatmada, veritabanındaki sistemler dosyada tanımlanan sistemlerle eşleşecek şekilde güncellenecektir."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Tek kullanımlık şifre"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Oku"
msgid "Received" msgid "Received"
msgstr "Alındı" msgstr "Alındı"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Tek kullanımlık şifre iste"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP iste"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Şifreyi Sıfırla" msgstr "Şifreyi Sıfırla"
@@ -888,10 +935,6 @@ msgstr "Gönderildi"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Sayaç renkleri için yüzde eşiklerini ayarlayın." msgstr "Sayaç renkleri için yüzde eşiklerini ayarlayın."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Bir sistem görüntülendiğinde grafikler için varsayılan zaman aralığını ayarlar."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "{extraFsName} verimliliği"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Kök dosya sisteminin verimliliği" msgstr "Kök dosya sisteminin verimliliği"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Zaman formatı"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "E-posta(lar)a" msgstr "E-posta(lar)a"
@@ -1034,6 +1081,14 @@ msgstr "Token'lar agentların bağlanıp kaydolmasına izin verir. Parmak izleri
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Token'lar ve parmak izleri hub'a WebSocket bağlantılarını doğrulamak için kullanılır." msgstr "Token'lar ve parmak izleri hub'a WebSocket bağlantılarını doğrulamak için kullanılır."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Her arayüz için alınan toplam veri"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Her arayüz için gönderilen toplam veri"
#: 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"
msgstr "1 dakikalık yük ortalaması bir eşiği aştığında tetiklenir" msgstr "1 dakikalık yük ortalaması bir eşiği aştığında tetiklenir"
@@ -1095,6 +1150,10 @@ msgstr "Açık"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Açık ({upSystemsLength})" msgstr "Açık ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Yükle"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Çalışma Süresi" msgstr "Çalışma Süresi"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Створено"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Критично (%)" msgstr "Критично (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Кумулятивне завантаження"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Кумулятивне відвантаження"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Не працює"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Не працює ({downSystemsLength})" msgstr "Не працює ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Завантажити"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Тривалість" msgstr "Тривалість"
@@ -452,6 +468,7 @@ msgstr "Редагувати"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Електронна пошта" msgstr "Електронна пошта"
@@ -472,6 +489,10 @@ msgstr "Введіть адресу електронної пошти для с
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Введіть адресу електронної пошти..." msgstr "Введіть адресу електронної пошти..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Введіть ваш одноразовий пароль."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Протягом <0>{min}</0> {min, plural, one {хвилини} other {
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Забули пароль?" msgstr "Забули пароль?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Команда FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Керуйте параметрами відображення та сповіщень." msgstr "Керуйте параметрами відображення та сповіщень."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Інструкції з ручного налаштування" msgstr "Інструкції з ручного налаштування"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Мережевий трафік контейнерів Docker" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "Мережевий трафік публічних інтерфейсів" msgstr "Мережевий трафік публічних інтерфейсів"
@@ -715,6 +750,10 @@ msgstr "Підтримка OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "При кожному перезапуску системи в базі даних будуть оновлені, щоб відповідати системам, визначеним у файлі." msgstr "При кожному перезапуску системи в базі даних будуть оновлені, щоб відповідати системам, визначеним у файлі."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Одноразовий пароль"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Читання"
msgid "Received" msgid "Received"
msgstr "Отримано" msgstr "Отримано"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Запит одноразового пароля"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Запит OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Скинути пароль" msgstr "Скинути пароль"
@@ -888,10 +935,6 @@ msgstr "Відправлено"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Встановіть відсоткові пороги для кольорів лічильників." msgstr "Встановіть відсоткові пороги для кольорів лічильників."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Встановлює стандартний діапазон часу для графіків при перегляді системи."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Пропускна здатність {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Пропускна здатність кореневої файлової системи" msgstr "Пропускна здатність кореневої файлової системи"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Формат часу"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "На електронну пошту" msgstr "На електронну пошту"
@@ -1034,6 +1081,14 @@ msgstr "Токени дозволяють агентам підключатис
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токени та відбитки використовуються для автентифікації WebSocket з'єднань до хабу." msgstr "Токени та відбитки використовуються для автентифікації WebSocket з'єднань до хабу."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Загальний обсяг отриманих даних для кожного інтерфейсу"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "Спрацьовує, коли середнє навантаження за 1 хвилину перевищує поріг" msgstr "Спрацьовує, коли середнє навантаження за 1 хвилину перевищує поріг"
@@ -1095,6 +1150,10 @@ msgstr "Працює"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Працює ({upSystemsLength})" msgstr "Працює ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Відвантажити"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Час роботи" msgstr "Час роботи"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "Đã tạo"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "Độ nghiêm trọng (%)" msgstr "Độ nghiêm trọng (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Tải xuống tích lũy"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Tải lên tích lũy"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "Mất kết nối"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Mất kết nối ({downSystemsLength})" msgstr "Mất kết nối ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Tải xuống"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "Thời lượng" msgstr "Thời lượng"
@@ -452,6 +468,7 @@ msgstr "Chỉnh sửa"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -472,6 +489,10 @@ msgstr "Nhập địa chỉ email để đặt lại mật khẩu"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Nhập địa chỉ email..." msgstr "Nhập địa chỉ email..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Nhập mật khẩu một lần của bạn."
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "Trong <0>{min}</0> {min, plural, one {phút} other {phút}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "Quên mật khẩu?" msgstr "Quên mật khẩu?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Lệnh FreeBSD"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "Quản lý tùy chọn hiển thị và thông báo." msgstr "Quản lý tùy chọn hiển thị và thông báo."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "Hướng dẫn cài đặt thủ công" msgstr "Hướng dẫn cài đặt thủ công"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Lưu lượng mạng của các container Docker" 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
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"
@@ -715,6 +750,10 @@ msgstr "Hỗ trợ OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Mỗi khi khởi động lại, các hệ thống trong cơ sở dữ liệu sẽ được cập nhật để khớp với các hệ thống được định nghĩa trong tệp." msgstr "Mỗi khi khởi động lại, các hệ thống trong cơ sở dữ liệu sẽ được cập nhật để khớp với các hệ thống được định nghĩa trong tệp."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Mật khẩu một lần"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "Đọc"
msgid "Received" msgid "Received"
msgstr "Đã nhận" msgstr "Đã nhận"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Yêu cầu mật khẩu một lần"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Yêu cầu OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Đặt lại Mật khẩu" msgstr "Đặt lại Mật khẩu"
@@ -888,10 +935,6 @@ msgstr "Đã gửi"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Đặt ngưỡng cho màu sắc đồng hồ." msgstr "Đặt ngưỡng cho màu sắc đồng hồ."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Đặt phạm vi thời gian mặc định cho biểu đồ khi một hệ thống được xem."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "Thông lượng của {extraFsName}"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Thông lượng của hệ thống tệp gốc" msgstr "Thông lượng của hệ thống tệp gốc"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Định dạng thời gian"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "Đến email(s)" msgstr "Đến email(s)"
@@ -1034,6 +1081,14 @@ msgstr "Token cho phép các tác nhân kết nối và đăng ký. Vân tay là
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Token và vân tay được sử dụng để xác thực các kết nối WebSocket đến trung tâm." msgstr "Token và vân tay được sử dụng để xác thực các kết nối WebSocket đến trung tâm."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Tổng dữ liệu nhận được cho mỗi giao diện"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Tổng dữ liệu gửi đi cho mỗi giao diện"
#: 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"
msgstr "Kích hoạt khi tải trung bình 1 phút vượt quá ngưỡng" msgstr "Kích hoạt khi tải trung bình 1 phút vượt quá ngưỡng"
@@ -1095,6 +1150,10 @@ msgstr "Hoạt động"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "Hoạt động ({upSystemsLength})" msgstr "Hoạt động ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Tải lên"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "Thời gian hoạt động" msgstr "Thời gian hoạt động"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "创建时间"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "临界 (%)" msgstr "临界 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "累计下载"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "累计上传"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "离线"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "离线 ({downSystemsLength})" msgstr "离线 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "下载"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "持续时间" msgstr "持续时间"
@@ -452,6 +468,7 @@ msgstr "编辑"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "电子邮件" msgstr "电子邮件"
@@ -472,6 +489,10 @@ msgstr "输入电子邮件地址以重置密码"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "输入电子邮件地址..." msgstr "输入电子邮件地址..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "输入您的一次性密码。"
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "持续<0>{min}</0> {min, plural, one {分钟} other {分钟}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "忘记密码?" msgstr "忘记密码?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD 命令"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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 功耗"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "管理显示和通知偏好。" msgstr "管理显示和通知偏好。"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "手动设置说明" msgstr "手动设置说明"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Docker 容器的网络流量" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "公共接口的网络流量" msgstr "公共接口的网络流量"
@@ -715,6 +750,10 @@ msgstr "支持 OAuth 2/OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "每次重启时,数据库中的系统将更新以匹配文件中定义的系统。" msgstr "每次重启时,数据库中的系统将更新以匹配文件中定义的系统。"
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "一次性密码"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "读取"
msgid "Received" msgid "Received"
msgstr "接收" msgstr "接收"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "请求一次性密码"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "请求 OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "重置密码" msgstr "重置密码"
@@ -888,10 +935,6 @@ msgstr "发送"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "设置仪表颜色的百分比阈值。" msgstr "设置仪表颜色的百分比阈值。"
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "设置查看系统时图表的默认时间范围。"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "{extraFsName}的吞吐量"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "根文件系统的吞吐量" msgstr "根文件系统的吞吐量"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "时间格式"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "发送到电子邮件" msgstr "发送到电子邮件"
@@ -1034,6 +1081,14 @@ msgstr "令牌允许客户端连接和注册。指纹是每个系统唯一的稳
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌与指纹用于验证到中心的 WebSocket 连接。" msgstr "令牌与指纹用于验证到中心的 WebSocket 连接。"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "每个接口的总接收数据量"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "当 1 分钟负载平均值超过阈值时触发" msgstr "当 1 分钟负载平均值超过阈值时触发"
@@ -1095,6 +1150,10 @@ msgstr "在线"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "在线 ({upSystemsLength})" msgstr "在线 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "上传"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "正常运行时间" msgstr "正常运行时间"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "已建立"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "嚴重 (%)" msgstr "嚴重 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "累計下載"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "累計上傳"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "中斷"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "中斷 ({downSystemsLength})" msgstr "中斷 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "下載"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "持續時間" msgstr "持續時間"
@@ -452,6 +468,7 @@ msgstr "編輯"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "電子郵件" msgstr "電子郵件"
@@ -472,6 +489,10 @@ msgstr "輸入電子郵件地址以重置密碼"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "輸入電子郵件地址..." msgstr "輸入電子郵件地址..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "輸入您的一次性密碼。"
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "持續<0>{min}</0> {min, plural, one {分鐘} other {分鐘}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "忘記密碼?" msgstr "忘記密碼?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD 指令"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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 功耗"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "管理顯示和通知偏好。" msgstr "管理顯示和通知偏好。"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "手動設定說明" msgstr "手動設定說明"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Docker 容器的網絡流量" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "公共接口的網絡流量" msgstr "公共接口的網絡流量"
@@ -715,6 +750,10 @@ msgstr "支援 OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "每次重新啟動時,將會以檔案中的系統定義更新資料庫。" msgstr "每次重新啟動時,將會以檔案中的系統定義更新資料庫。"
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "一次性密碼"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "讀取"
msgid "Received" msgid "Received"
msgstr "接收" msgstr "接收"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "請求一次性密碼"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "請求 OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "重設密碼" msgstr "重設密碼"
@@ -888,10 +935,6 @@ msgstr "發送"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "設定儀表顏色的百分比閾值。" msgstr "設定儀表顏色的百分比閾值。"
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "設置查看系統時圖表的默認時間範圍。"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "{extraFsName}的吞吐量"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "根文件系統的吞吐量" msgstr "根文件系統的吞吐量"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "時間格式"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "發送到電子郵件" msgstr "發送到電子郵件"
@@ -1034,6 +1081,14 @@ msgstr "令牌允許代理程式連接和註冊。指紋是每個系統唯一的
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌和指紋用於驗證到中心的WebSocket連接。" msgstr "令牌和指紋用於驗證到中心的WebSocket連接。"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "每個介面的總接收資料量"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "當 1 分鐘平均負載超過閾值時觸發" msgstr "當 1 分鐘平均負載超過閾值時觸發"
@@ -1095,6 +1150,10 @@ msgstr "上線"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "上線 ({upSystemsLength})" msgstr "上線 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "上傳"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "正常運行時間" msgstr "正常運行時間"
@@ -1128,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"
@@ -363,6 +367,14 @@ msgstr "已建立"
msgid "Critical (%)" msgid "Critical (%)"
msgstr "嚴重 (%)" msgstr "嚴重 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "累計下載"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "累計上傳"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
@@ -441,6 +453,10 @@ msgstr "離線"
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "離線 ({downSystemsLength})" msgstr "離線 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "下載"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "持續時間" msgstr "持續時間"
@@ -452,6 +468,7 @@ msgstr "編輯"
#: 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
#: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "電子郵件" msgstr "電子郵件"
@@ -472,6 +489,10 @@ msgstr "輸入電子郵件地址以重設密碼"
msgid "Enter email address..." msgid "Enter email address..."
msgstr "輸入電子郵件地址..." msgstr "輸入電子郵件地址..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "輸入您的一次性密碼。"
#: 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
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +563,12 @@ msgstr "持續<0>{min}</0> {min, plural, one {分鐘} other {分鐘}}"
msgid "Forgot password?" msgid "Forgot password?"
msgstr "忘記密碼?" msgstr "忘記密碼?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD 指令"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
@@ -553,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 功耗"
@@ -645,6 +676,7 @@ msgid "Manage display and notification preferences."
msgstr "管理顯示和通知偏好。" msgstr "管理顯示和通知偏好。"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "手動設定說明" msgstr "手動設定說明"
@@ -680,6 +712,9 @@ msgid "Network traffic of docker containers"
msgstr "Docker 容器的網路流量" 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
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "公開介面的網路流量" msgstr "公開介面的網路流量"
@@ -715,6 +750,10 @@ msgstr "支援 OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file." msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "每次重新啟動時,將會以檔案中的系統定義更新資料庫。" msgstr "每次重新啟動時,將會以檔案中的系統定義更新資料庫。"
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "一次性密碼"
#: 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
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +872,14 @@ msgstr "讀取"
msgid "Received" msgid "Received"
msgstr "接收" msgstr "接收"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "請求一次性密碼"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "請求 OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "重設密碼" msgstr "重設密碼"
@@ -888,10 +935,6 @@ msgstr "傳送"
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "設定儀表顏色的百分比閾值。" msgstr "設定儀表顏色的百分比閾值。"
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "設定顯示系統圖表的預設時間範圍。"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1002,6 +1045,10 @@ msgstr "{extraFsName}的傳輸速率"
msgid "Throughput of root filesystem" msgid "Throughput of root filesystem"
msgstr "Root文件系統的傳輸速率" msgstr "Root文件系統的傳輸速率"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "時間格式"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "To email(s)" msgid "To email(s)"
msgstr "發送到電子郵件" msgstr "發送到電子郵件"
@@ -1034,6 +1081,14 @@ msgstr "令牌允許代理程式連線和註冊。指紋是每個系統的唯一
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌和指紋被用於驗證到 Hub 的 WebSocket 連線。" msgstr "令牌和指紋被用於驗證到 Hub 的 WebSocket 連線。"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "每個介面的總接收資料量"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
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"
msgstr "當 1 分鐘平均負載超過閾值時觸發" msgstr "當 1 分鐘平均負載超過閾值時觸發"
@@ -1095,6 +1150,10 @@ msgstr "上線"
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "上線 ({upSystemsLength})" msgstr "上線 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "上傳"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
msgstr "運行時間" msgstr "運行時間"
@@ -1128,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,25 +75,8 @@ export interface SystemInfo {
dt?: number dt?: number
/** operating system */ /** operating system */
os?: Os os?: Os
/** network sent (mb) */ /** connection type */
ns?: number ct?: ConnectionType
/** network received (mb) */
nr?: number
}
export interface NetworkInterfaceStats {
/** network sent (mb) */
ns: number
/** network received (mb) */
nr: number
/** max network sent (mb) */
nsm?: number
/** max network received (mb) */
nrm?: number
/** total bytes sent since boot */
tbs?: number
/** total bytes received since boot */
tbr?: number
} }
export interface SystemStats { export interface SystemStats {
@@ -150,9 +133,7 @@ export interface SystemStats {
nsm?: number nsm?: number
/** max network received (mb) */ /** max network received (mb) */
nrm?: number nrm?: number
/** per-interface network stats */ /** max network sent (bytes) */
ni?: Record<string, NetworkInterfaceStats>
/** max bandwidth (bytes) [sent, received] */
bm?: [number, number] bm?: [number, number]
/** temperatures */ /** temperatures */
t?: Record<string, number> t?: Record<string, number>
@@ -162,8 +143,8 @@ export interface SystemStats {
g?: Record<string, GPUData> g?: Record<string, GPUData>
/** battery percent and state */ /** battery percent and state */
bat?: [number, BatteryState] bat?: [number, BatteryState]
/** network connection statistics */ /** network interfaces [upload bytes, download bytes, total upload bytes, total download bytes] */
nets?: Record<string, number> ni?: Record<string, [number, number, number, number]>
} }
export interface GPUData { export interface GPUData {
@@ -177,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

View File

@@ -125,3 +125,28 @@ func CreateSystems(app core.App, count int, userId string, status string) ([]*co
} }
return systems, nil return systems, nil
} }
// GetHubWithUser creates a test hub with a test user and user settings
func GetHubWithUser(t *testing.T) (*TestHub, *core.Record) {
hub, err := NewTestHub(t.TempDir())
assert.NoError(t, err)
hub.StartHub()
// Manually initialize the system manager to bind event hooks
err = hub.GetSystemManager().Initialize()
assert.NoError(t, err)
// Create a test user
user, err := CreateUser(hub, "test@example.com", "password")
assert.NoError(t, err)
// Create user settings for the test user (required for alert notifications)
userSettingsData := map[string]any{
"user": user.Id,
"settings": `{"emails":[test@example.com],"webhooks":[]}`,
}
_, err = CreateRecord(hub, "user_settings", userSettingsData)
assert.NoError(t, err)
return hub, user
}

Some files were not shown because too many files have changed in this diff Show More