Compare commits

...

26 Commits

Author SHA1 Message Date
henrygd
9840b99327 0.18.5 release 2026-03-27 16:27:53 -04:00
henrygd
f7b5a505e8 update translations 2026-03-27 15:57:26 -04:00
henrygd
3cb32ac046 hub(ui): add spacing at bottom of the page if temp tooltip is very long 2026-03-27 14:54:31 -04:00
henrygd
e610d9bfc8 ui: standardize table styles 2026-03-27 14:08:59 -04:00
henrygd
b53fdbe0ef fix(agent): find macmon if /opt/homebrew/bin is not in path (#1746) 2026-03-27 13:52:22 -04:00
henrygd
c7261b56f1 hub(ui): style cleanup and mobile improvements 2026-03-27 12:26:00 -04:00
henrygd
3f4c3d51b6 update go deps 2026-03-27 12:25:17 -04:00
Jim Haff
ad21cab457 Prevent temperature collection from blocking agent stats (#1839) 2026-03-26 20:03:51 -04:00
henrygd
f04684b30a hub(ui): small js optimizations 2026-03-26 19:16:39 -04:00
Stavros
4d4e4fba9b feat: use dropdown menu as navigation on mobile devices (#1840)
Co-authored-by: henrygd <hank@henrygd.me>
2026-03-26 18:27:42 -04:00
henrygd
62587919f4 hub(ui): tabs display for system + major frontend/charts refactoring
- System page tabs display option
- Remove very specific chart components (disk usage, container cpu, etc)
and refactor to use more flexible area and line chart components
- Optimizations around chart handling to decrease mem usage. Charts are
only redrawn now if in view.
- Resolve most of the react dev warnings

Co-authored-by: sveng93 <svenvanginkel@icloud.com>
2026-03-26 15:21:39 -04:00
henrygd
35528332fd hub: fix possible nil pointer panic in realtime worker 2026-03-26 10:48:37 -04:00
henrygd
e3e453140e fix(agent): isolate container network rate tracking per cache interval
Previously, the agent shared a single PrevReadTime timestamp across all
collection intervals (e.g., 1s and 60s). This caused the 60s collector
to divide its accumulated 60s byte delta by the tiny time elapsed since
the last 1s collection, resulting in astronomically inflated network
rates. The fix introduces per-cache-time read time tracking, ensuring
calculations for each interval use their own independent timing context.
2026-03-24 13:07:56 -04:00
henrygd
7a64da9f65 hub: add guard to WSConn.Ping to ensure no nil conn ptr 2026-03-23 15:25:43 -04:00
henrygd
8e71c8ad97 hub: don't retry update check within cache time if request fails 2026-03-22 18:18:31 -04:00
henrygd
97f3b8c61f test(hub): add tests for update endpoint 2026-03-22 17:56:27 -04:00
henrygd
0b0b5d16d7 refactor(hub): move api related code from hub.go to api.go 2026-03-22 17:31:06 -04:00
Sven van Ginkel
b2fd50211e feat(hub): show "update available" notification in hub web UI (#1830)
* refactor, make opt-in, and deprecate /api/beszel/getkey in favor of /api/beszel/info

---------

Co-authored-by: henrygd <hank@henrygd.me>
2026-03-22 17:23:54 -04:00
Sven van Ginkel
c159eaacd1 fix light flashes when refresh in dark mode (#1832) 2026-03-22 13:35:43 -04:00
Sven van Ginkel
441bdd2ec5 fix: correct DST offset handling in daily quiet hours (#1827) 2026-03-22 12:50:36 -04:00
henrygd
ff36138229 fix(hub): add onAfterBootstrapAndMigrations to properly queue fns after migrations
also remove error return from NewHub and improve comments in hub.go
2026-03-20 19:32:59 -04:00
henrygd
be70840609 test: update tests that use os.Setenv to t.Setenv 2026-03-20 15:00:28 -04:00
henrygd
565162ef5f refactor(hub): harden/enforce pb api rules and add tests
- separate collection related code from hub.go
- ensure hub is bootstrapped and collections updated automatically when
calling NewHub
2026-03-20 14:39:05 -04:00
henrygd
adbfe7cfb7 chore: upgrade action and go versions in vulncheck workflow 2026-03-19 11:36:10 -04:00
henrygd
1ff7762c80 test(hub): add status alert tests covering multiple users 2026-03-18 17:44:34 -04:00
henrygd
0ab8a606e0 fix(ui): hooks bug in all systems table disk cell 2026-03-18 17:17:58 -04:00
113 changed files with 9162 additions and 6289 deletions

View File

@@ -19,11 +19,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.25.x
go-version: 1.26.x
# cached: false
- name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest

View File

@@ -70,19 +70,11 @@ func TestNewWebSocketClient(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
// Set up environment
if tc.hubURL != "" {
os.Setenv("BESZEL_AGENT_HUB_URL", tc.hubURL)
} else {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
t.Setenv("BESZEL_AGENT_HUB_URL", tc.hubURL)
}
if tc.token != "" {
os.Setenv("BESZEL_AGENT_TOKEN", tc.token)
} else {
os.Unsetenv("BESZEL_AGENT_TOKEN")
t.Setenv("BESZEL_AGENT_TOKEN", tc.token)
}
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
client, err := newWebSocketClient(agent)
@@ -138,12 +130,8 @@ func TestWebSocketClient_GetOptions(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Set up environment
os.Setenv("BESZEL_AGENT_HUB_URL", tc.inputURL)
os.Setenv("BESZEL_AGENT_TOKEN", "test-token")
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", tc.inputURL)
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
client, err := newWebSocketClient(agent)
require.NoError(t, err)
@@ -185,12 +173,8 @@ func TestWebSocketClient_VerifySignature(t *testing.T) {
require.NoError(t, err)
// Set up environment
os.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
os.Setenv("BESZEL_AGENT_TOKEN", "test-token")
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
client, err := newWebSocketClient(agent)
require.NoError(t, err)
@@ -258,12 +242,8 @@ func TestWebSocketClient_HandleHubRequest(t *testing.T) {
agent := createTestAgent(t)
// Set up environment
os.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
os.Setenv("BESZEL_AGENT_TOKEN", "test-token")
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
client, err := newWebSocketClient(agent)
require.NoError(t, err)
@@ -350,13 +330,8 @@ func TestGetUserAgent(t *testing.T) {
func TestWebSocketClient_Close(t *testing.T) {
agent := createTestAgent(t)
// Set up environment
os.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
os.Setenv("BESZEL_AGENT_TOKEN", "test-token")
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
client, err := newWebSocketClient(agent)
require.NoError(t, err)
@@ -371,13 +346,8 @@ func TestWebSocketClient_Close(t *testing.T) {
func TestWebSocketClient_ConnectRateLimit(t *testing.T) {
agent := createTestAgent(t)
// Set up environment
os.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
os.Setenv("BESZEL_AGENT_TOKEN", "test-token")
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
client, err := newWebSocketClient(agent)
require.NoError(t, err)
@@ -393,20 +363,10 @@ func TestWebSocketClient_ConnectRateLimit(t *testing.T) {
// TestGetToken tests the getToken function with various scenarios
func TestGetToken(t *testing.T) {
unsetEnvVars := func() {
os.Unsetenv("BESZEL_AGENT_TOKEN")
os.Unsetenv("TOKEN")
os.Unsetenv("BESZEL_AGENT_TOKEN_FILE")
os.Unsetenv("TOKEN_FILE")
}
t.Run("token from TOKEN environment variable", func(t *testing.T) {
unsetEnvVars()
// Set TOKEN env var
expectedToken := "test-token-from-env"
os.Setenv("TOKEN", expectedToken)
defer os.Unsetenv("TOKEN")
t.Setenv("TOKEN", expectedToken)
token, err := getToken()
assert.NoError(t, err)
@@ -414,12 +374,9 @@ func TestGetToken(t *testing.T) {
})
t.Run("token from BESZEL_AGENT_TOKEN environment variable", func(t *testing.T) {
unsetEnvVars()
// Set BESZEL_AGENT_TOKEN env var (should take precedence)
expectedToken := "test-token-from-beszel-env"
os.Setenv("BESZEL_AGENT_TOKEN", expectedToken)
defer os.Unsetenv("BESZEL_AGENT_TOKEN")
t.Setenv("BESZEL_AGENT_TOKEN", expectedToken)
token, err := getToken()
assert.NoError(t, err)
@@ -427,8 +384,6 @@ func TestGetToken(t *testing.T) {
})
t.Run("token from TOKEN_FILE", func(t *testing.T) {
unsetEnvVars()
// Create a temporary token file
expectedToken := "test-token-from-file"
tokenFile, err := os.CreateTemp("", "token-test-*.txt")
@@ -440,8 +395,7 @@ func TestGetToken(t *testing.T) {
tokenFile.Close()
// Set TOKEN_FILE env var
os.Setenv("TOKEN_FILE", tokenFile.Name())
defer os.Unsetenv("TOKEN_FILE")
t.Setenv("TOKEN_FILE", tokenFile.Name())
token, err := getToken()
assert.NoError(t, err)
@@ -449,8 +403,6 @@ func TestGetToken(t *testing.T) {
})
t.Run("token from BESZEL_AGENT_TOKEN_FILE", func(t *testing.T) {
unsetEnvVars()
// Create a temporary token file
expectedToken := "test-token-from-beszel-file"
tokenFile, err := os.CreateTemp("", "token-test-*.txt")
@@ -462,8 +414,7 @@ func TestGetToken(t *testing.T) {
tokenFile.Close()
// Set BESZEL_AGENT_TOKEN_FILE env var (should take precedence)
os.Setenv("BESZEL_AGENT_TOKEN_FILE", tokenFile.Name())
defer os.Unsetenv("BESZEL_AGENT_TOKEN_FILE")
t.Setenv("BESZEL_AGENT_TOKEN_FILE", tokenFile.Name())
token, err := getToken()
assert.NoError(t, err)
@@ -471,8 +422,6 @@ func TestGetToken(t *testing.T) {
})
t.Run("TOKEN takes precedence over TOKEN_FILE", func(t *testing.T) {
unsetEnvVars()
// Create a temporary token file
fileToken := "token-from-file"
tokenFile, err := os.CreateTemp("", "token-test-*.txt")
@@ -485,12 +434,8 @@ func TestGetToken(t *testing.T) {
// Set both TOKEN and TOKEN_FILE
envToken := "token-from-env"
os.Setenv("TOKEN", envToken)
os.Setenv("TOKEN_FILE", tokenFile.Name())
defer func() {
os.Unsetenv("TOKEN")
os.Unsetenv("TOKEN_FILE")
}()
t.Setenv("TOKEN", envToken)
t.Setenv("TOKEN_FILE", tokenFile.Name())
token, err := getToken()
assert.NoError(t, err)
@@ -498,7 +443,10 @@ func TestGetToken(t *testing.T) {
})
t.Run("error when neither TOKEN nor TOKEN_FILE is set", func(t *testing.T) {
unsetEnvVars()
t.Setenv("BESZEL_AGENT_TOKEN", "")
t.Setenv("TOKEN", "")
t.Setenv("BESZEL_AGENT_TOKEN_FILE", "")
t.Setenv("TOKEN_FILE", "")
token, err := getToken()
assert.Error(t, err)
@@ -507,11 +455,8 @@ func TestGetToken(t *testing.T) {
})
t.Run("error when TOKEN_FILE points to non-existent file", func(t *testing.T) {
unsetEnvVars()
// Set TOKEN_FILE to a non-existent file
os.Setenv("TOKEN_FILE", "/non/existent/file.txt")
defer os.Unsetenv("TOKEN_FILE")
t.Setenv("TOKEN_FILE", "/non/existent/file.txt")
token, err := getToken()
assert.Error(t, err)
@@ -520,8 +465,6 @@ func TestGetToken(t *testing.T) {
})
t.Run("handles empty token file", func(t *testing.T) {
unsetEnvVars()
// Create an empty token file
tokenFile, err := os.CreateTemp("", "token-test-*.txt")
require.NoError(t, err)
@@ -529,8 +472,7 @@ func TestGetToken(t *testing.T) {
tokenFile.Close()
// Set TOKEN_FILE env var
os.Setenv("TOKEN_FILE", tokenFile.Name())
defer os.Unsetenv("TOKEN_FILE")
t.Setenv("TOKEN_FILE", tokenFile.Name())
token, err := getToken()
assert.NoError(t, err)
@@ -538,8 +480,6 @@ func TestGetToken(t *testing.T) {
})
t.Run("strips whitespace from TOKEN_FILE", func(t *testing.T) {
unsetEnvVars()
tokenWithWhitespace := " test-token-with-whitespace \n\t"
expectedToken := "test-token-with-whitespace"
tokenFile, err := os.CreateTemp("", "token-test-*.txt")
@@ -550,8 +490,7 @@ func TestGetToken(t *testing.T) {
require.NoError(t, err)
tokenFile.Close()
os.Setenv("TOKEN_FILE", tokenFile.Name())
defer os.Unsetenv("TOKEN_FILE")
t.Setenv("TOKEN_FILE", tokenFile.Name())
token, err := getToken()
assert.NoError(t, err)

View File

@@ -7,7 +7,6 @@ import (
"fmt"
"net"
"net/url"
"os"
"testing"
"time"
@@ -183,10 +182,6 @@ func TestConnectionManager_TickerManagement(t *testing.T) {
// TestConnectionManager_WebSocketConnectionFlow tests WebSocket connection logic
func TestConnectionManager_WebSocketConnectionFlow(t *testing.T) {
if testing.Short() {
t.Skip("Skipping WebSocket connection test in short mode")
}
agent := createTestAgent(t)
cm := agent.connectionManager
@@ -196,19 +191,18 @@ func TestConnectionManager_WebSocketConnectionFlow(t *testing.T) {
assert.Equal(t, Disconnected, cm.State, "State should remain Disconnected after failed connection")
// Test with invalid URL
os.Setenv("BESZEL_AGENT_HUB_URL", "invalid-url")
os.Setenv("BESZEL_AGENT_TOKEN", "test-token")
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
// Test with missing token
os.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
os.Unsetenv("BESZEL_AGENT_TOKEN")
t.Setenv("BESZEL_AGENT_HUB_URL", "1,33%")
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
_, err2 := newWebSocketClient(agent)
assert.Error(t, err2, "WebSocket client creation should fail without token")
assert.Error(t, err2, "WebSocket client creation should fail with invalid URL")
// Test with missing token
t.Setenv("BESZEL_AGENT_HUB_URL", "http://localhost:8080")
t.Setenv("BESZEL_AGENT_TOKEN", "")
_, err3 := newWebSocketClient(agent)
assert.Error(t, err3, "WebSocket client creation should fail without token")
}
// TestConnectionManager_ReconnectionLogic tests reconnection prevention logic
@@ -234,12 +228,8 @@ func TestConnectionManager_ConnectWithRateLimit(t *testing.T) {
cm := agent.connectionManager
// Set up environment for WebSocket client creation
os.Setenv("BESZEL_AGENT_HUB_URL", "ws://localhost:8080")
os.Setenv("BESZEL_AGENT_TOKEN", "test-token")
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", "ws://localhost:8080")
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
// Create WebSocket client
wsClient, err := newWebSocketClient(agent)
@@ -285,12 +275,8 @@ func TestConnectionManager_CloseWebSocket(t *testing.T) {
}, "Should not panic when closing nil WebSocket client")
// Set up environment and create WebSocket client
os.Setenv("BESZEL_AGENT_HUB_URL", "ws://localhost:8080")
os.Setenv("BESZEL_AGENT_TOKEN", "test-token")
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", "ws://localhost:8080")
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
wsClient, err := newWebSocketClient(agent)
require.NoError(t, err)

View File

@@ -39,17 +39,7 @@ func TestGetDataDir(t *testing.T) {
t.Run("DATA_DIR environment variable", func(t *testing.T) {
tempDir := t.TempDir()
// Set environment variable
oldValue := os.Getenv("DATA_DIR")
defer func() {
if oldValue == "" {
os.Unsetenv("BESZEL_AGENT_DATA_DIR")
} else {
os.Setenv("BESZEL_AGENT_DATA_DIR", oldValue)
}
}()
os.Setenv("BESZEL_AGENT_DATA_DIR", tempDir)
t.Setenv("BESZEL_AGENT_DATA_DIR", tempDir)
result, err := GetDataDir()
require.NoError(t, err)
@@ -65,17 +55,6 @@ func TestGetDataDir(t *testing.T) {
// Test fallback behavior (empty dataDir, no env var)
t.Run("fallback to default directories", func(t *testing.T) {
// Clear DATA_DIR environment variable
oldValue := os.Getenv("DATA_DIR")
defer func() {
if oldValue == "" {
os.Unsetenv("DATA_DIR")
} else {
os.Setenv("DATA_DIR", oldValue)
}
}()
os.Unsetenv("DATA_DIR")
// This will try platform-specific defaults, which may or may not work
// We're mainly testing that it doesn't panic and returns some result
result, err := GetDataDir()

View File

@@ -687,18 +687,8 @@ func TestIsDockerSpecialMountpoint(t *testing.T) {
}
func TestInitializeDiskInfoWithCustomNames(t *testing.T) {
// Set up environment variables
oldEnv := os.Getenv("EXTRA_FILESYSTEMS")
defer func() {
if oldEnv != "" {
os.Setenv("EXTRA_FILESYSTEMS", oldEnv)
} else {
os.Unsetenv("EXTRA_FILESYSTEMS")
}
}()
// Test with custom names
os.Setenv("EXTRA_FILESYSTEMS", "sda1__my-storage,/dev/sdb1__backup-drive,nvme0n1p2")
t.Setenv("EXTRA_FILESYSTEMS", "sda1__my-storage,/dev/sdb1__backup-drive,nvme0n1p2")
// Mock disk partitions (we'll just test the parsing logic)
// Since the actual disk operations are system-dependent, we'll focus on the parsing
@@ -726,7 +716,7 @@ func TestInitializeDiskInfoWithCustomNames(t *testing.T) {
for _, tc := range testCases {
t.Run("env_"+tc.envValue, func(t *testing.T) {
os.Setenv("EXTRA_FILESYSTEMS", tc.envValue)
t.Setenv("EXTRA_FILESYSTEMS", tc.envValue)
// Create mock partitions that would match our test cases
partitions := []disk.PartitionStat{}

View File

@@ -77,6 +77,7 @@ type dockerManager struct {
// cacheTimeMs -> DeltaTracker for network bytes sent/received
networkSentTrackers map[uint16]*deltatracker.DeltaTracker[string, uint64]
networkRecvTrackers map[uint16]*deltatracker.DeltaTracker[string, uint64]
lastNetworkReadTime map[uint16]map[string]time.Time // cacheTimeMs -> containerId -> last network read time
retrySleep func(time.Duration)
}
@@ -285,7 +286,7 @@ func (dm *dockerManager) cycleNetworkDeltasForCacheTime(cacheTimeMs uint16) {
}
// calculateNetworkStats calculates network sent/receive deltas using DeltaTracker
func (dm *dockerManager) calculateNetworkStats(ctr *container.ApiInfo, apiStats *container.ApiStats, stats *container.Stats, initialized bool, name string, cacheTimeMs uint16) (uint64, uint64) {
func (dm *dockerManager) calculateNetworkStats(ctr *container.ApiInfo, apiStats *container.ApiStats, name string, cacheTimeMs uint16) (uint64, uint64) {
var total_sent, total_recv uint64
for _, v := range apiStats.Networks {
total_sent += v.TxBytes
@@ -304,10 +305,11 @@ func (dm *dockerManager) calculateNetworkStats(ctr *container.ApiInfo, apiStats
sent_delta_raw := sentTracker.Delta(ctr.IdShort)
recv_delta_raw := recvTracker.Delta(ctr.IdShort)
// Calculate bytes per second independently for Tx and Rx if we have previous data
// Calculate bytes per second using per-cache-time read time to avoid
// interference between different cache intervals (e.g. 1000ms vs 60000ms)
var sent_delta, recv_delta uint64
if initialized {
millisecondsElapsed := uint64(time.Since(stats.PrevReadTime).Milliseconds())
if prevReadTime, ok := dm.lastNetworkReadTime[cacheTimeMs][ctr.IdShort]; ok {
millisecondsElapsed := uint64(time.Since(prevReadTime).Milliseconds())
if millisecondsElapsed > 0 {
if sent_delta_raw > 0 {
sent_delta = sent_delta_raw * 1000 / millisecondsElapsed
@@ -542,7 +544,13 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
}
// Calculate network stats using DeltaTracker
sent_delta, recv_delta := dm.calculateNetworkStats(ctr, res, stats, initialized, name, cacheTimeMs)
sent_delta, recv_delta := dm.calculateNetworkStats(ctr, res, name, cacheTimeMs)
// Store per-cache-time network read time for next rate calculation
if dm.lastNetworkReadTime[cacheTimeMs] == nil {
dm.lastNetworkReadTime[cacheTimeMs] = make(map[string]time.Time)
}
dm.lastNetworkReadTime[cacheTimeMs][ctr.IdShort] = time.Now()
// Store current network values for legacy compatibility
var total_sent, total_recv uint64
@@ -574,6 +582,9 @@ func (dm *dockerManager) deleteContainerStatsSync(id string) {
for ct := range dm.lastCpuReadTime {
delete(dm.lastCpuReadTime[ct], id)
}
for ct := range dm.lastNetworkReadTime {
delete(dm.lastNetworkReadTime[ct], id)
}
}
// Creates a new http client for Docker or Podman API
@@ -659,6 +670,7 @@ func newDockerManager() *dockerManager {
lastCpuReadTime: make(map[uint16]map[string]time.Time),
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
retrySleep: time.Sleep,
}

View File

@@ -408,6 +408,7 @@ func TestCalculateNetworkStats(t *testing.T) {
dm := &dockerManager{
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
}
cacheTimeMs := uint16(30000)
@@ -423,6 +424,11 @@ func TestCalculateNetworkStats(t *testing.T) {
dm.networkSentTrackers[cacheTimeMs] = sentTracker
dm.networkRecvTrackers[cacheTimeMs] = recvTracker
// Set per-cache-time network read time (1 second ago)
dm.lastNetworkReadTime[cacheTimeMs] = map[string]time.Time{
"container1": time.Now().Add(-time.Second),
}
ctr := &container.ApiInfo{
IdShort: "container1",
}
@@ -433,12 +439,8 @@ func TestCalculateNetworkStats(t *testing.T) {
},
}
stats := &container.Stats{
PrevReadTime: time.Now().Add(-time.Second), // 1 second ago
}
// Test with initialized container
sent, recv := dm.calculateNetworkStats(ctr, apiStats, stats, true, "test-container", cacheTimeMs)
sent, recv := dm.calculateNetworkStats(ctr, apiStats, "test-container", cacheTimeMs)
// Should return calculated byte rates per second
assert.GreaterOrEqual(t, sent, uint64(0))
@@ -446,12 +448,76 @@ func TestCalculateNetworkStats(t *testing.T) {
// Cycle and test one-direction change (Tx only) is reflected independently
dm.cycleNetworkDeltasForCacheTime(cacheTimeMs)
dm.lastNetworkReadTime[cacheTimeMs]["container1"] = time.Now().Add(-time.Second)
apiStats.Networks["eth0"] = container.NetworkStats{TxBytes: 2500, RxBytes: 1800} // +500 Tx only
sent, recv = dm.calculateNetworkStats(ctr, apiStats, stats, true, "test-container", cacheTimeMs)
sent, recv = dm.calculateNetworkStats(ctr, apiStats, "test-container", cacheTimeMs)
assert.Greater(t, sent, uint64(0))
assert.Equal(t, uint64(0), recv)
}
// TestNetworkStatsCacheTimeIsolation verifies that frequent collections at one cache time
// (e.g. 1000ms) don't cause inflated rates at another cache time (e.g. 60000ms).
// This was a bug where PrevReadTime was shared, so the 60000ms tracker would see a
// large byte delta divided by a tiny elapsed time (set by the 1000ms path).
func TestNetworkStatsCacheTimeIsolation(t *testing.T) {
dm := &dockerManager{
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
}
ctr := &container.ApiInfo{IdShort: "container1"}
fastCache := uint16(1000)
slowCache := uint16(60000)
// Baseline for both cache times at T=0 with 100 bytes total
baseline := &container.ApiStats{
Networks: map[string]container.NetworkStats{
"eth0": {TxBytes: 100, RxBytes: 100},
},
}
dm.calculateNetworkStats(ctr, baseline, "test", fastCache)
dm.calculateNetworkStats(ctr, baseline, "test", slowCache)
// Record read times and cycle both
now := time.Now()
dm.lastNetworkReadTime[fastCache] = map[string]time.Time{"container1": now}
dm.lastNetworkReadTime[slowCache] = map[string]time.Time{"container1": now}
dm.cycleNetworkDeltasForCacheTime(fastCache)
dm.cycleNetworkDeltasForCacheTime(slowCache)
// Simulate many fast (1000ms) collections over ~5 seconds, each adding 10 bytes
totalBytes := uint64(100)
for i := 0; i < 5; i++ {
totalBytes += 10
stats := &container.ApiStats{
Networks: map[string]container.NetworkStats{
"eth0": {TxBytes: totalBytes, RxBytes: totalBytes},
},
}
// Set fast cache read time to 1 second ago
dm.lastNetworkReadTime[fastCache]["container1"] = time.Now().Add(-time.Second)
sent, _ := dm.calculateNetworkStats(ctr, stats, "test", fastCache)
// Fast cache should see ~10 bytes/sec per interval
assert.LessOrEqual(t, sent, uint64(100), "fast cache rate should be reasonable")
dm.cycleNetworkDeltasForCacheTime(fastCache)
}
// Now do slow cache collection — total delta is 50 bytes over ~5 seconds
// Set slow cache read time to 5 seconds ago (the actual elapsed time)
dm.lastNetworkReadTime[slowCache]["container1"] = time.Now().Add(-5 * time.Second)
finalStats := &container.ApiStats{
Networks: map[string]container.NetworkStats{
"eth0": {TxBytes: totalBytes, RxBytes: totalBytes},
},
}
sent, _ := dm.calculateNetworkStats(ctr, finalStats, "test", slowCache)
// Slow cache rate should be ~10 bytes/sec (50 bytes / 5 seconds), NOT 100x inflated
assert.LessOrEqual(t, sent, uint64(100), "slow cache rate should NOT be inflated by fast cache collections")
assert.GreaterOrEqual(t, sent, uint64(1), "slow cache should still report some traffic")
}
func TestDockerManagerCreation(t *testing.T) {
// Test that dockerManager can be created without panicking
dm := &dockerManager{
@@ -460,6 +526,7 @@ func TestDockerManagerCreation(t *testing.T) {
lastCpuReadTime: make(map[uint16]map[string]time.Time),
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
}
assert.NotNil(t, dm)
@@ -467,6 +534,7 @@ func TestDockerManagerCreation(t *testing.T) {
assert.NotNil(t, dm.lastCpuSystem)
assert.NotNil(t, dm.networkSentTrackers)
assert.NotNil(t, dm.networkRecvTrackers)
assert.NotNil(t, dm.lastNetworkReadTime)
}
func TestCheckDockerVersion(t *testing.T) {
@@ -651,6 +719,7 @@ func TestDockerStatsWithMockData(t *testing.T) {
lastCpuReadTime: make(map[uint16]map[string]time.Time),
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
containerStatsMap: make(map[string]*container.Stats),
}
@@ -796,23 +865,22 @@ func TestNetworkStatsCalculationWithRealData(t *testing.T) {
dm := &dockerManager{
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
}
ctr := &container.ApiInfo{IdShort: "test-container"}
cacheTimeMs := uint16(30000) // Test with 30 second cache
// Use exact timing for deterministic results
exactly1000msAgo := time.Now().Add(-1000 * time.Millisecond)
stats := &container.Stats{
PrevReadTime: exactly1000msAgo,
}
// First call sets baseline
sent1, recv1 := dm.calculateNetworkStats(ctr, apiStats1, stats, true, "test", cacheTimeMs)
// First call sets baseline (no previous read time, so rates should be 0)
sent1, recv1 := dm.calculateNetworkStats(ctr, apiStats1, "test", cacheTimeMs)
assert.Equal(t, uint64(0), sent1)
assert.Equal(t, uint64(0), recv1)
// Cycle to establish baseline for this cache time
// Record read time and cycle to establish baseline for this cache time
exactly1000msAgo := time.Now().Add(-1000 * time.Millisecond)
dm.lastNetworkReadTime[cacheTimeMs] = map[string]time.Time{
"test-container": exactly1000msAgo,
}
dm.cycleNetworkDeltasForCacheTime(cacheTimeMs)
// Calculate expected results precisely
@@ -823,7 +891,7 @@ func TestNetworkStatsCalculationWithRealData(t *testing.T) {
expectedRecvRate := deltaRecv * 1000 / expectedElapsedMs // Should be exactly 1000000
// Second call with changed data
sent2, recv2 := dm.calculateNetworkStats(ctr, apiStats2, stats, true, "test", cacheTimeMs)
sent2, recv2 := dm.calculateNetworkStats(ctr, apiStats2, "test", cacheTimeMs)
// Should be exactly the expected rates (no tolerance needed)
assert.Equal(t, expectedSentRate, sent2)
@@ -831,12 +899,13 @@ func TestNetworkStatsCalculationWithRealData(t *testing.T) {
// Bad speed cap: set absurd delta over 1ms and expect 0 due to cap
dm.cycleNetworkDeltasForCacheTime(cacheTimeMs)
stats.PrevReadTime = time.Now().Add(-1 * time.Millisecond)
dm.lastNetworkReadTime[cacheTimeMs]["test-container"] = time.Now().Add(-1 * time.Millisecond)
apiStats1.Networks["eth0"] = container.NetworkStats{TxBytes: 0, RxBytes: 0}
apiStats2.Networks["eth0"] = container.NetworkStats{TxBytes: 10 * 1024 * 1024 * 1024, RxBytes: 0} // 10GB delta
_, _ = dm.calculateNetworkStats(ctr, apiStats1, stats, true, "test", cacheTimeMs) // baseline
_, _ = dm.calculateNetworkStats(ctr, apiStats1, "test", cacheTimeMs) // baseline
dm.cycleNetworkDeltasForCacheTime(cacheTimeMs)
sent3, recv3 := dm.calculateNetworkStats(ctr, apiStats2, stats, true, "test", cacheTimeMs)
dm.lastNetworkReadTime[cacheTimeMs]["test-container"] = time.Now().Add(-1 * time.Millisecond)
sent3, recv3 := dm.calculateNetworkStats(ctr, apiStats2, "test", cacheTimeMs)
assert.Equal(t, uint64(0), sent3)
assert.Equal(t, uint64(0), recv3)
}
@@ -857,6 +926,7 @@ func TestContainerStatsEndToEndWithRealData(t *testing.T) {
lastCpuReadTime: make(map[uint16]map[string]time.Time),
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
containerStatsMap: make(map[string]*container.Stats),
}
@@ -978,6 +1048,7 @@ func TestDockerStatsWorkflow(t *testing.T) {
lastCpuSystem: make(map[uint16]map[string]uint64),
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
containerStatsMap: make(map[string]*container.Stats),
}
@@ -1242,6 +1313,7 @@ func TestUpdateContainerStatsUsesPodmanInspectHealthFallback(t *testing.T) {
lastCpuReadTime: make(map[uint16]map[string]time.Time),
networkSentTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
networkRecvTrackers: make(map[uint16]*deltatracker.DeltaTracker[string, uint64]),
lastNetworkReadTime: make(map[uint16]map[string]time.Time),
}
ctr := &container.ApiInfo{

View File

@@ -461,7 +461,7 @@ func (gm *GPUManager) discoverGpuCapabilities() gpuCapabilities {
caps.hasNvtop = true
}
if runtime.GOOS == "darwin" {
if _, err := exec.LookPath(macmonCmd); err == nil {
if _, err := utils.LookPathHomebrew(macmonCmd); err == nil {
caps.hasMacmon = true
}
if _, err := exec.LookPath(powermetricsCmd); err == nil {

View File

@@ -13,6 +13,7 @@ import (
"strings"
"time"
"github.com/henrygd/beszel/agent/utils"
"github.com/henrygd/beszel/internal/entities/system"
)
@@ -171,7 +172,11 @@ type macmonSample struct {
}
func (gm *GPUManager) collectMacmonPipe() (err error) {
cmd := exec.Command(macmonCmd, "pipe", "-i", strconv.Itoa(macmonIntervalMs))
macmonPath, err := utils.LookPathHomebrew(macmonCmd)
if err != nil {
return err
}
cmd := exec.Command(macmonPath, "pipe", "-i", strconv.Itoa(macmonIntervalMs))
// Avoid blocking if macmon writes to stderr.
cmd.Stderr = io.Discard
stdout, err := cmd.StdoutPipe()

View File

@@ -1083,8 +1083,6 @@ func TestCalculateGPUAverage(t *testing.T) {
func TestGPUCapabilitiesAndLegacyPriority(t *testing.T) {
// Save original PATH
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
hasAmdSysfs := (&GPUManager{}).hasAmdSysfs()
tests := []struct {
@@ -1178,7 +1176,7 @@ echo "[]"`
{
name: "no gpu tools available",
setupCommands: func(_ string) error {
os.Setenv("PATH", "")
t.Setenv("PATH", "")
return nil
},
wantErr: true,
@@ -1188,7 +1186,7 @@ echo "[]"`
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := t.TempDir()
os.Setenv("PATH", tempDir)
t.Setenv("PATH", tempDir)
if err := tt.setupCommands(tempDir); err != nil {
t.Fatal(err)
}
@@ -1234,13 +1232,9 @@ echo "[]"`
}
func TestCollectorStartHelpers(t *testing.T) {
// Save original PATH
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
// Set up temp dir with the commands
dir := t.TempDir()
os.Setenv("PATH", dir)
t.Setenv("PATH", dir)
tests := []struct {
name string
@@ -1370,11 +1364,8 @@ echo '[{"device_name":"NVIDIA Test GPU","temp":"52C","power_draw":"31W","gpu_uti
}
func TestNewGPUManagerPriorityNvtopFallback(t *testing.T) {
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
dir := t.TempDir()
os.Setenv("PATH", dir)
t.Setenv("PATH", dir)
t.Setenv("BESZEL_AGENT_GPU_COLLECTOR", "nvtop,nvidia-smi")
nvtopPath := filepath.Join(dir, "nvtop")
@@ -1399,11 +1390,8 @@ echo "0, NVIDIA Priority GPU, 45, 512, 2048, 12, 25"`
}
func TestNewGPUManagerPriorityMixedCollectors(t *testing.T) {
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
dir := t.TempDir()
os.Setenv("PATH", dir)
t.Setenv("PATH", dir)
t.Setenv("BESZEL_AGENT_GPU_COLLECTOR", "intel_gpu_top,rocm-smi")
intelPath := filepath.Join(dir, "intel_gpu_top")
@@ -1433,11 +1421,8 @@ echo '{"card0": {"Temperature (Sensor edge) (C)": "49.0", "Current Socket Graphi
}
func TestNewGPUManagerPriorityNvmlFallbackToNvidiaSmi(t *testing.T) {
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
dir := t.TempDir()
os.Setenv("PATH", dir)
t.Setenv("PATH", dir)
t.Setenv("BESZEL_AGENT_GPU_COLLECTOR", "nvml,nvidia-smi")
nvidiaPath := filepath.Join(dir, "nvidia-smi")
@@ -1456,11 +1441,8 @@ echo "0, NVIDIA Fallback GPU, 41, 256, 1024, 8, 14"`
}
func TestNewGPUManagerConfiguredCollectorsMustStart(t *testing.T) {
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
dir := t.TempDir()
os.Setenv("PATH", dir)
t.Setenv("PATH", dir)
t.Run("configured valid collector unavailable", func(t *testing.T) {
t.Setenv("BESZEL_AGENT_GPU_COLLECTOR", "nvidia-smi")
@@ -1480,11 +1462,8 @@ func TestNewGPUManagerConfiguredCollectorsMustStart(t *testing.T) {
}
func TestNewGPUManagerJetsonIgnoresCollectorConfig(t *testing.T) {
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
dir := t.TempDir()
os.Setenv("PATH", dir)
t.Setenv("PATH", dir)
t.Setenv("BESZEL_AGENT_GPU_COLLECTOR", "nvidia-smi")
tegraPath := filepath.Join(dir, "tegrastats")
@@ -1719,12 +1698,8 @@ func TestIntelUpdateFromStats(t *testing.T) {
}
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)
t.Setenv("PATH", dir)
// Create a fake intel_gpu_top that prints -l format with four samples (first will be skipped) and exits
scriptPath := filepath.Join(dir, "intel_gpu_top")

View File

@@ -2,12 +2,14 @@ package agent
import (
"context"
"errors"
"fmt"
"log/slog"
"path"
"runtime"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/henrygd/beszel/agent/utils"
@@ -38,6 +40,11 @@ func (a *Agent) newSensorConfig() *SensorConfig {
// Matches sensors.TemperaturesWithContext to allow for panic recovery (gopsutil/issues/1832)
type getTempsFn func(ctx context.Context) ([]sensors.TemperatureStat, error)
var (
errTemperatureFetchTimeout = errors.New("temperature collection timed out")
temperatureFetchTimeout = 2 * time.Second
)
// newSensorConfigWithEnv creates a SensorConfig with the provided environment variables
// sensorsSet indicates if the SENSORS environment variable was explicitly set (even to empty string)
func (a *Agent) newSensorConfigWithEnv(primarySensor, sysSensors, sensorsEnvVal string, skipCollection bool) *SensorConfig {
@@ -86,10 +93,12 @@ func (a *Agent) updateTemperatures(systemStats *system.Stats) {
// reset high temp
a.systemInfo.DashboardTemp = 0
temps, err := a.getTempsWithPanicRecovery(getSensorTemps)
temps, err := a.getTempsWithTimeout(getSensorTemps)
if err != nil {
// retry once on panic (gopsutil/issues/1832)
temps, err = a.getTempsWithPanicRecovery(getSensorTemps)
if !errors.Is(err, errTemperatureFetchTimeout) {
temps, err = a.getTempsWithTimeout(getSensorTemps)
}
if err != nil {
slog.Warn("Error updating temperatures", "err", err)
if len(systemStats.Temperatures) > 0 {
@@ -152,6 +161,26 @@ func (a *Agent) getTempsWithPanicRecovery(getTemps getTempsFn) (temps []sensors.
return
}
func (a *Agent) getTempsWithTimeout(getTemps getTempsFn) ([]sensors.TemperatureStat, error) {
type result struct {
temps []sensors.TemperatureStat
err error
}
resultCh := make(chan result, 1)
go func() {
temps, err := a.getTempsWithPanicRecovery(getTemps)
resultCh <- result{temps: temps, err: err}
}()
select {
case res := <-resultCh:
return res.temps, res.err
case <-time.After(temperatureFetchTimeout):
return nil, errTemperatureFetchTimeout
}
}
// isValidSensor checks if a sensor is valid based on the sensor name and the sensor config
func isValidSensor(sensorName string, config *SensorConfig) bool {
// if no sensors configured, everything is valid

View File

@@ -5,8 +5,8 @@ package agent
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/henrygd/beszel/internal/entities/system"
@@ -329,34 +329,10 @@ func TestNewSensorConfigWithEnv(t *testing.T) {
}
func TestNewSensorConfig(t *testing.T) {
// Save original environment variables
originalPrimary, hasPrimary := os.LookupEnv("BESZEL_AGENT_PRIMARY_SENSOR")
originalSys, hasSys := os.LookupEnv("BESZEL_AGENT_SYS_SENSORS")
originalSensors, hasSensors := os.LookupEnv("BESZEL_AGENT_SENSORS")
// Restore environment variables after the test
defer func() {
// Clean up test environment variables
os.Unsetenv("BESZEL_AGENT_PRIMARY_SENSOR")
os.Unsetenv("BESZEL_AGENT_SYS_SENSORS")
os.Unsetenv("BESZEL_AGENT_SENSORS")
// Restore original values if they existed
if hasPrimary {
os.Setenv("BESZEL_AGENT_PRIMARY_SENSOR", originalPrimary)
}
if hasSys {
os.Setenv("BESZEL_AGENT_SYS_SENSORS", originalSys)
}
if hasSensors {
os.Setenv("BESZEL_AGENT_SENSORS", originalSensors)
}
}()
// Set test environment variables
os.Setenv("BESZEL_AGENT_PRIMARY_SENSOR", "test_primary")
os.Setenv("BESZEL_AGENT_SYS_SENSORS", "/test/path")
os.Setenv("BESZEL_AGENT_SENSORS", "test_sensor1,test_*,test_sensor3")
t.Setenv("BESZEL_AGENT_PRIMARY_SENSOR", "test_primary")
t.Setenv("BESZEL_AGENT_SYS_SENSORS", "/test/path")
t.Setenv("BESZEL_AGENT_SENSORS", "test_sensor1,test_*,test_sensor3")
agent := &Agent{}
result := agent.newSensorConfig()
@@ -551,3 +527,66 @@ func TestGetTempsWithPanicRecovery(t *testing.T) {
})
}
}
func TestGetTempsWithTimeout(t *testing.T) {
agent := &Agent{
sensorConfig: &SensorConfig{
context: context.Background(),
},
}
originalTimeout := temperatureFetchTimeout
t.Cleanup(func() {
temperatureFetchTimeout = originalTimeout
})
temperatureFetchTimeout = 10 * time.Millisecond
t.Run("returns temperatures before timeout", func(t *testing.T) {
temps, err := agent.getTempsWithTimeout(func(ctx context.Context) ([]sensors.TemperatureStat, error) {
return []sensors.TemperatureStat{{SensorKey: "cpu_temp", Temperature: 42}}, nil
})
require.NoError(t, err)
require.Len(t, temps, 1)
assert.Equal(t, "cpu_temp", temps[0].SensorKey)
})
t.Run("returns timeout error when collector hangs", func(t *testing.T) {
temps, err := agent.getTempsWithTimeout(func(ctx context.Context) ([]sensors.TemperatureStat, error) {
time.Sleep(50 * time.Millisecond)
return []sensors.TemperatureStat{{SensorKey: "cpu_temp", Temperature: 42}}, nil
})
assert.Nil(t, temps)
assert.ErrorIs(t, err, errTemperatureFetchTimeout)
})
}
func TestUpdateTemperaturesSkipsOnTimeout(t *testing.T) {
agent := &Agent{
systemInfo: system.Info{DashboardTemp: 99},
sensorConfig: &SensorConfig{
context: context.Background(),
},
}
originalTimeout := temperatureFetchTimeout
t.Cleanup(func() {
temperatureFetchTimeout = originalTimeout
getSensorTemps = sensors.TemperaturesWithContext
})
temperatureFetchTimeout = 10 * time.Millisecond
getSensorTemps = func(ctx context.Context) ([]sensors.TemperatureStat, error) {
time.Sleep(50 * time.Millisecond)
return nil, nil
}
stats := &system.Stats{
Temperatures: map[string]float64{"stale": 50},
}
agent.updateTemperatures(stats)
assert.Equal(t, 0.0, agent.systemInfo.DashboardTemp)
assert.Equal(t, map[string]float64{}, stats.Temperatures)
}

View File

@@ -183,8 +183,7 @@ func TestStartServer(t *testing.T) {
}
func TestStartServerDisableSSH(t *testing.T) {
os.Setenv("BESZEL_AGENT_DISABLE_SSH", "true")
defer os.Unsetenv("BESZEL_AGENT_DISABLE_SSH")
t.Setenv("BESZEL_AGENT_DISABLE_SSH", "true")
agent, err := NewAgent("")
require.NoError(t, err)

View File

@@ -1104,32 +1104,21 @@ func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) {
// detectSmartctl checks if smartctl is installed, returns an error if not
func (sm *SmartManager) detectSmartctl() (string, error) {
isWindows := runtime.GOOS == "windows"
// Load embedded smartctl.exe for Windows amd64 builds.
if isWindows && runtime.GOARCH == "amd64" {
if path, err := ensureEmbeddedSmartctl(); err == nil {
return path, nil
if runtime.GOOS == "windows" {
// Load embedded smartctl.exe for Windows amd64 builds.
if runtime.GOARCH == "amd64" {
if path, err := ensureEmbeddedSmartctl(); err == nil {
return path, nil
}
}
}
if path, err := exec.LookPath("smartctl"); err == nil {
return path, nil
}
locations := []string{}
if isWindows {
locations = append(locations,
"C:\\Program Files\\smartmontools\\bin\\smartctl.exe",
)
} else {
locations = append(locations, "/opt/homebrew/bin/smartctl")
}
for _, location := range locations {
// Try to find smartctl in the default installation location
const location = "C:\\Program Files\\smartmontools\\bin\\smartctl.exe"
if _, err := os.Stat(location); err == nil {
return location, nil
}
}
return "", errors.New("smartctl not found")
return utils.LookPathHomebrew("smartctl")
}
// isNvmeControllerPath checks if the path matches an NVMe controller pattern

View File

@@ -1035,7 +1035,7 @@ func TestRefreshExcludedDevices(t *testing.T) {
t.Setenv("EXCLUDE_SMART", tt.envValue)
} else {
// Ensure env var is not set for empty test
os.Unsetenv("EXCLUDE_SMART")
t.Setenv("EXCLUDE_SMART", "")
}
sm := &SmartManager{}

View File

@@ -167,16 +167,12 @@ func TestGetServicePatterns(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clean up any existing env vars
os.Unsetenv("BESZEL_AGENT_SERVICE_PATTERNS")
os.Unsetenv("SERVICE_PATTERNS")
// Set up environment variables
if tt.prefixedEnv != "" {
os.Setenv("BESZEL_AGENT_SERVICE_PATTERNS", tt.prefixedEnv)
t.Setenv("BESZEL_AGENT_SERVICE_PATTERNS", tt.prefixedEnv)
}
if tt.unprefixedEnv != "" {
os.Setenv("SERVICE_PATTERNS", tt.unprefixedEnv)
t.Setenv("SERVICE_PATTERNS", tt.unprefixedEnv)
}
// Run the function
@@ -184,12 +180,6 @@ func TestGetServicePatterns(t *testing.T) {
// Verify results
assert.Equal(t, tt.expected, result, "Patterns should match expected values")
// Cleanup
if tt.cleanupEnvVars {
os.Unsetenv("BESZEL_AGENT_SERVICE_PATTERNS")
os.Unsetenv("SERVICE_PATTERNS")
}
})
}
}

View File

@@ -4,6 +4,9 @@ import (
"io"
"math"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
)
@@ -86,3 +89,24 @@ func ReadUintFile(path string) (uint64, bool) {
}
return parsed, true
}
// LookPathHomebrew is like exec.LookPath but also checks Homebrew paths.
func LookPathHomebrew(file string) (string, error) {
foundPath, lookPathErr := exec.LookPath(file)
if lookPathErr == nil {
return foundPath, nil
}
var homebrewPath string
switch runtime.GOOS {
case "darwin":
homebrewPath = filepath.Join("/opt", "homebrew", "bin", file)
case "linux":
homebrewPath = filepath.Join("/home", "linuxbrew", ".linuxbrew", "bin", file)
}
if homebrewPath != "" {
if _, err := os.Stat(homebrewPath); err == nil {
return homebrewPath, nil
}
}
return "", lookPathErr
}

View File

@@ -134,10 +134,8 @@ func TestGetEnv(t *testing.T) {
prefixedKey := "BESZEL_AGENT_" + key
t.Run("prefixed variable exists", func(t *testing.T) {
os.Setenv(prefixedKey, "prefixed_val")
os.Setenv(key, "unprefixed_val")
defer os.Unsetenv(prefixedKey)
defer os.Unsetenv(key)
t.Setenv(prefixedKey, "prefixed_val")
t.Setenv(key, "unprefixed_val")
val, exists := GetEnv(key)
assert.True(t, exists)
@@ -145,9 +143,7 @@ func TestGetEnv(t *testing.T) {
})
t.Run("only unprefixed variable exists", func(t *testing.T) {
os.Unsetenv(prefixedKey)
os.Setenv(key, "unprefixed_val")
defer os.Unsetenv(key)
t.Setenv(key, "unprefixed_val")
val, exists := GetEnv(key)
assert.True(t, exists)
@@ -155,9 +151,6 @@ func TestGetEnv(t *testing.T) {
})
t.Run("neither variable exists", func(t *testing.T) {
os.Unsetenv(prefixedKey)
os.Unsetenv(key)
val, exists := GetEnv(key)
assert.False(t, exists)
assert.Empty(t, val)

View File

@@ -6,7 +6,7 @@ import "github.com/blang/semver"
const (
// Version is the current version of the application.
Version = "0.18.4"
Version = "0.18.5"
// AppName is the name of the application.
AppName = "beszel"
)

41
go.mod
View File

@@ -6,22 +6,22 @@ require (
github.com/blang/semver v3.5.1+incompatible
github.com/coreos/go-systemd/v22 v22.7.0
github.com/distatus/battery v0.11.0
github.com/ebitengine/purego v0.9.1
github.com/ebitengine/purego v0.10.0
github.com/fxamacker/cbor/v2 v2.9.0
github.com/gliderlabs/ssh v0.3.8
github.com/google/uuid v1.6.0
github.com/lxzan/gws v1.8.9
github.com/nicholas-fedor/shoutrrr v0.13.2
github.com/lxzan/gws v1.9.1
github.com/nicholas-fedor/shoutrrr v0.14.1
github.com/pocketbase/dbx v1.12.0
github.com/pocketbase/pocketbase v0.36.4
github.com/shirou/gopsutil/v4 v4.26.1
github.com/pocketbase/pocketbase v0.36.7
github.com/shirou/gopsutil/v4 v4.26.2
github.com/spf13/cast v1.10.0
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.48.0
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa
golang.org/x/sys v0.41.0
golang.org/x/crypto v0.49.0
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90
golang.org/x/sys v0.42.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -30,10 +30,10 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/dolthub/maphash v0.1.0 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/eclipse/paho.golang v0.23.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/ganigeorgiev/fexpr v0.5.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
@@ -41,9 +41,10 @@ require (
github.com/go-sql-driver/mysql v1.9.1 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.4 // indirect
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/lufia/plan9stats v0.0.0-20260324052639-156f7da3f749 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
@@ -54,15 +55,15 @@ require (
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/image v0.36.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
golang.org/x/image v0.38.0 // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/term v0.41.0 // indirect
golang.org/x/text v0.35.0 // indirect
howett.net/plist v1.0.1 // indirect
modernc.org/libc v1.67.6 // indirect
modernc.org/libc v1.70.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.45.0 // indirect
modernc.org/sqlite v1.46.2 // indirect
)

112
go.sum
View File

@@ -19,16 +19,16 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/distatus/battery v0.11.0 h1:KJk89gz90Iq/wJtbjjM9yUzBXV+ASV/EG2WOOL7N8lc=
github.com/distatus/battery v0.11.0/go.mod h1:KmVkE8A8hpIX4T78QRdMktYpEp35QfOL8A8dwZBxq2k=
github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ=
github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4=
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
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/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
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/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/eclipse/paho.golang v0.23.0 h1:KHgl2wz6EJo7cMBmkuhpt7C576vP+kpPv7jjvSyR6Mk=
github.com/eclipse/paho.golang v0.23.0/go.mod h1:nQRhTkoZv8EAiNs5UU0/WdQIx2NrnWUpL9nsGJTQN04=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
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/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@@ -58,10 +58,12 @@ github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs
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/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc=
github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc h1:VBbFa1lDYWEeV5FZKUiYKYT0VxCp9twUmmaq9eb8sXw=
github.com/google/pprof v0.0.0-20260302011040-a15ffb7f9dcc/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -69,24 +71,24 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
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/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM=
github.com/lxzan/gws v1.8.9/go.mod h1:d9yHaR1eDTBHagQC6KY7ycUOaz5KWeqQtP3xu7aMK8Y=
github.com/lufia/plan9stats v0.0.0-20260324052639-156f7da3f749 h1:Qj3hTcdWH8uMZDI41HNuTuJN525C7NBrbtH5kSO6fPk=
github.com/lufia/plan9stats v0.0.0-20260324052639-156f7da3f749/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lxzan/gws v1.9.1 h1:4lbIp4cW0hOLP3ejFHR/uWRy741AURx7oKkNNi2OT9o=
github.com/lxzan/gws v1.9.1/go.mod h1:gXHSCPmTGryWJ4icuqy8Yho32E4YIMHH0fkDRYJRbdc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nicholas-fedor/shoutrrr v0.13.2 h1:hfsYBIqSFYGg92pZP5CXk/g7/OJIkLYmiUnRl+AD1IA=
github.com/nicholas-fedor/shoutrrr v0.13.2/go.mod h1:ZqzV3gY/Wj6AvWs1etlO7+yKbh4iptSbeL8avBpMQbA=
github.com/nicholas-fedor/shoutrrr v0.14.1 h1:6sx4cJNfNuUtD6ygGlB0dqcCQ+abfsUh+b+6jgujf6A=
github.com/nicholas-fedor/shoutrrr v0.14.1/go.mod h1:U7IywBkLpBV7rgn8iLbQ9/LklJG1gm24bFv5cXXsDKs=
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
@@ -96,8 +98,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.12.0 h1:/oLErM+A0b4xI0PWTGPqSDVjzix48PqI/bng2l0PzoA=
github.com/pocketbase/dbx v1.12.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.36.4 h1:zTjRZbp2WfTOJJfb+pFRWa200UaQwxZYt8RzkFMlAZ4=
github.com/pocketbase/pocketbase v0.36.4/go.mod h1:9CiezhRudd9FZGa5xZa53QZBTNxc5vvw/FGG+diAECI=
github.com/pocketbase/pocketbase v0.36.7 h1:MrViB7BptPYrf2Nt25pJEYBqUdFjuhRKu1p5GTrkvPA=
github.com/pocketbase/pocketbase v0.36.7/go.mod h1:qX4HuVjoKXtEg41fSJVM0JLfGWXbBmHxVv/FaE446r4=
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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -105,8 +107,8 @@ 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/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
@@ -115,6 +117,8 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
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.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
@@ -126,44 +130,44 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -175,18 +179,18 @@ howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -195,8 +199,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.45.0 h1:r51cSGzKpbptxnby+EIIz5fop4VuE4qFoVEjNvWoObs=
modernc.org/sqlite v1.45.0/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
modernc.org/sqlite v1.46.2 h1:gkXQ6R0+AjxFC/fTDaeIVLbNLNrRoOK7YYVz5BKhTcE=
modernc.org/sqlite v1.46.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -15,6 +15,19 @@ import (
"github.com/stretchr/testify/require"
)
func setStatusAlertEmail(t *testing.T, hub core.App, userID, email string) {
t.Helper()
userSettings, err := hub.FindFirstRecordByFilter("user_settings", "user={:user}", map[string]any{"user": userID})
require.NoError(t, err)
userSettings.Set("settings", map[string]any{
"emails": []string{email},
"webhooks": []string{},
})
require.NoError(t, hub.Save(userSettings))
}
func TestStatusAlerts(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
@@ -322,6 +335,181 @@ func TestStatusAlertDownFiresAfterDelayExpires(t *testing.T) {
assert.True(t, alertRecord.GetBool("triggered"), "alert should be marked triggered after downtime matures")
}
func TestStatusAlertMultipleUsersRespectDifferentMinutes(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
hub, user1 := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
setStatusAlertEmail(t, hub, user1.Id, "user1@example.com")
user2, err := beszelTests.CreateUser(hub, "user2@example.com", "password")
require.NoError(t, err)
_, err = beszelTests.CreateRecord(hub, "user_settings", map[string]any{
"user": user2.Id,
"settings": map[string]any{
"emails": []string{"user2@example.com"},
"webhooks": []string{},
},
})
require.NoError(t, err)
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "shared-system",
"users": []string{user1.Id, user2.Id},
"host": "127.0.0.1",
})
require.NoError(t, err)
system.Set("status", "up")
require.NoError(t, hub.SaveNoValidate(system))
alertUser1, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "Status",
"system": system.Id,
"user": user1.Id,
"min": 1,
})
require.NoError(t, err)
alertUser2, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "Status",
"system": system.Id,
"user": user2.Id,
"min": 2,
})
require.NoError(t, err)
time.Sleep(10 * time.Millisecond)
system.Set("status", "down")
require.NoError(t, hub.SaveNoValidate(system))
assert.Equal(t, 2, hub.GetPendingAlertsCount(), "both user alerts should be pending after the system goes down")
time.Sleep(59 * time.Second)
synctest.Wait()
assert.Zero(t, hub.TestMailer.TotalSend(), "no messages should be sent before the earliest alert minute elapses")
time.Sleep(2 * time.Second)
synctest.Wait()
messages := hub.TestMailer.Messages()
require.Len(t, messages, 1, "only the first user's alert should send after one minute")
require.Len(t, messages[0].To, 1)
assert.Equal(t, "user1@example.com", messages[0].To[0].Address)
assert.Contains(t, messages[0].Subject, "Connection to shared-system is down")
assert.Equal(t, 1, hub.GetPendingAlertsCount(), "the later user alert should still be pending")
time.Sleep(58 * time.Second)
synctest.Wait()
assert.Equal(t, 1, hub.TestMailer.TotalSend(), "the second user's alert should still be waiting before two minutes")
time.Sleep(2 * time.Second)
synctest.Wait()
messages = hub.TestMailer.Messages()
require.Len(t, messages, 2, "both users should eventually receive their own status alert")
require.Len(t, messages[1].To, 1)
assert.Equal(t, "user2@example.com", messages[1].To[0].Address)
assert.Contains(t, messages[1].Subject, "Connection to shared-system is down")
assert.Zero(t, hub.GetPendingAlertsCount(), "all pending alerts should be consumed after both timers fire")
alertUser1, err = hub.FindRecordById("alerts", alertUser1.Id)
require.NoError(t, err)
assert.True(t, alertUser1.GetBool("triggered"), "user1 alert should be marked triggered after delivery")
alertUser2, err = hub.FindRecordById("alerts", alertUser2.Id)
require.NoError(t, err)
assert.True(t, alertUser2.GetBool("triggered"), "user2 alert should be marked triggered after delivery")
})
}
func TestStatusAlertMultipleUsersRecoveryBetweenMinutesOnlyAlertsEarlierUser(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
hub, user1 := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
setStatusAlertEmail(t, hub, user1.Id, "user1@example.com")
user2, err := beszelTests.CreateUser(hub, "user2@example.com", "password")
require.NoError(t, err)
_, err = beszelTests.CreateRecord(hub, "user_settings", map[string]any{
"user": user2.Id,
"settings": map[string]any{
"emails": []string{"user2@example.com"},
"webhooks": []string{},
},
})
require.NoError(t, err)
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "shared-system",
"users": []string{user1.Id, user2.Id},
"host": "127.0.0.1",
})
require.NoError(t, err)
system.Set("status", "up")
require.NoError(t, hub.SaveNoValidate(system))
alertUser1, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "Status",
"system": system.Id,
"user": user1.Id,
"min": 1,
})
require.NoError(t, err)
alertUser2, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "Status",
"system": system.Id,
"user": user2.Id,
"min": 2,
})
require.NoError(t, err)
time.Sleep(10 * time.Millisecond)
system.Set("status", "down")
require.NoError(t, hub.SaveNoValidate(system))
time.Sleep(61 * time.Second)
synctest.Wait()
messages := hub.TestMailer.Messages()
require.Len(t, messages, 1, "the first user's down alert should send before recovery")
require.Len(t, messages[0].To, 1)
assert.Equal(t, "user1@example.com", messages[0].To[0].Address)
assert.Contains(t, messages[0].Subject, "Connection to shared-system is down")
assert.Equal(t, 1, hub.GetPendingAlertsCount(), "the second user's alert should still be pending")
system.Set("status", "up")
require.NoError(t, hub.SaveNoValidate(system))
time.Sleep(time.Second)
synctest.Wait()
messages = hub.TestMailer.Messages()
require.Len(t, messages, 2, "recovery should notify only the user whose down alert had already triggered")
for _, message := range messages {
require.Len(t, message.To, 1)
assert.Equal(t, "user1@example.com", message.To[0].Address)
}
assert.Contains(t, messages[1].Subject, "Connection to shared-system is up")
assert.Zero(t, hub.GetPendingAlertsCount(), "recovery should cancel the later user's pending alert")
time.Sleep(61 * time.Second)
synctest.Wait()
messages = hub.TestMailer.Messages()
require.Len(t, messages, 2, "user2 should never receive a down alert once recovery cancels the pending timer")
alertUser1, err = hub.FindRecordById("alerts", alertUser1.Id)
require.NoError(t, err)
assert.False(t, alertUser1.GetBool("triggered"), "user1 alert should be cleared after recovery")
alertUser2, err = hub.FindRecordById("alerts", alertUser2.Id)
require.NoError(t, err)
assert.False(t, alertUser2.GetBool("triggered"), "user2 alert should remain untriggered because it never fired")
})
}
func TestStatusAlertDuplicateDownCallIsIdempotent(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()

View File

@@ -28,8 +28,8 @@ func main() {
}
baseApp := getBaseApp()
h := hub.NewHub(baseApp)
if err := h.StartHub(); err != nil {
hub := hub.NewHub(baseApp)
if err := hub.StartHub(); err != nil {
log.Fatal(err)
}
}

View File

@@ -110,21 +110,13 @@ func (p *updater) update() (updated bool, err error) {
}
var latest *release
var useMirror bool
// Determine the API endpoint based on UseMirror flag
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", p.config.Owner, p.config.Repo)
apiURL := getApiURL(p.config.UseMirror, p.config.Owner, p.config.Repo)
if p.config.UseMirror {
useMirror = true
apiURL = fmt.Sprintf("https://gh.beszel.dev/repos/%s/%s/releases/latest?api=true", p.config.Owner, p.config.Repo)
ColorPrint(ColorYellow, "Using mirror for update.")
}
latest, err = fetchLatestRelease(
p.config.Context,
p.config.HttpClient,
apiURL,
)
latest, err = FetchLatestRelease(p.config.Context, p.config.HttpClient, apiURL)
if err != nil {
return false, err
}
@@ -150,7 +142,7 @@ func (p *updater) update() (updated bool, err error) {
// download the release asset
assetPath := filepath.Join(releaseDir, asset.Name)
if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetPath, useMirror); err != nil {
if err := downloadFile(p.config.Context, p.config.HttpClient, asset.DownloadUrl, assetPath, p.config.UseMirror); err != nil {
return false, err
}
@@ -226,11 +218,11 @@ func (p *updater) update() (updated bool, err error) {
return true, nil
}
func fetchLatestRelease(
ctx context.Context,
client HttpClient,
url string,
) (*release, error) {
func FetchLatestRelease(ctx context.Context, client HttpClient, url string) (*release, error) {
if url == "" {
url = getApiURL(false, "henrygd", "beszel")
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
@@ -375,3 +367,10 @@ func isGlibc() bool {
}
return false
}
func getApiURL(useMirror bool, owner, repo string) string {
if useMirror {
return fmt.Sprintf("https://gh.beszel.dev/repos/%s/%s/releases/latest?api=true", owner, repo)
}
return fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)
}

View File

@@ -32,7 +32,7 @@ func createTestHub(t testing.TB) (*Hub, *pbtests.TestApp, error) {
if err != nil {
return nil, nil, err
}
return NewHub(testApp), testApp, nil
return NewHub(testApp), testApp, err
}
// cleanupTestHub stops background system goroutines before tearing down the app.
@@ -897,12 +897,8 @@ func TestAgentWebSocketIntegration(t *testing.T) {
require.NoError(t, err)
// Set up environment variables for the agent
os.Setenv("BESZEL_AGENT_HUB_URL", ts.URL)
os.Setenv("BESZEL_AGENT_TOKEN", tc.agentToken)
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", ts.URL)
t.Setenv("BESZEL_AGENT_TOKEN", tc.agentToken)
// Start agent in background
done := make(chan error, 1)
@@ -1080,12 +1076,8 @@ func TestMultipleSystemsWithSameUniversalToken(t *testing.T) {
require.NoError(t, err)
// Set up environment variables for the agent
os.Setenv("BESZEL_AGENT_HUB_URL", ts.URL)
os.Setenv("BESZEL_AGENT_TOKEN", universalToken)
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", ts.URL)
t.Setenv("BESZEL_AGENT_TOKEN", universalToken)
// Count systems before connection
systemsBefore, err := testApp.FindRecordsByFilter("systems", "users ~ {:userId}", "", -1, 0, map[string]any{"userId": userRecord.Id})
@@ -1243,12 +1235,8 @@ func TestPermanentUniversalTokenFromDB(t *testing.T) {
require.NoError(t, err)
// Set up environment variables for the agent
os.Setenv("BESZEL_AGENT_HUB_URL", ts.URL)
os.Setenv("BESZEL_AGENT_TOKEN", universalToken)
defer func() {
os.Unsetenv("BESZEL_AGENT_HUB_URL")
os.Unsetenv("BESZEL_AGENT_TOKEN")
}()
t.Setenv("BESZEL_AGENT_HUB_URL", ts.URL)
t.Setenv("BESZEL_AGENT_TOKEN", universalToken)
// Start agent in background
done := make(chan error, 1)

361
internal/hub/api.go Normal file
View File

@@ -0,0 +1,361 @@
package hub
import (
"context"
"net/http"
"strings"
"time"
"github.com/blang/semver"
"github.com/google/uuid"
"github.com/henrygd/beszel"
"github.com/henrygd/beszel/internal/alerts"
"github.com/henrygd/beszel/internal/ghupdate"
"github.com/henrygd/beszel/internal/hub/config"
"github.com/henrygd/beszel/internal/hub/systems"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
)
// UpdateInfo holds information about the latest update check
type UpdateInfo struct {
lastCheck time.Time
Version string `json:"v"`
Url string `json:"url"`
}
// registerMiddlewares registers custom middlewares
func (h *Hub) registerMiddlewares(se *core.ServeEvent) {
// authorizes request with user matching the provided email
authorizeRequestWithEmail := func(e *core.RequestEvent, email string) (err error) {
if e.Auth != nil || email == "" {
return e.Next()
}
isAuthRefresh := e.Request.URL.Path == "/api/collections/users/auth-refresh" && e.Request.Method == http.MethodPost
e.Auth, err = e.App.FindFirstRecordByData("users", "email", email)
if err != nil || !isAuthRefresh {
return e.Next()
}
// auth refresh endpoint, make sure token is set in header
token, _ := e.Auth.NewAuthToken()
e.Request.Header.Set("Authorization", token)
return e.Next()
}
// authenticate with trusted header
if autoLogin, _ := GetEnv("AUTO_LOGIN"); autoLogin != "" {
se.Router.BindFunc(func(e *core.RequestEvent) error {
return authorizeRequestWithEmail(e, autoLogin)
})
}
// authenticate with trusted header
if trustedHeader, _ := GetEnv("TRUSTED_AUTH_HEADER"); trustedHeader != "" {
se.Router.BindFunc(func(e *core.RequestEvent) error {
return authorizeRequestWithEmail(e, e.Request.Header.Get(trustedHeader))
})
}
}
// registerApiRoutes registers custom API routes
func (h *Hub) registerApiRoutes(se *core.ServeEvent) error {
// auth protected routes
apiAuth := se.Router.Group("/api/beszel")
apiAuth.Bind(apis.RequireAuth())
// auth optional routes
apiNoAuth := se.Router.Group("/api/beszel")
// create first user endpoint only needed if no users exist
if totalUsers, _ := se.App.CountRecords("users"); totalUsers == 0 {
apiNoAuth.POST("/create-user", h.um.CreateFirstUser)
}
// check if first time setup on login page
apiNoAuth.GET("/first-run", func(e *core.RequestEvent) error {
total, err := e.App.CountRecords("users")
return e.JSON(http.StatusOK, map[string]bool{"firstRun": err == nil && total == 0})
})
// get public key and version
apiAuth.GET("/info", h.getInfo)
apiAuth.GET("/getkey", h.getInfo) // deprecated - keep for compatibility w/ integrations
// check for updates
if optIn, _ := GetEnv("CHECK_UPDATES"); optIn == "true" {
var updateInfo UpdateInfo
apiAuth.GET("/update", updateInfo.getUpdate)
}
// send test notification
apiAuth.POST("/test-notification", h.SendTestNotification)
// heartbeat status and test
apiAuth.GET("/heartbeat-status", h.getHeartbeatStatus)
apiAuth.POST("/test-heartbeat", h.testHeartbeat)
// get config.yml content
apiAuth.GET("/config-yaml", config.GetYamlConfig)
// handle agent websocket connection
apiNoAuth.GET("/agent-connect", h.handleAgentConnect)
// get or create universal tokens
apiAuth.GET("/universal-token", h.getUniversalToken)
// update / delete user alerts
apiAuth.POST("/user-alerts", alerts.UpsertUserAlerts)
apiAuth.DELETE("/user-alerts", alerts.DeleteUserAlerts)
// refresh SMART devices for a system
apiAuth.POST("/smart/refresh", h.refreshSmartData)
// get systemd service details
apiAuth.GET("/systemd/info", h.getSystemdInfo)
// /containers routes
if enabled, _ := GetEnv("CONTAINER_DETAILS"); enabled != "false" {
// get container logs
apiAuth.GET("/containers/logs", h.getContainerLogs)
// get container info
apiAuth.GET("/containers/info", h.getContainerInfo)
}
return nil
}
// getInfo returns data needed by authenticated users, such as the public key and current version
func (h *Hub) getInfo(e *core.RequestEvent) error {
type infoResponse struct {
Key string `json:"key"`
Version string `json:"v"`
CheckUpdate bool `json:"cu"`
}
info := infoResponse{
Key: h.pubKey,
Version: beszel.Version,
}
if optIn, _ := GetEnv("CHECK_UPDATES"); optIn == "true" {
info.CheckUpdate = true
}
return e.JSON(http.StatusOK, info)
}
// getUpdate checks for the latest release on GitHub and returns update info if a newer version is available
func (info *UpdateInfo) getUpdate(e *core.RequestEvent) error {
if time.Since(info.lastCheck) < 6*time.Hour {
return e.JSON(http.StatusOK, info)
}
info.lastCheck = time.Now()
latestRelease, err := ghupdate.FetchLatestRelease(context.Background(), http.DefaultClient, "")
if err != nil {
return err
}
currentVersion, err := semver.Parse(strings.TrimPrefix(beszel.Version, "v"))
if err != nil {
return err
}
latestVersion, err := semver.Parse(strings.TrimPrefix(latestRelease.Tag, "v"))
if err != nil {
return err
}
if latestVersion.GT(currentVersion) {
info.Version = strings.TrimPrefix(latestRelease.Tag, "v")
info.Url = latestRelease.Url
}
return e.JSON(http.StatusOK, info)
}
// GetUniversalToken handles the universal token API endpoint (create, read, delete)
func (h *Hub) getUniversalToken(e *core.RequestEvent) error {
tokenMap := universalTokenMap.GetMap()
userID := e.Auth.Id
query := e.Request.URL.Query()
token := query.Get("token")
enable := query.Get("enable")
permanent := query.Get("permanent")
// helper for deleting any existing permanent token record for this user
deletePermanent := func() error {
rec, err := h.FindFirstRecordByFilter("universal_tokens", "user = {:user}", dbx.Params{"user": userID})
if err != nil {
return nil // no record
}
return h.Delete(rec)
}
// helper for upserting a permanent token record for this user
upsertPermanent := func(token string) error {
rec, err := h.FindFirstRecordByFilter("universal_tokens", "user = {:user}", dbx.Params{"user": userID})
if err == nil {
rec.Set("token", token)
return h.Save(rec)
}
col, err := h.FindCachedCollectionByNameOrId("universal_tokens")
if err != nil {
return err
}
newRec := core.NewRecord(col)
newRec.Set("user", userID)
newRec.Set("token", token)
return h.Save(newRec)
}
// Disable universal tokens (both ephemeral and permanent)
if enable == "0" {
tokenMap.RemovebyValue(userID)
_ = deletePermanent()
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": false, "permanent": false})
}
// Enable universal token (ephemeral or permanent)
if enable == "1" {
if token == "" {
token = uuid.New().String()
}
if permanent == "1" {
// make token permanent (persist across restarts)
tokenMap.RemovebyValue(userID)
if err := upsertPermanent(token); err != nil {
return err
}
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": true, "permanent": true})
}
// default: ephemeral mode (1 hour)
_ = deletePermanent()
tokenMap.Set(token, userID, time.Hour)
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": true, "permanent": false})
}
// Read current state
// Prefer permanent token if it exists.
if rec, err := h.FindFirstRecordByFilter("universal_tokens", "user = {:user}", dbx.Params{"user": userID}); err == nil {
dbToken := rec.GetString("token")
// If no token was provided, or the caller is asking about their permanent token, return it.
if token == "" || token == dbToken {
return e.JSON(http.StatusOK, map[string]any{"token": dbToken, "active": true, "permanent": true})
}
// Token doesn't match their permanent token (avoid leaking other info)
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": false, "permanent": false})
}
// No permanent token; fall back to ephemeral token map.
if token == "" {
// return existing token if it exists
if token, _, ok := tokenMap.GetByValue(userID); ok {
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": true, "permanent": false})
}
// if no token is provided, generate a new one
token = uuid.New().String()
}
// Token is considered active only if it belongs to the current user.
activeUser, ok := tokenMap.GetOk(token)
active := ok && activeUser == userID
response := map[string]any{"token": token, "active": active, "permanent": false}
return e.JSON(http.StatusOK, response)
}
// getHeartbeatStatus returns current heartbeat configuration and whether it's enabled
func (h *Hub) getHeartbeatStatus(e *core.RequestEvent) error {
if e.Auth.GetString("role") != "admin" {
return e.ForbiddenError("Requires admin role", nil)
}
if h.hb == nil {
return e.JSON(http.StatusOK, map[string]any{
"enabled": false,
"msg": "Set HEARTBEAT_URL to enable outbound heartbeat monitoring",
})
}
cfg := h.hb.GetConfig()
return e.JSON(http.StatusOK, map[string]any{
"enabled": true,
"url": cfg.URL,
"interval": cfg.Interval,
"method": cfg.Method,
})
}
// testHeartbeat triggers a single heartbeat ping and returns the result
func (h *Hub) testHeartbeat(e *core.RequestEvent) error {
if e.Auth.GetString("role") != "admin" {
return e.ForbiddenError("Requires admin role", nil)
}
if h.hb == nil {
return e.JSON(http.StatusOK, map[string]any{
"err": "Heartbeat not configured. Set HEARTBEAT_URL environment variable.",
})
}
if err := h.hb.Send(); err != nil {
return e.JSON(http.StatusOK, map[string]any{"err": err.Error()})
}
return e.JSON(http.StatusOK, map[string]any{"err": false})
}
// containerRequestHandler handles both container logs and info requests
func (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*systems.System, string) (string, error), responseKey string) error {
systemID := e.Request.URL.Query().Get("system")
containerID := e.Request.URL.Query().Get("container")
if systemID == "" || containerID == "" {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and container parameters are required"})
}
if !containerIDPattern.MatchString(containerID) {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "invalid container parameter"})
}
system, err := h.sm.GetSystem(systemID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
}
data, err := fetchFunc(system, containerID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
}
return e.JSON(http.StatusOK, map[string]string{responseKey: data})
}
// getContainerLogs handles GET /api/beszel/containers/logs requests
func (h *Hub) getContainerLogs(e *core.RequestEvent) error {
return h.containerRequestHandler(e, func(system *systems.System, containerID string) (string, error) {
return system.FetchContainerLogsFromAgent(containerID)
}, "logs")
}
func (h *Hub) getContainerInfo(e *core.RequestEvent) error {
return h.containerRequestHandler(e, func(system *systems.System, containerID string) (string, error) {
return system.FetchContainerInfoFromAgent(containerID)
}, "info")
}
// getSystemdInfo handles GET /api/beszel/systemd/info requests
func (h *Hub) getSystemdInfo(e *core.RequestEvent) error {
query := e.Request.URL.Query()
systemID := query.Get("system")
serviceName := query.Get("service")
if systemID == "" || serviceName == "" {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and service parameters are required"})
}
system, err := h.sm.GetSystem(systemID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
}
details, err := system.FetchSystemdInfoFromAgent(serviceName)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
}
e.Response.Header().Set("Cache-Control", "public, max-age=60")
return e.JSON(http.StatusOK, map[string]any{"details": details})
}
// refreshSmartData handles POST /api/beszel/smart/refresh requests
// Fetches fresh SMART data from the agent and updates the collection
func (h *Hub) refreshSmartData(e *core.RequestEvent) error {
systemID := e.Request.URL.Query().Get("system")
if systemID == "" {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system parameter is required"})
}
system, err := h.sm.GetSystem(systemID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
}
// Fetch and save SMART devices
if err := system.FetchAndSaveSmartDevices(); err != nil {
return e.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return e.JSON(http.StatusOK, map[string]string{"status": "ok"})
}

780
internal/hub/api_test.go Normal file
View File

@@ -0,0 +1,780 @@
package hub_test
import (
"bytes"
"encoding/json"
"io"
"net/http"
"testing"
beszelTests "github.com/henrygd/beszel/internal/tests"
"github.com/henrygd/beszel/internal/migrations"
"github.com/pocketbase/pocketbase/core"
pbTests "github.com/pocketbase/pocketbase/tests"
"github.com/stretchr/testify/require"
)
// marshal to json and return an io.Reader (for use in ApiScenario.Body)
func jsonReader(v any) io.Reader {
data, err := json.Marshal(v)
if err != nil {
panic(err)
}
return bytes.NewReader(data)
}
func TestApiRoutesAuthentication(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
// Create test user and get auth token
user, err := beszelTests.CreateUser(hub, "testuser@example.com", "password123")
require.NoError(t, err, "Failed to create test user")
adminUser, err := beszelTests.CreateRecord(hub, "users", map[string]any{
"email": "admin@example.com",
"password": "password123",
"role": "admin",
})
require.NoError(t, err, "Failed to create admin user")
adminUserToken, err := adminUser.NewAuthToken()
// superUser, err := beszelTests.CreateRecord(hub, core.CollectionNameSuperusers, map[string]any{
// "email": "superuser@example.com",
// "password": "password123",
// })
// require.NoError(t, err, "Failed to create superuser")
userToken, err := user.NewAuthToken()
require.NoError(t, err, "Failed to create auth token")
// Create test system for user-alerts endpoints
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "test-system",
"users": []string{user.Id},
"host": "127.0.0.1",
})
require.NoError(t, err, "Failed to create test system")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
// Auth Protected Routes - Should require authentication
{
Name: "POST /test-notification - no auth should fail",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
},
{
Name: "POST /test-notification - with auth should succeed",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
TestAppFactory: testAppFactory,
Headers: map[string]string{
"Authorization": userToken,
},
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
ExpectedStatus: 200,
ExpectedContent: []string{"sending message"},
},
{
Name: "GET /config-yaml - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /config-yaml - with user auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 403,
ExpectedContent: []string{"Requires admin"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /config-yaml - with admin auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
Headers: map[string]string{
"Authorization": adminUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"test-system"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /heartbeat-status - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/heartbeat-status",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /heartbeat-status - with user auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/heartbeat-status",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 403,
ExpectedContent: []string{"Requires admin role"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /heartbeat-status - with admin auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/heartbeat-status",
Headers: map[string]string{
"Authorization": adminUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{`"enabled":false`},
TestAppFactory: testAppFactory,
},
{
Name: "POST /test-heartbeat - with user auth should fail",
Method: http.MethodPost,
URL: "/api/beszel/test-heartbeat",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 403,
ExpectedContent: []string{"Requires admin role"},
TestAppFactory: testAppFactory,
},
{
Name: "POST /test-heartbeat - with admin auth should report disabled state",
Method: http.MethodPost,
URL: "/api/beszel/test-heartbeat",
Headers: map[string]string{
"Authorization": adminUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"Heartbeat not configured"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /universal-token - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/universal-token",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /universal-token - with auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/universal-token",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"active", "token", "permanent"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /universal-token - enable permanent should succeed",
Method: http.MethodGet,
URL: "/api/beszel/universal-token?enable=1&permanent=1&token=permanent-token-123",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"permanent\":true", "permanent-token-123"},
TestAppFactory: testAppFactory,
},
{
Name: "POST /user-alerts - no auth should fail",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
{
Name: "POST /user-alerts - with auth should succeed",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
{
Name: "DELETE /user-alerts - no auth should fail",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system.Id},
}),
},
{
Name: "DELETE /user-alerts - with auth should succeed",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system.Id},
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
// Create an alert to delete
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU",
"system": system.Id,
"user": user.Id,
"value": 80,
"min": 10,
})
},
},
{
Name: "GET /containers/logs - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?system=test-system&container=test-container",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/logs - with auth but missing system param should fail",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?container=test-container",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"system and container parameters are required"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/logs - with auth but missing container param should fail",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?system=test-system",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"system and container parameters are required"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/logs - with auth but invalid system should fail",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?system=invalid-system&container=0123456789ab",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 404,
ExpectedContent: []string{"system not found"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/logs - traversal container should fail validation",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?system=" + system.Id + "&container=..%2F..%2Fversion",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"invalid container parameter"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/info - traversal container should fail validation",
Method: http.MethodGet,
URL: "/api/beszel/containers/info?system=" + system.Id + "&container=../../version?x=",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"invalid container parameter"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/info - non-hex container should fail validation",
Method: http.MethodGet,
URL: "/api/beszel/containers/info?system=" + system.Id + "&container=container_name",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"invalid container parameter"},
TestAppFactory: testAppFactory,
},
// Auth Optional Routes - Should work without authentication
{
Name: "GET /getkey - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auth should also succeed",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /info - should return the same as /getkey",
Method: http.MethodGet,
URL: "/api/beszel/info",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /first-run - no auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/first-run",
ExpectedStatus: 200,
ExpectedContent: []string{"\"firstRun\":false"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /first-run - with auth should also succeed",
Method: http.MethodGet,
URL: "/api/beszel/first-run",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"firstRun\":false"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /agent-connect - no auth should succeed (websocket upgrade fails but route is accessible)",
Method: http.MethodGet,
URL: "/api/beszel/agent-connect",
ExpectedStatus: 400,
ExpectedContent: []string{},
TestAppFactory: testAppFactory,
},
{
Name: "POST /test-notification - invalid auth token should fail",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
Headers: map[string]string{
"Authorization": "invalid-token",
},
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "POST /user-alerts - invalid auth token should fail",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": "invalid-token",
},
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
{
Name: "GET /update - shouldn't exist without CHECK_UPDATES env var",
Method: http.MethodGet,
URL: "/api/beszel/update",
ExpectedStatus: 502,
TestAppFactory: testAppFactory,
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestFirstUserCreation(t *testing.T) {
t.Run("CreateUserEndpoint available when no users exist", func(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
testAppFactoryExisting := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "POST /create-user - should be available when no users exist",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
Body: jsonReader(map[string]any{
"email": "firstuser@example.com",
"password": "password123",
}),
ExpectedStatus: 200,
ExpectedContent: []string{"User created"},
TestAppFactory: testAppFactoryExisting,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
userCount, err := hub.CountRecords("users")
require.NoError(t, err)
require.Zero(t, userCount, "Should start with no users")
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
require.NoError(t, err)
require.EqualValues(t, 1, len(superusers), "Should start with one temporary superuser")
require.EqualValues(t, migrations.TempAdminEmail, superusers[0].GetString("email"), "Should have created one temporary superuser")
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
userCount, err := hub.CountRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, userCount, "Should have created one user")
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
require.NoError(t, err)
require.EqualValues(t, 1, len(superusers), "Should have created one superuser")
require.EqualValues(t, "firstuser@example.com", superusers[0].GetString("email"), "Should have created one superuser")
},
},
{
Name: "POST /create-user - should not be available when users exist",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
Body: jsonReader(map[string]any{
"email": "firstuser@example.com",
"password": "password123",
}),
ExpectedStatus: 404,
ExpectedContent: []string{"wasn't found"},
TestAppFactory: testAppFactoryExisting,
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
})
t.Run("CreateUserEndpoint not available when USER_EMAIL, USER_PASSWORD are set", func(t *testing.T) {
t.Setenv("BESZEL_HUB_USER_EMAIL", "me@example.com")
t.Setenv("BESZEL_HUB_USER_PASSWORD", "password123")
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenario := beszelTests.ApiScenario{
Name: "POST /create-user - should not be available when USER_EMAIL, USER_PASSWORD are set",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
ExpectedStatus: 404,
ExpectedContent: []string{"wasn't found"},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
users, err := hub.FindAllRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, len(users), "Should start with one user")
require.EqualValues(t, "me@example.com", users[0].GetString("email"), "Should have created one user")
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
require.NoError(t, err)
require.EqualValues(t, 1, len(superusers), "Should start with one superuser")
require.EqualValues(t, "me@example.com", superusers[0].GetString("email"), "Should have created one superuser")
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
users, err := hub.FindAllRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, len(users), "Should still have one user")
require.EqualValues(t, "me@example.com", users[0].GetString("email"), "Should have created one user")
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
require.NoError(t, err)
require.EqualValues(t, 1, len(superusers), "Should still have one superuser")
require.EqualValues(t, "me@example.com", superusers[0].GetString("email"), "Should have created one superuser")
},
}
scenario.Test(t)
})
}
func TestCreateUserEndpointAvailability(t *testing.T) {
t.Run("CreateUserEndpoint available when no users exist", func(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
// Ensure no users exist
userCount, err := hub.CountRecords("users")
require.NoError(t, err)
require.Zero(t, userCount, "Should start with no users")
hub.StartHub()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenario := beszelTests.ApiScenario{
Name: "POST /create-user - should be available when no users exist",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
Body: jsonReader(map[string]any{
"email": "firstuser@example.com",
"password": "password123",
}),
ExpectedStatus: 200,
ExpectedContent: []string{"User created"},
TestAppFactory: testAppFactory,
}
scenario.Test(t)
// Verify user was created
userCount, err = hub.CountRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, userCount, "Should have created one user")
})
t.Run("CreateUserEndpoint not available when users exist", func(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
// Create a user first
_, err := beszelTests.CreateUser(hub, "existing@example.com", "password")
require.NoError(t, err)
hub.StartHub()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenario := beszelTests.ApiScenario{
Name: "POST /create-user - should not be available when users exist",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
Body: jsonReader(map[string]any{
"email": "another@example.com",
"password": "password123",
}),
ExpectedStatus: 404,
ExpectedContent: []string{"wasn't found"},
TestAppFactory: testAppFactory,
}
scenario.Test(t)
})
}
func TestAutoLoginMiddleware(t *testing.T) {
var hubs []*beszelTests.TestHub
defer func() {
for _, hub := range hubs {
hub.Cleanup()
}
}()
t.Setenv("AUTO_LOGIN", "user@test.com")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
hub, _ := beszelTests.NewTestHub(t.TempDir())
hubs = append(hubs, hub)
hub.StartHub()
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "GET /getkey - without auto login should fail",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auto login should fail if no matching user",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auto login should succeed",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.CreateUser(app, "user@test.com", "password123")
},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestTrustedHeaderMiddleware(t *testing.T) {
var hubs []*beszelTests.TestHub
defer func() {
for _, hub := range hubs {
hub.Cleanup()
}
}()
t.Setenv("TRUSTED_AUTH_HEADER", "X-Beszel-Trusted")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
hub, _ := beszelTests.NewTestHub(t.TempDir())
hubs = append(hubs, hub)
hub.StartHub()
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "GET /getkey - without trusted header should fail",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with trusted header should fail if no matching user",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
Headers: map[string]string{
"X-Beszel-Trusted": "user@test.com",
},
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with trusted header should succeed",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
Headers: map[string]string{
"X-Beszel-Trusted": "user@test.com",
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.CreateUser(app, "user@test.com", "password123")
},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestUpdateEndpoint(t *testing.T) {
t.Setenv("CHECK_UPDATES", "true")
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
// Create test user and get auth token
// user, err := beszelTests.CreateUser(hub, "testuser@example.com", "password123")
// require.NoError(t, err, "Failed to create test user")
// userToken, err := user.NewAuthToken()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "update endpoint shouldn't work without auth",
Method: http.MethodGet,
URL: "/api/beszel/update",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
// leave this out for now since it actually makes a request to github
// {
// Name: "GET /update - with valid auth should succeed",
// Method: http.MethodGet,
// URL: "/api/beszel/update",
// Headers: map[string]string{
// "Authorization": userToken,
// },
// ExpectedStatus: 200,
// ExpectedContent: []string{`"v":`},
// TestAppFactory: testAppFactory,
// },
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}

128
internal/hub/collections.go Normal file
View File

@@ -0,0 +1,128 @@
package hub
import "github.com/pocketbase/pocketbase/core"
type collectionRules struct {
list *string
view *string
create *string
update *string
delete *string
}
// setCollectionAuthSettings applies Beszel's collection auth settings.
func setCollectionAuthSettings(app core.App) error {
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
superusersCollection, err := app.FindCollectionByNameOrId(core.CollectionNameSuperusers)
if err != nil {
return err
}
// disable email auth if DISABLE_PASSWORD_AUTH env var is set
disablePasswordAuth, _ := GetEnv("DISABLE_PASSWORD_AUTH")
usersCollection.PasswordAuth.Enabled = disablePasswordAuth != "true"
usersCollection.PasswordAuth.IdentityFields = []string{"email"}
// allow oauth user creation if USER_CREATION is set
if userCreation, _ := GetEnv("USER_CREATION"); userCreation == "true" {
cr := "@request.context = 'oauth2'"
usersCollection.CreateRule = &cr
} else {
usersCollection.CreateRule = nil
}
// enable mfaOtp mfa if MFA_OTP env var is set
mfaOtp, _ := GetEnv("MFA_OTP")
usersCollection.OTP.Length = 6
superusersCollection.OTP.Length = 6
usersCollection.OTP.Enabled = mfaOtp == "true"
usersCollection.MFA.Enabled = mfaOtp == "true"
superusersCollection.OTP.Enabled = mfaOtp == "true" || mfaOtp == "superusers"
superusersCollection.MFA.Enabled = mfaOtp == "true" || mfaOtp == "superusers"
if err := app.Save(superusersCollection); err != nil {
return err
}
if err := app.Save(usersCollection); err != nil {
return err
}
// When SHARE_ALL_SYSTEMS is enabled, any authenticated user can read
// system-scoped data. Write rules continue to block readonly users.
shareAllSystems, _ := GetEnv("SHARE_ALL_SYSTEMS")
authenticatedRule := "@request.auth.id != \"\""
systemsMemberRule := authenticatedRule + " && users.id ?= @request.auth.id"
systemMemberRule := authenticatedRule + " && system.users.id ?= @request.auth.id"
systemsReadRule := systemsMemberRule
systemScopedReadRule := systemMemberRule
if shareAllSystems == "true" {
systemsReadRule = authenticatedRule
systemScopedReadRule = authenticatedRule
}
systemsWriteRule := systemsReadRule + " && @request.auth.role != \"readonly\""
systemScopedWriteRule := systemScopedReadRule + " && @request.auth.role != \"readonly\""
if err := applyCollectionRules(app, []string{"systems"}, collectionRules{
list: &systemsReadRule,
view: &systemsReadRule,
create: &systemsWriteRule,
update: &systemsWriteRule,
delete: &systemsWriteRule,
}); err != nil {
return err
}
if err := applyCollectionRules(app, []string{"containers", "container_stats", "system_stats", "systemd_services"}, collectionRules{
list: &systemScopedReadRule,
}); err != nil {
return err
}
if err := applyCollectionRules(app, []string{"smart_devices"}, collectionRules{
list: &systemScopedReadRule,
view: &systemScopedReadRule,
delete: &systemScopedWriteRule,
}); err != nil {
return err
}
if err := applyCollectionRules(app, []string{"fingerprints"}, collectionRules{
list: &systemScopedReadRule,
view: &systemScopedReadRule,
create: &systemScopedWriteRule,
update: &systemScopedWriteRule,
delete: &systemScopedWriteRule,
}); err != nil {
return err
}
if err := applyCollectionRules(app, []string{"system_details"}, collectionRules{
list: &systemScopedReadRule,
view: &systemScopedReadRule,
}); err != nil {
return err
}
return nil
}
func applyCollectionRules(app core.App, collectionNames []string, rules collectionRules) error {
for _, collectionName := range collectionNames {
collection, err := app.FindCollectionByNameOrId(collectionName)
if err != nil {
return err
}
collection.ListRule = rules.list
collection.ViewRule = rules.view
collection.CreateRule = rules.create
collection.UpdateRule = rules.update
collection.DeleteRule = rules.delete
if err := app.Save(collection); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,527 @@
//go:build testing
package hub_test
import (
"fmt"
"net/http"
"testing"
beszelTests "github.com/henrygd/beszel/internal/tests"
"github.com/pocketbase/pocketbase/core"
pbTests "github.com/pocketbase/pocketbase/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCollectionRulesDefault(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
const isUserMatchesUser = `@request.auth.id != "" && user = @request.auth.id`
const isUserInUsers = `@request.auth.id != "" && users.id ?= @request.auth.id`
const isUserInUsersNotReadonly = `@request.auth.id != "" && users.id ?= @request.auth.id && @request.auth.role != "readonly"`
const isUserInSystemUsers = `@request.auth.id != "" && system.users.id ?= @request.auth.id`
const isUserInSystemUsersNotReadonly = `@request.auth.id != "" && system.users.id ?= @request.auth.id && @request.auth.role != "readonly"`
// users collection
usersCollection, err := hub.FindCollectionByNameOrId("users")
assert.NoError(t, err, "Failed to find users collection")
assert.True(t, usersCollection.PasswordAuth.Enabled)
assert.Equal(t, usersCollection.PasswordAuth.IdentityFields, []string{"email"})
assert.Nil(t, usersCollection.CreateRule)
assert.False(t, usersCollection.MFA.Enabled)
// superusers collection
superusersCollection, err := hub.FindCollectionByNameOrId(core.CollectionNameSuperusers)
assert.NoError(t, err, "Failed to find superusers collection")
assert.True(t, superusersCollection.PasswordAuth.Enabled)
assert.Equal(t, superusersCollection.PasswordAuth.IdentityFields, []string{"email"})
assert.Nil(t, superusersCollection.CreateRule)
assert.False(t, superusersCollection.MFA.Enabled)
// alerts collection
alertsCollection, err := hub.FindCollectionByNameOrId("alerts")
require.NoError(t, err, "Failed to find alerts collection")
assert.Equal(t, isUserMatchesUser, *alertsCollection.ListRule)
assert.Nil(t, alertsCollection.ViewRule)
assert.Equal(t, isUserMatchesUser, *alertsCollection.CreateRule)
assert.Equal(t, isUserMatchesUser, *alertsCollection.UpdateRule)
assert.Equal(t, isUserMatchesUser, *alertsCollection.DeleteRule)
// alerts_history collection
alertsHistoryCollection, err := hub.FindCollectionByNameOrId("alerts_history")
require.NoError(t, err, "Failed to find alerts_history collection")
assert.Equal(t, isUserMatchesUser, *alertsHistoryCollection.ListRule)
assert.Nil(t, alertsHistoryCollection.ViewRule)
assert.Nil(t, alertsHistoryCollection.CreateRule)
assert.Nil(t, alertsHistoryCollection.UpdateRule)
assert.Equal(t, isUserMatchesUser, *alertsHistoryCollection.DeleteRule)
// containers collection
containersCollection, err := hub.FindCollectionByNameOrId("containers")
require.NoError(t, err, "Failed to find containers collection")
assert.Equal(t, isUserInSystemUsers, *containersCollection.ListRule)
assert.Nil(t, containersCollection.ViewRule)
assert.Nil(t, containersCollection.CreateRule)
assert.Nil(t, containersCollection.UpdateRule)
assert.Nil(t, containersCollection.DeleteRule)
// container_stats collection
containerStatsCollection, err := hub.FindCollectionByNameOrId("container_stats")
require.NoError(t, err, "Failed to find container_stats collection")
assert.Equal(t, isUserInSystemUsers, *containerStatsCollection.ListRule)
assert.Nil(t, containerStatsCollection.ViewRule)
assert.Nil(t, containerStatsCollection.CreateRule)
assert.Nil(t, containerStatsCollection.UpdateRule)
assert.Nil(t, containerStatsCollection.DeleteRule)
// fingerprints collection
fingerprintsCollection, err := hub.FindCollectionByNameOrId("fingerprints")
require.NoError(t, err, "Failed to find fingerprints collection")
assert.Equal(t, isUserInSystemUsers, *fingerprintsCollection.ListRule)
assert.Equal(t, isUserInSystemUsers, *fingerprintsCollection.ViewRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.CreateRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.UpdateRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.DeleteRule)
// quiet_hours collection
quietHoursCollection, err := hub.FindCollectionByNameOrId("quiet_hours")
require.NoError(t, err, "Failed to find quiet_hours collection")
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.ListRule)
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.ViewRule)
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.CreateRule)
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.UpdateRule)
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.DeleteRule)
// smart_devices collection
smartDevicesCollection, err := hub.FindCollectionByNameOrId("smart_devices")
require.NoError(t, err, "Failed to find smart_devices collection")
assert.Equal(t, isUserInSystemUsers, *smartDevicesCollection.ListRule)
assert.Equal(t, isUserInSystemUsers, *smartDevicesCollection.ViewRule)
assert.Nil(t, smartDevicesCollection.CreateRule)
assert.Nil(t, smartDevicesCollection.UpdateRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *smartDevicesCollection.DeleteRule)
// system_details collection
systemDetailsCollection, err := hub.FindCollectionByNameOrId("system_details")
require.NoError(t, err, "Failed to find system_details collection")
assert.Equal(t, isUserInSystemUsers, *systemDetailsCollection.ListRule)
assert.Equal(t, isUserInSystemUsers, *systemDetailsCollection.ViewRule)
assert.Nil(t, systemDetailsCollection.CreateRule)
assert.Nil(t, systemDetailsCollection.UpdateRule)
assert.Nil(t, systemDetailsCollection.DeleteRule)
// system_stats collection
systemStatsCollection, err := hub.FindCollectionByNameOrId("system_stats")
require.NoError(t, err, "Failed to find system_stats collection")
assert.Equal(t, isUserInSystemUsers, *systemStatsCollection.ListRule)
assert.Nil(t, systemStatsCollection.ViewRule)
assert.Nil(t, systemStatsCollection.CreateRule)
assert.Nil(t, systemStatsCollection.UpdateRule)
assert.Nil(t, systemStatsCollection.DeleteRule)
// systemd_services collection
systemdServicesCollection, err := hub.FindCollectionByNameOrId("systemd_services")
require.NoError(t, err, "Failed to find systemd_services collection")
assert.Equal(t, isUserInSystemUsers, *systemdServicesCollection.ListRule)
assert.Nil(t, systemdServicesCollection.ViewRule)
assert.Nil(t, systemdServicesCollection.CreateRule)
assert.Nil(t, systemdServicesCollection.UpdateRule)
assert.Nil(t, systemdServicesCollection.DeleteRule)
// systems collection
systemsCollection, err := hub.FindCollectionByNameOrId("systems")
require.NoError(t, err, "Failed to find systems collection")
assert.Equal(t, isUserInUsers, *systemsCollection.ListRule)
assert.Equal(t, isUserInUsers, *systemsCollection.ViewRule)
assert.Equal(t, isUserInUsersNotReadonly, *systemsCollection.CreateRule)
assert.Equal(t, isUserInUsersNotReadonly, *systemsCollection.UpdateRule)
assert.Equal(t, isUserInUsersNotReadonly, *systemsCollection.DeleteRule)
// universal_tokens collection
universalTokensCollection, err := hub.FindCollectionByNameOrId("universal_tokens")
require.NoError(t, err, "Failed to find universal_tokens collection")
assert.Nil(t, universalTokensCollection.ListRule)
assert.Nil(t, universalTokensCollection.ViewRule)
assert.Nil(t, universalTokensCollection.CreateRule)
assert.Nil(t, universalTokensCollection.UpdateRule)
assert.Nil(t, universalTokensCollection.DeleteRule)
// user_settings collection
userSettingsCollection, err := hub.FindCollectionByNameOrId("user_settings")
require.NoError(t, err, "Failed to find user_settings collection")
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.ListRule)
assert.Nil(t, userSettingsCollection.ViewRule)
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.CreateRule)
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.UpdateRule)
assert.Nil(t, userSettingsCollection.DeleteRule)
}
func TestCollectionRulesShareAllSystems(t *testing.T) {
t.Setenv("SHARE_ALL_SYSTEMS", "true")
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
const isUser = `@request.auth.id != ""`
const isUserNotReadonly = `@request.auth.id != "" && @request.auth.role != "readonly"`
const isUserMatchesUser = `@request.auth.id != "" && user = @request.auth.id`
// alerts collection
alertsCollection, err := hub.FindCollectionByNameOrId("alerts")
require.NoError(t, err, "Failed to find alerts collection")
assert.Equal(t, isUserMatchesUser, *alertsCollection.ListRule)
assert.Nil(t, alertsCollection.ViewRule)
assert.Equal(t, isUserMatchesUser, *alertsCollection.CreateRule)
assert.Equal(t, isUserMatchesUser, *alertsCollection.UpdateRule)
assert.Equal(t, isUserMatchesUser, *alertsCollection.DeleteRule)
// alerts_history collection
alertsHistoryCollection, err := hub.FindCollectionByNameOrId("alerts_history")
require.NoError(t, err, "Failed to find alerts_history collection")
assert.Equal(t, isUserMatchesUser, *alertsHistoryCollection.ListRule)
assert.Nil(t, alertsHistoryCollection.ViewRule)
assert.Nil(t, alertsHistoryCollection.CreateRule)
assert.Nil(t, alertsHistoryCollection.UpdateRule)
assert.Equal(t, isUserMatchesUser, *alertsHistoryCollection.DeleteRule)
// containers collection
containersCollection, err := hub.FindCollectionByNameOrId("containers")
require.NoError(t, err, "Failed to find containers collection")
assert.Equal(t, isUser, *containersCollection.ListRule)
assert.Nil(t, containersCollection.ViewRule)
assert.Nil(t, containersCollection.CreateRule)
assert.Nil(t, containersCollection.UpdateRule)
assert.Nil(t, containersCollection.DeleteRule)
// container_stats collection
containerStatsCollection, err := hub.FindCollectionByNameOrId("container_stats")
require.NoError(t, err, "Failed to find container_stats collection")
assert.Equal(t, isUser, *containerStatsCollection.ListRule)
assert.Nil(t, containerStatsCollection.ViewRule)
assert.Nil(t, containerStatsCollection.CreateRule)
assert.Nil(t, containerStatsCollection.UpdateRule)
assert.Nil(t, containerStatsCollection.DeleteRule)
// fingerprints collection
fingerprintsCollection, err := hub.FindCollectionByNameOrId("fingerprints")
require.NoError(t, err, "Failed to find fingerprints collection")
assert.Equal(t, isUser, *fingerprintsCollection.ListRule)
assert.Equal(t, isUser, *fingerprintsCollection.ViewRule)
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.CreateRule)
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.UpdateRule)
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.DeleteRule)
// quiet_hours collection
quietHoursCollection, err := hub.FindCollectionByNameOrId("quiet_hours")
require.NoError(t, err, "Failed to find quiet_hours collection")
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.ListRule)
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.ViewRule)
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.CreateRule)
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.UpdateRule)
assert.Equal(t, isUserMatchesUser, *quietHoursCollection.DeleteRule)
// smart_devices collection
smartDevicesCollection, err := hub.FindCollectionByNameOrId("smart_devices")
require.NoError(t, err, "Failed to find smart_devices collection")
assert.Equal(t, isUser, *smartDevicesCollection.ListRule)
assert.Equal(t, isUser, *smartDevicesCollection.ViewRule)
assert.Nil(t, smartDevicesCollection.CreateRule)
assert.Nil(t, smartDevicesCollection.UpdateRule)
assert.Equal(t, isUserNotReadonly, *smartDevicesCollection.DeleteRule)
// system_details collection
systemDetailsCollection, err := hub.FindCollectionByNameOrId("system_details")
require.NoError(t, err, "Failed to find system_details collection")
assert.Equal(t, isUser, *systemDetailsCollection.ListRule)
assert.Equal(t, isUser, *systemDetailsCollection.ViewRule)
assert.Nil(t, systemDetailsCollection.CreateRule)
assert.Nil(t, systemDetailsCollection.UpdateRule)
assert.Nil(t, systemDetailsCollection.DeleteRule)
// system_stats collection
systemStatsCollection, err := hub.FindCollectionByNameOrId("system_stats")
require.NoError(t, err, "Failed to find system_stats collection")
assert.Equal(t, isUser, *systemStatsCollection.ListRule)
assert.Nil(t, systemStatsCollection.ViewRule)
assert.Nil(t, systemStatsCollection.CreateRule)
assert.Nil(t, systemStatsCollection.UpdateRule)
assert.Nil(t, systemStatsCollection.DeleteRule)
// systemd_services collection
systemdServicesCollection, err := hub.FindCollectionByNameOrId("systemd_services")
require.NoError(t, err, "Failed to find systemd_services collection")
assert.Equal(t, isUser, *systemdServicesCollection.ListRule)
assert.Nil(t, systemdServicesCollection.ViewRule)
assert.Nil(t, systemdServicesCollection.CreateRule)
assert.Nil(t, systemdServicesCollection.UpdateRule)
assert.Nil(t, systemdServicesCollection.DeleteRule)
// systems collection
systemsCollection, err := hub.FindCollectionByNameOrId("systems")
require.NoError(t, err, "Failed to find systems collection")
assert.Equal(t, isUser, *systemsCollection.ListRule)
assert.Equal(t, isUser, *systemsCollection.ViewRule)
assert.Equal(t, isUserNotReadonly, *systemsCollection.CreateRule)
assert.Equal(t, isUserNotReadonly, *systemsCollection.UpdateRule)
assert.Equal(t, isUserNotReadonly, *systemsCollection.DeleteRule)
// universal_tokens collection
universalTokensCollection, err := hub.FindCollectionByNameOrId("universal_tokens")
require.NoError(t, err, "Failed to find universal_tokens collection")
assert.Nil(t, universalTokensCollection.ListRule)
assert.Nil(t, universalTokensCollection.ViewRule)
assert.Nil(t, universalTokensCollection.CreateRule)
assert.Nil(t, universalTokensCollection.UpdateRule)
assert.Nil(t, universalTokensCollection.DeleteRule)
// user_settings collection
userSettingsCollection, err := hub.FindCollectionByNameOrId("user_settings")
require.NoError(t, err, "Failed to find user_settings collection")
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.ListRule)
assert.Nil(t, userSettingsCollection.ViewRule)
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.CreateRule)
assert.Equal(t, isUserMatchesUser, *userSettingsCollection.UpdateRule)
assert.Nil(t, userSettingsCollection.DeleteRule)
}
func TestDisablePasswordAuth(t *testing.T) {
t.Setenv("DISABLE_PASSWORD_AUTH", "true")
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
usersCollection, err := hub.FindCollectionByNameOrId("users")
assert.NoError(t, err)
assert.False(t, usersCollection.PasswordAuth.Enabled)
}
func TestUserCreation(t *testing.T) {
t.Setenv("USER_CREATION", "true")
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
usersCollection, err := hub.FindCollectionByNameOrId("users")
assert.NoError(t, err)
assert.Equal(t, "@request.context = 'oauth2'", *usersCollection.CreateRule)
}
func TestMFAOtp(t *testing.T) {
t.Setenv("MFA_OTP", "true")
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
usersCollection, err := hub.FindCollectionByNameOrId("users")
assert.NoError(t, err)
assert.True(t, usersCollection.OTP.Enabled)
assert.True(t, usersCollection.MFA.Enabled)
superusersCollection, err := hub.FindCollectionByNameOrId(core.CollectionNameSuperusers)
assert.NoError(t, err)
assert.True(t, superusersCollection.OTP.Enabled)
assert.True(t, superusersCollection.MFA.Enabled)
}
func TestApiCollectionsAuthRules(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
user1, _ := beszelTests.CreateUser(hub, "user1@example.com", "password")
user1Token, _ := user1.NewAuthToken()
user2, _ := beszelTests.CreateUser(hub, "user2@example.com", "password")
// user2Token, _ := user2.NewAuthToken()
userReadonly, _ := beszelTests.CreateUserWithRole(hub, "userreadonly@example.com", "password", "readonly")
userReadonlyToken, _ := userReadonly.NewAuthToken()
userOneSystem, _ := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "system1",
"users": []string{user1.Id},
"host": "127.0.0.1",
})
sharedSystem, _ := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "system2",
"users": []string{user1.Id, user2.Id},
"host": "127.0.0.2",
})
userTwoSystem, _ := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "system3",
"users": []string{user2.Id},
"host": "127.0.0.2",
})
userRecords, _ := hub.CountRecords("users")
assert.EqualValues(t, 3, userRecords, "all users should be created")
systemRecords, _ := hub.CountRecords("systems")
assert.EqualValues(t, 3, systemRecords, "all systems should be created")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "Unauthorized user cannot list systems",
Method: http.MethodGet,
URL: "/api/collections/systems/records",
ExpectedStatus: 200, // https://github.com/pocketbase/pocketbase/discussions/1570
TestAppFactory: testAppFactory,
ExpectedContent: []string{`"items":[]`, `"totalItems":0`},
NotExpectedContent: []string{userOneSystem.Id, sharedSystem.Id, userTwoSystem.Id},
},
{
Name: "Unauthorized user cannot delete a system",
Method: http.MethodDelete,
URL: fmt.Sprintf("/api/collections/systems/records/%s", userOneSystem.Id),
ExpectedStatus: 404,
TestAppFactory: testAppFactory,
ExpectedContent: []string{"resource wasn't found"},
NotExpectedContent: []string{userOneSystem.Id},
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 3, systemsCount, "should have 3 systems before deletion")
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 3, systemsCount, "should still have 3 systems after failed deletion")
},
},
{
Name: "User 1 can list their own systems",
Method: http.MethodGet,
URL: "/api/collections/systems/records",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{userOneSystem.Id, sharedSystem.Id},
NotExpectedContent: []string{userTwoSystem.Id},
TestAppFactory: testAppFactory,
},
{
Name: "User 1 cannot list user 2's system",
Method: http.MethodGet,
URL: "/api/collections/systems/records",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{userOneSystem.Id, sharedSystem.Id},
NotExpectedContent: []string{userTwoSystem.Id},
TestAppFactory: testAppFactory,
},
{
Name: "User 1 can see user 2's system if SHARE_ALL_SYSTEMS is enabled",
Method: http.MethodGet,
URL: "/api/collections/systems/records",
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 200,
ExpectedContent: []string{userOneSystem.Id, sharedSystem.Id, userTwoSystem.Id},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
t.Setenv("SHARE_ALL_SYSTEMS", "true")
hub.SetCollectionAuthSettings()
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
t.Setenv("SHARE_ALL_SYSTEMS", "")
hub.SetCollectionAuthSettings()
},
},
{
Name: "User 1 can delete their own system",
Method: http.MethodDelete,
URL: fmt.Sprintf("/api/collections/systems/records/%s", userOneSystem.Id),
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 204,
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 3, systemsCount, "should have 3 systems before deletion")
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 2, systemsCount, "should have 2 systems after deletion")
},
},
{
Name: "User 1 cannot delete user 2's system",
Method: http.MethodDelete,
URL: fmt.Sprintf("/api/collections/systems/records/%s", userTwoSystem.Id),
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 404,
TestAppFactory: testAppFactory,
ExpectedContent: []string{"resource wasn't found"},
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 2, systemsCount)
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 2, systemsCount)
},
},
{
Name: "Readonly cannot delete a system even if SHARE_ALL_SYSTEMS is enabled",
Method: http.MethodDelete,
URL: fmt.Sprintf("/api/collections/systems/records/%s", sharedSystem.Id),
Headers: map[string]string{
"Authorization": userReadonlyToken,
},
ExpectedStatus: 404,
ExpectedContent: []string{"resource wasn't found"},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
t.Setenv("SHARE_ALL_SYSTEMS", "true")
hub.SetCollectionAuthSettings()
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 2, systemsCount)
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
t.Setenv("SHARE_ALL_SYSTEMS", "")
hub.SetCollectionAuthSettings()
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 2, systemsCount)
},
},
{
Name: "User 1 can delete user 2's system if SHARE_ALL_SYSTEMS is enabled",
Method: http.MethodDelete,
URL: fmt.Sprintf("/api/collections/systems/records/%s", userTwoSystem.Id),
Headers: map[string]string{
"Authorization": user1Token,
},
ExpectedStatus: 204,
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
t.Setenv("SHARE_ALL_SYSTEMS", "true")
hub.SetCollectionAuthSettings()
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 2, systemsCount)
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
t.Setenv("SHARE_ALL_SYSTEMS", "")
hub.SetCollectionAuthSettings()
systemsCount, _ := app.CountRecords("systems")
assert.EqualValues(t, 1, systemsCount)
},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}

View File

@@ -4,16 +4,14 @@ package hub
import (
"crypto/ed25519"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"path"
"regexp"
"strings"
"time"
"github.com/henrygd/beszel"
"github.com/henrygd/beszel/internal/alerts"
"github.com/henrygd/beszel/internal/hub/config"
"github.com/henrygd/beszel/internal/hub/heartbeat"
@@ -21,14 +19,12 @@ import (
"github.com/henrygd/beszel/internal/records"
"github.com/henrygd/beszel/internal/users"
"github.com/google/uuid"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"golang.org/x/crypto/ssh"
)
// Hub is the application. It embeds the PocketBase app and keeps references to subcomponents.
type Hub struct {
core.App
*alerts.AlertManager
@@ -46,18 +42,16 @@ var containerIDPattern = regexp.MustCompile(`^[a-fA-F0-9]{12,64}$`)
// NewHub creates a new Hub instance with default configuration
func NewHub(app core.App) *Hub {
hub := &Hub{}
hub.App = app
hub := &Hub{App: app}
hub.AlertManager = alerts.NewAlertManager(hub)
hub.um = users.NewUserManager(hub)
hub.rm = records.NewRecordManager(hub)
hub.sm = systems.NewSystemManager(hub)
hub.appURL, _ = GetEnv("APP_URL")
hub.hb = heartbeat.New(app, GetEnv)
if hub.hb != nil {
hub.hbStop = make(chan struct{})
}
_ = onAfterBootstrapAndMigrations(app, hub.initialize)
return hub
}
@@ -70,12 +64,28 @@ func GetEnv(key string) (value string, exists bool) {
return os.LookupEnv(key)
}
func (h *Hub) StartHub() error {
h.App.OnServe().BindFunc(func(e *core.ServeEvent) error {
// initialize settings / collections
if err := h.initialize(e); err != nil {
// onAfterBootstrapAndMigrations ensures the provided function runs after the database is set up and migrations are applied.
// This is a workaround for behavior in PocketBase where onBootstrap runs before migrations, forcing use of onServe for this purpose.
// However, PB's tests.TestApp is already bootstrapped, generally doesn't serve, but does handle migrations.
// So this ensures that the provided function runs at the right time either way, after DB is ready and migrations are done.
func onAfterBootstrapAndMigrations(app core.App, fn func(app core.App) error) error {
// pb tests.TestApp is already bootstrapped and doesn't serve
if app.IsBootstrapped() {
return fn(app)
}
// Must use OnServe because OnBootstrap appears to run before migrations, even if calling e.Next() before anything else
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
if err := fn(e.App); err != nil {
return err
}
return e.Next()
})
return nil
}
// StartHub sets up event handlers and starts the PocketBase server
func (h *Hub) StartHub() error {
h.App.OnServe().BindFunc(func(e *core.ServeEvent) error {
// sync systems with config
if err := config.SyncSystems(e); err != nil {
return err
@@ -110,132 +120,29 @@ func (h *Hub) StartHub() error {
h.App.OnRecordCreate("users").BindFunc(h.um.InitializeUserRole)
h.App.OnRecordCreate("user_settings").BindFunc(h.um.InitializeUserSettings)
if pb, ok := h.App.(*pocketbase.PocketBase); ok {
// log.Println("Starting pocketbase")
err := pb.Start()
if err != nil {
return err
}
pb, ok := h.App.(*pocketbase.PocketBase)
if !ok {
return errors.New("not a pocketbase app")
}
return nil
return pb.Start()
}
// initialize sets up initial configuration (collections, settings, etc.)
func (h *Hub) initialize(e *core.ServeEvent) error {
func (h *Hub) initialize(app core.App) error {
// set general settings
settings := e.App.Settings()
// batch requests (for global alerts)
settings := app.Settings()
// batch requests (for alerts)
settings.Batch.Enabled = true
// set URL if BASE_URL env is set
if h.appURL != "" {
settings.Meta.AppURL = h.appURL
// set URL if APP_URL env is set
if appURL, isSet := GetEnv("APP_URL"); isSet {
h.appURL = appURL
settings.Meta.AppURL = appURL
}
if err := e.App.Save(settings); err != nil {
if err := app.Save(settings); err != nil {
return err
}
// set auth settings
if err := setCollectionAuthSettings(e.App); err != nil {
return err
}
return nil
}
// setCollectionAuthSettings sets up default authentication settings for the app
func setCollectionAuthSettings(app core.App) error {
usersCollection, err := app.FindCollectionByNameOrId("users")
if err != nil {
return err
}
superusersCollection, err := app.FindCollectionByNameOrId(core.CollectionNameSuperusers)
if err != nil {
return err
}
// disable email auth if DISABLE_PASSWORD_AUTH env var is set
disablePasswordAuth, _ := GetEnv("DISABLE_PASSWORD_AUTH")
usersCollection.PasswordAuth.Enabled = disablePasswordAuth != "true"
usersCollection.PasswordAuth.IdentityFields = []string{"email"}
// allow oauth user creation if USER_CREATION is set
if userCreation, _ := GetEnv("USER_CREATION"); userCreation == "true" {
cr := "@request.context = 'oauth2'"
usersCollection.CreateRule = &cr
} else {
usersCollection.CreateRule = nil
}
// enable mfaOtp mfa if MFA_OTP env var is set
mfaOtp, _ := GetEnv("MFA_OTP")
usersCollection.OTP.Length = 6
superusersCollection.OTP.Length = 6
usersCollection.OTP.Enabled = mfaOtp == "true"
usersCollection.MFA.Enabled = mfaOtp == "true"
superusersCollection.OTP.Enabled = mfaOtp == "true" || mfaOtp == "superusers"
superusersCollection.MFA.Enabled = mfaOtp == "true" || mfaOtp == "superusers"
if err := app.Save(superusersCollection); err != nil {
return err
}
if err := app.Save(usersCollection); err != nil {
return err
}
shareAllSystems, _ := GetEnv("SHARE_ALL_SYSTEMS")
// allow all users to access systems if SHARE_ALL_SYSTEMS is set
systemsCollection, err := app.FindCollectionByNameOrId("systems")
if err != nil {
return err
}
var systemsReadRule string
if shareAllSystems == "true" {
systemsReadRule = "@request.auth.id != \"\""
} else {
systemsReadRule = "@request.auth.id != \"\" && users.id ?= @request.auth.id"
}
updateDeleteRule := systemsReadRule + " && @request.auth.role != \"readonly\""
systemsCollection.ListRule = &systemsReadRule
systemsCollection.ViewRule = &systemsReadRule
systemsCollection.UpdateRule = &updateDeleteRule
systemsCollection.DeleteRule = &updateDeleteRule
if err := app.Save(systemsCollection); err != nil {
return err
}
// allow all users to access all containers if SHARE_ALL_SYSTEMS is set
containersCollection, err := app.FindCollectionByNameOrId("containers")
if err != nil {
return err
}
containersListRule := strings.Replace(systemsReadRule, "users.id", "system.users.id", 1)
containersCollection.ListRule = &containersListRule
if err := app.Save(containersCollection); err != nil {
return err
}
// allow all users to access system-related collections if SHARE_ALL_SYSTEMS is set
// these collections all have a "system" relation field
systemRelatedCollections := []string{"system_details", "smart_devices", "systemd_services"}
for _, collectionName := range systemRelatedCollections {
collection, err := app.FindCollectionByNameOrId(collectionName)
if err != nil {
return err
}
collection.ListRule = &containersListRule
// set viewRule for collections that need it (system_details, smart_devices)
if collection.ViewRule != nil {
collection.ViewRule = &containersListRule
}
// set deleteRule for smart_devices (allows user to dismiss disk warnings)
if collectionName == "smart_devices" {
deleteRule := containersListRule + " && @request.auth.role != \"readonly\""
collection.DeleteRule = &deleteRule
}
if err := app.Save(collection); err != nil {
return err
}
}
return nil
return setCollectionAuthSettings(app)
}
// registerCronJobs sets up scheduled tasks
@@ -247,296 +154,7 @@ func (h *Hub) registerCronJobs(_ *core.ServeEvent) error {
return nil
}
// custom middlewares
func (h *Hub) registerMiddlewares(se *core.ServeEvent) {
// authorizes request with user matching the provided email
authorizeRequestWithEmail := func(e *core.RequestEvent, email string) (err error) {
if e.Auth != nil || email == "" {
return e.Next()
}
isAuthRefresh := e.Request.URL.Path == "/api/collections/users/auth-refresh" && e.Request.Method == http.MethodPost
e.Auth, err = e.App.FindFirstRecordByData("users", "email", email)
if err != nil || !isAuthRefresh {
return e.Next()
}
// auth refresh endpoint, make sure token is set in header
token, _ := e.Auth.NewAuthToken()
e.Request.Header.Set("Authorization", token)
return e.Next()
}
// authenticate with trusted header
if autoLogin, _ := GetEnv("AUTO_LOGIN"); autoLogin != "" {
se.Router.BindFunc(func(e *core.RequestEvent) error {
return authorizeRequestWithEmail(e, autoLogin)
})
}
// authenticate with trusted header
if trustedHeader, _ := GetEnv("TRUSTED_AUTH_HEADER"); trustedHeader != "" {
se.Router.BindFunc(func(e *core.RequestEvent) error {
return authorizeRequestWithEmail(e, e.Request.Header.Get(trustedHeader))
})
}
}
// custom api routes
func (h *Hub) registerApiRoutes(se *core.ServeEvent) error {
// auth protected routes
apiAuth := se.Router.Group("/api/beszel")
apiAuth.Bind(apis.RequireAuth())
// auth optional routes
apiNoAuth := se.Router.Group("/api/beszel")
// create first user endpoint only needed if no users exist
if totalUsers, _ := se.App.CountRecords("users"); totalUsers == 0 {
apiNoAuth.POST("/create-user", h.um.CreateFirstUser)
}
// check if first time setup on login page
apiNoAuth.GET("/first-run", func(e *core.RequestEvent) error {
total, err := e.App.CountRecords("users")
return e.JSON(http.StatusOK, map[string]bool{"firstRun": err == nil && total == 0})
})
// get public key and version
apiAuth.GET("/getkey", func(e *core.RequestEvent) error {
return e.JSON(http.StatusOK, map[string]string{"key": h.pubKey, "v": beszel.Version})
})
// send test notification
apiAuth.POST("/test-notification", h.SendTestNotification)
// heartbeat status and test
apiAuth.GET("/heartbeat-status", h.getHeartbeatStatus)
apiAuth.POST("/test-heartbeat", h.testHeartbeat)
// get config.yml content
apiAuth.GET("/config-yaml", config.GetYamlConfig)
// handle agent websocket connection
apiNoAuth.GET("/agent-connect", h.handleAgentConnect)
// get or create universal tokens
apiAuth.GET("/universal-token", h.getUniversalToken)
// update / delete user alerts
apiAuth.POST("/user-alerts", alerts.UpsertUserAlerts)
apiAuth.DELETE("/user-alerts", alerts.DeleteUserAlerts)
// refresh SMART devices for a system
apiAuth.POST("/smart/refresh", h.refreshSmartData)
// get systemd service details
apiAuth.GET("/systemd/info", h.getSystemdInfo)
// /containers routes
if enabled, _ := GetEnv("CONTAINER_DETAILS"); enabled != "false" {
// get container logs
apiAuth.GET("/containers/logs", h.getContainerLogs)
// get container info
apiAuth.GET("/containers/info", h.getContainerInfo)
}
return nil
}
// Handler for universal token API endpoint (create, read, delete)
func (h *Hub) getUniversalToken(e *core.RequestEvent) error {
tokenMap := universalTokenMap.GetMap()
userID := e.Auth.Id
query := e.Request.URL.Query()
token := query.Get("token")
enable := query.Get("enable")
permanent := query.Get("permanent")
// helper for deleting any existing permanent token record for this user
deletePermanent := func() error {
rec, err := h.FindFirstRecordByFilter("universal_tokens", "user = {:user}", dbx.Params{"user": userID})
if err != nil {
return nil // no record
}
return h.Delete(rec)
}
// helper for upserting a permanent token record for this user
upsertPermanent := func(token string) error {
rec, err := h.FindFirstRecordByFilter("universal_tokens", "user = {:user}", dbx.Params{"user": userID})
if err == nil {
rec.Set("token", token)
return h.Save(rec)
}
col, err := h.FindCachedCollectionByNameOrId("universal_tokens")
if err != nil {
return err
}
newRec := core.NewRecord(col)
newRec.Set("user", userID)
newRec.Set("token", token)
return h.Save(newRec)
}
// Disable universal tokens (both ephemeral and permanent)
if enable == "0" {
tokenMap.RemovebyValue(userID)
_ = deletePermanent()
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": false, "permanent": false})
}
// Enable universal token (ephemeral or permanent)
if enable == "1" {
if token == "" {
token = uuid.New().String()
}
if permanent == "1" {
// make token permanent (persist across restarts)
tokenMap.RemovebyValue(userID)
if err := upsertPermanent(token); err != nil {
return err
}
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": true, "permanent": true})
}
// default: ephemeral mode (1 hour)
_ = deletePermanent()
tokenMap.Set(token, userID, time.Hour)
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": true, "permanent": false})
}
// Read current state
// Prefer permanent token if it exists.
if rec, err := h.FindFirstRecordByFilter("universal_tokens", "user = {:user}", dbx.Params{"user": userID}); err == nil {
dbToken := rec.GetString("token")
// If no token was provided, or the caller is asking about their permanent token, return it.
if token == "" || token == dbToken {
return e.JSON(http.StatusOK, map[string]any{"token": dbToken, "active": true, "permanent": true})
}
// Token doesn't match their permanent token (avoid leaking other info)
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": false, "permanent": false})
}
// No permanent token; fall back to ephemeral token map.
if token == "" {
// return existing token if it exists
if token, _, ok := tokenMap.GetByValue(userID); ok {
return e.JSON(http.StatusOK, map[string]any{"token": token, "active": true, "permanent": false})
}
// if no token is provided, generate a new one
token = uuid.New().String()
}
// Token is considered active only if it belongs to the current user.
activeUser, ok := tokenMap.GetOk(token)
active := ok && activeUser == userID
response := map[string]any{"token": token, "active": active, "permanent": false}
return e.JSON(http.StatusOK, response)
}
// getHeartbeatStatus returns current heartbeat configuration and whether it's enabled
func (h *Hub) getHeartbeatStatus(e *core.RequestEvent) error {
if e.Auth.GetString("role") != "admin" {
return e.ForbiddenError("Requires admin role", nil)
}
if h.hb == nil {
return e.JSON(http.StatusOK, map[string]any{
"enabled": false,
"msg": "Set HEARTBEAT_URL to enable outbound heartbeat monitoring",
})
}
cfg := h.hb.GetConfig()
return e.JSON(http.StatusOK, map[string]any{
"enabled": true,
"url": cfg.URL,
"interval": cfg.Interval,
"method": cfg.Method,
})
}
// testHeartbeat triggers a single heartbeat ping and returns the result
func (h *Hub) testHeartbeat(e *core.RequestEvent) error {
if e.Auth.GetString("role") != "admin" {
return e.ForbiddenError("Requires admin role", nil)
}
if h.hb == nil {
return e.JSON(http.StatusOK, map[string]any{
"err": "Heartbeat not configured. Set HEARTBEAT_URL environment variable.",
})
}
if err := h.hb.Send(); err != nil {
return e.JSON(http.StatusOK, map[string]any{"err": err.Error()})
}
return e.JSON(http.StatusOK, map[string]any{"err": false})
}
// containerRequestHandler handles both container logs and info requests
func (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*systems.System, string) (string, error), responseKey string) error {
systemID := e.Request.URL.Query().Get("system")
containerID := e.Request.URL.Query().Get("container")
if systemID == "" || containerID == "" {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and container parameters are required"})
}
if !containerIDPattern.MatchString(containerID) {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "invalid container parameter"})
}
system, err := h.sm.GetSystem(systemID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
}
data, err := fetchFunc(system, containerID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
}
return e.JSON(http.StatusOK, map[string]string{responseKey: data})
}
// getContainerLogs handles GET /api/beszel/containers/logs requests
func (h *Hub) getContainerLogs(e *core.RequestEvent) error {
return h.containerRequestHandler(e, func(system *systems.System, containerID string) (string, error) {
return system.FetchContainerLogsFromAgent(containerID)
}, "logs")
}
func (h *Hub) getContainerInfo(e *core.RequestEvent) error {
return h.containerRequestHandler(e, func(system *systems.System, containerID string) (string, error) {
return system.FetchContainerInfoFromAgent(containerID)
}, "info")
}
// getSystemdInfo handles GET /api/beszel/systemd/info requests
func (h *Hub) getSystemdInfo(e *core.RequestEvent) error {
query := e.Request.URL.Query()
systemID := query.Get("system")
serviceName := query.Get("service")
if systemID == "" || serviceName == "" {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and service parameters are required"})
}
system, err := h.sm.GetSystem(systemID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
}
details, err := system.FetchSystemdInfoFromAgent(serviceName)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
}
e.Response.Header().Set("Cache-Control", "public, max-age=60")
return e.JSON(http.StatusOK, map[string]any{"details": details})
}
// refreshSmartData handles POST /api/beszel/smart/refresh requests
// Fetches fresh SMART data from the agent and updates the collection
func (h *Hub) refreshSmartData(e *core.RequestEvent) error {
systemID := e.Request.URL.Query().Get("system")
if systemID == "" {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system parameter is required"})
}
system, err := h.sm.GetSystem(systemID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
}
// Fetch and save SMART devices
if err := system.FetchAndSaveSmartDevices(); err != nil {
return e.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return e.JSON(http.StatusOK, map[string]string{"status": "ok"})
}
// generates key pair if it doesn't exist and returns signer
// GetSSHKey generates key pair if it doesn't exist and returns signer
func (h *Hub) GetSSHKey(dataDir string) (ssh.Signer, error) {
if h.signer != nil {
return h.signer, nil

View File

@@ -3,36 +3,20 @@
package hub_test
import (
"bytes"
"crypto/ed25519"
"encoding/json"
"encoding/pem"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"github.com/henrygd/beszel/internal/migrations"
beszelTests "github.com/henrygd/beszel/internal/tests"
"github.com/pocketbase/pocketbase/core"
pbTests "github.com/pocketbase/pocketbase/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
)
// marshal to json and return an io.Reader (for use in ApiScenario.Body)
func jsonReader(v any) io.Reader {
data, err := json.Marshal(v)
if err != nil {
panic(err)
}
return bytes.NewReader(data)
}
func TestMakeLink(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
@@ -265,699 +249,20 @@ func TestGetSSHKey(t *testing.T) {
})
}
func TestApiRoutesAuthentication(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
// Create test user and get auth token
user, err := beszelTests.CreateUser(hub, "testuser@example.com", "password123")
require.NoError(t, err, "Failed to create test user")
adminUser, err := beszelTests.CreateRecord(hub, "users", map[string]any{
"email": "admin@example.com",
"password": "password123",
"role": "admin",
})
require.NoError(t, err, "Failed to create admin user")
adminUserToken, err := adminUser.NewAuthToken()
// superUser, err := beszelTests.CreateRecord(hub, core.CollectionNameSuperusers, map[string]any{
// "email": "superuser@example.com",
// "password": "password123",
// })
// require.NoError(t, err, "Failed to create superuser")
userToken, err := user.NewAuthToken()
require.NoError(t, err, "Failed to create auth token")
// Create test system for user-alerts endpoints
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "test-system",
"users": []string{user.Id},
"host": "127.0.0.1",
})
require.NoError(t, err, "Failed to create test system")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
// Auth Protected Routes - Should require authentication
{
Name: "POST /test-notification - no auth should fail",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
},
{
Name: "POST /test-notification - with auth should succeed",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
TestAppFactory: testAppFactory,
Headers: map[string]string{
"Authorization": userToken,
},
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
ExpectedStatus: 200,
ExpectedContent: []string{"sending message"},
},
{
Name: "GET /config-yaml - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /config-yaml - with user auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 403,
ExpectedContent: []string{"Requires admin"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /config-yaml - with admin auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/config-yaml",
Headers: map[string]string{
"Authorization": adminUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"test-system"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /heartbeat-status - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/heartbeat-status",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /heartbeat-status - with user auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/heartbeat-status",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 403,
ExpectedContent: []string{"Requires admin role"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /heartbeat-status - with admin auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/heartbeat-status",
Headers: map[string]string{
"Authorization": adminUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{`"enabled":false`},
TestAppFactory: testAppFactory,
},
{
Name: "POST /test-heartbeat - with user auth should fail",
Method: http.MethodPost,
URL: "/api/beszel/test-heartbeat",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 403,
ExpectedContent: []string{"Requires admin role"},
TestAppFactory: testAppFactory,
},
{
Name: "POST /test-heartbeat - with admin auth should report disabled state",
Method: http.MethodPost,
URL: "/api/beszel/test-heartbeat",
Headers: map[string]string{
"Authorization": adminUserToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"Heartbeat not configured"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /universal-token - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/universal-token",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /universal-token - with auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/universal-token",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"active", "token", "permanent"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /universal-token - enable permanent should succeed",
Method: http.MethodGet,
URL: "/api/beszel/universal-token?enable=1&permanent=1&token=permanent-token-123",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"permanent\":true", "permanent-token-123"},
TestAppFactory: testAppFactory,
},
{
Name: "POST /user-alerts - no auth should fail",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
{
Name: "POST /user-alerts - with auth should succeed",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
{
Name: "DELETE /user-alerts - no auth should fail",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system.Id},
}),
},
{
Name: "DELETE /user-alerts - with auth should succeed",
Method: http.MethodDelete,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"success\":true"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"systems": []string{system.Id},
}),
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
// Create an alert to delete
beszelTests.CreateRecord(app, "alerts", map[string]any{
"name": "CPU",
"system": system.Id,
"user": user.Id,
"value": 80,
"min": 10,
})
},
},
{
Name: "GET /containers/logs - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?system=test-system&container=test-container",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/logs - with auth but missing system param should fail",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?container=test-container",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"system and container parameters are required"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/logs - with auth but missing container param should fail",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?system=test-system",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"system and container parameters are required"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/logs - with auth but invalid system should fail",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?system=invalid-system&container=0123456789ab",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 404,
ExpectedContent: []string{"system not found"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/logs - traversal container should fail validation",
Method: http.MethodGet,
URL: "/api/beszel/containers/logs?system=" + system.Id + "&container=..%2F..%2Fversion",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"invalid container parameter"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/info - traversal container should fail validation",
Method: http.MethodGet,
URL: "/api/beszel/containers/info?system=" + system.Id + "&container=../../version?x=",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"invalid container parameter"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /containers/info - non-hex container should fail validation",
Method: http.MethodGet,
URL: "/api/beszel/containers/info?system=" + system.Id + "&container=container_name",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 400,
ExpectedContent: []string{"invalid container parameter"},
TestAppFactory: testAppFactory,
},
// Auth Optional Routes - Should work without authentication
{
Name: "GET /getkey - no auth should fail",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auth should also succeed",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /first-run - no auth should succeed",
Method: http.MethodGet,
URL: "/api/beszel/first-run",
ExpectedStatus: 200,
ExpectedContent: []string{"\"firstRun\":false"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /first-run - with auth should also succeed",
Method: http.MethodGet,
URL: "/api/beszel/first-run",
Headers: map[string]string{
"Authorization": userToken,
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"firstRun\":false"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /agent-connect - no auth should succeed (websocket upgrade fails but route is accessible)",
Method: http.MethodGet,
URL: "/api/beszel/agent-connect",
ExpectedStatus: 400,
ExpectedContent: []string{},
TestAppFactory: testAppFactory,
},
{
Name: "POST /test-notification - invalid auth token should fail",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
}),
Headers: map[string]string{
"Authorization": "invalid-token",
},
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "POST /user-alerts - invalid auth token should fail",
Method: http.MethodPost,
URL: "/api/beszel/user-alerts",
Headers: map[string]string{
"Authorization": "invalid-token",
},
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"name": "CPU",
"value": 80,
"min": 10,
"systems": []string{system.Id},
}),
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestFirstUserCreation(t *testing.T) {
t.Run("CreateUserEndpoint available when no users exist", func(t *testing.T) {
func TestAppUrl(t *testing.T) {
t.Run("no APP_URL does't change app url", func(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
testAppFactoryExisting := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "POST /create-user - should be available when no users exist",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
Body: jsonReader(map[string]any{
"email": "firstuser@example.com",
"password": "password123",
}),
ExpectedStatus: 200,
ExpectedContent: []string{"User created"},
TestAppFactory: testAppFactoryExisting,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
userCount, err := hub.CountRecords("users")
require.NoError(t, err)
require.Zero(t, userCount, "Should start with no users")
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
require.NoError(t, err)
require.EqualValues(t, 1, len(superusers), "Should start with one temporary superuser")
require.EqualValues(t, migrations.TempAdminEmail, superusers[0].GetString("email"), "Should have created one temporary superuser")
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
userCount, err := hub.CountRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, userCount, "Should have created one user")
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
require.NoError(t, err)
require.EqualValues(t, 1, len(superusers), "Should have created one superuser")
require.EqualValues(t, "firstuser@example.com", superusers[0].GetString("email"), "Should have created one superuser")
},
},
{
Name: "POST /create-user - should not be available when users exist",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
Body: jsonReader(map[string]any{
"email": "firstuser@example.com",
"password": "password123",
}),
ExpectedStatus: 404,
ExpectedContent: []string{"wasn't found"},
TestAppFactory: testAppFactoryExisting,
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
settings := hub.Settings()
assert.Equal(t, "http://localhost:8090", settings.Meta.AppURL)
})
t.Run("CreateUserEndpoint not available when USER_EMAIL, USER_PASSWORD are set", func(t *testing.T) {
os.Setenv("BESZEL_HUB_USER_EMAIL", "me@example.com")
os.Setenv("BESZEL_HUB_USER_PASSWORD", "password123")
defer os.Unsetenv("BESZEL_HUB_USER_EMAIL")
defer os.Unsetenv("BESZEL_HUB_USER_PASSWORD")
t.Run("APP_URL changes app url", func(t *testing.T) {
t.Setenv("APP_URL", "http://example.com/app")
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
hub.StartHub()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenario := beszelTests.ApiScenario{
Name: "POST /create-user - should not be available when USER_EMAIL, USER_PASSWORD are set",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
ExpectedStatus: 404,
ExpectedContent: []string{"wasn't found"},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
users, err := hub.FindAllRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, len(users), "Should start with one user")
require.EqualValues(t, "me@example.com", users[0].GetString("email"), "Should have created one user")
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
require.NoError(t, err)
require.EqualValues(t, 1, len(superusers), "Should start with one superuser")
require.EqualValues(t, "me@example.com", superusers[0].GetString("email"), "Should have created one superuser")
},
AfterTestFunc: func(t testing.TB, app *pbTests.TestApp, res *http.Response) {
users, err := hub.FindAllRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, len(users), "Should still have one user")
require.EqualValues(t, "me@example.com", users[0].GetString("email"), "Should have created one user")
superusers, err := hub.FindAllRecords(core.CollectionNameSuperusers)
require.NoError(t, err)
require.EqualValues(t, 1, len(superusers), "Should still have one superuser")
require.EqualValues(t, "me@example.com", superusers[0].GetString("email"), "Should have created one superuser")
},
}
scenario.Test(t)
settings := hub.Settings()
assert.Equal(t, "http://example.com/app", settings.Meta.AppURL)
})
}
func TestCreateUserEndpointAvailability(t *testing.T) {
t.Run("CreateUserEndpoint available when no users exist", func(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
// Ensure no users exist
userCount, err := hub.CountRecords("users")
require.NoError(t, err)
require.Zero(t, userCount, "Should start with no users")
hub.StartHub()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenario := beszelTests.ApiScenario{
Name: "POST /create-user - should be available when no users exist",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
Body: jsonReader(map[string]any{
"email": "firstuser@example.com",
"password": "password123",
}),
ExpectedStatus: 200,
ExpectedContent: []string{"User created"},
TestAppFactory: testAppFactory,
}
scenario.Test(t)
// Verify user was created
userCount, err = hub.CountRecords("users")
require.NoError(t, err)
require.EqualValues(t, 1, userCount, "Should have created one user")
})
t.Run("CreateUserEndpoint not available when users exist", func(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
// Create a user first
_, err := beszelTests.CreateUser(hub, "existing@example.com", "password")
require.NoError(t, err)
hub.StartHub()
testAppFactory := func(t testing.TB) *pbTests.TestApp {
return hub.TestApp
}
scenario := beszelTests.ApiScenario{
Name: "POST /create-user - should not be available when users exist",
Method: http.MethodPost,
URL: "/api/beszel/create-user",
Body: jsonReader(map[string]any{
"email": "another@example.com",
"password": "password123",
}),
ExpectedStatus: 404,
ExpectedContent: []string{"wasn't found"},
TestAppFactory: testAppFactory,
}
scenario.Test(t)
})
}
func TestAutoLoginMiddleware(t *testing.T) {
var hubs []*beszelTests.TestHub
defer func() {
defer os.Unsetenv("AUTO_LOGIN")
for _, hub := range hubs {
hub.Cleanup()
}
}()
os.Setenv("AUTO_LOGIN", "user@test.com")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
hub, _ := beszelTests.NewTestHub(t.TempDir())
hubs = append(hubs, hub)
hub.StartHub()
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "GET /getkey - without auto login should fail",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auto login should fail if no matching user",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with auto login should succeed",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.CreateUser(app, "user@test.com", "password123")
},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}
func TestTrustedHeaderMiddleware(t *testing.T) {
var hubs []*beszelTests.TestHub
defer func() {
defer os.Unsetenv("TRUSTED_AUTH_HEADER")
for _, hub := range hubs {
hub.Cleanup()
}
}()
os.Setenv("TRUSTED_AUTH_HEADER", "X-Beszel-Trusted")
testAppFactory := func(t testing.TB) *pbTests.TestApp {
hub, _ := beszelTests.NewTestHub(t.TempDir())
hubs = append(hubs, hub)
hub.StartHub()
return hub.TestApp
}
scenarios := []beszelTests.ApiScenario{
{
Name: "GET /getkey - without trusted header should fail",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with trusted header should fail if no matching user",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
Headers: map[string]string{
"X-Beszel-Trusted": "user@test.com",
},
ExpectedStatus: 401,
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
},
{
Name: "GET /getkey - with trusted header should succeed",
Method: http.MethodGet,
URL: "/api/beszel/getkey",
Headers: map[string]string{
"X-Beszel-Trusted": "user@test.com",
},
ExpectedStatus: 200,
ExpectedContent: []string{"\"key\":", "\"v\":"},
TestAppFactory: testAppFactory,
BeforeTestFunc: func(t testing.TB, app *pbTests.TestApp, e *core.ServeEvent) {
beszelTests.CreateUser(app, "user@test.com", "password123")
},
},
}
for _, scenario := range scenarios {
scenario.Test(t)
}
}

View File

@@ -2,7 +2,9 @@
package hub
import "github.com/henrygd/beszel/internal/hub/systems"
import (
"github.com/henrygd/beszel/internal/hub/systems"
)
// TESTING ONLY: GetSystemManager returns the system manager
func (h *Hub) GetSystemManager() *systems.SystemManager {
@@ -18,3 +20,7 @@ func (h *Hub) GetPubkey() string {
func (h *Hub) SetPubkey(pubkey string) {
h.pubKey = pubkey
}
func (h *Hub) SetCollectionAuthSettings() error {
return setCollectionAuthSettings(h)
}

View File

@@ -19,7 +19,6 @@ type subscriptionInfo struct {
var (
activeSubscriptions = make(map[string]*subscriptionInfo)
workerRunning bool
realtimeTicker *time.Ticker
tickerStopChan chan struct{}
realtimeMutex sync.Mutex
)
@@ -70,7 +69,7 @@ func (sm *SystemManager) onRealtimeSubscribeRequest(e *core.RealtimeSubscribeReq
}
// onRealtimeSubscriptionAdded initializes or starts the realtime worker when the first subscription is added.
// It ensures only one worker runs at a time and creates the ticker for periodic data fetching.
// It ensures only one worker runs at a time.
func (sm *SystemManager) onRealtimeSubscriptionAdded() {
realtimeMutex.Lock()
defer realtimeMutex.Unlock()
@@ -82,11 +81,6 @@ func (sm *SystemManager) onRealtimeSubscriptionAdded() {
tickerStopChan = make(chan struct{})
go sm.startRealtimeWorker()
}
// If no ticker exists, create one
if realtimeTicker == nil {
realtimeTicker = time.NewTicker(1 * time.Second)
}
}
// checkSubscriptions stops the realtime worker when there are no active subscriptions.
@@ -107,11 +101,6 @@ func (sm *SystemManager) checkSubscriptions() {
}
}
if realtimeTicker != nil {
realtimeTicker.Stop()
realtimeTicker = nil
}
// Mark worker as stopped (will be reset when next subscription comes in)
workerRunning = false
}
@@ -135,17 +124,16 @@ func (sm *SystemManager) removeRealtimeSubscription(subscription string, options
// It continuously fetches system data and broadcasts it to subscribed clients via WebSocket.
func (sm *SystemManager) startRealtimeWorker() {
sm.fetchRealtimeDataAndNotify()
tick := time.Tick(1 * time.Second)
for {
select {
case <-tickerStopChan:
return
case <-realtimeTicker.C:
// Check if ticker is still valid (might have been stopped)
if realtimeTicker == nil || len(activeSubscriptions) == 0 {
case <-tick:
if len(activeSubscriptions) == 0 {
return
}
// slog.Debug("activeSubscriptions", "count", len(activeSubscriptions))
sm.fetchRealtimeDataAndNotify()
}
}

View File

@@ -111,6 +111,9 @@ func (ws *WsConn) Close(msg []byte) {
// Ping sends a ping frame to keep the connection alive.
func (ws *WsConn) Ping() error {
if ws.conn == nil {
return gws.ErrConnClosed
}
ws.conn.SetDeadline(time.Now().Add(deadline))
return ws.conn.WritePing(nil)
}

View File

@@ -11,11 +11,11 @@ func init() {
jsonData := `[
{
"id": "elngm8x1l60zi2v",
"listRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"viewRule": "",
"createRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"updateRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"deleteRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"listRule": "@request.auth.id != \"\" && user = @request.auth.id",
"viewRule": null,
"createRule": "@request.auth.id != \"\" && user = @request.auth.id",
"updateRule": "@request.auth.id != \"\" && user = @request.auth.id",
"deleteRule": "@request.auth.id != \"\" && user = @request.auth.id",
"name": "alerts",
"type": "base",
"fields": [
@@ -143,11 +143,11 @@ func init() {
},
{
"id": "pbc_1697146157",
"listRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"viewRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"listRule": "@request.auth.id != \"\" && user = @request.auth.id",
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"deleteRule": "@request.auth.id != \"\" && user = @request.auth.id",
"name": "alerts_history",
"type": "base",
"fields": [
@@ -261,7 +261,7 @@ func init() {
},
{
"id": "juohu4jipgc13v7",
"listRule": "@request.auth.id != \"\"",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
@@ -351,10 +351,10 @@ func init() {
},
{
"id": "pbc_3663931638",
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
"viewRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
"createRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
"updateRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "fingerprints",
"type": "base",
@@ -433,7 +433,7 @@ func init() {
},
{
"id": "ej9oowivz8b2mht",
"listRule": "@request.auth.id != \"\"",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
@@ -523,10 +523,10 @@ func init() {
},
{
"id": "4afacsdnlu8q8r2",
"listRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"listRule": "@request.auth.id != \"\" && user = @request.auth.id",
"viewRule": null,
"createRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"updateRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"createRule": "@request.auth.id != \"\" && user = @request.auth.id",
"updateRule": "@request.auth.id != \"\" && user = @request.auth.id",
"deleteRule": null,
"name": "user_settings",
"type": "base",
@@ -596,11 +596,11 @@ func init() {
},
{
"id": "2hz5ncl8tizk5nx",
"listRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id",
"viewRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id",
"createRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
"updateRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
"deleteRule": "@request.auth.id != \"\" && users.id ?= @request.auth.id && @request.auth.role != \"readonly\"",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"name": "systems",
"type": "base",
"fields": [
@@ -866,7 +866,7 @@ func init() {
},
{
"id": "pbc_1864144027",
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
@@ -1159,7 +1159,7 @@ func init() {
"CREATE INDEX ` + "`" + `idx_4Z7LuLNdQb` + "`" + ` ON ` + "`" + `systemd_services` + "`" + ` (` + "`" + `system` + "`" + `)",
"CREATE INDEX ` + "`" + `idx_pBp1fF837e` + "`" + ` ON ` + "`" + `systemd_services` + "`" + ` (` + "`" + `updated` + "`" + `)"
],
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
"listRule": null,
"name": "systemd_services",
"system": false,
"type": "base",
@@ -1167,8 +1167,8 @@ func init() {
"viewRule": null
},
{
"createRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"deleteRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"createRule": "@request.auth.id != \"\" && user = @request.auth.id",
"deleteRule": "@request.auth.id != \"\" && user = @request.auth.id",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{10}",
@@ -1252,16 +1252,16 @@ func init() {
"CREATE INDEX ` + "`" + `idx_q0iKnRP9v8` + "`" + ` ON ` + "`" + `quiet_hours` + "`" + ` (\n ` + "`" + `user` + "`" + `,\n ` + "`" + `system` + "`" + `\n)",
"CREATE INDEX ` + "`" + `idx_6T7ljT7FJd` + "`" + ` ON ` + "`" + `quiet_hours` + "`" + ` (\n ` + "`" + `type` + "`" + `,\n ` + "`" + `end` + "`" + `\n)"
],
"listRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"listRule": "@request.auth.id != \"\" && user = @request.auth.id",
"name": "quiet_hours",
"system": false,
"type": "base",
"updateRule": "@request.auth.id != \"\" && user.id = @request.auth.id",
"viewRule": "@request.auth.id != \"\" && user.id = @request.auth.id"
"updateRule": "@request.auth.id != \"\" && user = @request.auth.id",
"viewRule": "@request.auth.id != \"\" && user = @request.auth.id"
},
{
"createRule": null,
"deleteRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{10}",
@@ -1447,16 +1447,16 @@ func init() {
"indexes": [
"CREATE INDEX ` + "`" + `idx_DZ9yhvgl44` + "`" + ` ON ` + "`" + `smart_devices` + "`" + ` (` + "`" + `system` + "`" + `)"
],
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
"listRule": null,
"name": "smart_devices",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id"
"viewRule": null
},
{
"createRule": "",
"deleteRule": "",
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
@@ -1625,12 +1625,12 @@ func init() {
],
"id": "pbc_3116237454",
"indexes": [],
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
"name": "system_details",
"system": false,
"type": "base",
"updateRule": "",
"viewRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id"
"updateRule": null,
"listRule": null,
"viewRule": null
},
{
"createRule": null,

View File

@@ -7,6 +7,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="robots" content="noindex, nofollow" />
<title>Beszel</title>
<style>
.dark { background: hsl(220 5.5% 9%); color-scheme: dark; }
</style>
<script>
(function() {
try {
var theme = localStorage.getItem('ui-theme');
var isDark = theme === 'dark' ||
(theme !== 'light' && window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList.add(isDark ? 'dark' : 'light');
} catch (e) {}
})();
</script>
<script>
globalThis.BESZEL = {
BASE_PATH: "%BASE_URL%",

View File

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

View File

@@ -1,8 +1,8 @@
import { msg, 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 { ChevronDownIcon, ExternalLinkIcon } from "lucide-react"
import { memo, useEffect, useRef, useState } from "react"
import { Button } from "@/components/ui/button"
import {
@@ -12,7 +12,6 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
@@ -35,28 +34,19 @@ 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 }) {
if (isReadOnlyUser()) {
return null
}
const [open, setOpen] = useState(false)
// To avoid a refactor of the dialog, we will just keep this function as a "skeleton" for the actual dialog
export function AddSystemDialog({ open, setOpen }: { open: boolean; setOpen: (open: boolean) => void }) {
const opened = useRef(false)
if (open) {
opened.current = true
}
if (isReadOnlyUser()) {
return null
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline" className={cn("flex gap-1 max-xs:h-[2.4rem]", className)}>
<PlusIcon className="h-4 w-4 450:-ms-1" />
<span className="hidden 450:inline">
<Trans>
Add <span className="hidden sm:inline">System</span>
</Trans>
</span>
</Button>
</DialogTrigger>
{opened.current && <SystemDialog setOpen={setOpen} />}
</Dialog>
)
@@ -276,7 +266,13 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
/>
</TabsContent>
{/* Save */}
<Button>{system ? <Trans>Save system</Trans> : <Trans>Add system</Trans>}</Button>
<Button>
{system ? (
<Trans>Save {{ foo: systemTranslation }}</Trans>
) : (
<Trans>Add {{ foo: systemTranslation }}</Trans>
)}
</Button>
</DialogFooter>
</form>
</Tabs>

View File

@@ -1,4 +1,4 @@
import { useMemo } from "react"
import { type ReactNode, useEffect, useMemo, useState } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
ChartContainer,
@@ -11,18 +11,23 @@ import {
import { chartMargin, cn, formatShortDate } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { useYAxisWidth } from "./hooks"
import { AxisDomain } from "recharts/types/util/types"
import type { AxisDomain } from "recharts/types/util/types"
import { useIntersectionObserver } from "@/lib/use-intersection-observer"
export type DataPoint = {
export type DataPoint<T = SystemStatsRecord> = {
label: string
dataKey: (data: SystemStatsRecord) => number | undefined
dataKey: (data: T) => number | null | undefined
color: number | string
opacity: number
stackId?: string | number
order?: number
strokeOpacity?: number
activeDot?: boolean
}
export default function AreaChartDefault({
chartData,
customData,
max,
maxToggled,
tickFormatter,
@@ -34,96 +39,129 @@ export default function AreaChartDefault({
showTotal = false,
reverseStackOrder = false,
hideYAxis = false,
filter,
truncate = false,
}: // 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?: AxisDomain
legend?: boolean
showTotal?: boolean
itemSorter?: (a: any, b: any) => number
reverseStackOrder?: boolean
hideYAxis?: boolean
// logRender?: boolean
}) {
{
chartData: ChartData
// biome-ignore lint/suspicious/noExplicitAny: accepts different data source types (systemStats or containerData)
customData?: any[]
max?: number
maxToggled?: boolean
tickFormatter: (value: number, index: number) => string
// biome-ignore lint/suspicious/noExplicitAny: recharts tooltip item interop
contentFormatter: (item: any, key: string) => ReactNode
// biome-ignore lint/suspicious/noExplicitAny: accepts DataPoint with different generic types
dataPoints?: DataPoint<any>[]
domain?: AxisDomain
legend?: boolean
showTotal?: boolean
// biome-ignore lint/suspicious/noExplicitAny: recharts tooltip item interop
itemSorter?: (a: any, b: any) => number
reverseStackOrder?: boolean
hideYAxis?: boolean
filter?: string
truncate?: boolean
// logRender?: boolean
}) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { isIntersecting, ref } = useIntersectionObserver({ freeze: false })
const sourceData = customData ?? chartData.systemStats
// Only update the rendered data while the chart is visible
const [displayData, setDisplayData] = useState(sourceData)
// Reduce chart redraws by only updating while visible or when chart time changes
useEffect(() => {
const shouldPrimeData = sourceData.length && !displayData.length
const sourceChanged = sourceData !== displayData
const shouldUpdate = shouldPrimeData || (sourceChanged && isIntersecting)
if (shouldUpdate) {
setDisplayData(sourceData)
}
}, [displayData, isIntersecting, sourceData])
// Use a stable key derived from data point identities and visual properties
const areasKey = dataPoints?.map((d) => `${d.label}:${d.opacity}`).join("\0")
const Areas = useMemo(() => {
return dataPoints?.map((dataPoint, i) => {
let { color } = dataPoint
if (typeof color === "number") {
color = `var(--chart-${color})`
}
return (
<Area
key={dataPoint.label}
dataKey={dataPoint.dataKey}
name={dataPoint.label}
type="monotoneX"
fill={color}
fillOpacity={dataPoint.opacity}
stroke={color}
strokeOpacity={dataPoint.strokeOpacity}
isAnimationActive={false}
stackId={dataPoint.stackId}
order={dataPoint.order || i}
activeDot={dataPoint.activeDot ?? true}
/>
)
})
}, [areasKey, maxToggled])
// biome-ignore lint/correctness/useExhaustiveDependencies: ignore
return useMemo(() => {
if (chartData.systemStats.length === 0) {
if (displayData.length === 0) {
return null
}
// if (logRender) {
// console.log("Rendered at", new Date())
// console.log("Rendered at", new Date(), "for", dataPoints?.at(0)?.label)
// }
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth || hideYAxis,
"ps-4": hideYAxis,
})}
<ChartContainer
ref={ref}
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth || hideYAxis,
"ps-4": hideYAxis,
})}
>
<AreaChart
reverseStackOrder={reverseStackOrder}
accessibilityLayer
data={displayData}
margin={hideYAxis ? { ...chartMargin, left: 5 } : chartMargin}
>
<AreaChart
reverseStackOrder={reverseStackOrder}
accessibilityLayer
data={chartData.systemStats}
margin={hideYAxis ? { ...chartMargin, left: 5 } : chartMargin}
>
<CartesianGrid vertical={false} />
{!hideYAxis && (
<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}
showTotal={showTotal}
/>
}
<CartesianGrid vertical={false} />
{!hideYAxis && (
<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}
/>
{dataPoints?.map((dataPoint) => {
let { color } = dataPoint
if (typeof color === "number") {
color = `var(--chart-${color})`
}
return (
<Area
key={dataPoint.label}
dataKey={dataPoint.dataKey}
name={dataPoint.label}
type="monotoneX"
fill={color}
fillOpacity={dataPoint.opacity}
stroke={color}
isAnimationActive={false}
stackId={dataPoint.stackId}
/>
)
})}
{legend && <ChartLegend content={<ChartLegendContent reverse={reverseStackOrder} />} />}
</AreaChart>
</ChartContainer>
</div>
)}
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
// @ts-expect-error
itemSorter={itemSorter}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={contentFormatter}
showTotal={showTotal}
filter={filter}
truncate={truncate}
/>
}
/>
{Areas}
{legend && <ChartLegend content={<ChartLegendContent />} />}
</AreaChart>
</ChartContainer>
)
}, [chartData.systemStats.at(-1), yAxisWidth, maxToggled, showTotal])
}, [displayData, yAxisWidth, showTotal, filter])
}

View File

@@ -1,215 +0,0 @@
// import Spinner from '../spinner'
import { useStore } from "@nanostores/react"
import { memo, useMemo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
pinnedAxisDomain,
xAxis,
} from "@/components/ui/chart"
import { ChartType, Unit } from "@/lib/enums"
import { $containerFilter, $userSettings } from "@/lib/stores"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { Separator } from "../ui/separator"
import { useYAxisWidth } from "./hooks"
export default memo(function ContainerChart({
dataKey,
chartData,
chartType,
chartConfig,
unit = "%",
}: {
dataKey: string
chartData: ChartData
chartType: ChartType
chartConfig: ChartConfig
unit?: string
}) {
const filter = useStore($containerFilter)
const userSettings = useStore($userSettings)
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { containerData } = chartData
const isNetChart = chartType === ChartType.Network
// Filter with set lookup
const filteredKeys = useMemo(() => {
if (!filter) {
return new Set<string>()
}
const filterTerms = filter
.toLowerCase()
.split(" ")
.filter((term) => term.length > 0)
return new Set(
Object.keys(chartConfig).filter((key) => {
const keyLower = key.toLowerCase()
return !filterTerms.some((term) => keyLower.includes(term))
})
)
}, [chartConfig, filter])
// biome-ignore lint/correctness/useExhaustiveDependencies: not necessary
const { toolTipFormatter, dataFunction, tickFormatter } = useMemo(() => {
const obj = {} as {
toolTipFormatter: (item: any, key: string) => React.ReactNode | string
dataFunction: (key: string, data: any) => number | null
tickFormatter: (value: any) => string
}
// tick formatter
if (chartType === ChartType.CPU) {
obj.tickFormatter = (value) => {
const val = `${toFixedFloat(value, 2)}%`
return updateYAxisWidth(val)
}
} else {
const chartUnit = isNetChart ? userSettings.unitNet : Unit.Bytes
obj.tickFormatter = (val) => {
const { value, unit } = formatBytes(val, isNetChart, chartUnit, !isNetChart)
return updateYAxisWidth(`${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`)
}
}
// tooltip formatter
if (isNetChart) {
const getRxTxBytes = (record?: { b?: [number, number]; ns?: number; nr?: number }) => {
if (record?.b?.length && record.b.length >= 2) {
return [Number(record.b[0]) || 0, Number(record.b[1]) || 0]
}
return [(record?.ns ?? 0) * 1024 * 1024, (record?.nr ?? 0) * 1024 * 1024]
}
const formatRxTx = (recv: number, sent: number) => {
const { value: receivedValue, unit: receivedUnit } = formatBytes(recv, true, userSettings.unitNet, false)
const { value: sentValue, unit: sentUnit } = formatBytes(sent, true, userSettings.unitNet, false)
return (
<span className="flex">
{decimalString(receivedValue)} {receivedUnit}
<span className="opacity-70 ms-0.5"> rx </span>
<Separator orientation="vertical" className="h-3 mx-1.5 bg-primary/40" />
{decimalString(sentValue)} {sentUnit}
<span className="opacity-70 ms-0.5"> tx</span>
</span>
)
}
obj.toolTipFormatter = (item: any, key: string) => {
try {
if (key === "__total__") {
let totalSent = 0
let totalRecv = 0
const payloadData = item?.payload && typeof item.payload === "object" ? item.payload : {}
for (const [containerKey, value] of Object.entries(payloadData)) {
if (!value || typeof value !== "object") {
continue
}
// Skip filtered out containers
if (filteredKeys.has(containerKey)) {
continue
}
const [sent, recv] = getRxTxBytes(value as { b?: [number, number]; ns?: number; nr?: number })
totalSent += sent
totalRecv += recv
}
return formatRxTx(totalRecv, totalSent)
}
const [sent, recv] = getRxTxBytes(item?.payload?.[key])
return formatRxTx(recv, sent)
} catch (e) {
return null
}
}
} else if (chartType === ChartType.Memory) {
obj.toolTipFormatter = (item: any) => {
const { value, unit } = formatBytes(item.value, false, Unit.Bytes, true)
return `${decimalString(value)} ${unit}`
}
} else {
obj.toolTipFormatter = (item: any) => `${decimalString(item.value)}${unit}`
}
// data function
if (isNetChart) {
obj.dataFunction = (key: string, data: any) => {
const payload = data[key]
if (!payload) {
return null
}
const sent = payload?.b?.[0] ?? (payload?.ns ?? 0) * 1024 * 1024
const recv = payload?.b?.[1] ?? (payload?.nr ?? 0) * 1024 * 1024
return sent + recv
}
} else {
obj.dataFunction = (key: string, data: any) => data[key]?.[dataKey] ?? null
}
return obj
}, [filteredKeys])
// console.log('rendered at', new Date())
if (containerData.length === 0) {
return null
}
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<AreaChart
accessibilityLayer
// syncId={'cpu'}
data={containerData}
margin={chartMargin}
reverseStackOrder={true}
>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
domain={pinnedAxisDomain()}
orientation={chartData.orientation}
className="tracking-tighter"
width={yAxisWidth}
tickFormatter={tickFormatter}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
truncate={true}
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
// @ts-expect-error
itemSorter={(a, b) => b.value - a.value}
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} showTotal={true} />}
/>
{Object.keys(chartConfig).map((key) => {
const filtered = filteredKeys.has(key)
const fillOpacity = filtered ? 0.05 : 0.4
const strokeOpacity = filtered ? 0.1 : 1
return (
<Area
key={key}
isAnimationActive={false}
dataKey={dataFunction.bind(null, key)}
name={key}
type="monotoneX"
fill={chartConfig[key].color}
fillOpacity={fillOpacity}
stroke={chartConfig[key].color}
strokeOpacity={strokeOpacity}
activeDot={{ opacity: filtered ? 0 : 1 }}
stackId="a"
/>
)
})}
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,83 +0,0 @@
import { useLingui } from "@lingui/react/macro"
import { memo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { Unit } from "@/lib/enums"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { useYAxisWidth } from "./hooks"
export default memo(function DiskChart({
dataKey,
diskSize,
chartData,
}: {
dataKey: string | ((data: SystemStatsRecord) => number | undefined)
diskSize: number
chartData: ChartData
}) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { t } = useLingui()
// round to nearest GB
if (diskSize >= 100) {
diskSize = Math.round(diskSize)
}
if (chartData.systemStats.length === 0) {
return null
}
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}
domain={[0, diskSize]}
tickCount={9}
minTickGap={6}
tickLine={false}
axisLine={false}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val * 1024, false, Unit.Bytes, true)
return updateYAxisWidth(toFixedFloat(value, value >= 10 ? 0 : 1) + " " + unit)
}}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return decimalString(convertedValue) + " " + unit
}}
/>
}
/>
<Area
dataKey={dataKey}
name={t`Disk Usage`}
type="monotoneX"
fill="var(--chart-4)"
fillOpacity={0.4}
stroke="var(--chart-4)"
// animationDuration={1200}
isAnimationActive={false}
/>
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,117 +0,0 @@
import { memo, useMemo } from "react"
import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
xAxis,
} from "@/components/ui/chart"
import { chartMargin, cn, decimalString, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData, GPUData } from "@/types"
import { useYAxisWidth } from "./hooks"
import type { DataPoint } from "./line-chart"
export default memo(function GpuPowerChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const packageKey = " package"
const { gpuData, dataPoints } = useMemo(() => {
const dataPoints = [] as DataPoint[]
const gpuData = [] as Record<string, GPUData | string>[]
const addedKeys = new Map<string, number>()
const addKey = (key: string, value: number) => {
addedKeys.set(key, (addedKeys.get(key) ?? 0) + value)
}
for (const stats of chartData.systemStats) {
const gpus = stats.stats?.g ?? {}
const data = { created: stats.created } as Record<string, GPUData | string>
for (const id in gpus) {
const gpu = gpus[id] as GPUData
data[gpu.n] = gpu
addKey(gpu.n, gpu.p ?? 0)
if (gpu.pp) {
data[`${gpu.n}${packageKey}`] = gpu
addKey(`${gpu.n}${packageKey}`, gpu.pp ?? 0)
}
}
gpuData.push(data)
}
const sortedKeys = Array.from(addedKeys.entries())
.sort(([, a], [, b]) => b - a)
.map(([key]) => key)
for (let i = 0; i < sortedKeys.length; i++) {
const id = sortedKeys[i]
dataPoints.push({
label: id,
dataKey: (gpuData: Record<string, GPUData>) => {
return id.endsWith(packageKey) ? (gpuData[id]?.pp ?? 0) : (gpuData[id]?.p ?? 0)
},
color: `hsl(${226 + (((i * 360) / addedKeys.size) % 360)}, 65%, 52%)`,
})
}
return { gpuData, dataPoints }
}, [chartData])
if (chartData.systemStats.length === 0) {
return null
}
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
>
<LineChart accessibilityLayer data={gpuData} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
domain={[0, "auto"]}
width={yAxisWidth}
tickFormatter={(value) => {
const val = toFixedFloat(value, 2)
return updateYAxisWidth(`${val}W`)
}}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
// @ts-expect-error
itemSorter={(a, b) => b.value - a.value}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={(item) => `${decimalString(item.value)}W`}
// indicator="line"
/>
}
/>
{dataPoints.map((dataPoint) => (
<Line
key={dataPoint.label}
dataKey={dataPoint.dataKey}
name={dataPoint.label}
type="monotoneX"
dot={false}
strokeWidth={1.5}
stroke={dataPoint.color as string}
isAnimationActive={false}
/>
))}
{dataPoints.length > 1 && <ChartLegend content={<ChartLegendContent />} />}
</LineChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,6 +1,9 @@
import { useMemo, useState } from "react"
import { useStore } from "@nanostores/react"
import type { ChartConfig } from "@/components/ui/chart"
import type { ChartData, SystemStats, SystemStatsRecord } from "@/types"
import type { DataPoint } from "./area-chart"
import { $containerFilter } from "@/lib/stores"
/** Chart configurations for CPU, memory, and network usage charts */
export interface ContainerChartConfigs {
@@ -96,9 +99,9 @@ export function useYAxisWidth() {
clearTimeout(timeout)
timeout = setTimeout(() => {
document.body.appendChild(div)
const width = div.offsetWidth + 24
const width = div.offsetWidth + 20
if (width > yAxisWidth) {
setYAxisWidth(div.offsetWidth + 24)
setYAxisWidth(width)
}
document.body.removeChild(div)
})
@@ -108,6 +111,44 @@ export function useYAxisWidth() {
return { yAxisWidth, updateYAxisWidth }
}
/** Subscribes to the container filter store and returns filtered DataPoints for container charts */
export function useContainerDataPoints(
chartConfig: ChartConfig,
// biome-ignore lint/suspicious/noExplicitAny: container data records have dynamic keys
dataFn: (key: string, data: Record<string, any>) => number | null
) {
const filter = useStore($containerFilter)
const { dataPoints, filteredKeys } = useMemo(() => {
const filterTerms = filter
? filter
.toLowerCase()
.split(" ")
.filter((term) => term.length > 0)
: []
const filtered = new Set<string>()
const points = Object.keys(chartConfig).map((key) => {
const isFiltered = filterTerms.length > 0 && !filterTerms.some((term) => key.toLowerCase().includes(term))
if (isFiltered) filtered.add(key)
return {
label: key,
// biome-ignore lint/suspicious/noExplicitAny: container data records have dynamic keys
dataKey: (data: Record<string, any>) => dataFn(key, data),
color: chartConfig[key].color ?? "",
opacity: isFiltered ? 0.05 : 0.4,
strokeOpacity: isFiltered ? 0.1 : 1,
activeDot: !isFiltered,
stackId: "a",
}
})
return {
// biome-ignore lint/suspicious/noExplicitAny: container data records have dynamic keys
dataPoints: points as DataPoint<Record<string, any>>[],
filteredKeys: filtered,
}
}, [chartConfig, filter])
return { filter, dataPoints, filteredKeys }
}
// Assures consistent colors for network interfaces
export function useNetworkInterfaces(interfaces: SystemStats["ni"]) {
const keys = Object.keys(interfaces ?? {})

View File

@@ -1,4 +1,4 @@
import { useMemo } from "react"
import { type ReactNode, useEffect, useMemo, useState } from "react"
import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
import {
ChartContainer,
@@ -11,15 +11,22 @@ import {
import { chartMargin, cn, formatShortDate } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { useYAxisWidth } from "./hooks"
import type { AxisDomain } from "recharts/types/util/types"
import { useIntersectionObserver } from "@/lib/use-intersection-observer"
export type DataPoint = {
export type DataPoint<T = SystemStatsRecord> = {
label: string
dataKey: (data: SystemStatsRecord) => number | undefined
dataKey: (data: T) => number | null | undefined
color: number | string
stackId?: string | number
order?: number
strokeOpacity?: number
activeDot?: boolean
}
export default function LineChartDefault({
chartData,
customData,
max,
maxToggled,
tickFormatter,
@@ -28,38 +35,101 @@ export default function LineChartDefault({
domain,
legend,
itemSorter,
showTotal = false,
reverseStackOrder = false,
hideYAxis = false,
filter,
truncate = false,
}: // logRender = false,
{
chartData: ChartData
// biome-ignore lint/suspicious/noExplicitAny: accepts different data source types (systemStats or containerData)
customData?: any[]
max?: number
maxToggled?: boolean
tickFormatter: (value: number, index: number) => string
contentFormatter: ({ value, payload }: { value: number; payload: SystemStatsRecord }) => string
dataPoints?: DataPoint[]
domain?: [number, number]
// biome-ignore lint/suspicious/noExplicitAny: recharts tooltip item interop
contentFormatter: (item: any, key: string) => ReactNode
// biome-ignore lint/suspicious/noExplicitAny: accepts DataPoint with different generic types
dataPoints?: DataPoint<any>[]
domain?: AxisDomain
legend?: boolean
showTotal?: boolean
// biome-ignore lint/suspicious/noExplicitAny: recharts tooltip item interop
itemSorter?: (a: any, b: any) => number
reverseStackOrder?: boolean
hideYAxis?: boolean
filter?: string
truncate?: boolean
// logRender?: boolean
}) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { isIntersecting, ref } = useIntersectionObserver({ freeze: false })
const sourceData = customData ?? chartData.systemStats
// Only update the rendered data while the chart is visible
const [displayData, setDisplayData] = useState(sourceData)
// Reduce chart redraws by only updating while visible or when chart time changes
useEffect(() => {
const shouldPrimeData = sourceData.length && !displayData.length
const sourceChanged = sourceData !== displayData
const shouldUpdate = shouldPrimeData || (sourceChanged && isIntersecting)
if (shouldUpdate) {
setDisplayData(sourceData)
}
}, [displayData, isIntersecting, sourceData])
// Use a stable key derived from data point identities and visual properties
const linesKey = dataPoints?.map((d) => `${d.label}:${d.strokeOpacity ?? ""}`).join("\0")
const Lines = useMemo(() => {
return dataPoints?.map((dataPoint, i) => {
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}
strokeOpacity={dataPoint.strokeOpacity}
isAnimationActive={false}
// stackId={dataPoint.stackId}
order={dataPoint.order || i}
// activeDot={dataPoint.activeDot ?? true}
/>
)
})
}, [linesKey, maxToggled])
// biome-ignore lint/correctness/useExhaustiveDependencies: ignore
return useMemo(() => {
if (chartData.systemStats.length === 0) {
if (displayData.length === 0) {
return null
}
// if (logRender) {
// console.log("Rendered at", new Date())
// console.log("Rendered at", new Date(), "for", dataPoints?.at(0)?.label)
// }
return (
<div>
<ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth,
})}
<ChartContainer
ref={ref}
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth || hideYAxis,
"ps-4": hideYAxis,
})}
>
<LineChart
reverseStackOrder={reverseStackOrder}
accessibilityLayer
data={displayData}
margin={hideYAxis ? { ...chartMargin, left: 5 } : chartMargin}
>
<LineChart accessibilityLayer data={chartData.systemStats} margin={chartMargin}>
<CartesianGrid vertical={false} />
<CartesianGrid vertical={false} />
{!hideYAxis && (
<YAxis
direction="ltr"
orientation={chartData.orientation}
@@ -70,41 +140,27 @@ export default function LineChartDefault({
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>
)}
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
// @ts-expect-error
itemSorter={itemSorter}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={contentFormatter}
showTotal={showTotal}
filter={filter}
truncate={truncate}
/>
}
/>
{Lines}
{legend && <ChartLegend content={<ChartLegendContent />} />}
</LineChart>
</ChartContainer>
)
}, [chartData.systemStats.at(-1), yAxisWidth, maxToggled])
}, [displayData, yAxisWidth, showTotal, filter, chartData.chartTime])
}

View File

@@ -1,83 +0,0 @@
import { t } from "@lingui/core/macro"
import { memo } from "react"
import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
xAxis,
} from "@/components/ui/chart"
import { chartMargin, cn, decimalString, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemStats } from "@/types"
import { useYAxisWidth } from "./hooks"
export default memo(function LoadAverageChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const keys: { color: string; label: string }[] = [
{
color: "hsl(271, 81%, 60%)", // Purple
label: t({ message: `1 min`, comment: "Load average" }),
},
{
color: "hsl(217, 91%, 60%)", // Blue
label: t({ message: `5 min`, comment: "Load average" }),
},
{
color: "hsl(25, 95%, 53%)", // Orange
label: t({ message: `15 min`, comment: "Load average" }),
},
]
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"
domain={[0, "auto"]}
width={yAxisWidth}
tickFormatter={(value) => {
return updateYAxisWidth(String(toFixedFloat(value, 2)))
}}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={(item) => decimalString(item.value)}
/>
}
/>
{keys.map(({ color, label }, i) => (
<Line
key={label}
dataKey={(value: { stats: SystemStats }) => value.stats?.la?.[i]}
name={label}
type="monotoneX"
dot={false}
strokeWidth={1.5}
stroke={color}
isAnimationActive={false}
/>
))}
<ChartLegend content={<ChartLegendContent />} />
</LineChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,108 +0,0 @@
import { useLingui } from "@lingui/react/macro"
import { memo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { Unit } from "@/lib/enums"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks"
export default memo(function MemChart({ chartData, showMax }: { chartData: ChartData; showMax: boolean }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const { t } = useLingui()
const totalMem = toFixedFloat(chartData.systemStats.at(-1)?.stats.m ?? 0, 1)
// console.log('rendered at', new Date())
if (chartData.systemStats.length === 0) {
return null
}
return (
<div>
{/* {!yAxisSet && <Spinner />} */}
<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} />
{totalMem && (
<YAxis
direction="ltr"
orientation={chartData.orientation}
// use "ticks" instead of domain / tickcount if need more control
domain={[0, totalMem]}
tickCount={9}
className="tracking-tighter"
width={yAxisWidth}
tickLine={false}
axisLine={false}
tickFormatter={(value) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return updateYAxisWidth(toFixedFloat(convertedValue, value >= 10 ? 0 : 1) + " " + unit)
}}
/>
)}
{xAxis(chartData)}
<ChartTooltip
// cursor={false}
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
// @ts-expect-error
itemSorter={(a, b) => a.order - b.order}
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }) => {
// mem values are supplied as GB
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit
}}
showTotal={true}
/>
}
/>
<Area
name={t`Used`}
order={3}
dataKey={({ stats }) => (showMax ? stats?.mm : stats?.mu)}
type="monotoneX"
fill="var(--chart-2)"
fillOpacity={0.4}
stroke="var(--chart-2)"
stackId="1"
isAnimationActive={false}
/>
{/* {chartData.systemStats.at(-1)?.stats.mz && ( */}
<Area
name="ZFS ARC"
order={2}
dataKey={({ stats }) => (showMax ? null : stats?.mz)}
type="monotoneX"
fill="hsla(175 60% 45% / 0.8)"
fillOpacity={0.5}
stroke="hsla(175 60% 45% / 0.8)"
stackId="1"
isAnimationActive={false}
/>
{/* )} */}
<Area
name={t`Cache / Buffers`}
order={1}
dataKey={({ stats }) => (showMax ? null : stats?.mb)}
type="monotoneX"
fill="hsla(160 60% 45% / 0.5)"
fillOpacity={0.4}
stroke="hsla(160 60% 45% / 0.5)"
stackId="1"
isAnimationActive={false}
/>
{/* <ChartLegend content={<ChartLegendContent />} /> */}
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,70 +0,0 @@
import { t } from "@lingui/core/macro"
import { useStore } from "@nanostores/react"
import { memo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart"
import { $userSettings } from "@/lib/stores"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks"
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
const userSettings = useStore($userSettings)
if (chartData.systemStats.length === 0) {
return null
}
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"
domain={[0, () => toFixedFloat(chartData.systemStats.at(-1)?.stats.s ?? 0.04, 2)]}
width={yAxisWidth}
tickLine={false}
axisLine={false}
tickFormatter={(value) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, userSettings.unitDisk, true)
return updateYAxisWidth(toFixedFloat(convertedValue, value >= 10 ? 0 : 1) + " " + unit)
}}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={({ value }) => {
// mem values are supplied as GB
const { value: convertedValue, unit } = formatBytes(value * 1024, false, userSettings.unitDisk, true)
return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit
}}
// indicator="line"
/>
}
/>
<Area
dataKey="stats.su"
name={t`Used`}
type="monotoneX"
fill="var(--chart-2)"
fillOpacity={0.4}
stroke="var(--chart-2)"
isAnimationActive={false}
/>
</AreaChart>
</ChartContainer>
</div>
)
})

View File

@@ -1,117 +0,0 @@
import { useStore } from "@nanostores/react"
import { memo, useMemo } from "react"
import { CartesianGrid, Line, LineChart, YAxis } from "recharts"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltip,
ChartTooltipContent,
xAxis,
} from "@/components/ui/chart"
import { $temperatureFilter, $userSettings } from "@/lib/stores"
import { chartMargin, cn, decimalString, formatShortDate, formatTemperature, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks"
export default memo(function TemperatureChart({ chartData }: { chartData: ChartData }) {
const filter = useStore($temperatureFilter)
const userSettings = useStore($userSettings)
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
if (chartData.systemStats.length === 0) {
return null
}
/** Format temperature data for chart and assign colors */
const newChartData = useMemo(() => {
const newChartData = { data: [], colors: {} } as {
data: Record<string, number | string>[]
colors: Record<string, string>
}
const tempSums = {} as Record<string, number>
for (const data of chartData.systemStats) {
const newData = { created: data.created } as Record<string, number | string>
const keys = Object.keys(data.stats?.t ?? {})
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
newData[key] = data.stats.t![key]
tempSums[key] = (tempSums[key] ?? 0) + newData[key]
}
newChartData.data.push(newData)
}
const keys = Object.keys(tempSums).sort((a, b) => tempSums[b] - tempSums[a])
for (const key of keys) {
newChartData.colors[key] = `hsl(${((keys.indexOf(key) * 360) / keys.length) % 360}, 60%, 55%)`
}
return newChartData
}, [chartData])
const colors = Object.keys(newChartData.colors)
// 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={newChartData.data} margin={chartMargin}>
<CartesianGrid vertical={false} />
<YAxis
direction="ltr"
orientation={chartData.orientation}
className="tracking-tighter"
domain={["auto", "auto"]}
width={yAxisWidth}
tickFormatter={(val) => {
const { value, unit } = formatTemperature(val, userSettings.unitTemp)
return updateYAxisWidth(toFixedFloat(value, 2) + " " + unit)
}}
tickLine={false}
axisLine={false}
/>
{xAxis(chartData)}
<ChartTooltip
animationEasing="ease-out"
animationDuration={150}
// @ts-expect-error
itemSorter={(a, b) => b.value - a.value}
content={
<ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={(item) => {
const { value, unit } = formatTemperature(item.value, userSettings.unitTemp)
return decimalString(value) + " " + unit
}}
filter={filter}
/>
}
/>
{colors.map((key) => {
const filterTerms = filter ? filter.toLowerCase().split(" ").filter(term => term.length > 0) : []
const filtered = filterTerms.length > 0 && !filterTerms.some(term => key.toLowerCase().includes(term))
const strokeOpacity = filtered ? 0.1 : 1
return (
<Line
key={key}
dataKey={key}
name={key}
type="monotoneX"
dot={false}
strokeWidth={1.5}
stroke={newChartData.colors[key]}
strokeOpacity={strokeOpacity}
activeDot={{ opacity: filtered ? 0 : 1 }}
isAnimationActive={false}
/>
)
})}
{colors.length < 12 && <ChartLegend content={<ChartLegendContent />} />}
</LineChart>
</ChartContainer>
</div>
)
})

View File

@@ -163,9 +163,9 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
const visibleColumns = table.getVisibleLeafColumns()
return (
<Card className="p-6 @container w-full">
<CardHeader className="p-0 mb-4">
<div className="grid md:flex gap-5 w-full items-end">
<Card className="@container w-full px-3 py-5 sm:py-6 sm:px-6">
<CardHeader className="p-0 mb-3 sm:mb-4">
<div className="grid md:flex gap-x-5 gap-y-3 w-full items-end">
<div className="px-2 sm:px-1">
<CardTitle className="mb-2">
<Trans>All Containers</Trans>
@@ -462,7 +462,6 @@ function ContainerSheet({
function ContainersTableHead({ table }: { table: TableType<ContainerRecord> }) {
return (
<TableHeader className="sticky top-0 z-50 w-full border-b-2">
<div className="absolute -top-2 left-0 w-full h-4 bg-table-header z-50"></div>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {

View File

@@ -1,7 +1,11 @@
import { useStore } from "@nanostores/react"
import { GithubIcon } from "lucide-react"
import { $newVersion } from "@/lib/stores"
import { Separator } from "./ui/separator"
import { Trans } from "@lingui/react/macro"
export function FooterRepoLink() {
const newVersion = useStore($newVersion)
return (
<div className="flex gap-1.5 justify-end items-center pe-3 sm:pe-6 mt-3.5 mb-4 text-xs opacity-80">
<a
@@ -21,6 +25,19 @@ export function FooterRepoLink() {
>
Beszel {globalThis.BESZEL.HUB_VERSION}
</a>
{newVersion?.v && (
<>
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
<a
href={newVersion.url}
target="_blank"
className="text-yellow-500 hover:text-yellow-400 duration-75"
rel="noopener"
>
<Trans context="New version available">{newVersion.v} available</Trans>
</a>
</>
)}
</div>
)
}

View File

@@ -1,6 +1,6 @@
import { Trans, useLingui } from "@lingui/react/macro"
import { LanguagesIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { buttonVariants } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { dynamicActivate } from "@/lib/i18n"
import languages from "@/lib/languages"
@@ -14,31 +14,29 @@ export function LangToggle() {
return (
<DropdownMenu>
<DropdownMenuTrigger>
<Tooltip>
<TooltipTrigger asChild>
<Button variant={"ghost"} size="icon" className="hidden sm:flex">
<LanguagesIcon className="absolute h-[1.2rem] w-[1.2rem] light:opacity-85" />
<span className="sr-only">{LangTrans}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{LangTrans}</TooltipContent>
</Tooltip>
</DropdownMenuTrigger>
<DropdownMenuContent className="grid grid-cols-3">
{languages.map(([lang, label, e]) => (
<DropdownMenuItem
key={lang}
className={cn("px-2.5 flex gap-2.5 cursor-pointer", lang === i18n.locale && "bg-accent/70 font-medium")}
onClick={() => dynamicActivate(lang)}
>
<span>
{e || <code className="font-mono bg-muted text-[.65em] w-5 h-4 grid place-items-center">{lang}</code>}
</span>{" "}
{label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuTrigger className={cn(buttonVariants({ variant: "ghost", size: "icon" }))}>
<LanguagesIcon className="absolute h-[1.2rem] w-[1.2rem] light:opacity-85" />
<span className="sr-only">{LangTrans}</span>
<TooltipContent>{LangTrans}</TooltipContent>
</DropdownMenuTrigger>
</TooltipTrigger>
<DropdownMenuContent className="grid grid-cols-3">
{languages.map(([lang, label, e]) => (
<DropdownMenuItem
key={lang}
className={cn("px-2.5 flex gap-2.5 cursor-pointer", lang === i18n.locale && "bg-accent/70 font-medium")}
onClick={() => dynamicActivate(lang)}
>
<span>
{e || <code className="font-mono bg-muted text-[.65em] w-5 h-4 grid place-items-center">{lang}</code>}
</span>{" "}
{label}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</Tooltip>
</DropdownMenu>
)
}

View File

@@ -10,7 +10,7 @@ export function ModeToggle() {
return (
<Tooltip>
<TooltipTrigger>
<TooltipTrigger asChild>
<Button
variant={"ghost"}
size="icon"

View File

@@ -1,3 +1,4 @@
import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
import { getPagePath } from "@nanostores/router"
import {
@@ -6,6 +7,8 @@ import {
HardDriveIcon,
LogOutIcon,
LogsIcon,
MenuIcon,
PlusIcon,
SearchIcon,
ServerIcon,
SettingsIcon,
@@ -21,15 +24,18 @@ import {
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} 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 { AddSystemDialog } from "./add-system"
import { LangToggle } from "./lang-toggle"
import { Logo } from "./logo"
import { ModeToggle } from "./mode-toggle"
import { $router, basePath, Link, prependBasePath } from "./router"
import { $router, basePath, Link, navigate, prependBasePath } from "./router"
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"
const CommandPalette = lazy(() => import("./command-palette"))
@@ -37,8 +43,20 @@ const CommandPalette = lazy(() => import("./command-palette"))
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
export default function Navbar() {
const [addSystemDialogOpen, setAddSystemDialogOpen] = useState(false)
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false)
const AdminLinks = AdminDropdownGroup()
const systemTranslation = t`System`
return (
<div className="flex items-center h-14 md:h-16 bg-card px-4 pe-3 sm:px-6 border border-border/60 bt-0 rounded-md my-4">
<Suspense>
<CommandPalette open={commandPaletteOpen} setOpen={setCommandPaletteOpen} />
</Suspense>
<AddSystemDialog open={addSystemDialogOpen} setOpen={setAddSystemDialogOpen} />
<Link
href={basePath}
aria-label="Home"
@@ -47,10 +65,93 @@ export default function Navbar() {
>
<Logo className="h-[1.1rem] md:h-5 fill-foreground" />
</Link>
<SearchButton />
<Button
variant="outline"
className="hidden md:block text-sm text-muted-foreground px-4"
onClick={() => setCommandPaletteOpen(true)}
>
<span className="flex items-center">
<SearchIcon className="me-1.5 h-4 w-4" />
<Trans>Search</Trans>
<span className="flex items-center ms-3.5">
<Kbd>{isMac ? "⌘" : "Ctrl"}</Kbd>
<Kbd>K</Kbd>
</span>
</span>
</Button>
{/* mobile menu */}
<div className="ms-auto flex items-center text-xl md:hidden">
<ModeToggle />
<Button variant="ghost" size="icon" onClick={() => setCommandPaletteOpen(true)}>
<SearchIcon className="h-[1.2rem] w-[1.2rem]" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger
onMouseEnter={() => import("@/components/routes/settings/general")}
className="ms-3"
aria-label="Open Menu"
>
<MenuIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel className="max-w-40 truncate">{pb.authStore.record?.email}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => navigate(getPagePath($router, "containers"))}
className="flex items-center"
>
<ContainerIcon className="h-4 w-4 me-2.5" strokeWidth={1.5} />
<Trans>All Containers</Trans>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigate(getPagePath($router, "smart"))} className="flex items-center">
<HardDriveIcon className="h-4 w-4 me-2.5" strokeWidth={1.5} />
<span>S.M.A.R.T.</span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => navigate(getPagePath($router, "settings", { name: "general" }))}
className="flex items-center"
>
<SettingsIcon className="h-4 w-4 me-2.5" />
<Trans>Settings</Trans>
</DropdownMenuItem>
{isAdmin() && (
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<UserIcon className="h-4 w-4 me-2.5" />
<Trans>Admin</Trans>
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>{AdminLinks}</DropdownMenuSubContent>
</DropdownMenuSub>
)}
<DropdownMenuItem
className="flex items-center"
onSelect={() => {
setAddSystemDialogOpen(true)
}}
>
<PlusIcon className="h-4 w-4 me-2.5" />
<Trans>Add {{ foo: systemTranslation }}</Trans>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem onSelect={logOut} className="flex items-center">
<LogOutIcon className="h-4 w-4 me-2.5" />
<Trans>Log Out</Trans>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* desktop nav */}
{/** biome-ignore lint/a11y/noStaticElementInteractions: ignore */}
<div className="flex items-center ms-auto" onMouseEnter={() => import("@/components/routes/settings/general")}>
<div
className="hidden md:flex items-center ms-auto"
onMouseEnter={() => import("@/components/routes/settings/general")}
>
<Tooltip>
<TooltipTrigger asChild>
<Link
@@ -102,45 +203,12 @@ export default function Navbar() {
<DropdownMenuContent align={isReadOnlyUser() ? "end" : "center"} className="min-w-44">
<DropdownMenuLabel>{pb.authStore.record?.email}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
{isAdmin() && (
<>
<DropdownMenuItem asChild>
<a href={prependBasePath("/_/")} target="_blank">
<UsersIcon className="me-2.5 h-4 w-4" />
<span>
<Trans>Users</Trans>
</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={prependBasePath("/_/#/collections?collection=systems")} target="_blank">
<ServerIcon className="me-2.5 h-4 w-4" />
<span>
<Trans>Systems</Trans>
</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={prependBasePath("/_/#/logs")} target="_blank">
<LogsIcon className="me-2.5 h-4 w-4" />
<span>
<Trans>Logs</Trans>
</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={prependBasePath("/_/#/settings/backups")} target="_blank">
<DatabaseBackupIcon className="me-2.5 h-4 w-4" />
<span>
<Trans>Backups</Trans>
</span>
</a>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
</DropdownMenuGroup>
{isAdmin() && (
<>
{AdminLinks}
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem onSelect={logOut}>
<LogOutIcon className="me-2.5 h-4 w-4" />
<span>
@@ -149,7 +217,10 @@ export default function Navbar() {
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<AddSystemButton className="ms-2" />
<Button variant="outline" className="flex gap-1 ms-2" onClick={() => setAddSystemDialogOpen(true)}>
<PlusIcon className="h-4 w-4 -ms-1" />
<Trans>Add {{ foo: systemTranslation }}</Trans>
</Button>
</div>
</div>
)
@@ -161,28 +232,41 @@ const Kbd = ({ children }: { children: React.ReactNode }) => (
</kbd>
)
function SearchButton() {
const [open, setOpen] = useState(false)
function AdminDropdownGroup() {
return (
<>
<Button
variant="outline"
className="hidden md:block text-sm text-muted-foreground px-4"
onClick={() => setOpen(true)}
>
<span className="flex items-center">
<SearchIcon className="me-1.5 h-4 w-4" />
<Trans>Search</Trans>
<span className="flex items-center ms-3.5">
<Kbd>{isMac ? "⌘" : "Ctrl"}</Kbd>
<Kbd>K</Kbd>
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<a href={prependBasePath("/_/")} target="_blank">
<UsersIcon className="me-2.5 h-4 w-4" />
<span>
<Trans>Users</Trans>
</span>
</span>
</Button>
<Suspense>
<CommandPalette open={open} setOpen={setOpen} />
</Suspense>
</>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={prependBasePath("/_/#/collections?collection=systems")} target="_blank">
<ServerIcon className="me-2.5 h-4 w-4" />
<span>
<Trans>Systems</Trans>
</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={prependBasePath("/_/#/logs")} target="_blank">
<LogsIcon className="me-2.5 h-4 w-4" />
<span>
<Trans>Logs</Trans>
</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a href={prependBasePath("/_/#/settings/backups")} target="_blank">
<DatabaseBackupIcon className="me-2.5 h-4 w-4" />
<span>
<Trans>Backups</Trans>
</span>
</a>
</DropdownMenuItem>
</DropdownMenuGroup>
)
}

View File

@@ -134,10 +134,10 @@ export function QuietHours() {
const startMinutes = startDate.getUTCHours() * 60 + startDate.getUTCMinutes()
const endMinutes = endDate.getUTCHours() * 60 + endDate.getUTCMinutes()
// Convert UTC to local time offset
const offset = now.getTimezoneOffset()
const localStartMinutes = (startMinutes - offset + 1440) % 1440
const localEndMinutes = (endMinutes - offset + 1440) % 1440
// Convert UTC to local time using the stored date's offset, not the current date's offset
// This avoids DST mismatch when records were saved in a different DST period
const localStartMinutes = (startMinutes - startDate.getTimezoneOffset() + 1440) % 1440
const localEndMinutes = (endMinutes - endDate.getTimezoneOffset() + 1440) % 1440
// Handle cases where window spans midnight
if (localStartMinutes <= localEndMinutes) {
@@ -347,12 +347,13 @@ function QuietHoursDialog({
if (windowType === "daily") {
// For daily windows, convert local time to UTC
// Create a date with the time in local timezone, then convert to UTC
const startDate = new Date(`2000-01-01T${startTime}:00`)
// Use today's date so the current DST offset is applied (not a fixed historical date)
const today = new Date().toISOString().split("T")[0]
const startDate = new Date(`${today}T${startTime}:00`)
startValue = startDate.toISOString()
if (endTime) {
const endDate = new Date(`2000-01-01T${endTime}:00`)
const endDate = new Date(`${today}T${endTime}:00`)
endValue = endDate.toISOString()
}
} else {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,133 @@
import { t } from "@lingui/core/macro"
import { Trans, useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react"
import { XIcon } from "lucide-react"
import React, { type JSX, memo, useCallback, useEffect, useState } from "react"
import { $containerFilter, $maxValues } from "@/lib/stores"
import { useIntersectionObserver } from "@/lib/use-intersection-observer"
import { cn } from "@/lib/utils"
import Spinner from "../../spinner"
import { Button } from "../../ui/button"
import { Card, CardDescription, CardHeader, CardTitle } from "../../ui/card"
import { ChartAverage, ChartMax } from "../../ui/icons"
import { Input } from "../../ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../ui/select"
export function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
const storeValue = useStore(store)
const [inputValue, setInputValue] = useState(storeValue)
const { t } = useLingui()
useEffect(() => {
setInputValue(storeValue)
}, [storeValue])
useEffect(() => {
if (inputValue === storeValue) {
return
}
const handle = window.setTimeout(() => store.set(inputValue), 80)
return () => clearTimeout(handle)
}, [inputValue, storeValue, store])
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setInputValue(value)
}, [])
const handleClear = useCallback(() => {
setInputValue("")
store.set("")
}, [store])
return (
<>
<Input
placeholder={t`Filter...`}
className="ps-4 pe-8 w-full sm:w-44"
onChange={handleChange}
value={inputValue}
/>
{inputValue && (
<Button
type="button"
variant="ghost"
size="icon"
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"
onClick={handleClear}
>
<XIcon className="h-4 w-4" />
</Button>
)}
</>
)
}
export const SelectAvgMax = memo(({ max }: { max: boolean }) => {
const Icon = max ? ChartMax : ChartAverage
return (
<Select value={max ? "max" : "avg"} onValueChange={(e) => $maxValues.set(e === "max")}>
<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" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem key="avg" value="avg">
<Trans>Average</Trans>
</SelectItem>
<SelectItem key="max" value="max">
<Trans comment="Chart select field. Please try to keep this short.">Max 1 min</Trans>
</SelectItem>
</SelectContent>
</Select>
)
})
export function ChartCard({
title,
description,
children,
grid,
empty,
cornerEl,
legend,
className,
}: {
title: string
description: string
children: React.ReactNode
grid?: boolean
empty?: boolean
cornerEl?: JSX.Element | null
legend?: boolean
className?: string
}) {
const { isIntersecting, ref } = useIntersectionObserver()
return (
<Card
className={cn(
"px-3 py-5 sm:py-6 sm:px-6 odd:last-of-type:col-span-full min-h-full",
{ "col-span-full": !grid },
className
)}
ref={ref}
>
<CardHeader className="gap-1.5 relative p-0 mb-3 sm:mb-4">
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
{cornerEl && <div className="grid sm:justify-end sm:absolute sm:top-0 sm:end-0 my-1 sm:my-0">{cornerEl}</div>}
</CardHeader>
<div className={cn("ps-0 -me-1 -ms-3.5 relative group", legend ? "h-54 md:h-56" : "h-48 md:h-52")}>
{
<Spinner
msg={empty ? t`Waiting for enough records to display` : undefined}
className="group-has-[.opacity-100]:invisible duration-100"
/>
}
{isIntersecting && children}
</div>
</Card>
)
}

View File

@@ -0,0 +1,116 @@
import { timeTicks } from "d3-time"
import { getPbTimestamp, pb } from "@/lib/api"
import { chartTimeData } from "@/lib/utils"
import type { ChartData, ChartTimes, ContainerStatsRecord, SystemStatsRecord } from "@/types"
type ChartTimeData = {
time: number
data: {
ticks: number[]
domain: number[]
}
chartTime: ChartTimes
}
export const cache = new Map<
string,
ChartTimeData | SystemStatsRecord[] | ContainerStatsRecord[] | ChartData["containerData"]
>()
// create ticks and domain for charts
export function getTimeData(chartTime: ChartTimes, lastCreated: number) {
const cached = cache.get("td") as ChartTimeData | undefined
if (cached && cached.chartTime === chartTime) {
if (!lastCreated || cached.time >= lastCreated) {
return cached.data
}
}
// const buffer = chartTime === "1m" ? 400 : 20_000
const now = new Date(Date.now())
const startTime = chartTimeData[chartTime].getOffset(now)
const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime())
const data = {
ticks,
domain: [chartTimeData[chartTime].getOffset(now).getTime(), now.getTime()],
}
cache.set("td", { time: now.getTime(), data, chartTime })
return data
}
/** Append new records onto prev with gap detection. Converts string `created` values to ms timestamps in place.
* Pass `maxLen` to cap the result length in one copy instead of slicing again after the call. */
export function appendData<T extends { created: string | number | null }>(
prev: T[],
newRecords: T[],
expectedInterval: number,
maxLen?: number
): T[] {
if (!newRecords.length) return prev
// Pre-trim prev so the single slice() below is the only copy we make
const trimmed = maxLen && prev.length >= maxLen ? prev.slice(-(maxLen - newRecords.length)) : prev
const result = trimmed.slice()
let prevTime = (trimmed.at(-1)?.created as number) ?? 0
for (const record of newRecords) {
if (record.created !== null) {
if (typeof record.created === "string") {
record.created = new Date(record.created).getTime()
}
if (prevTime && (record.created as number) - prevTime > expectedInterval * 1.5) {
result.push({ created: null, ...("stats" in record ? { stats: null } : {}) } as T)
}
prevTime = record.created as number
}
result.push(record)
}
return result
}
export async function getStats<T extends SystemStatsRecord | ContainerStatsRecord>(
collection: string,
systemId: string,
chartTime: ChartTimes
): Promise<T[]> {
const cachedStats = cache.get(`${systemId}_${chartTime}_${collection}`) as T[] | undefined
const lastCached = cachedStats?.at(-1)?.created as number
return await pb.collection<T>(collection).getFullList({
filter: pb.filter("system={:id} && created > {:created} && type={:type}", {
id: systemId,
created: getPbTimestamp(chartTime, lastCached ? new Date(lastCached + 1000) : undefined),
type: chartTimeData[chartTime].type,
}),
fields: "created,stats",
sort: "created",
})
}
export function makeContainerData(containers: ContainerStatsRecord[]): ChartData["containerData"] {
const result = [] as ChartData["containerData"]
for (const { created, stats } of containers) {
if (!created) {
result.push({ created: null } as ChartData["containerData"][0])
continue
}
result.push(makeContainerPoint(new Date(created).getTime(), stats))
}
return result
}
/** Transform a single realtime container stats message into a ChartDataContainer point. */
export function makeContainerPoint(
created: number,
stats: ContainerStatsRecord["stats"]
): ChartData["containerData"][0] {
const point: ChartData["containerData"][0] = { created } as ChartData["containerData"][0]
for (const container of stats) {
;(point as Record<string, unknown>)[container.n] = container
}
return point
}
export function dockerOrPodman(str: string, isPodman: boolean): string {
if (isPodman) {
return str.replace("docker", "podman").replace("Docker", "Podman")
}
return str
}

View File

@@ -0,0 +1,99 @@
import { t } from "@lingui/core/macro"
import AreaChartDefault from "@/components/charts/area-chart"
import { useContainerDataPoints } from "@/components/charts/hooks"
import { decimalString, toFixedFloat } from "@/lib/utils"
import type { ChartConfig } from "@/components/ui/chart"
import type { ChartData } from "@/types"
import { pinnedAxisDomain } from "@/components/ui/chart"
import CpuCoresSheet from "../cpu-sheet"
import { ChartCard, FilterBar, SelectAvgMax } from "../chart-card"
import { dockerOrPodman } from "../chart-data"
export function CpuChart({
chartData,
grid,
dataEmpty,
showMax,
isLongerChart,
maxValues,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
showMax: boolean
isLongerChart: boolean
maxValues: boolean
}) {
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`CPU Usage`}
description={t`Average system-wide CPU utilization`}
cornerEl={
<div className="flex gap-2">
{maxValSelect}
<CpuCoresSheet chartData={chartData} dataEmpty={dataEmpty} grid={grid} maxValues={maxValues} />
</div>
}
>
<AreaChartDefault
chartData={chartData}
maxToggled={showMax}
dataPoints={[
{
label: t`CPU Usage`,
dataKey: ({ stats }) => (showMax ? stats?.cpum : stats?.cpu),
color: 1,
opacity: 0.4,
},
]}
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
contentFormatter={({ value }) => `${decimalString(value)}%`}
domain={pinnedAxisDomain()}
/>
</ChartCard>
)
}
export function ContainerCpuChart({
chartData,
grid,
dataEmpty,
isPodman,
cpuConfig,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
isPodman: boolean
cpuConfig: ChartConfig
}) {
const { filter, dataPoints } = useContainerDataPoints(cpuConfig, (key, data) => data[key]?.c ?? null)
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={dockerOrPodman(t`Docker CPU Usage`, isPodman)}
description={t`Average CPU utilization of containers`}
cornerEl={<FilterBar />}
>
<AreaChartDefault
chartData={chartData}
customData={chartData.containerData}
dataPoints={dataPoints}
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
contentFormatter={({ value }) => `${decimalString(value)}%`}
domain={pinnedAxisDomain()}
showTotal={true}
reverseStackOrder={true}
filter={filter}
truncate={true}
itemSorter={(a, b) => b.value - a.value}
/>
</ChartCard>
)
}

View File

@@ -0,0 +1,106 @@
import { t } from "@lingui/core/macro"
import AreaChartDefault from "@/components/charts/area-chart"
import { $userSettings } from "@/lib/stores"
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { ChartCard, SelectAvgMax } from "../chart-card"
import { Unit } from "@/lib/enums"
export function DiskCharts({
chartData,
grid,
dataEmpty,
showMax,
isLongerChart,
maxValues,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
showMax: boolean
isLongerChart: boolean
maxValues: boolean
systemStats: SystemStatsRecord[]
}) {
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
const userSettings = $userSettings.get()
let diskSize = chartData.systemStats?.at(-1)?.stats.d ?? NaN
// round to nearest GB
if (diskSize >= 100) {
diskSize = Math.round(diskSize)
}
return (
<>
<ChartCard empty={dataEmpty} grid={grid} title={t`Disk Usage`} description={t`Usage of root partition`}>
<AreaChartDefault
chartData={chartData}
domain={[0, diskSize]}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val * 1024, false, Unit.Bytes, true)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return `${decimalString(convertedValue)} ${unit}`
}}
dataPoints={[
{
label: t`Disk Usage`,
color: 4,
opacity: 0.4,
dataKey: ({ stats }) => stats?.du,
},
]}
></AreaChartDefault>
</ChartCard>
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Disk I/O`}
description={t`Throughput of root filesystem`}
cornerEl={maxValSelect}
>
<AreaChartDefault
chartData={chartData}
maxToggled={showMax}
dataPoints={[
{
label: t({ message: "Write", comment: "Disk write" }),
dataKey: ({ stats }: SystemStatsRecord) => {
if (showMax) {
return stats?.dio?.[1] ?? (stats?.dwm ?? 0) * 1024 * 1024
}
return stats?.dio?.[1] ?? (stats?.dw ?? 0) * 1024 * 1024
},
color: 3,
opacity: 0.3,
},
{
label: t({ message: "Read", comment: "Disk read" }),
dataKey: ({ stats }: SystemStatsRecord) => {
if (showMax) {
return stats?.diom?.[0] ?? (stats?.drm ?? 0) * 1024 * 1024
}
return stats?.dio?.[0] ?? (stats?.dr ?? 0) * 1024 * 1024
},
color: 1,
opacity: 0.3,
},
]}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, true, userSettings.unitDisk, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
showTotal={true}
/>
</ChartCard>
</>
)
}

View File

@@ -0,0 +1,120 @@
import { t } from "@lingui/core/macro"
import AreaChartDefault from "@/components/charts/area-chart"
import { $userSettings } from "@/lib/stores"
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { ChartCard, SelectAvgMax } from "../chart-card"
import { Unit } from "@/lib/enums"
export function ExtraFsCharts({
chartData,
grid,
dataEmpty,
showMax,
isLongerChart,
maxValues,
systemStats,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
showMax: boolean
isLongerChart: boolean
maxValues: boolean
systemStats: SystemStatsRecord[]
}) {
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
const userSettings = $userSettings.get()
const extraFs = systemStats.at(-1)?.stats.efs
if (!extraFs || Object.keys(extraFs).length === 0) {
return null
}
return (
<div className="grid xl:grid-cols-2 gap-4">
{Object.keys(extraFs).map((extraFsName) => {
let diskSize = systemStats.at(-1)?.stats.efs?.[extraFsName].d ?? NaN
// round to nearest GB
if (diskSize >= 100) {
diskSize = Math.round(diskSize)
}
return (
<div key={extraFsName} className="contents">
<ChartCard
empty={dataEmpty}
grid={grid}
title={`${extraFsName} ${t`Usage`}`}
description={t`Disk usage of ${extraFsName}`}
>
<AreaChartDefault
chartData={chartData}
domain={[0, diskSize]}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val * 1024, false, Unit.Bytes, true)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return `${decimalString(convertedValue)} ${unit}`
}}
dataPoints={[
{
label: t`Disk Usage`,
color: 4,
opacity: 0.4,
dataKey: ({ stats }) => stats?.efs?.[extraFsName]?.du,
},
]}
></AreaChartDefault>
</ChartCard>
<ChartCard
empty={dataEmpty}
grid={grid}
title={`${extraFsName} I/O`}
description={t`Throughput of ${extraFsName}`}
cornerEl={maxValSelect}
>
<AreaChartDefault
chartData={chartData}
showTotal={true}
dataPoints={[
{
label: t`Write`,
dataKey: ({ stats }) => {
if (showMax) {
return stats?.efs?.[extraFsName]?.wbm || (stats?.efs?.[extraFsName]?.wm ?? 0) * 1024 * 1024
}
return stats?.efs?.[extraFsName]?.wb || (stats?.efs?.[extraFsName]?.w ?? 0) * 1024 * 1024
},
color: 3,
opacity: 0.3,
},
{
label: t`Read`,
dataKey: ({ stats }) => {
if (showMax) {
return stats?.efs?.[extraFsName]?.rbm ?? (stats?.efs?.[extraFsName]?.rm ?? 0) * 1024 * 1024
}
return stats?.efs?.[extraFsName]?.rb ?? (stats?.efs?.[extraFsName]?.r ?? 0) * 1024 * 1024
},
color: 1,
opacity: 0.3,
},
]}
maxToggled={showMax}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, true, userSettings.unitDisk, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
/>
</ChartCard>
</div>
)
})}
</div>
)
}

View File

@@ -0,0 +1,232 @@
import { t } from "@lingui/core/macro"
import { useRef, useMemo } from "react"
import AreaChartDefault, { type DataPoint } from "@/components/charts/area-chart"
import LineChartDefault from "@/components/charts/line-chart"
import { Unit } from "@/lib/enums"
import { cn, decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
import type { ChartData, GPUData, SystemStatsRecord } from "@/types"
import { ChartCard } from "../chart-card"
/** GPU power draw chart for the main grid */
export function GpuPowerChart({
chartData,
grid,
dataEmpty,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
}) {
const packageKey = " package"
const statsRef = useRef(chartData.systemStats)
statsRef.current = chartData.systemStats
// Derive GPU power config key (cheap per render)
let gpuPowerKey = ""
for (let i = chartData.systemStats.length - 1; i >= 0; i--) {
const gpus = chartData.systemStats[i].stats?.g
if (gpus) {
const parts: string[] = []
for (const id in gpus) {
const gpu = gpus[id] as GPUData
if (gpu.p !== undefined) parts.push(`${id}:${gpu.n}`)
if (gpu.pp !== undefined) parts.push(`${id}:${gpu.n}${packageKey}`)
}
gpuPowerKey = parts.sort().join("\0")
break
}
}
const dataPoints = useMemo((): DataPoint[] => {
if (!gpuPowerKey) return []
const totals = new Map<string, { label: string; gpuId: string; isPackage: boolean; total: number }>()
for (const record of statsRef.current) {
const gpus = record.stats?.g
if (!gpus) continue
for (const id in gpus) {
const gpu = gpus[id] as GPUData
const key = gpu.n
const existing = totals.get(key)
if (existing) {
existing.total += gpu.p ?? 0
} else {
totals.set(key, { label: gpu.n, gpuId: id, isPackage: false, total: gpu.p ?? 0 })
}
if (gpu.pp !== undefined) {
const pkgKey = `${gpu.n}${packageKey}`
const existingPkg = totals.get(pkgKey)
if (existingPkg) {
existingPkg.total += gpu.pp
} else {
totals.set(pkgKey, { label: pkgKey, gpuId: id, isPackage: true, total: gpu.pp })
}
}
}
}
const sorted = Array.from(totals.values()).sort((a, b) => b.total - a.total)
return sorted.map(
(entry, i): DataPoint => ({
label: entry.label,
dataKey: (data: SystemStatsRecord) => {
const gpu = data.stats?.g?.[entry.gpuId]
return entry.isPackage ? (gpu?.pp ?? 0) : (gpu?.p ?? 0)
},
color: `hsl(${226 + (((i * 360) / sorted.length) % 360)}, 65%, 52%)`,
opacity: 1,
})
)
}, [gpuPowerKey])
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`GPU Power Draw`}
description={t`Average power consumption of GPUs`}
>
<LineChartDefault
legend={dataPoints.length > 1}
chartData={chartData}
dataPoints={dataPoints}
itemSorter={(a: { value: number }, b: { value: number }) => b.value - a.value}
tickFormatter={(val) => `${toFixedFloat(val, 2)}W`}
contentFormatter={({ value }) => `${decimalString(value)}W`}
/>
</ChartCard>
)
}
/** GPU detail grid (engines + per-GPU usage/VRAM) — rendered outside the main 2-col grid */
export function GpuDetailCharts({
chartData,
grid,
dataEmpty,
lastGpus,
hasGpuEnginesData,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
lastGpus: Record<string, GPUData>
hasGpuEnginesData: boolean
}) {
return (
<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(lastGpus).map((id) => {
const gpu = lastGpus[id] as GPUData
return (
<div key={id} className="contents">
<ChartCard
className={cn(grid && "!col-span-1")}
empty={dataEmpty}
grid={grid}
title={`${gpu.n} ${t`Usage`}`}
description={t`Average utilization of ${gpu.n}`}
>
<AreaChartDefault
chartData={chartData}
dataPoints={[
{
label: t`Usage`,
dataKey: ({ stats }) => stats?.g?.[id]?.u ?? 0,
color: 1,
opacity: 0.35,
},
]}
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
contentFormatter={({ value }) => `${decimalString(value)}%`}
/>
</ChartCard>
{(gpu.mt ?? 0) > 0 && (
<ChartCard
empty={dataEmpty}
grid={grid}
title={`${gpu.n} VRAM`}
description={t`Precise utilization at the recorded time`}
>
<AreaChartDefault
chartData={chartData}
dataPoints={[
{
label: t`Usage`,
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)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value, false, Unit.Bytes, true)
return `${decimalString(convertedValue)} ${unit}`
}}
/>
</ChartCard>
)}
</div>
)
})}
</div>
)
}
function GpuEnginesChart({ chartData }: { chartData: ChartData }) {
// Derive stable engine config key (cheap per render)
let enginesKey = ""
for (let i = chartData.systemStats.length - 1; i >= 0; i--) {
const gpus = chartData.systemStats[i].stats?.g
if (!gpus) continue
for (const id in gpus) {
if (gpus[id].e) {
enginesKey = id + "\0" + Object.keys(gpus[id].e).sort().join("\0")
break
}
}
if (enginesKey) break
}
const { gpuId, dataPoints } = useMemo((): { gpuId: string | null; dataPoints: DataPoint[] } => {
if (!enginesKey) return { gpuId: null, dataPoints: [] }
const parts = enginesKey.split("\0")
const gId = parts[0]
const engineNames = parts.slice(1)
return {
gpuId: gId,
dataPoints: engineNames.map((engine, i) => ({
label: engine,
dataKey: ({ stats }: SystemStatsRecord) => stats?.g?.[gId]?.e?.[engine] ?? 0,
color: `hsl(${140 + (((i * 360) / engineNames.length) % 360)}, 65%, 52%)`,
opacity: 0.35,
})),
}
}, [enginesKey])
if (!gpuId) {
return null
}
return (
<LineChartDefault
legend={true}
chartData={chartData}
dataPoints={dataPoints}
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
contentFormatter={({ value }) => `${decimalString(value)}%`}
/>
)
}

View File

@@ -0,0 +1,55 @@
import { t } from "@lingui/core/macro"
import type { ChartData } from "@/types"
import { ChartCard } from "../chart-card"
import LineChartDefault from "@/components/charts/line-chart"
import { decimalString, toFixedFloat } from "@/lib/utils"
export function LoadAverageChart({
chartData,
grid,
dataEmpty,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
}) {
const { major, minor } = chartData.agentVersion
if (major === 0 && minor <= 12) {
return null
}
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Load Average`}
description={t`System load averages over time`}
legend={true}
>
<LineChartDefault
chartData={chartData}
contentFormatter={(item) => decimalString(item.value)}
tickFormatter={(value) => {
return String(toFixedFloat(value, 2))
}}
legend={true}
dataPoints={[
{
label: t({ message: `1 min`, comment: "Load average" }),
color: "hsl(271, 81%, 60%)", // Purple
dataKey: ({ stats }) => stats?.la?.[0],
},
{
label: t({ message: `5 min`, comment: "Load average" }),
color: "hsl(217, 91%, 60%)", // Blue
dataKey: ({ stats }) => stats?.la?.[1],
},
{
label: t({ message: `15 min`, comment: "Load average" }),
color: "hsl(25, 95%, 53%)", // Orange
dataKey: ({ stats }) => stats?.la?.[2],
},
]}
></LineChartDefault>
</ChartCard>
)
}

View File

@@ -0,0 +1,170 @@
import { t } from "@lingui/core/macro"
import AreaChartDefault from "@/components/charts/area-chart"
import { useContainerDataPoints } from "@/components/charts/hooks"
import { Unit } from "@/lib/enums"
import type { ChartConfig } from "@/components/ui/chart"
import type { ChartData, SystemStatsRecord } from "@/types"
import { ChartCard, FilterBar, SelectAvgMax } from "../chart-card"
import { dockerOrPodman } from "../chart-data"
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
import { pinnedAxisDomain } from "@/components/ui/chart"
export function MemoryChart({
chartData,
grid,
dataEmpty,
showMax,
isLongerChart,
maxValues,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
showMax: boolean
isLongerChart: boolean
maxValues: boolean
}) {
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
const totalMem = toFixedFloat(chartData.systemStats.at(-1)?.stats.m ?? 0, 1)
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Memory Usage`}
description={t`Precise utilization at the recorded time`}
cornerEl={maxValSelect}
>
<AreaChartDefault
chartData={chartData}
domain={[0, totalMem]}
itemSorter={(a, b) => a.order - b.order}
maxToggled={showMax}
showTotal={true}
tickFormatter={(value) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return `${toFixedFloat(convertedValue, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={({ value }) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
dataPoints={[
{
label: t`Used`,
dataKey: ({ stats }) => (showMax ? stats?.mm : stats?.mu),
color: 2,
opacity: 0.4,
stackId: "1",
order: 3,
},
{
label: "ZFS ARC",
dataKey: ({ stats }) => (showMax ? null : stats?.mz),
color: "hsla(175 60% 45% / 0.8)",
opacity: 0.5,
order: 2,
},
{
label: t`Cache / Buffers`,
dataKey: ({ stats }) => (showMax ? null : stats?.mb),
color: "hsla(160 60% 45% / 0.5)",
opacity: 0.4,
stackId: "1",
order: 1,
},
]}
/>
</ChartCard>
)
}
export function ContainerMemoryChart({
chartData,
grid,
dataEmpty,
isPodman,
memoryConfig,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
isPodman: boolean
memoryConfig: ChartConfig
}) {
const { filter, dataPoints } = useContainerDataPoints(memoryConfig, (key, data) => data[key]?.m ?? null)
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={dockerOrPodman(t`Docker Memory Usage`, isPodman)}
description={dockerOrPodman(t`Memory usage of docker containers`, isPodman)}
cornerEl={<FilterBar />}
>
<AreaChartDefault
chartData={chartData}
customData={chartData.containerData}
dataPoints={dataPoints}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, false, Unit.Bytes, true)
return `${toFixedFloat(value, val >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={(item) => {
const { value, unit } = formatBytes(item.value, false, Unit.Bytes, true)
return `${decimalString(value)} ${unit}`
}}
domain={pinnedAxisDomain()}
showTotal={true}
reverseStackOrder={true}
filter={filter}
truncate={true}
itemSorter={(a, b) => b.value - a.value}
/>
</ChartCard>
)
}
export function SwapChart({
chartData,
grid,
dataEmpty,
systemStats,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
systemStats: SystemStatsRecord[]
}) {
// const userSettings = useStore($userSettings)
const hasSwapData = (systemStats.at(-1)?.stats.su ?? 0) > 0
if (!hasSwapData) {
return null
}
return (
<ChartCard empty={dataEmpty} grid={grid} title={t`Swap Usage`} description={t`Swap space used by the system`}>
<AreaChartDefault
chartData={chartData}
domain={[0, () => toFixedFloat(chartData.systemStats.at(-1)?.stats.s ?? 0.04, 2)]}
contentFormatter={({ value }) => {
// mem values are supplied as GB
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}}
tickFormatter={(value) => {
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return `${toFixedFloat(convertedValue, value >= 10 ? 0 : 1)} ${unit}`
}}
dataPoints={[
{
label: t`Used`,
dataKey: ({ stats }) => stats?.su,
color: 2,
opacity: 0.4,
},
]}
></AreaChartDefault>
</ChartCard>
)
}

View File

@@ -0,0 +1,183 @@
import { useMemo } from "react"
import { t } from "@lingui/core/macro"
import AreaChartDefault from "@/components/charts/area-chart"
import { useContainerDataPoints } from "@/components/charts/hooks"
import { $userSettings } from "@/lib/stores"
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
import type { ChartConfig } from "@/components/ui/chart"
import { pinnedAxisDomain } from "@/components/ui/chart"
import type { ChartData, SystemStatsRecord } from "@/types"
import { Separator } from "@/components/ui/separator"
import NetworkSheet from "../network-sheet"
import { ChartCard, FilterBar, SelectAvgMax } from "../chart-card"
import { dockerOrPodman } from "../chart-data"
export function BandwidthChart({
chartData,
grid,
dataEmpty,
showMax,
isLongerChart,
maxValues,
systemStats,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
showMax: boolean
isLongerChart: boolean
maxValues: boolean
systemStats: SystemStatsRecord[]
}) {
const maxValSelect = isLongerChart ? <SelectAvgMax max={maxValues} /> : null
const userSettings = $userSettings.get()
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Bandwidth`}
cornerEl={
<div className="flex gap-2">
{maxValSelect}
<NetworkSheet chartData={chartData} dataEmpty={dataEmpty} grid={grid} maxValues={maxValues} />
</div>
}
description={t`Network traffic of public interfaces`}
>
<AreaChartDefault
chartData={chartData}
maxToggled={showMax}
dataPoints={[
{
label: t`Sent`,
dataKey(data: SystemStatsRecord) {
if (showMax) {
return data?.stats?.bm?.[0] ?? (data?.stats?.nsm ?? 0) * 1024 * 1024
}
return data?.stats?.b?.[0] ?? (data?.stats?.ns ?? 0) * 1024 * 1024
},
color: 5,
opacity: 0.2,
},
{
label: t`Received`,
dataKey(data: SystemStatsRecord) {
if (showMax) {
return data?.stats?.bm?.[1] ?? (data?.stats?.nrm ?? 0) * 1024 * 1024
}
return data?.stats?.b?.[1] ?? (data?.stats?.nr ?? 0) * 1024 * 1024
},
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}`
}}
showTotal={true}
/>
</ChartCard>
)
}
export function ContainerNetworkChart({
chartData,
grid,
dataEmpty,
isPodman,
networkConfig,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
isPodman: boolean
networkConfig: ChartConfig
}) {
const userSettings = $userSettings.get()
const { filter, dataPoints, filteredKeys } = useContainerDataPoints(networkConfig, (key, data) => {
const payload = data[key]
if (!payload) return null
const sent = payload?.b?.[0] ?? (payload?.ns ?? 0) * 1024 * 1024
const recv = payload?.b?.[1] ?? (payload?.nr ?? 0) * 1024 * 1024
return sent + recv
})
const contentFormatter = useMemo(() => {
const getRxTxBytes = (record?: { b?: [number, number]; ns?: number; nr?: number }) => {
if (record?.b?.length && record.b.length >= 2) {
return [Number(record.b[0]) || 0, Number(record.b[1]) || 0]
}
return [(record?.ns ?? 0) * 1024 * 1024, (record?.nr ?? 0) * 1024 * 1024]
}
const formatRxTx = (recv: number, sent: number) => {
const { value: receivedValue, unit: receivedUnit } = formatBytes(recv, true, userSettings.unitNet, false)
const { value: sentValue, unit: sentUnit } = formatBytes(sent, true, userSettings.unitNet, false)
return (
<span className="flex">
{decimalString(receivedValue)} {receivedUnit}
<span className="opacity-70 ms-0.5"> rx </span>
<Separator orientation="vertical" className="h-3 mx-1.5 bg-primary/40" />
{decimalString(sentValue)} {sentUnit}
<span className="opacity-70 ms-0.5"> tx</span>
</span>
)
}
// biome-ignore lint/suspicious/noExplicitAny: recharts tooltip item
return (item: any, key: string) => {
try {
if (key === "__total__") {
let totalSent = 0
let totalRecv = 0
const payloadData = item?.payload && typeof item.payload === "object" ? item.payload : {}
for (const [containerKey, value] of Object.entries(payloadData)) {
if (!value || typeof value !== "object") continue
if (filteredKeys.has(containerKey)) continue
const [sent, recv] = getRxTxBytes(value as { b?: [number, number]; ns?: number; nr?: number })
totalSent += sent
totalRecv += recv
}
return formatRxTx(totalRecv, totalSent)
}
const [sent, recv] = getRxTxBytes(item?.payload?.[key])
return formatRxTx(recv, sent)
} catch {
return null
}
}
}, [filteredKeys, userSettings.unitNet])
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={dockerOrPodman(t`Docker Network I/O`, isPodman)}
description={dockerOrPodman(t`Network traffic of docker containers`, isPodman)}
cornerEl={<FilterBar />}
>
<AreaChartDefault
chartData={chartData}
customData={chartData.containerData}
dataPoints={dataPoints}
tickFormatter={(val) => {
const { value, unit } = formatBytes(val, true, userSettings.unitNet, false)
return `${toFixedFloat(value, value >= 10 ? 0 : 1)} ${unit}`
}}
contentFormatter={contentFormatter}
domain={pinnedAxisDomain()}
showTotal={true}
reverseStackOrder={true}
filter={filter}
truncate={true}
itemSorter={(a, b) => b.value - a.value}
/>
</ChartCard>
)
}

View File

@@ -0,0 +1,209 @@
import { t } from "@lingui/core/macro"
import AreaChartDefault from "@/components/charts/area-chart"
import { batteryStateTranslations } from "@/lib/i18n"
import { $temperatureFilter, $userSettings } from "@/lib/stores"
import { cn, decimalString, formatTemperature, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { ChartCard, FilterBar } from "../chart-card"
import LineChartDefault from "@/components/charts/line-chart"
import { useStore } from "@nanostores/react"
import { useRef, useMemo, useState, useEffect } from "react"
export function BatteryChart({
chartData,
grid,
dataEmpty,
maxValues,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
maxValues: boolean
}) {
const showBatteryChart = chartData.systemStats.at(-1)?.stats.bat
if (!showBatteryChart) {
return null
}
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Battery`}
description={`${t({
message: "Current state",
comment: "Context: Battery state",
})}: ${batteryStateTranslations[chartData.systemStats.at(-1)?.stats.bat?.[1] ?? 0]()}`}
>
<AreaChartDefault
chartData={chartData}
maxToggled={maxValues}
dataPoints={[
{
label: t`Charge`,
dataKey: ({ stats }) => stats?.bat?.[0],
color: 1,
opacity: 0.35,
},
]}
domain={[0, 100]}
tickFormatter={(val) => `${val}%`}
contentFormatter={({ value }) => `${value}%`}
/>
</ChartCard>
)
}
export function TemperatureChart({
chartData,
grid,
dataEmpty,
setPageBottomExtraMargin,
}: {
chartData: ChartData
grid: boolean
dataEmpty: boolean
setPageBottomExtraMargin?: (margin: number) => void
}) {
const showTempChart = chartData.systemStats.at(-1)?.stats.t
const filter = useStore($temperatureFilter)
const userSettings = useStore($userSettings)
const statsRef = useRef(chartData.systemStats)
statsRef.current = chartData.systemStats
// Derive sensor names key from latest data point
let sensorNamesKey = ""
for (let i = chartData.systemStats.length - 1; i >= 0; i--) {
const t = chartData.systemStats[i].stats?.t
if (t) {
sensorNamesKey = Object.keys(t).sort().join("\0")
break
}
}
// Only recompute colors and dataKey functions when sensor names change
const { colorMap, dataKeys, sortedKeys } = useMemo(() => {
const stats = statsRef.current
const tempSums = {} as Record<string, number>
for (const data of stats) {
const t = data.stats?.t
if (!t) continue
for (const key of Object.keys(t)) {
tempSums[key] = (tempSums[key] ?? 0) + t[key]
}
}
const sorted = Object.keys(tempSums).sort((a, b) => tempSums[b] - tempSums[a])
const colorMap = {} as Record<string, string>
const dataKeys = {} as Record<string, (d: SystemStatsRecord) => number | undefined>
for (let i = 0; i < sorted.length; i++) {
const key = sorted[i]
colorMap[key] = `hsl(${((i * 360) / sorted.length) % 360}, 60%, 55%)`
dataKeys[key] = (d: SystemStatsRecord) => d.stats?.t?.[key]
}
return { colorMap, dataKeys, sortedKeys: sorted }
}, [sensorNamesKey])
const dataPoints = useMemo(() => {
return sortedKeys.map((key) => {
const filterTerms = filter
? filter
.toLowerCase()
.split(" ")
.filter((term) => term.length > 0)
: []
const filtered = filterTerms.length > 0 && !filterTerms.some((term) => key.toLowerCase().includes(term))
const strokeOpacity = filtered ? 0.1 : 1
return {
label: key,
dataKey: dataKeys[key],
color: colorMap[key],
opacity: strokeOpacity,
}
})
}, [sortedKeys, filter, dataKeys, colorMap])
// test with lots of data points
// const totalPoints = 50
// if (dataPoints.length > 0 && dataPoints.length < totalPoints) {
// let i = 0
// while (dataPoints.length < totalPoints) {
// dataPoints.push({
// label: `Test ${++i}`,
// dataKey: () => 0,
// color: "red",
// opacity: 1,
// })
// }
// }
const chartRef = useRef<HTMLDivElement>(null)
const [addMargin, setAddMargin] = useState(false)
const marginPx = (dataPoints.length - 13) * 18
useEffect(() => {
if (setPageBottomExtraMargin && dataPoints.length > 13 && chartRef.current) {
const checkPosition = () => {
if (!chartRef.current) return
const rect = chartRef.current.getBoundingClientRect()
const actualScrollHeight = addMargin
? document.documentElement.scrollHeight - marginPx
: document.documentElement.scrollHeight
const distanceToBottom = actualScrollHeight - (rect.bottom + window.scrollY)
if (distanceToBottom < 250) {
setAddMargin(true)
setPageBottomExtraMargin(marginPx)
} else {
setAddMargin(false)
setPageBottomExtraMargin(0)
}
}
checkPosition()
const timer = setTimeout(checkPosition, 500)
return () => {
clearTimeout(timer)
}
} else if (addMargin) {
setAddMargin(false)
if (setPageBottomExtraMargin) setPageBottomExtraMargin(0)
}
}, [dataPoints.length, addMargin, marginPx, setPageBottomExtraMargin])
if (!showTempChart) {
return null
}
const legend = dataPoints.length < 12
return (
<div ref={chartRef} className={cn("odd:last-of-type:col-span-full", { "col-span-full": !grid })}>
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`Temperature`}
description={t`Temperatures of system sensors`}
cornerEl={<FilterBar store={$temperatureFilter} />}
legend={legend}
>
<LineChartDefault
chartData={chartData}
itemSorter={(a, b) => b.value - a.value}
domain={["auto", "auto"]}
legend={legend}
tickFormatter={(val) => {
const { value, unit } = formatTemperature(val, userSettings.unitTemp)
return `${toFixedFloat(value, 2)} ${unit}`
}}
contentFormatter={(item) => {
const { value, unit } = formatTemperature(item.value, userSettings.unitTemp)
return `${decimalString(value)} ${unit}`
}}
dataPoints={dataPoints}
></LineChartDefault>
</ChartCard>
</div>
)
}

View File

@@ -1,14 +1,14 @@
import { t } from "@lingui/core/macro"
import { MoreHorizontalIcon } from "lucide-react"
import { memo, useRef, useState } from "react"
import AreaChartDefault, { DataPoint } from "@/components/charts/area-chart"
import AreaChartDefault, { type DataPoint } from "@/components/charts/area-chart"
import ChartTimeSelect from "@/components/charts/chart-time-select"
import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { DialogTitle } from "@/components/ui/dialog"
import { compareSemVer, decimalString, parseSemVer, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { ChartCard } from "../system"
import { ChartCard } from "./chart-card"
const minAgentVersion = parseSemVer("0.15.3")
@@ -42,41 +42,54 @@ export default memo(function CpuCoresSheet({
const numCores = cpus.length
const hasBreakdown = (latest?.cpub?.length ?? 0) > 0
// make sure all individual core data points have the same y axis domain to make relative comparison easier
let highestCpuCorePct = 1
if (hasOpened.current) {
for (let i = 0; i < numCores; i++) {
for (let j = 0; j < chartData.systemStats.length; j++) {
const pct = chartData.systemStats[j].stats?.cpus?.[i] ?? 0
if (pct > highestCpuCorePct) {
highestCpuCorePct = pct
}
}
}
}
const breakdownDataPoints = [
{
label: "System",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[1],
color: 3,
opacity: 0.35,
stackId: "a"
stackId: "a",
},
{
label: "User",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[0],
color: 1,
opacity: 0.35,
stackId: "a"
stackId: "a",
},
{
label: "IOWait",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[2],
color: 4,
opacity: 0.35,
stackId: "a"
stackId: "a",
},
{
label: "Steal",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[3],
color: 5,
opacity: 0.35,
stackId: "a"
stackId: "a",
},
{
label: "Idle",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[4],
color: 2,
opacity: 0.35,
stackId: "a"
stackId: "a",
},
{
label: t`Other`,
@@ -86,11 +99,10 @@ export default memo(function CpuCoresSheet({
},
color: `hsl(80, 65%, 52%)`,
opacity: 0.35,
stackId: "a"
stackId: "a",
},
] as DataPoint[]
return (
<Sheet open={cpuCoresOpen} onOpenChange={setCpuCoresOpen}>
<DialogTitle className="sr-only">{t`CPU Usage`}</DialogTitle>
@@ -99,7 +111,7 @@ export default memo(function CpuCoresSheet({
title={t`View more`}
variant="outline"
size="icon"
className="shrink-0 max-sm:absolute max-sm:top-3 max-sm:end-3"
className="shrink-0 max-sm:absolute max-sm:top-0 max-sm:end-0"
>
<MoreHorizontalIcon />
</Button>
@@ -151,7 +163,7 @@ export default memo(function CpuCoresSheet({
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpus?.[i] ?? 1 / (stats?.cpus?.length ?? 1),
color: `hsl(${226 + (((i * 360) / Math.max(1, numCores)) % 360)}, var(--chart-saturation), var(--chart-lightness))`,
opacity: 0.35,
stackId: "a"
stackId: "a",
}))}
tickFormatter={(val) => `${val}%`}
contentFormatter={({ value }) => `${value}%`}
@@ -174,7 +186,7 @@ export default memo(function CpuCoresSheet({
<AreaChartDefault
chartData={chartData}
maxToggled={maxValues}
legend={false}
domain={[0, highestCpuCorePct]}
dataPoints={[
{
label: t`Usage`,

View File

@@ -1,20 +1,28 @@
import { plural } from "@lingui/core/macro"
import { useLingui } from "@lingui/react/macro"
import { Trans, useLingui } from "@lingui/react/macro"
import {
AppleIcon,
ChevronRightSquareIcon,
ClockArrowUp,
CpuIcon,
GlobeIcon,
LayoutGridIcon,
MemoryStickIcon,
MonitorIcon,
Rows,
Settings2Icon,
} from "lucide-react"
import { useMemo } from "react"
import ChartTimeSelect from "@/components/charts/chart-time-select"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { FreeBsdIcon, TuxIcon, WebSocketIcon, WindowsIcon } from "@/components/ui/icons"
import { Separator } from "@/components/ui/separator"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
@@ -27,12 +35,16 @@ export default function InfoBar({
chartData,
grid,
setGrid,
displayMode,
setDisplayMode,
details,
}: {
system: SystemRecord
chartData: ChartData
grid: boolean
setGrid: (grid: boolean) => void
displayMode: "default" | "tabs"
setDisplayMode: (mode: "default" | "tabs") => void
details: SystemDetailsRecord | null
}) {
const { t } = useLingui()
@@ -123,10 +135,10 @@ export default function InfoBar({
return (
<Card>
<div className="grid xl:flex gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
<div>
<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="grid xl:flex xl:gap-4 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
<div className="min-w-0">
<h1 className="text-2xl sm:text-[1.6rem] font-semibold mb-1.5">{system.name}</h1>
<div className="flex xl:flex-wrap items-center py-4 xl:p-0 -mt-3 xl:mt-1 gap-3 text-sm text-nowrap opacity-90 overflow-x-auto scrollbar-hide -mx-4 px-4 xl:mx-0">
<Tooltip>
<TooltipTrigger asChild>
<div className="capitalize flex gap-2 items-center">
@@ -190,24 +202,53 @@ export default function InfoBar({
</div>
<div className="xl:ms-auto flex items-center gap-2 max-sm:-mb-1">
<ChartTimeSelect className="w-full xl:w-40" agentVersion={chartData.agentVersion} />
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
aria-label={t`Toggle grid`}
aria-label={t`Settings`}
variant="outline"
size="icon"
className="hidden xl:flex p-0 text-primary"
onClick={() => setGrid(!grid)}
>
{grid ? (
<LayoutGridIcon className="h-[1.2rem] w-[1.2rem] opacity-75" />
) : (
<Rows className="h-[1.3rem] w-[1.3rem] opacity-75" />
)}
<Settings2Icon className="size-4 opacity-90" />
</Button>
</TooltipTrigger>
<TooltipContent>{t`Toggle grid`}</TooltipContent>
</Tooltip>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-44">
<DropdownMenuLabel className="px-3.5">
<Trans context="Layout display options">Display</Trans>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
className="px-1 pb-1"
value={displayMode}
onValueChange={(v) => setDisplayMode(v as "default" | "tabs")}
>
<DropdownMenuRadioItem value="default" onSelect={(e) => e.preventDefault()}>
<Trans context="Default system layout option">Default</Trans>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="tabs" onSelect={(e) => e.preventDefault()}>
<Trans context="Tabs system layout option">Tabs</Trans>
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
<DropdownMenuSeparator />
<DropdownMenuLabel className="px-3.5">
<Trans>Chart width</Trans>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
className="px-1 pb-1"
value={grid ? "grid" : "full"}
onValueChange={(v) => setGrid(v === "grid")}
>
<DropdownMenuRadioItem value="grid" onSelect={(e) => e.preventDefault()}>
<Trans>Grid</Trans>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="full" onSelect={(e) => e.preventDefault()}>
<Trans>Full</Trans>
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</Card>

View File

@@ -0,0 +1,36 @@
import { lazy } from "react"
import { useIntersectionObserver } from "@/lib/use-intersection-observer"
import { cn } from "@/lib/utils"
const ContainersTable = lazy(() => import("../../containers-table/containers-table"))
export function LazyContainersTable({ systemId }: { systemId: string }) {
const { isIntersecting, ref } = useIntersectionObserver({ rootMargin: "90px" })
return (
<div ref={ref} className={cn(isIntersecting && "contents")}>
{isIntersecting && <ContainersTable systemId={systemId} />}
</div>
)
}
const SmartTable = lazy(() => import("./smart-table"))
export function LazySmartTable({ systemId }: { systemId: string }) {
const { isIntersecting, ref } = useIntersectionObserver({ rootMargin: "90px" })
return (
<div ref={ref} className={cn(isIntersecting && "contents")}>
{isIntersecting && <SmartTable systemId={systemId} />}
</div>
)
}
const SystemdTable = lazy(() => import("../../systemd-table/systemd-table"))
export function LazySystemdTable({ systemId }: { systemId: string }) {
const { isIntersecting, ref } = useIntersectionObserver()
return (
<div ref={ref} className={cn(isIntersecting && "contents")}>
{isIntersecting && <SystemdTable systemId={systemId} />}
</div>
)
}

View File

@@ -11,7 +11,7 @@ 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"
import { ChartCard } from "./chart-card"
export default memo(function NetworkSheet({
chartData,
@@ -46,7 +46,7 @@ export default memo(function NetworkSheet({
title={t`View more`}
variant="outline"
size="icon"
className="shrink-0 max-sm:absolute max-sm:top-3 max-sm:end-3"
className="shrink-0 max-sm:absolute max-sm:top-0 max-sm:end-0"
>
<MoreHorizontalIcon />
</Button>

View File

@@ -146,7 +146,9 @@ export const createColumns = (
{
accessorKey: "model",
sortingFn: (a, b) => a.original.model.localeCompare(b.original.model),
header: ({ column }) => <HeaderButton column={column} name={t`Model`} Icon={Box} />,
header: ({ column }) => (
<HeaderButton column={column} name={t({ message: "Model", comment: "Device model" })} Icon={Box} />
),
cell: ({ getValue }) => (
<div
className="max-w-48 truncate ms-1"
@@ -532,9 +534,9 @@ export default function DisksTable({ systemId }: { systemId?: string }) {
return (
<div>
<Card className="p-6 @container w-full">
<CardHeader className="p-0 mb-4">
<div className="grid md:flex gap-5 w-full items-end">
<Card className="@container w-full px-3 py-5 sm:py-6 sm:px-6">
<CardHeader className="p-0 mb-3 sm:mb-4">
<div className="grid md:flex gap-x-5 gap-y-3 w-full items-end">
<div className="px-2 sm:px-1">
<CardTitle className="mb-2">S.M.A.R.T.</CardTitle>
<CardDescription className="flex">
@@ -620,11 +622,13 @@ const SmartDevicesTable = memo(function SmartDevicesTable({
return <SmartDeviceTableRow key={row.id} row={row} virtualRow={virtualRow} openSheet={openSheet} />
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-24 text-center pointer-events-none">
{data ? t`No results.` : <LoaderCircleIcon className="animate-spin size-10 opacity-60 mx-auto" />}
</TableCell>
</TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
{data ? (
<Trans>No results.</Trans>
) : (
<LoaderCircleIcon className="animate-spin size-10 opacity-60 mx-auto" />
)}
</TableCell>
)}
</TableBody>
</table>
@@ -636,7 +640,6 @@ const SmartDevicesTable = memo(function SmartDevicesTable({
function SmartTableHead({ table }: { table: TableType<SmartDeviceRecord> }) {
return (
<TableHeader className="sticky top-0 z-50 w-full border-b-2">
<div className="absolute -top-2 left-0 w-full h-4 bg-table-header z-50"></div>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (

View File

@@ -0,0 +1,344 @@
import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router"
import { subscribeKeys } from "nanostores"
import { useEffect, useMemo, useRef, useState } from "react"
import { useContainerChartConfigs } from "@/components/charts/hooks"
import { pb } from "@/lib/api"
import { SystemStatus } from "@/lib/enums"
import {
$allSystemsById,
$allSystemsByName,
$chartTime,
$containerFilter,
$direction,
$maxValues,
$systems,
$userSettings,
} from "@/lib/stores"
import { chartTimeData, listen, parseSemVer, useBrowserStorage } from "@/lib/utils"
import type {
ChartData,
ContainerStatsRecord,
SystemDetailsRecord,
SystemInfo,
SystemRecord,
SystemStats,
SystemStatsRecord,
} from "@/types"
import { $router, navigate } from "../../router"
import { appendData, cache, getStats, getTimeData, makeContainerData, makeContainerPoint } from "./chart-data"
export function useSystemData(id: string) {
const direction = useStore($direction)
const systems = useStore($systems)
const chartTime = useStore($chartTime)
const maxValues = useStore($maxValues)
const [grid, setGrid] = useBrowserStorage("grid", true)
const [displayMode, setDisplayMode] = useBrowserStorage<"default" | "tabs">("displayMode", "default")
const [activeTab, setActiveTabRaw] = useState("core")
const [mountedTabs, setMountedTabs] = useState(() => new Set<string>(["core"]))
const tabsRef = useRef<string[]>(["core", "disk"])
function setActiveTab(tab: string) {
setActiveTabRaw(tab)
setMountedTabs((prev) => (prev.has(tab) ? prev : new Set([...prev, tab])))
}
const [system, setSystem] = useState({} as SystemRecord)
const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[])
const [containerData, setContainerData] = useState([] as ChartData["containerData"])
const persistChartTime = useRef(false)
const statsRequestId = useRef(0)
const [chartLoading, setChartLoading] = useState(true)
const [details, setDetails] = useState<SystemDetailsRecord>({} as SystemDetailsRecord)
useEffect(() => {
return () => {
if (!persistChartTime.current) {
$chartTime.set($userSettings.get().chartTime)
}
persistChartTime.current = false
setSystemStats([])
setContainerData([])
setDetails({} as SystemDetailsRecord)
$containerFilter.set("")
}
}, [id])
// find matching system and update when it changes
useEffect(() => {
if (!systems.length) {
return
}
// allow old system-name slug to work
const store = $allSystemsById.get()[id] ? $allSystemsById : $allSystemsByName
return subscribeKeys(store, [id], (newSystems) => {
const sys = newSystems[id]
if (sys) {
setSystem(sys)
document.title = `${sys?.name} / Beszel`
}
})
}, [id, systems.length])
// hide 1m chart time if system agent version is less than 0.13.0
useEffect(() => {
if (parseSemVer(system?.info?.v) < parseSemVer("0.13.0")) {
$chartTime.set("1h")
}
}, [system?.info?.v])
// fetch system details
useEffect(() => {
// if system.info.m exists, agent is old version without system details
if (!system.id || system.info?.m) {
return
}
pb.collection<SystemDetailsRecord>("system_details")
.getOne(system.id, {
fields: "hostname,kernel,cores,threads,cpu,os,os_name,arch,memory,podman",
headers: {
"Cache-Control": "public, max-age=60",
},
})
.then(setDetails)
}, [system.id])
// subscribe to realtime metrics if chart time is 1m
useEffect(() => {
let unsub = () => {}
if (!system.id || chartTime !== "1m") {
return
}
if (system.status !== SystemStatus.Up || parseSemVer(system?.info?.v).minor < 13) {
$chartTime.set("1h")
return
}
let isFirst = true
pb.realtime
.subscribe(
`rt_metrics`,
(data: { container: ContainerStatsRecord[]; info: SystemInfo; stats: SystemStats }) => {
const now = Date.now()
const statsPoint = { created: now, stats: data.stats } as SystemStatsRecord
const containerPoint =
data.container?.length > 0
? makeContainerPoint(now, data.container as unknown as ContainerStatsRecord["stats"])
: null
// on first message, make sure we clear out data from other time periods
if (isFirst) {
isFirst = false
setSystemStats([statsPoint])
setContainerData(containerPoint ? [containerPoint] : [])
return
}
setSystemStats((prev) => appendData(prev, [statsPoint], 1000, 60))
if (containerPoint) {
setContainerData((prev) => appendData(prev, [containerPoint], 1000, 60))
}
},
{ query: { system: system.id } }
)
.then((us) => {
unsub = us
})
return () => {
unsub?.()
}
}, [chartTime, system.id])
const agentVersion = useMemo(() => parseSemVer(system?.info?.v), [system?.info?.v])
const chartData: ChartData = useMemo(() => {
const lastCreated = Math.max(
(systemStats.at(-1)?.created as number) ?? 0,
(containerData.at(-1)?.created as number) ?? 0
)
return {
systemStats,
containerData,
chartTime,
orientation: direction === "rtl" ? "right" : "left",
...getTimeData(chartTime, lastCreated),
agentVersion,
}
}, [systemStats, containerData, direction])
// Share chart config computation for all container charts
const containerChartConfigs = useContainerChartConfigs(containerData)
// get stats when system "changes." (Not just system to system,
// also when new info comes in via systemManager realtime connection, indicating an update)
useEffect(() => {
if (!system.id || !chartTime || chartTime === "1m") {
return
}
const systemId = system.id
const { expectedInterval } = chartTimeData[chartTime]
const ss_cache_key = `${systemId}_${chartTime}_system_stats`
const cs_cache_key = `${systemId}_${chartTime}_container_stats`
const requestId = ++statsRequestId.current
const cachedSystemStats = cache.get(ss_cache_key) as SystemStatsRecord[] | undefined
const cachedContainerData = cache.get(cs_cache_key) as ChartData["containerData"] | undefined
// Render from cache immediately if available
if (cachedSystemStats?.length) {
setSystemStats(cachedSystemStats)
setContainerData(cachedContainerData || [])
setChartLoading(false)
// Skip the fetch if the latest cached point is recent enough that no new point is expected yet
const lastCreated = cachedSystemStats.at(-1)?.created as number | undefined
if (lastCreated && Date.now() - lastCreated < expectedInterval) {
return
}
} else {
setChartLoading(true)
}
Promise.allSettled([
getStats<SystemStatsRecord>("system_stats", systemId, chartTime),
getStats<ContainerStatsRecord>("container_stats", systemId, chartTime),
]).then(([systemStats, containerStats]) => {
// If another request has been made since this one, ignore the results
if (requestId !== statsRequestId.current) {
return
}
setChartLoading(false)
// make new system stats
let systemData = (cache.get(ss_cache_key) || []) as SystemStatsRecord[]
if (systemStats.status === "fulfilled" && systemStats.value.length) {
systemData = appendData(systemData, systemStats.value, expectedInterval, 100)
cache.set(ss_cache_key, systemData)
}
setSystemStats(systemData)
// make new container stats
let containerData = (cache.get(cs_cache_key) || []) as ChartData["containerData"]
if (containerStats.status === "fulfilled" && containerStats.value.length) {
containerData = appendData(containerData, makeContainerData(containerStats.value), expectedInterval, 100)
cache.set(cs_cache_key, containerData)
}
setContainerData(containerData)
})
}, [system, chartTime])
// keyboard navigation between systems
// in tabs mode: arrow keys switch tabs, shift+arrow switches systems
// in default mode: arrow keys switch systems
useEffect(() => {
if (!systems.length) {
return
}
const handleKeyUp = (e: KeyboardEvent) => {
if (
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement ||
e.ctrlKey ||
e.metaKey ||
e.altKey
) {
return
}
const isLeft = e.key === "ArrowLeft" || e.key === "h"
const isRight = e.key === "ArrowRight" || e.key === "l"
if (!isLeft && !isRight) {
return
}
// in tabs mode, plain arrows switch tabs, shift+arrows switch systems
if (displayMode === "tabs") {
if (!e.shiftKey) {
// skip if focused in tablist (Radix handles it natively)
if (e.target instanceof HTMLElement && e.target.closest('[role="tablist"]')) {
return
}
const tabs = tabsRef.current
const currentIdx = tabs.indexOf(activeTab)
const nextIdx = isLeft ? (currentIdx - 1 + tabs.length) % tabs.length : (currentIdx + 1) % tabs.length
setActiveTab(tabs[nextIdx])
return
}
} else if (e.shiftKey) {
return
}
const currentIndex = systems.findIndex((s) => s.id === id)
if (currentIndex === -1 || systems.length <= 1) {
return
}
if (isLeft) {
const prevIndex = (currentIndex - 1 + systems.length) % systems.length
persistChartTime.current = true
setActiveTabRaw("core")
setMountedTabs(new Set(["core"]))
return navigate(getPagePath($router, "system", { id: systems[prevIndex].id }))
}
if (isRight) {
const nextIndex = (currentIndex + 1) % systems.length
persistChartTime.current = true
setActiveTabRaw("core")
setMountedTabs(new Set(["core"]))
return navigate(getPagePath($router, "system", { id: systems[nextIndex].id }))
}
}
return listen(document, "keyup", handleKeyUp)
}, [id, systems, displayMode, activeTab])
// derived values
const isLongerChart = !["1m", "1h"].includes(chartTime)
const showMax = maxValues && isLongerChart
const dataEmpty = !chartLoading && chartData.systemStats.length === 0
const lastGpus = systemStats.at(-1)?.stats?.g
const isPodman = details?.podman ?? system.info?.p ?? false
let hasGpuData = false
let hasGpuEnginesData = false
let hasGpuPowerData = false
if (lastGpus) {
hasGpuData = Object.keys(lastGpus).length > 0
for (let i = 0; i < systemStats.length && (!hasGpuEnginesData || !hasGpuPowerData); i++) {
const gpus = systemStats[i].stats?.g
if (!gpus) continue
for (const id in gpus) {
if (!hasGpuEnginesData && gpus[id].e !== undefined) {
hasGpuEnginesData = true
}
if (!hasGpuPowerData && (gpus[id].p !== undefined || gpus[id].pp !== undefined)) {
hasGpuPowerData = true
}
if (hasGpuEnginesData && hasGpuPowerData) break
}
}
}
return {
system,
systemStats,
containerData,
chartData,
containerChartConfigs,
details,
grid,
setGrid,
displayMode,
setDisplayMode,
activeTab,
setActiveTab,
mountedTabs,
tabsRef,
maxValues,
isLongerChart,
showMax,
dataEmpty,
isPodman,
lastGpus,
hasGpuData,
hasGpuEnginesData,
hasGpuPowerData,
}
}

View File

@@ -154,9 +154,9 @@ export default function SystemdTable({ systemId }: { systemId?: string }) {
}
return (
<Card className="p-6 @container w-full">
<CardHeader className="p-0 mb-4">
<div className="grid md:flex gap-5 w-full items-end">
<Card className="@container w-full px-3 py-5 sm:py-6 sm:px-6">
<CardHeader className="p-0 mb-3 sm:mb-4">
<div className="grid md:flex gap-x-5 gap-y-3 w-full items-end">
<div className="px-2 sm:px-1">
<CardTitle className="mb-2">
<Trans>Systemd Services</Trans>
@@ -614,7 +614,6 @@ function SystemdSheet({
function SystemdTableHead({ table }: { table: TableType<SystemdRecord> }) {
return (
<TableHeader className="sticky top-0 z-50 w-full border-b-2">
<div className="absolute -top-2 left-0 w-full h-4 bg-table-header z-50"></div>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {

View File

@@ -184,7 +184,8 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<Syste
accessorFn: ({ info }) => info.dp || undefined,
id: "disk",
name: () => t`Disk`,
cell: DiskCellWithMultiple,
cell: (info: CellContext<SystemRecord, unknown>) =>
info.row.original.info.efs ? DiskCellWithMultiple(info) : TableCellWithMeter(info),
Icon: HardDriveIcon,
header: sortableHeader,
},
@@ -479,11 +480,6 @@ function DiskCellWithMultiple(info: CellContext<SystemRecord, unknown>) {
const { colorWarn = 65, colorCrit = 90 } = useStore($userSettings, { keys: ["colorWarn", "colorCrit"] })
const { info: sysInfo, status, id } = info.row.original
const extraFs = Object.entries(sysInfo.efs ?? {})
if (extraFs.length === 0) {
return TableCellWithMeter(info)
}
const rootDiskPct = sysInfo.dp
// sort extra disks by percentage descending

View File

@@ -134,8 +134,8 @@ export default function SystemsTable() {
const CardHead = useMemo(() => {
return (
<CardHeader className="pb-4.5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
<div className="grid md:flex gap-5 w-full items-end">
<CardHeader className="p-0 mb-3 sm:mb-4">
<div className="grid md:flex gap-x-5 gap-y-3 w-full items-end">
<div className="px-2 sm:px-1">
<CardTitle className="mb-2">
<Trans>All Systems</Trans>
@@ -302,29 +302,27 @@ export default function SystemsTable() {
])
return (
<Card>
<Card className="w-full px-3 py-5 sm:py-6 sm:px-6">
{CardHead}
<div className="p-6 pt-0 max-sm:py-3 max-sm:px-2">
{viewMode === "table" ? (
// table layout
<div className="rounded-md">
<AllSystemsTable table={table} rows={rows} colLength={visibleColumns.length} />
</div>
) : (
// grid layout
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{rows?.length ? (
rows.map((row) => {
return <SystemCard key={row.original.id} row={row} table={table} colLength={visibleColumns.length} />
})
) : (
<div className="col-span-full text-center py-8">
<Trans>No systems found.</Trans>
</div>
)}
</div>
)}
</div>
{viewMode === "table" ? (
// table layout
<div className="rounded-md">
<AllSystemsTable table={table} rows={rows} colLength={visibleColumns.length} />
</div>
) : (
// grid layout
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{rows?.length ? (
rows.map((row) => {
return <SystemCard key={row.original.id} row={row} table={table} colLength={visibleColumns.length} />
})
) : (
<div className="col-span-full text-center py-8">
<Trans>No systems found.</Trans>
</div>
)}
</div>
)}
</Card>
)
}
@@ -391,7 +389,6 @@ function SystemsTableHead({ table }: { table: TableType<SystemRecord> }) {
const { t } = useLingui()
return (
<TableHeader className="sticky top-0 z-50 w-full border-b-2">
<div className="absolute -top-2 left-0 w-full h-4 bg-table-header z-50"></div>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {

View File

@@ -18,7 +18,11 @@ CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3 ref={ref} className={cn("text-2xl font-semibold leading-none tracking-tight", className)} {...props} />
<h3
ref={ref}
className={cn("text-[1.4em] sm:text-2xl font-semibold leading-none tracking-tight", className)}
{...props}
/>
)
)
CardTitle.displayName = "CardTitle"

View File

@@ -25,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 data-[state=open]:bg-accent/70",
"flex select-none items-center rounded-sm px-2.5 py-1.5 text-[.95em] outline-hidden focus:bg-accent/70 data-[state=open]:bg-accent/70",
inset && "ps-8",
className
)}
@@ -79,7 +79,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"cursor-pointer relative flex select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
"cursor-pointer relative flex select-none items-center rounded-sm px-2.5 py-1.5 text-[.95em] outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
inset && "ps-8",
className
)}
@@ -95,7 +95,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2.5 text-[.95em] outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className
)}
checked={checked}
@@ -118,7 +118,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 ps-8 pe-2.5 text-[.95em] outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className
)}
{...props}
@@ -141,7 +141,7 @@ const DropdownMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn("px-2.5 py-1.5 text-sm font-semibold", inset && "ps-8", className)}
className={cn("px-2.5 py-1.5 text-[.95em] font-semibold", inset && "ps-8", className)}
{...props}
/>
))

View File

@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs cursor-pointer",
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs cursor-pointer hover:text-foreground",
className
)}
{...props}

View File

@@ -147,6 +147,12 @@
button {
cursor: pointer;
}
/* cosmetic patch for half pixel gap in table headers when scrolling content shows at top */
thead.sticky:before {
content: "";
@apply absolute -top-2 left-0 w-full h-4 bg-table-header z-50
}
}
@utility container {
@@ -163,6 +169,14 @@
min-width: 30.3rem;
}
@utility scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.recharts-tooltip-wrapper {
z-index: 51;
@apply tabular-nums;

View File

@@ -1,5 +1,5 @@
import { atom, computed, listenKeys, map, type ReadableAtom } from "nanostores"
import type { AlertMap, ChartTimes, SystemRecord, UserSettings } from "@/types"
import type { AlertMap, ChartTimes, SystemRecord, UpdateInfo, UserSettings } from "@/types"
import { pb } from "./api"
import { Unit } from "./enums"
@@ -28,6 +28,9 @@ export const $alerts = map<AlertMap>({})
/** SSH public key */
export const $publicKey = atom("")
/** New version info if an update is available, otherwise undefined */
export const $newVersion = atom<UpdateInfo | undefined>()
/** Chart time period */
export const $chartTime = atom<ChartTimes>("1h")

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ar\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1 ساعة"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "دقيقة واحدة"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 ساعة"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 دقيقة"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 يومًا"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 دقائق"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "الحالة النشطة"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "إضافة {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "إضافة <0>نظام</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "إضافة نظام"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "إضافة رابط"
@@ -134,6 +135,7 @@ msgstr "تعديل عرض التخطيط الرئيسي"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "مسؤول"
@@ -163,6 +165,7 @@ msgstr "التنبيهات"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "جميع الحاويات"
@@ -188,11 +191,11 @@ msgstr "هل أنت متأكد؟"
msgid "Automatic copy requires a secure context."
msgstr "النسخ التلقائي يتطلب سياقًا آمنًا."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "متوسط"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "متوسط استخدام وحدة المعالجة المركزية للحاويات"
@@ -206,20 +209,20 @@ msgstr "المتوسط ينخفض أقل من <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "المتوسط يتجاوز <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "متوسط ​​استهلاك طاقة وحدة معالجة الرسوميات"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "متوسط استخدام وحدة المعالجة المركزية على مستوى النظام"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "متوسط ​​استخدام {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "متوسط استغلال محركات GPU"
@@ -228,7 +231,7 @@ msgstr "متوسط استغلال محركات GPU"
msgid "Backups"
msgstr "النسخ الاحتياطية"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "عرض النطاق الترددي"
@@ -238,7 +241,7 @@ msgstr "عرض النطاق الترددي"
msgid "Bat"
msgstr "بطارية"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "البطارية"
@@ -288,7 +291,7 @@ msgstr "حالة التمهيد"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "بايت (كيلوبايت/ثانية، ميجابايت/ثانية، جيجابايت/ثانية)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "ذاكرة التخزين المؤقت / المخازن المؤقتة"
@@ -334,7 +337,7 @@ msgstr "تغيير وحدات عرض المقاييس."
msgid "Change general application options."
msgstr "تغيير خيارات التطبيق العامة."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "الشحن"
@@ -347,6 +350,10 @@ msgstr "قيد الشحن"
msgid "Chart options"
msgstr "خيارات الرسم البياني"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "عرض الرسم البياني"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "تحقق من {email} للحصول على رابط إعادة التعيين."
@@ -407,6 +414,10 @@ msgstr "التعارضات"
msgid "Connection is down"
msgstr "الاتصال مقطوع"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "الحاويات"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,6 +473,11 @@ msgstr "انسخ محتوى <0>docker-compose.yml</0> للوكيل أدناه،
msgid "Copy YAML"
msgstr "نسخ YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -484,8 +500,8 @@ msgstr "وقت المعالج"
msgid "CPU Time Breakdown"
msgstr "تفصيل وقت المعالج"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "الرفع التراكمي"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "الحالة الحالية"
@@ -531,6 +547,11 @@ msgstr "الدورات"
msgid "Daily"
msgstr "يوميًا"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "الفترة الزمنية الافتراضية"
@@ -563,11 +584,12 @@ msgstr "الجهاز"
msgid "Discharging"
msgstr "قيد التفريغ"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "القرص"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "إدخال/إخراج القرص"
@@ -575,25 +597,31 @@ msgstr "إدخال/إخراج القرص"
msgid "Disk unit"
msgstr "وحدة القرص"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "استخدام القرص"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "استخدام القرص لـ {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "استخدام المعالج للدوكر"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "استخدام الذاكرة للدوكر"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "إدخال/إخراج الشبكة للدوكر"
@@ -770,7 +798,7 @@ msgstr "فشل: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "أمر FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "ممتلئة"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "عالمي"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "معالج الرسوميات"
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "محركات GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "استهلاك طاقة وحدة معالجة الرسوميات"
@@ -826,6 +859,7 @@ msgstr "استهلاك طاقة وحدة معالجة الرسوميات"
msgid "GPU Usage"
msgstr "استخدام وحدة معالجة الرسوميات"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "شبكة"
@@ -912,7 +946,7 @@ msgstr "دورة الحياة"
msgid "limit"
msgstr "الحد"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "متوسط التحميل"
@@ -941,6 +975,7 @@ msgstr "حالة التحميل"
msgid "Loading..."
msgstr "جاري التحميل..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "تسجيل الخروج"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "تعليمات الإعداد اليدوي"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "الحد الأقصى دقيقة"
@@ -999,15 +1034,16 @@ msgstr "حد الذاكرة"
msgid "Memory Peak"
msgstr "ذروة الذاكرة"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "استخدام الذاكرة"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "استخدام الذاكرة لحاويات دوكر"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "الموديل"
@@ -1025,11 +1061,11 @@ msgstr "الاسم"
msgid "Net"
msgstr "الشبكة"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "حركة مرور الشبكة لحاويات الدوكر"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "يرجى تسجيل الدخول إلى حسابك"
msgid "Port"
msgstr "المنفذ"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "تشغيل الطاقة"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "الاستخدام الدقيق في الوقت المسجل"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "ساعات الهدوء"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "قراءة"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "تم الاستلام"
@@ -1326,6 +1367,10 @@ msgstr "تفاصيل S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "اختبار S.M.A.R.T. الذاتي"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "حفظ {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "احفظ العنوان باستخدام مفتاح الإدخال أو الفاصلة. اتركه فارغًا لتعطيل إشعارات البريد الإشباكي."
@@ -1335,10 +1380,6 @@ msgstr "احفظ العنوان باستخدام مفتاح الإدخال أو
msgid "Save Settings"
msgstr "حفظ الإعدادات"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "احفظ النظام"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "محفوظ في قاعدة البيانات ولا ينتهي حتى تقوم بتعطيله."
@@ -1387,7 +1428,7 @@ msgstr "أرسل pings صادرة دورية إلى خدمة مراقبة خار
msgid "Send test heartbeat"
msgstr "إرسال نبضة قلب اختبارية"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "تم الإرسال"
@@ -1399,6 +1440,7 @@ msgstr "الرقم التسلسلي"
msgid "Service Details"
msgstr "تفاصيل الخدمة"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "الخدمات"
@@ -1414,8 +1456,10 @@ msgstr "قم بتعيين متغيرات البيئة التالية على مر
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "الإعدادات"
@@ -1459,17 +1503,18 @@ msgstr "الحالة"
msgid "Sub State"
msgstr "الحالة الفرعية"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "مساحة التبديل المستخدمة من قبل النظام"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "استخدام التبديل"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "استخدام التبديل"
msgid "System"
msgstr "النظام"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "متوسط تحميل النظام مع مرور الوقت"
@@ -1501,6 +1546,11 @@ msgstr "يمكن إدارة الأنظمة في ملف <0>config.yml</0> داخ
msgid "Table"
msgstr "جدول"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "المهام"
@@ -1511,7 +1561,7 @@ msgstr "المهام"
msgid "Temp"
msgstr "درجة الحرارة"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "درجة الحرارة"
@@ -1520,7 +1570,7 @@ msgstr "درجة الحرارة"
msgid "Temperature unit"
msgstr "وحدة درجة الحرارة"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "درجات حرارة مستشعرات النظام"
@@ -1552,11 +1602,11 @@ msgstr "لا يمكن التراجع عن هذا الإجراء. سيؤدي ذل
msgid "This will permanently delete all selected records from the database."
msgstr "سيؤدي هذا إلى حذف جميع السجلات المحددة من قاعدة البيانات بشكل دائم."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "معدل نقل {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "معدل نقل نظام الملفات الجذر"
@@ -1568,11 +1618,6 @@ msgstr "تنسيق الوقت"
msgid "To email(s)"
msgstr "إلى البريد الإشباكي"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "تبديل الشبكة"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1730,20 +1775,20 @@ msgstr "رفع"
msgid "Uptime"
msgstr "مدة التشغيل"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "الاستخدام"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "استخدام القسم الجذر"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "مستخدم"
@@ -1773,7 +1818,7 @@ msgstr "عرض أحدث 200 تنبيه."
msgid "Visible Fields"
msgstr "الأعمدة الظاهرة"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "في انتظار وجود سجلات كافية للعرض"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "أمر ويندوز"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "كتابة"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: bg\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1 час"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 минута"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 часа"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 минути"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 дни"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 минути"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Активно състояние"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Добави {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Добави <0>Система</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Добави система"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Добави URL"
@@ -134,6 +135,7 @@ msgstr "Настройка ширината на основния макет"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Администратор"
@@ -163,6 +165,7 @@ msgstr "Тревоги"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Всички контейнери"
@@ -188,11 +191,11 @@ msgstr "Сигурни ли сте?"
msgid "Automatic copy requires a secure context."
msgstr "Автоматичното копиране изисква защитен контескт."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Средно"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Средно използване на процесора на контейнерите"
@@ -206,20 +209,20 @@ msgstr "Средната стойност пада под <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Средната стойност надхвърля <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Средна консумация на ток от графични карти"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Средно използване на процесора на цялата система"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Средно използване на {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Средно използване на GPU двигатели"
@@ -228,7 +231,7 @@ msgstr "Средно използване на GPU двигатели"
msgid "Backups"
msgstr "Архиви"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Bandwidth на мрежата"
@@ -238,7 +241,7 @@ msgstr "Bandwidth на мрежата"
msgid "Bat"
msgstr "Бат"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Батерия"
@@ -288,7 +291,7 @@ msgstr "Състояние при зареждане"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Байта (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Кеш / Буфери"
@@ -334,7 +337,7 @@ msgstr "Промяна на единиците за показване на ме
msgid "Change general application options."
msgstr "Смени общите опции на приложението."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Заряд"
@@ -347,6 +350,10 @@ msgstr "Зареждане"
msgid "Chart options"
msgstr "Опции на диаграмата"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Ширина на графиката"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Провери {email} за линк за нулиране."
@@ -357,7 +364,7 @@ msgstr "Провери log-овете за повече информация."
#: src/components/routes/settings/heartbeat.tsx
msgid "Check your monitoring service"
msgstr "Проверете вашата услуга за мониторинг"
msgstr "Проверете мониторинг услугата си"
#: src/components/routes/settings/notifications.tsx
msgid "Check your notification service"
@@ -407,6 +414,10 @@ msgstr "Конфликти"
msgid "Connection is down"
msgstr "Връзката е прекъсната"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Контейнери"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,6 +473,11 @@ msgstr "Копирайте съдържанието на<0>docker-compose.yml</0
msgid "Copy YAML"
msgstr "Копирай YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -484,8 +500,8 @@ msgstr "Време на CPU"
msgid "CPU Time Breakdown"
msgstr "Разбивка на времето на CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Кумулативно качване"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Текущо състояние"
@@ -531,6 +547,11 @@ msgstr "Цикли"
msgid "Daily"
msgstr "Дневно"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Времеви диапазон по подразбиране"
@@ -563,11 +584,12 @@ msgstr "Устройство"
msgid "Discharging"
msgstr "Разреждане"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Диск"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Диск I/O"
@@ -575,25 +597,31 @@ msgstr "Диск I/O"
msgid "Disk unit"
msgstr "Единица за диск"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Използване на диск"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Изполване на диск от {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Използване на процесор от docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Изполване на памет от docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Мрежов I/O използван от docker"
@@ -770,7 +798,7 @@ msgstr "Неуспешни: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD команда"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Пълна"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "Глобален"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU двигатели"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Консумация на ток от графична карта"
@@ -826,6 +859,7 @@ msgstr "Консумация на ток от графична карта"
msgid "GPU Usage"
msgstr "Употреба на GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Мрежово"
@@ -836,7 +870,7 @@ msgstr "Здраве"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -912,7 +946,7 @@ msgstr "Жизнен цикъл"
msgid "limit"
msgstr "лимит"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Средно натоварване"
@@ -941,6 +975,7 @@ msgstr "Състояние на зареждане"
msgid "Loading..."
msgstr "Зареждане..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Изход"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Инструкции за ръчна настройка"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Максимум 1 минута"
@@ -999,15 +1034,16 @@ msgstr "Лимит на памет"
msgid "Memory Peak"
msgstr "Пик на памет"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Употреба на паметта"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Използването на памет от docker контейнерите"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Модел"
@@ -1025,11 +1061,11 @@ msgstr "Име"
msgid "Net"
msgstr "Мрежа"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Мрежов трафик на docker контейнери"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "Моля влез в акаунта ти"
msgid "Port"
msgstr "Порт"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Включване"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Точно използване в записаното време"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Тихи часове"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Прочети"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Получени"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T. Детайли"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Самотест"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Запази {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Запази адреса с enter или запетая. Остави празно за да изключиш нотификациите чрез имейл."
@@ -1335,10 +1380,6 @@ msgstr "Запази адреса с enter или запетая. Остави
msgid "Save Settings"
msgstr "Запази настройките"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Запази система"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Запазен е в базата данни и не изтича, докато не го деактивирате."
@@ -1377,7 +1418,7 @@ msgstr "Избери {foo}"
#: src/components/routes/settings/heartbeat.tsx
msgid "Send a single heartbeat ping to verify your endpoint is working."
msgstr "Изпратете единичен heartbeat пинг, за да проверите дали вашата крайна точка работи."
msgstr "Изпратете единичен heartbeat пинг, за да се уверите, че крайната Ви точка работи."
#: src/components/routes/settings/heartbeat.tsx
msgid "Send periodic outbound pings to an external monitoring service so you can monitor Beszel without exposing it to the internet."
@@ -1387,7 +1428,7 @@ msgstr "Изпращайте периодични изходящи пингов
msgid "Send test heartbeat"
msgstr "Изпращане на тестов heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Изпратени"
@@ -1399,6 +1440,7 @@ msgstr "Сериен номер"
msgid "Service Details"
msgstr "Детайли на услугата"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Услуги"
@@ -1409,13 +1451,15 @@ msgstr "Задайте процентни прагове за цветовете
#: src/components/routes/settings/heartbeat.tsx
msgid "Set the following environment variables on your Beszel hub to enable heartbeat monitoring:"
msgstr "Задайте следните променливи на средата на вашия Beszel hub, за да активирате мониторинга на heartbeat:"
msgstr "Задайте следните променливи на средата на вашия Beszel hub, за да активирате heartbeat мониторинг:"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Настройки"
@@ -1459,17 +1503,18 @@ msgstr "Статус"
msgid "Sub State"
msgstr "Подсъстояние"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Изполван swap от системата"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Използване на swap"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Използване на swap"
msgid "System"
msgstr "Система"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Средно натоварване на системата във времето"
@@ -1501,6 +1546,11 @@ msgstr "Системите могат да бъдат управлявани в
msgid "Table"
msgstr "Таблица"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Задачи"
@@ -1511,7 +1561,7 @@ msgstr "Задачи"
msgid "Temp"
msgstr "Температура"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Температура"
@@ -1520,7 +1570,7 @@ msgstr "Температура"
msgid "Temperature unit"
msgstr "Единица за температура"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Температири на системни сензори"
@@ -1552,11 +1602,11 @@ msgstr "Това действие не може да бъде отменено.
msgid "This will permanently delete all selected records from the database."
msgstr "Това ще доведе до трайно изтриване на всички избрани записи от базата данни."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Пропускателна способност на {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Пропускателна способност на root файловата система"
@@ -1568,11 +1618,6 @@ msgstr "Формат на времето"
msgid "To email(s)"
msgstr "До имейл(ите)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Превключване на мрежа"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1728,22 +1773,22 @@ msgstr "Качване"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Време на работа"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Употреба"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Употреба на root partition-а"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Използвани"
@@ -1773,7 +1818,7 @@ msgstr "Прегледайте последните си 200 сигнала."
msgid "Visible Fields"
msgstr "Видими полета"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Изчаква се за достатъчно записи за показване"
@@ -1799,11 +1844,11 @@ msgstr "Webhook / Пуш нотификации"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "When enabled, this token allows agents to self-register without prior system creation."
msgstr "Когато е активиран, този символ позволява на агентите да се регистрират сами без предварително създаване на система."
msgstr "Когато е активиран, този токен позволява на агентите да се регистрират сами без предварително създаване на система."
#: src/components/routes/settings/heartbeat.tsx
msgid "When using POST, each heartbeat includes a JSON payload with system status summary, list of down systems, and triggered alerts."
msgstr "При използване на POST всеки heartbeat включва JSON полезен товар с резюме на състоянието на системата, списък на спрените системи и задействаните предупреждения."
msgstr "При използване на POST, всеки heartbeat включва JSON полезен товар с резюме на състоянието на системата, списък на спрените системи и задействаните предупреждения."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Команда Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Запиши"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: cs\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,9 +55,9 @@ msgid "1 hour"
msgstr "1 hodina"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -66,9 +72,9 @@ msgid "12 hours"
msgstr "12 hodin"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -79,9 +85,9 @@ msgid "30 days"
msgstr "30 dní"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Aktivní stav"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Přidat {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Přidat <0>Systém</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Přidat systém"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Přidat URL"
@@ -134,6 +135,7 @@ msgstr "Upravit šířku hlavního rozvržení"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Administrátor"
@@ -147,7 +149,7 @@ msgstr "Po nastavení proměnných prostředí restartujte hub Beszel, aby se zm
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -163,6 +165,7 @@ msgstr "Výstrahy"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Všechny kontejnery"
@@ -188,11 +191,11 @@ msgstr "Jste si jistý?"
msgid "Automatic copy requires a secure context."
msgstr "Automatická kopie vyžaduje zabezpečený kontext."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Průměr"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Průměrné využití CPU kontejnerů"
@@ -206,20 +209,20 @@ msgstr "Průměr klesne pod <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Průměr je vyšší než <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Průměrná spotřeba energie GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Průměrné využití CPU v celém systému"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Průměrné využití {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Průměrné využití GPU engine"
@@ -228,7 +231,7 @@ msgstr "Průměrné využití GPU engine"
msgid "Backups"
msgstr "Zálohy"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Přenos"
@@ -236,9 +239,9 @@ msgstr "Přenos"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Baterie"
@@ -288,7 +291,7 @@ msgstr "Stav zavádění"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Byty (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / vyrovnávací paměť"
@@ -334,7 +337,7 @@ msgstr "Změnit jednotky zobrazení metrik."
msgid "Change general application options."
msgstr "Změnit obecné nastavení aplikace."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Nabíjení"
@@ -347,6 +350,10 @@ msgstr "Nabíjení"
msgid "Chart options"
msgstr "Možnosti grafu"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Šířka grafu"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Zkontrolujte {email} pro odkaz na obnovení."
@@ -407,6 +414,10 @@ msgstr "Konflikty"
msgid "Connection is down"
msgstr "Připojení je nedostupné"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Kontejnery"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,6 +473,11 @@ msgstr "Zkopírujte obsah <0>docker-compose.yml</0> pro agenta níže nebo autom
msgid "Copy YAML"
msgstr "Kopírovat YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -484,8 +500,8 @@ msgstr "Čas CPU"
msgid "CPU Time Breakdown"
msgstr "Rozdělení času CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Kumulativní odeslání"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Aktuální stav"
@@ -531,6 +547,11 @@ msgstr "Cykly"
msgid "Daily"
msgstr "Denně"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Výchozí doba"
@@ -552,7 +573,7 @@ msgstr "Popis"
#: src/components/containers-table/containers-table.tsx
msgid "Detail"
msgstr "Detail"
msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Device"
@@ -563,37 +584,44 @@ msgstr "Zařízení"
msgid "Discharging"
msgstr "Vybíjení"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Disk I/O"
msgstr "I/O disku"
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
msgstr "Disková jednotka"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Využití disku"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Využití disku {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Využití CPU Dockeru"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Využití paměti Dockeru"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Síťové I/O Dockeru"
@@ -770,7 +798,7 @@ msgstr "Neúspěšné: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -783,7 +811,7 @@ msgstr "Otisk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD příkaz"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Plná"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "Globální"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU enginy"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Spotřeba energie GPU"
@@ -826,6 +859,7 @@ msgstr "Spotřeba energie GPU"
msgid "GPU Usage"
msgstr "Využití GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Mřížka"
@@ -836,7 +870,7 @@ msgstr "Zdraví"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -884,7 +918,7 @@ msgstr "Neaktivní"
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr "Interval"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
@@ -910,9 +944,9 @@ msgstr "Životní cyklus"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "limit"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Průměrné vytížení"
@@ -941,6 +975,7 @@ msgstr "Stav načtení"
msgid "Loading..."
msgstr "Načítání..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Odhlásit"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Pokyny k manuálnímu nastavení"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max. 1 min"
@@ -999,18 +1034,19 @@ msgstr "Limit paměti"
msgid "Memory Peak"
msgstr "Špička paměti"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Využití paměti"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Využití paměti docker kontejnerů"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1025,11 +1061,11 @@ msgstr "Název"
msgid "Net"
msgstr "Síť"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Síťový provoz kontejnerů docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1218,15 +1254,20 @@ msgstr "Přihlaste se prosím k vašemu účtu"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Zapnutí"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Přesné využití v zaznamenaném čase"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Tiché hodiny"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Číst"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Přijato"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T. Detaily"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Vlastní test"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Uložit {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Adresu uložte pomocí klávesy enter nebo čárky. Pro deaktivaci e-mailových oznámení ponechte prázdné pole."
@@ -1335,10 +1380,6 @@ msgstr "Adresu uložte pomocí klávesy enter nebo čárky. Pro deaktivaci e-mai
msgid "Save Settings"
msgstr "Uložit nastavení"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Uložit systém"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Uložen v databázi a nevyprší, dokud jej nezablokujete."
@@ -1387,7 +1428,7 @@ msgstr "Odesílejte periodické odchozí pingy na externí monitorovací službu
msgid "Send test heartbeat"
msgstr "Odeslat testovací heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Odeslat"
@@ -1399,6 +1440,7 @@ msgstr "Sériové číslo"
msgid "Service Details"
msgstr "Detaily služby"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Služby"
@@ -1414,8 +1456,10 @@ msgstr "Pro povolení monitorování heartbeat nastavte na hubu Beszel následuj
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Nastavení"
@@ -1459,17 +1503,18 @@ msgstr "Stav"
msgid "Sub State"
msgstr "Podstav"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Swap prostor využívaný systémem"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Swap využití"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Swap využití"
msgid "System"
msgstr "Systém"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Průměry zatížení systému v průběhu času"
@@ -1501,6 +1546,11 @@ msgstr "Systémy lze spravovat v souboru <0>config.yml</0> uvnitř datového adr
msgid "Table"
msgstr "Tabulka"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Úlohy"
@@ -1511,7 +1561,7 @@ msgstr "Úlohy"
msgid "Temp"
msgstr "Teplota"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Teplota"
@@ -1520,7 +1570,7 @@ msgstr "Teplota"
msgid "Temperature unit"
msgstr "Jednotky teploty"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Teploty systémových senzorů"
@@ -1552,11 +1602,11 @@ msgstr "Tuto akci nelze vzít zpět. Tím se z databáze trvale odstraní všech
msgid "This will permanently delete all selected records from the database."
msgstr "Tímto trvale odstraníte všechny vybrané záznamy z databáze."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Propustnost {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Propustnost kořenového souborového systému"
@@ -1568,11 +1618,6 @@ msgstr "Formát času"
msgid "To email(s)"
msgstr "Na email(y)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Přepnout mřížku"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Přepnout motiv"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1728,22 +1773,22 @@ msgstr "Odeslání"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Doba provozu"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Využití"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Využití kořenového oddílu"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Využito"
@@ -1773,7 +1818,7 @@ msgstr "Zobrazit vašich 200 nejnovějších upozornění."
msgid "Visible Fields"
msgstr "Viditelné sloupce"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Čeká se na dostatek záznamů k zobrazení"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows příkaz"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Psát"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: da\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1 time"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 minut"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 timer"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 minutter"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 dage"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 minutter"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Aktiv tilstand"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Tilføj {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Tilføj <0>System</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Tilføj system"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Tilføj URL"
@@ -134,6 +135,7 @@ msgstr "Juster bredden af hovedlayoutet"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Administrator"
@@ -147,7 +149,7 @@ msgstr "Efter indstilling af miljøvariablerne skal du genstarte din Beszel-hub
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -163,6 +165,7 @@ msgstr "Alarmer"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Alle containere"
@@ -188,11 +191,11 @@ msgstr "Er du sikker?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisk kopiering kræver en sikker kontekst."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Gennemsnitlig"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Gennemsnitlig CPU udnyttelse af containere"
@@ -206,20 +209,20 @@ msgstr "Gennemsnit falder under <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Gennemsnittet overstiger <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Gennemsnitligt strømforbrug for GPU'er"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Gennemsnitlig systembaseret CPU-udnyttelse"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Gennemsnitlig udnyttelse af {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Gennemsnitlig udnyttelse af GPU-enheder"
@@ -228,7 +231,7 @@ msgstr "Gennemsnitlig udnyttelse af GPU-enheder"
msgid "Backups"
msgstr "Sikkerhedskopier"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Båndbredde"
@@ -236,9 +239,9 @@ msgstr "Båndbredde"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Batteri"
@@ -277,7 +280,7 @@ msgstr "Binær"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -286,9 +289,9 @@ msgstr "Opstartstilstand"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffere"
@@ -324,7 +327,7 @@ msgstr "Forsigtig - muligt tab af data"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Ændre viste enheder for målinger."
msgid "Change general application options."
msgstr "Skift generelle applikationsindstillinger."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Opladning"
@@ -347,6 +350,10 @@ msgstr "Oplader"
msgid "Chart options"
msgstr "Diagrammuligheder"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Grafbredde"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Tjek {email} for et nulstillingslink."
@@ -407,6 +414,10 @@ msgstr "Konflikter"
msgid "Connection is down"
msgstr "Forbindelsen er nede"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Containere"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Kopier <0>docker-compose.yml</0> indholdet for agenten nedenfor, eller r
msgid "Copy YAML"
msgstr "Kopier YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -474,7 +490,7 @@ msgstr "CPU-kerner"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "CPU Peak"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
@@ -484,8 +500,8 @@ msgstr "CPU tid"
msgid "CPU Time Breakdown"
msgstr "CPU-tidsfordeling"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Kumulativ upload"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Nuværende tilstand"
@@ -531,6 +547,11 @@ msgstr "Cykler"
msgid "Daily"
msgstr "Dagligt"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Standard tidsperiode"
@@ -563,37 +584,44 @@ msgstr "Enhed"
msgid "Discharging"
msgstr "Aflader"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Disk I/O"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
msgstr "Diskenhed"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Diskforbrug"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Diskforbrug af {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Docker CPU forbrug"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Docker Hukommelsesforbrug"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker Netværk I/O"
@@ -636,7 +664,7 @@ msgstr "Rediger {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -731,7 +759,7 @@ msgstr "Eksporter din nuværende systemkonfiguration."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,12 +798,12 @@ msgstr "Mislykkedes: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
msgstr "Filter..."
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint"
@@ -783,7 +811,7 @@ msgstr "Fingeraftryk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD kommando"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Fuldt opladt"
@@ -812,13 +841,17 @@ msgstr "Generelt"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU-enheder"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "GPU Strøm Træk"
@@ -826,6 +859,7 @@ msgstr "GPU Strøm Træk"
msgid "GPU Usage"
msgstr "GPU-forbrug"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Gitter"
@@ -836,7 +870,7 @@ msgstr "Sundhed"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -884,7 +918,7 @@ msgstr "Inaktiv"
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr "Interval"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
@@ -912,7 +946,7 @@ msgstr "Livscyklus"
msgid "limit"
msgstr "grænse"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Belastning Gennemsnitlig"
@@ -941,6 +975,7 @@ msgstr "Indlæsningstilstand"
msgid "Loading..."
msgstr "Indlæser..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Log ud"
@@ -958,7 +993,7 @@ msgstr "Loginforsøg mislykkedes"
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
msgid "Logs"
msgstr "Logs"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Manuel opsætningsvejledning"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Maks. 1 min"
@@ -999,18 +1034,19 @@ msgstr "Hukommelsesgrænse"
msgid "Memory Peak"
msgstr "Hukommelsesspids"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Hukommelsesforbrug"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Hukommelsesforbrug af dockercontainere"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1023,13 +1059,13 @@ msgstr "Navn"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Net"
msgstr "Net"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Netværkstrafik af dockercontainere"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1152,7 +1188,7 @@ msgstr "Tidligere"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Pause"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Paused"
@@ -1177,7 +1213,7 @@ msgstr "Procentdel af tid brugt i hver tilstand"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Permanent"
msgstr "Permanent"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Persistence"
@@ -1218,15 +1254,20 @@ msgstr "Log venligst ind på din konto"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Tænd"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Præcis udnyttelse på det registrerede tidspunkt"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Stille timer"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Læs"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Modtaget"
@@ -1304,7 +1345,7 @@ msgstr "Genoptag"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T.-detaljer"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. selvtest"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Gem {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Gem adresse ved hjælp af enter eller komma. Lad feltet stå tomt for at deaktivere e-mail-meddelelser."
@@ -1335,10 +1380,6 @@ msgstr "Gem adresse ved hjælp af enter eller komma. Lad feltet stå tomt for at
msgid "Save Settings"
msgstr "Gem indstillinger"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Gem system"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Gemt i databasen og udløber ikke, før du deaktiverer det."
@@ -1387,7 +1428,7 @@ msgstr "Send periodiske udgående pings til en ekstern overvågningstjeneste, s
msgid "Send test heartbeat"
msgstr "Send test-heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Sendt"
@@ -1399,6 +1440,7 @@ msgstr "Serienummer"
msgid "Service Details"
msgstr "Tjenestedetaljer"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Tjenester"
@@ -1414,8 +1456,10 @@ msgstr "Indstil følgende miljøvariabler på din Beszel-hub for at aktivere hea
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Indstillinger"
@@ -1453,23 +1497,24 @@ msgstr "Tilstand"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Undertilstand"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Swap plads brugt af systemet"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Swap forbrug"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1479,15 +1524,15 @@ msgstr "Swap forbrug"
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Gennemsnitlig system belastning over tid"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Systemd Services"
msgstr ""
#: src/components/navbar.tsx
msgid "Systems"
@@ -1501,6 +1546,11 @@ msgstr "Systemer kan være administreres i filen <0>config.yml</0> i din datamap
msgid "Table"
msgstr "Tabel"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Opgaver"
@@ -1511,7 +1561,7 @@ msgstr "Opgaver"
msgid "Temp"
msgstr "Temperatur"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatur"
@@ -1520,13 +1570,13 @@ msgstr "Temperatur"
msgid "Temperature unit"
msgstr "Temperaturenhed"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperaturer i systemsensorer"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1552,11 +1602,11 @@ msgstr "Denne handling kan ikke fortrydes. Dette vil permanent slette alle aktue
msgid "This will permanently delete all selected records from the database."
msgstr "Dette vil permanent slette alle poster fra databasen."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Gennemløb af {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Gennemløb af rodfilsystemet"
@@ -1568,11 +1618,6 @@ msgstr "Tidsformat"
msgid "To email(s)"
msgstr "Til email(s)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Slå gitter til/fra"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1671,7 +1716,7 @@ msgstr "Udløser når brugen af en disk overstiger en tærskel"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Type"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
@@ -1730,20 +1775,20 @@ msgstr "Overfør"
msgid "Uptime"
msgstr "Oppetid"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Forbrug"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Brug af rodpartition"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Brugt"
@@ -1773,7 +1818,7 @@ msgstr "Se dine 200 nyeste alarmer."
msgid "Visible Fields"
msgstr "Synlige felter"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Venter på nok posteringer til at vise"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows-kommando"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Skriv"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-14 20:37\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1 Stunde"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 Min"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 Stunden"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 Min"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 Tage"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 Min"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Aktiver Zustand"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "{foo} hinzufügen"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "<0>System</0> hinzufügen"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "System hinzufügen"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "URL hinzufügen"
@@ -134,8 +135,9 @@ msgstr "Breite des Hauptlayouts anpassen"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -147,7 +149,7 @@ msgstr "Starten Sie nach dem Festlegen der Umgebungsvariablen Ihren Beszel-Hub n
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -163,6 +165,7 @@ msgstr "Warnungen"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Alle Container"
@@ -188,11 +191,11 @@ msgstr "Bist du sicher?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisches Kopieren erfordert einen sicheren Kontext."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Durchschnitt"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Durchschnittliche CPU-Auslastung der Container"
@@ -206,29 +209,29 @@ msgstr "Durchschnitt unterschreitet <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Durchschnitt überschreitet <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Durchschnittlicher Stromverbrauch der GPUs"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Durchschnittliche systemweite CPU-Auslastung"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Durchschnittliche Auslastung von {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Durchschnittliche Auslastung der GPU-Engines"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups"
msgstr "Backups"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Bandbreite"
@@ -236,9 +239,9 @@ msgstr "Bandbreite"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr "Batt"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Batterie"
@@ -277,7 +280,7 @@ msgstr "Binär"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -286,9 +289,9 @@ msgstr "Boot-Zustand"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Puffer"
@@ -324,7 +327,7 @@ msgstr "Vorsicht - potenzieller Datenverlust"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Anzeigeeinheiten der Werte ändern."
msgid "Change general application options."
msgstr "Allgemeine Anwendungsoptionen ändern."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Ladung"
@@ -347,6 +350,10 @@ msgstr "Wird geladen"
msgid "Chart options"
msgstr "Diagrammoptionen"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Diagrammbreite"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Überprüfe {email} auf einen Link zum Zurücksetzen."
@@ -407,6 +414,10 @@ msgstr "Konflikte"
msgid "Connection is down"
msgstr "Verbindung unterbrochen"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Container"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Kopiere den<0>docker-compose.yml</0> Inhalt für den Agent unten oder re
msgid "Copy YAML"
msgstr "YAML kopieren"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "CPU-Zeit"
msgid "CPU Time Breakdown"
msgstr "CPU-Zeit-Aufschlüsselung"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Kumulativer Upload"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Aktueller Zustand"
@@ -531,6 +547,11 @@ msgstr "Zyklen"
msgid "Daily"
msgstr "Täglich"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Standardzeitraum"
@@ -563,11 +584,12 @@ msgstr "Gerät"
msgid "Discharging"
msgstr "Wird entladen"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Festplatte"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Festplatten-I/O"
@@ -575,25 +597,31 @@ msgstr "Festplatten-I/O"
msgid "Disk unit"
msgstr "Festplatteneinheit"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Festplattennutzung"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Festplattennutzung von {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Docker-CPU-Auslastung"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Docker-Arbeitsspeichernutzung"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker-Netzwerk-I/O"
@@ -731,7 +759,7 @@ msgstr "Exportiere die aktuelle Systemkonfiguration."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,12 +798,12 @@ msgstr "Fehlgeschlagen: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
msgstr "Filter..."
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD Befehl"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Voll"
@@ -812,13 +841,17 @@ msgstr "Allgemein"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU-Engines"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "GPU-Leistungsaufnahme"
@@ -826,6 +859,7 @@ msgstr "GPU-Leistungsaufnahme"
msgid "GPU Usage"
msgstr "GPU-Auslastung"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Raster"
@@ -836,7 +870,7 @@ msgstr "Gesundheit"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -854,7 +888,7 @@ msgstr "Homebrew-Befehl"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -876,7 +910,7 @@ msgstr "Wenn du das Passwort für dein Administratorkonto verloren hast, kannst
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Image"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -912,7 +946,7 @@ msgstr "Lebenszyklus"
msgid "limit"
msgstr "Limit"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Durchschnittliche Systemlast"
@@ -941,6 +975,7 @@ msgstr "Ladezustand"
msgid "Loading..."
msgstr "Lädt..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Abmelden"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Anleitung zur manuellen Einrichtung"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 Min"
@@ -999,15 +1034,16 @@ msgstr "Arbeitsspeicherlimit"
msgid "Memory Peak"
msgstr "Arbeitsspeicher-Spitze"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Arbeitsspeichernutzung"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Arbeitsspeichernutzung der Docker-Container"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modell"
@@ -1018,18 +1054,18 @@ msgstr "Modell"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name"
msgstr "Name"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Net"
msgstr "Netzwerk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Netzwerkverkehr der Docker-Container"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1152,7 +1188,7 @@ msgstr "Vergangen"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Pause"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Paused"
@@ -1177,7 +1213,7 @@ msgstr "Prozentsatz der Zeit in jedem Zustand"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Permanent"
msgstr "Permanent"
msgstr "Dauerhaft"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Persistence"
@@ -1218,15 +1254,20 @@ msgstr "Bitte melde dich bei deinem Konto an"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Eingeschaltet"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Genaue Nutzung zum aufgezeichneten Zeitpunkt"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Ruhezeiten"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Lesen"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Empfangen"
@@ -1304,7 +1345,7 @@ msgstr "Fortsetzen"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T.-Details"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T.-Selbsttest"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "{foo} speichern"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Adresse mit der Enter-Taste oder Komma speichern. Leer lassen, um E-Mail-Benachrichtigungen zu deaktivieren."
@@ -1335,10 +1380,6 @@ msgstr "Adresse mit der Enter-Taste oder Komma speichern. Leer lassen, um E-Mail
msgid "Save Settings"
msgstr "Einstellungen speichern"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "System speichern"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "In der Datenbank gespeichert und läuft nicht ab, bis Sie es deaktivieren."
@@ -1387,7 +1428,7 @@ msgstr "Senden Sie regelmäßige ausgehende Pings an einen externen Überwachung
msgid "Send test heartbeat"
msgstr "Test-Heartbeat senden"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Gesendet"
@@ -1399,6 +1440,7 @@ msgstr "Seriennummer"
msgid "Service Details"
msgstr "Servicedetails"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Dienste"
@@ -1414,8 +1456,10 @@ msgstr "Legen Sie die folgenden Umgebungsvariablen auf Ihrem Beszel-Hub fest, um
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Einstellungen"
@@ -1453,23 +1497,24 @@ msgstr "Status"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Unterzustand"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Vom System genutzter Swap-Speicher"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Swap-Nutzung"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1479,9 +1524,9 @@ msgstr "Swap-Nutzung"
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Systemlastdurchschnitt im Zeitverlauf"
@@ -1501,6 +1546,11 @@ msgstr "Systeme können in einer <0>config.yml</0>-Datei im Datenverzeichnis ver
msgid "Table"
msgstr "Tabelle"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Aufgaben"
@@ -1511,7 +1561,7 @@ msgstr "Aufgaben"
msgid "Temp"
msgstr "Temperatur"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatur"
@@ -1520,13 +1570,13 @@ msgstr "Temperatur"
msgid "Temperature unit"
msgstr "Temperatureinheit"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperaturen der Systemsensoren"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1552,11 +1602,11 @@ msgstr "Diese Aktion kann nicht rückgängig gemacht werden. Dadurch werden alle
msgid "This will permanently delete all selected records from the database."
msgstr "Dadurch werden alle ausgewählten Datensätze dauerhaft aus der Datenbank gelöscht."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Durchsatz von {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Durchsatz des Root-Dateisystems"
@@ -1568,11 +1618,6 @@ msgstr "Zeitformat"
msgid "To email(s)"
msgstr "An E-Mail(s)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Raster umschalten"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Darstellung umschalten"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1730,20 +1775,20 @@ msgstr "Hochladen"
msgid "Uptime"
msgstr "Betriebszeit"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Nutzung"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Nutzung der Root-Partition"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Verwendet"
@@ -1773,7 +1818,7 @@ msgstr "Sieh dir die neusten 200 Alarme an."
msgid "Visible Fields"
msgstr "Sichtbare Spalten"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Warten auf genügend Datensätze zur Anzeige"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows-Befehl"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Schreiben"

View File

@@ -13,6 +13,12 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr "{0} available"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -44,7 +50,7 @@ msgid "1 hour"
msgstr "1 hour"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
@@ -61,7 +67,7 @@ msgid "12 hours"
msgstr "12 hours"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
@@ -74,7 +80,7 @@ msgid "30 days"
msgstr "30 days"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
@@ -102,19 +108,14 @@ msgid "Active state"
msgstr "Active state"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Add {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Add <0>System</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Add system"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Add URL"
@@ -129,6 +130,7 @@ msgstr "Adjust the width of the main layout"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
@@ -158,6 +160,7 @@ msgstr "Alerts"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "All Containers"
@@ -183,11 +186,11 @@ msgstr "Are you sure?"
msgid "Automatic copy requires a secure context."
msgstr "Automatic copy requires a secure context."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Average"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Average CPU utilization of containers"
@@ -201,20 +204,20 @@ msgstr "Average drops below <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Average exceeds <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Average power consumption of GPUs"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Average system-wide CPU utilization"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Average utilization of {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Average utilization of GPU engines"
@@ -223,7 +226,7 @@ msgstr "Average utilization of GPU engines"
msgid "Backups"
msgstr "Backups"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Bandwidth"
@@ -233,7 +236,7 @@ msgstr "Bandwidth"
msgid "Bat"
msgstr "Bat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Battery"
@@ -283,7 +286,7 @@ msgstr "Boot state"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffers"
@@ -329,7 +332,7 @@ msgstr "Change display units for metrics."
msgid "Change general application options."
msgstr "Change general application options."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Charge"
@@ -342,6 +345,10 @@ msgstr "Charging"
msgid "Chart options"
msgstr "Chart options"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Chart width"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Check {email} for a reset link."
@@ -402,6 +409,10 @@ msgstr "Conflicts"
msgid "Connection is down"
msgstr "Connection is down"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Containers"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -457,6 +468,11 @@ msgstr "Copy the<0>docker-compose.yml</0> content for the agent below, or regist
msgid "Copy YAML"
msgstr "Copy YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr "Core"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -479,8 +495,8 @@ msgstr "CPU time"
msgid "CPU Time Breakdown"
msgstr "CPU Time Breakdown"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -512,7 +528,7 @@ msgid "Cumulative Upload"
msgstr "Cumulative Upload"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Current state"
@@ -526,6 +542,11 @@ msgstr "Cycles"
msgid "Daily"
msgstr "Daily"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr "Default"
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Default time period"
@@ -558,11 +579,12 @@ msgstr "Device"
msgid "Discharging"
msgstr "Discharging"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Disk I/O"
@@ -570,25 +592,31 @@ msgstr "Disk I/O"
msgid "Disk unit"
msgstr "Disk unit"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Disk Usage"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Disk usage of {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr "Display"
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Docker CPU Usage"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Docker Memory Usage"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker Network I/O"
@@ -765,7 +793,7 @@ msgstr "Failed: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -795,6 +823,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD command"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Full"
@@ -810,10 +839,14 @@ msgid "Global"
msgstr "Global"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU Engines"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "GPU Power Draw"
@@ -821,6 +854,7 @@ msgstr "GPU Power Draw"
msgid "GPU Usage"
msgstr "GPU Usage"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Grid"
@@ -907,7 +941,7 @@ msgstr "Lifecycle"
msgid "limit"
msgstr "limit"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Load Average"
@@ -936,6 +970,7 @@ msgstr "Load state"
msgid "Loading..."
msgstr "Loading..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Log Out"
@@ -973,7 +1008,7 @@ msgid "Manual setup instructions"
msgstr "Manual setup instructions"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 min"
@@ -994,15 +1029,16 @@ msgstr "Memory limit"
msgid "Memory Peak"
msgstr "Memory Peak"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Memory Usage"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Memory usage of docker containers"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
@@ -1020,11 +1056,11 @@ msgstr "Name"
msgid "Net"
msgstr "Net"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Network traffic of docker containers"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1215,13 +1251,18 @@ msgstr "Please sign in to your account"
msgid "Port"
msgstr "Port"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr "Ports"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Power On"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Precise utilization at the recorded time"
@@ -1243,12 +1284,12 @@ msgid "Quiet Hours"
msgstr "Quiet Hours"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Read"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Received"
@@ -1321,6 +1362,10 @@ msgstr "S.M.A.R.T. Details"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Self-Test"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Save {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Save address using enter key or comma. Leave blank to disable email notifications."
@@ -1330,10 +1375,6 @@ msgstr "Save address using enter key or comma. Leave blank to disable email noti
msgid "Save Settings"
msgstr "Save Settings"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Save system"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Saved in the database and does not expire until you disable it."
@@ -1382,7 +1423,7 @@ msgstr "Send periodic outbound pings to an external monitoring service so you ca
msgid "Send test heartbeat"
msgstr "Send test heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Sent"
@@ -1394,6 +1435,7 @@ msgstr "Serial Number"
msgid "Service Details"
msgstr "Service Details"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Services"
@@ -1409,8 +1451,10 @@ msgstr "Set the following environment variables on your Beszel hub to enable hea
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Settings"
@@ -1454,17 +1498,18 @@ msgstr "Status"
msgid "Sub State"
msgstr "Sub State"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Swap space used by the system"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Swap Usage"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1476,7 +1521,7 @@ msgstr "Swap Usage"
msgid "System"
msgstr "System"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "System load averages over time"
@@ -1496,6 +1541,11 @@ msgstr "Systems may be managed in a <0>config.yml</0> file inside your data dire
msgid "Table"
msgstr "Table"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr "Tabs"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Tasks"
@@ -1506,7 +1556,7 @@ msgstr "Tasks"
msgid "Temp"
msgstr "Temp"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperature"
@@ -1515,7 +1565,7 @@ msgstr "Temperature"
msgid "Temperature unit"
msgstr "Temperature unit"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperatures of system sensors"
@@ -1547,11 +1597,11 @@ msgstr "This action cannot be undone. This will permanently delete all current r
msgid "This will permanently delete all selected records from the database."
msgstr "This will permanently delete all selected records from the database."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Throughput of {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Throughput of root filesystem"
@@ -1563,11 +1613,6 @@ msgstr "Time format"
msgid "To email(s)"
msgstr "To email(s)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Toggle grid"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1725,20 +1770,20 @@ msgstr "Upload"
msgid "Uptime"
msgstr "Uptime"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Usage"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Usage of root partition"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Used"
@@ -1768,7 +1813,7 @@ msgstr "View your 200 most recent alerts."
msgid "Visible Fields"
msgstr "Visible Fields"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Waiting for enough records to display"
@@ -1807,8 +1852,8 @@ msgid "Windows command"
msgstr "Windows command"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Write"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,9 +55,9 @@ msgid "1 hour"
msgstr "1 hora"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -66,9 +72,9 @@ msgid "12 hours"
msgstr "12 horas"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -79,9 +85,9 @@ msgid "30 days"
msgstr "30 días"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Estado activo"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Agregar {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Agregar <0>sistema</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Agregar sistema"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Agregar URL"
@@ -134,6 +135,7 @@ msgstr "Ajustar el ancho del diseño principal"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Administrador"
@@ -163,6 +165,7 @@ msgstr "Alertas"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Todos los contenedores"
@@ -188,11 +191,11 @@ msgstr "¿Estás seguro?"
msgid "Automatic copy requires a secure context."
msgstr "La copia automática requiere un contexto seguro."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Promedio"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Utilización promedio de CPU de los contenedores"
@@ -206,20 +209,20 @@ msgstr "El promedio cae por debajo de <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "El promedio excede <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Consumo de energía promedio de GPUs"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Utilización promedio de CPU del sistema"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Uso promedio de {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Utilización promedio de motores GPU"
@@ -228,7 +231,7 @@ msgstr "Utilización promedio de motores GPU"
msgid "Backups"
msgstr "Copias de seguridad"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Ancho de banda"
@@ -236,9 +239,9 @@ msgstr "Ancho de banda"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Batería"
@@ -288,7 +291,7 @@ msgstr "Estado de arranque"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (kB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Caché / Buffers"
@@ -324,7 +327,7 @@ msgstr "Precaución - posible pérdida de datos"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Cambiar las unidades de visualización de las métricas."
msgid "Change general application options."
msgstr "Cambiar las opciones generales de la aplicación."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Carga"
@@ -347,6 +350,10 @@ msgstr "Cargando"
msgid "Chart options"
msgstr "Opciones de gráficos"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Ancho del gráfico"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Revisa {email} para un enlace de restablecimiento."
@@ -407,6 +414,10 @@ msgstr "Conflictos"
msgid "Connection is down"
msgstr "La conexión está caída"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Contenedores"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Copia el contenido del<0>docker-compose.yml</0> para el agente a continu
msgid "Copy YAML"
msgstr "Copiar YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "Tiempo de CPU"
msgid "CPU Time Breakdown"
msgstr "Desglose de tiempo de CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Carga acumulada"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Estado actual"
@@ -531,6 +547,11 @@ msgstr "Ciclos"
msgid "Daily"
msgstr "Diariamente"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Periodo de tiempo predeterminado"
@@ -563,11 +584,12 @@ msgstr "Dispositivo"
msgid "Discharging"
msgstr "Descargando"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disco"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "E/S de Disco"
@@ -575,25 +597,31 @@ msgstr "E/S de Disco"
msgid "Disk unit"
msgstr "Unidad de disco"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Uso de disco"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Uso de disco de {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Uso de CPU de Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Uso de memoria de Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "E/S de red de Docker"
@@ -688,7 +716,7 @@ msgstr "Efímero"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error"
msgstr "Error"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Example:"
@@ -731,7 +759,7 @@ msgstr "Exporta la configuración actual de sus sistemas."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,7 +798,7 @@ msgstr "Fallidos: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -783,7 +811,7 @@ msgstr "Huella dactilar"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Llena"
@@ -808,17 +837,21 @@ msgstr "Llena"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General"
msgstr "General"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "Motores GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Consumo de energía de la GPU"
@@ -826,6 +859,7 @@ msgstr "Consumo de energía de la GPU"
msgid "GPU Usage"
msgstr "Uso de GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Cuadrícula"
@@ -836,7 +870,7 @@ msgstr "Estado"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -912,7 +946,7 @@ msgstr "Ciclo de vida"
msgid "limit"
msgstr "límite"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Carga media"
@@ -941,6 +975,7 @@ msgstr "Estado de carga"
msgid "Loading..."
msgstr "Cargando..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Cerrar sesión"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Instrucciones manuales de configuración"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Máx. 1 min"
@@ -999,15 +1034,16 @@ msgstr "Límite de memoria"
msgid "Memory Peak"
msgstr "Pico de memoria"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Uso de memoria"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Uso de memoria de los contenedores Docker"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modelo"
@@ -1025,11 +1061,11 @@ msgstr "Nombre"
msgid "Net"
msgstr "Red"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Tráfico de red de los contenedores Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1045,7 +1081,7 @@ msgstr "Unidad de red"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "No"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
@@ -1220,13 +1256,18 @@ msgstr "Por favor, inicia sesión en tu cuenta"
msgid "Port"
msgstr "Puerto"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Encendido"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Utilización precisa en el momento registrado"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Horas de silencio"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Lectura"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Recibido"
@@ -1326,6 +1367,10 @@ msgstr "Detalles S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "Autoprueba S.M.A.R.T."
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Guardar {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Guarda la dirección usando la tecla enter o coma. Deja en blanco para desactivar las notificaciones por correo."
@@ -1335,10 +1380,6 @@ msgstr "Guarda la dirección usando la tecla enter o coma. Deja en blanco para d
msgid "Save Settings"
msgstr "Guardar configuración"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Guardar sistema"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Guardado en la base de datos y no expira hasta que lo desactives."
@@ -1387,7 +1428,7 @@ msgstr "Envíe pings salientes periódicos a un servicio de monitorización exte
msgid "Send test heartbeat"
msgstr "Enviar latido de prueba"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Enviado"
@@ -1399,6 +1440,7 @@ msgstr "Número de serie"
msgid "Service Details"
msgstr "Detalles del servicio"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Servicios"
@@ -1414,8 +1456,10 @@ msgstr "Configure las siguientes variables de entorno en su hub Beszel para habi
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Configuración"
@@ -1459,17 +1503,18 @@ msgstr "Estado"
msgid "Sub State"
msgstr "Subestado"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Espacio de swap utilizado por el sistema"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Uso de swap"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Uso de swap"
msgid "System"
msgstr "Sistema"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Promedios de carga del sistema a lo largo del tiempo"
@@ -1501,6 +1546,11 @@ msgstr "Los sistemas pueden ser gestionados en un archivo <0>config.yml</0> dent
msgid "Table"
msgstr "Tabla"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Tareas"
@@ -1511,7 +1561,7 @@ msgstr "Tareas"
msgid "Temp"
msgstr "Temperatura"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatura"
@@ -1520,7 +1570,7 @@ msgstr "Temperatura"
msgid "Temperature unit"
msgstr "Unidad de temperatura"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperaturas de los sensores del sistema"
@@ -1552,11 +1602,11 @@ msgstr "Esta acción no se puede deshacer. Esto eliminará permanentemente todos
msgid "This will permanently delete all selected records from the database."
msgstr "Esto eliminará permanentemente todos los registros seleccionados de la base de datos."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Rendimiento de {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Rendimiento del sistema de archivos raíz"
@@ -1568,11 +1618,6 @@ msgstr "Formato de hora"
msgid "To email(s)"
msgstr "A correo(s)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Alternar cuadrícula"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Alternar tema"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1600,7 +1645,7 @@ msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conex
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1613,7 +1658,7 @@ msgstr "Datos totales enviados por cada interfaz"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total: {0}"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
@@ -1728,22 +1773,22 @@ msgstr "Cargar"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Tiempo de actividad"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Uso"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Uso de la partición raíz"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Usado"
@@ -1773,7 +1818,7 @@ msgstr "Ver tus 200 alertas más recientes."
msgid "Visible Fields"
msgstr "Columnas visibles"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Esperando suficientes registros para mostrar"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Comando Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Escritura"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: fa\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Persian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "۱ ساعت"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "۱ دقیقه"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "۱۲ ساعت"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "۱۵ دقیقه"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "۳۰ روز"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "۵ دقیقه"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "وضعیت فعال"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "افزودن {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "افزودن <0>سیستم</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "افزودن سیستم"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "افزودن آدرس اینترنتی"
@@ -134,6 +135,7 @@ msgstr "تنظیم عرض چیدمان اصلی"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "مدیر"
@@ -163,6 +165,7 @@ msgstr "هشدارها"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "همه کانتینرها"
@@ -188,11 +191,11 @@ msgstr "آیا مطمئن هستید؟"
msgid "Automatic copy requires a secure context."
msgstr "کپی خودکار نیاز به یک زمینه امن دارد."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "میانگین"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "میانگین استفاده از CPU کانتینرها"
@@ -206,20 +209,20 @@ msgstr "میانگین به زیر <0>{value}{0}</0> می‌افتد"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "میانگین از <0>{value}{0}</0> فراتر رفته است"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "میانگین مصرف برق پردازنده‌های گرافیکی"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "میانگین استفاده از CPU در کل سیستم"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "میانگین استفاده از {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "میانگین استفاده از موتورهای GPU"
@@ -228,7 +231,7 @@ msgstr "میانگین استفاده از موتورهای GPU"
msgid "Backups"
msgstr "پشتیبان‌گیری‌ها"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "پهنای باند"
@@ -238,7 +241,7 @@ msgstr "پهنای باند"
msgid "Bat"
msgstr "باتری"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "باتری"
@@ -288,7 +291,7 @@ msgstr "وضعیت بوت"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "بایت (کیلوبایت بر ثانیه، مگابایت بر ثانیه، گیگابایت بر ثانیه)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "حافظه پنهان / بافرها"
@@ -334,7 +337,7 @@ msgstr "تغییر واحدهای نمایش برای معیارها."
msgid "Change general application options."
msgstr "تغییر گزینه‌های کلی برنامه."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "شارژ"
@@ -347,6 +350,10 @@ msgstr "در حال شارژ"
msgid "Chart options"
msgstr "گزینه‌های نمودار"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "عرض نمودار"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "ایمیل {email} خود را برای لینک بازنشانی بررسی کنید."
@@ -407,6 +414,10 @@ msgstr "تعارض‌ها"
msgid "Connection is down"
msgstr "اتصال قطع است"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "کانتینرها"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,6 +473,11 @@ msgstr "محتوای <0>docker-compose.yml</0> عامل زیر را کپی کن
msgid "Copy YAML"
msgstr "کپی YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -484,8 +500,8 @@ msgstr "زمان CPU"
msgid "CPU Time Breakdown"
msgstr "تجزیه زمان CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "آپلود تجمعی"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "وضعیت فعلی"
@@ -531,6 +547,11 @@ msgstr "چرخه‌ها"
msgid "Daily"
msgstr "روزانه"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "بازه زمانی پیش‌فرض"
@@ -563,11 +584,12 @@ msgstr "دستگاه"
msgid "Discharging"
msgstr "در حال تخلیه"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "دیسک"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "ورودی/خروجی دیسک"
@@ -575,25 +597,31 @@ msgstr "ورودی/خروجی دیسک"
msgid "Disk unit"
msgstr "واحد دیسک"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "میزان استفاده از دیسک"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "میزان استفاده از دیسک {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "میزان استفاده از CPU داکر"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "میزان استفاده از حافظه داکر"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "ورودی/خروجی شبکه داکر"
@@ -770,7 +798,7 @@ msgstr "ناموفق: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "دستور FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "پر"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "جهانی"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "پردازنده گرافیکی"
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "موتورهای GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "مصرف برق پردازنده گرافیکی"
@@ -826,6 +859,7 @@ msgstr "مصرف برق پردازنده گرافیکی"
msgid "GPU Usage"
msgstr "میزان استفاده از GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "جدول"
@@ -912,7 +946,7 @@ msgstr "چرخه حیات"
msgid "limit"
msgstr "محدودیت"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "میانگین بار"
@@ -941,6 +975,7 @@ msgstr "وضعیت بارگذاری"
msgid "Loading..."
msgstr "در حال بارگذاری..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "خروج"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "دستورالعمل‌های راه‌اندازی دستی"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "حداکثر ۱ دقیقه"
@@ -999,15 +1034,16 @@ msgstr "محدودیت حافظه"
msgid "Memory Peak"
msgstr "حداکثر حافظه"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "میزان استفاده از حافظه"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "میزان استفاده از حافظه کانتینرهای داکر"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "مدل"
@@ -1025,11 +1061,11 @@ msgstr "نام"
msgid "Net"
msgstr "شبکه"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "ترافیک شبکه کانتینرهای داکر"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "لطفاً به حساب کاربری خود وارد شوید"
msgid "Port"
msgstr "پورت"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "روشن کردن"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "میزان دقیق استفاده در زمان ثبت شده"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "ساعات آرام"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "خواندن"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "دریافت شد"
@@ -1326,6 +1367,10 @@ msgstr "جزئیات S.M.A.R.T"
msgid "S.M.A.R.T. Self-Test"
msgstr "تست خود S.M.A.R.T"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "ذخیره {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "آدرس را با استفاده از کلید Enter یا کاما ذخیره کنید. برای غیرفعال کردن اعلان‌های ایمیلی، خالی بگذارید."
@@ -1335,10 +1380,6 @@ msgstr "آدرس را با استفاده از کلید Enter یا کاما ذخ
msgid "Save Settings"
msgstr "ذخیره تنظیمات"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "ذخیره سیستم"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "در پایگاه داده ذخیره شده و تا زمانی که آن را غیرفعال نکنید، منقضی نمی‌شود."
@@ -1387,7 +1428,7 @@ msgstr "پینگ‌های خروجی دوره‌ای را به یک سرویس
msgid "Send test heartbeat"
msgstr "ارسال ضربان قلب آزمایشی"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "ارسال شد"
@@ -1399,6 +1440,7 @@ msgstr "شماره سریال"
msgid "Service Details"
msgstr "جزئیات سرویس"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "سرویس‌ها"
@@ -1414,8 +1456,10 @@ msgstr "متغیرهای محیطی زیر را در هاب Beszel خود تنظ
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "تنظیمات"
@@ -1459,17 +1503,18 @@ msgstr "وضعیت"
msgid "Sub State"
msgstr "وضعیت فرعی"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "فضای Swap استفاده شده توسط سیستم"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "میزان استفاده از Swap"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "میزان استفاده از Swap"
msgid "System"
msgstr "سیستم"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "میانگین بار سیستم در طول زمان"
@@ -1501,6 +1546,11 @@ msgstr "سیستم‌ها ممکن است در یک فایل <0>config.yml</0>
msgid "Table"
msgstr "جدول"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "وظایف"
@@ -1511,7 +1561,7 @@ msgstr "وظایف"
msgid "Temp"
msgstr "دما"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "دما"
@@ -1520,7 +1570,7 @@ msgstr "دما"
msgid "Temperature unit"
msgstr "واحد دما"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "دمای حسگرهای سیستم"
@@ -1552,11 +1602,11 @@ msgstr "این عمل قابل برگشت نیست. این کار تمام رک
msgid "This will permanently delete all selected records from the database."
msgstr "این کار تمام رکوردهای انتخاب شده را برای همیشه از پایگاه داده حذف خواهد کرد."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "توان عملیاتی {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "توان عملیاتی سیستم فایل ریشه"
@@ -1568,11 +1618,6 @@ msgstr "فرمت زمان"
msgid "To email(s)"
msgstr "به ایمیل(ها)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "تغییر نمایش جدول"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1730,20 +1775,20 @@ msgstr "آپلود"
msgid "Uptime"
msgstr "آپتایم"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "میزان استفاده"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "میزان استفاده از پارتیشن ریشه"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "استفاده شده"
@@ -1773,7 +1818,7 @@ msgstr "۲۰۰ هشدار اخیر خود را مشاهده کنید."
msgid "Visible Fields"
msgstr "فیلدهای قابل مشاهده"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "در انتظار رکوردهای کافی برای نمایش"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "دستور Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "نوشتن"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
@@ -18,11 +18,17 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected."
msgstr "{0} sur {1} ligne(s) sélectionnée(s)."
msgstr "{0} sur {1} ligne(s) sectionner."
#: src/components/routes/system/info-bar.tsx
msgid "{cores, plural, one {# core} other {# cores}}"
@@ -49,13 +55,13 @@ msgid "1 hour"
msgstr "1 heure"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
msgstr "1 minute"
msgstr ""
#: src/lib/utils.ts
msgid "1 week"
@@ -66,9 +72,9 @@ msgid "12 hours"
msgstr "12 heures"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -79,9 +85,9 @@ msgid "30 days"
msgstr "30 jours"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
@@ -89,14 +95,14 @@ msgstr "5 min"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Actions"
msgstr ""
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/heartbeat.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Active"
msgstr "Active"
msgstr ""
#: src/components/active-alerts.tsx
msgid "Active Alerts"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "État actif"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Ajouter {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Ajouter <0>un Système</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Ajouter un système"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Ajouter lURL"
@@ -134,8 +135,9 @@ msgstr "Ajuster la largeur de la mise en page principale"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -147,7 +149,7 @@ msgstr "Après avoir défini les variables d'environnement, redémarrez votre hu
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -163,6 +165,7 @@ msgstr "Alertes"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Tous les conteneurs"
@@ -188,11 +191,11 @@ msgstr "Êtes-vous sûr ?"
msgid "Automatic copy requires a secure context."
msgstr "La copie automatique nécessite un contexte sécurisé."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Moyenne"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Utilisation moyenne du CPU des conteneurs"
@@ -206,20 +209,20 @@ msgstr "La moyenne descend en dessous de <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "La moyenne dépasse <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Consommation d'énergie moyenne des GPUs"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Utilisation moyenne du CPU à l'échelle du système"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Utilisation moyenne de {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Utilisation moyenne des moteurs GPU"
@@ -228,7 +231,7 @@ msgstr "Utilisation moyenne des moteurs GPU"
msgid "Backups"
msgstr "Sauvegardes"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Bande passante"
@@ -236,9 +239,9 @@ msgstr "Bande passante"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Batterie"
@@ -277,7 +280,7 @@ msgstr "Binaire"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -286,9 +289,9 @@ msgstr "État de démarrage"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Tampons"
@@ -324,7 +327,7 @@ msgstr "Attention - perte de données potentielle"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,9 +337,9 @@ msgstr "Ajuster les unités d'affichage pour les métriques."
msgid "Change general application options."
msgstr "Modifier les options générales de l'application."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Charge"
msgstr ""
#. Context: Battery state
#: src/lib/i18n.ts
@@ -347,6 +350,10 @@ msgstr "En charge"
msgid "Chart options"
msgstr "Options de graphique"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Largeur du graphique"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Vérifiez {email} pour un lien de réinitialisation."
@@ -407,6 +414,10 @@ msgstr "Conflits"
msgid "Connection is down"
msgstr "Connexion interrompue"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Conteneurs"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Copiez le contenu du<0>docker-compose.yml</0> pour l'agent ci-dessous, o
msgid "Copy YAML"
msgstr "Copier YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "Temps CPU"
msgid "CPU Time Breakdown"
msgstr "Répartition du temps CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,20 +533,25 @@ msgid "Cumulative Upload"
msgstr "Téléversement cumulatif"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "État actuel"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cycles"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Daily"
msgstr "Quotidien"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Période par défaut"
@@ -548,7 +569,7 @@ msgstr "Supprimer l'empreinte"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Description"
msgstr ""
#: src/components/containers-table/containers-table.tsx
msgid "Detail"
@@ -563,11 +584,12 @@ msgstr "Appareil"
msgid "Discharging"
msgstr "En décharge"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disque"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Entrée/Sortie disque"
@@ -575,32 +597,38 @@ msgstr "Entrée/Sortie disque"
msgid "Disk unit"
msgstr "Unité disque"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Utilisation du disque"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Utilisation du disque de {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Utilisation du CPU Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Utilisation de la mémoire Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Entrée/Sortie réseau Docker"
#: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation"
msgstr "Documentation"
msgstr ""
#. Context: System is down
#: src/components/alerts-history-columns.tsx
@@ -636,7 +664,7 @@ msgstr "Modifier {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -731,7 +759,7 @@ msgstr "Exportez la configuration actuelle de vos systèmes."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,7 +798,7 @@ msgstr "Échec : {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "Commande FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Pleine"
@@ -812,13 +841,17 @@ msgstr "Général"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "Moteurs GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Consommation du GPU"
@@ -826,6 +859,7 @@ msgstr "Consommation du GPU"
msgid "GPU Usage"
msgstr "Utilisation GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Grille"
@@ -836,7 +870,7 @@ msgstr "Santé"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -876,7 +910,7 @@ msgstr "Si vous avez perdu le mot de passe de votre compte administrateur, vous
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Image"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -912,7 +946,7 @@ msgstr "Cycle de vie"
msgid "limit"
msgstr "limite"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Charge moyenne"
@@ -941,6 +975,7 @@ msgstr "État de charge"
msgid "Loading..."
msgstr "Chargement..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Déconnexion"
@@ -978,9 +1013,9 @@ msgid "Manual setup instructions"
msgstr "Guide pour une installation manuelle"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 min"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
@@ -999,15 +1034,16 @@ msgstr "Limite mémoire"
msgid "Memory Peak"
msgstr "Pic mémoire"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Utilisation de la mémoire"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Utilisation de la mémoire des conteneurs Docker"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modèle"
@@ -1025,11 +1061,11 @@ msgstr "Nom"
msgid "Net"
msgstr "Rés"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Trafic réseau des conteneurs Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1074,7 +1110,7 @@ msgstr "Aucun système trouvé."
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications"
msgstr "Notifications"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "OAuth 2 / OIDC support"
@@ -1117,7 +1153,7 @@ msgstr "Écraser les alertes existantes"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
msgid "Page"
msgstr "Page"
msgstr ""
#. placeholder {0}: table.getState().pagination.pageIndex + 1
#. placeholder {1}: table.getPageCount()
@@ -1152,7 +1188,7 @@ msgstr "Passé"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Pause"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Paused"
@@ -1177,7 +1213,7 @@ msgstr "Pourcentage de temps passé dans chaque état"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Permanent"
msgstr "Permanent"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Persistence"
@@ -1218,15 +1254,20 @@ msgstr "Veuillez vous connecter à votre compte"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Allumage"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Utilisation précise au moment enregistré"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Heures calmes"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Lecture"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Reçu"
@@ -1326,6 +1367,10 @@ msgstr "Détails S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "Auto-test S.M.A.R.T."
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Enregistrer {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Enregistrez l'adresse en utilisant la touche Entrée ou la virgule. Laissez vide pour désactiver les notifications par email."
@@ -1335,10 +1380,6 @@ msgstr "Enregistrez l'adresse en utilisant la touche Entrée ou la virgule. Lais
msgid "Save Settings"
msgstr "Enregistrer les paramètres"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Sauvegarder le système"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Enregistré dans la base de données et n'expire pas tant que vous ne le désactivez pas."
@@ -1387,7 +1428,7 @@ msgstr "Envoyez des pings sortants périodiques vers un service de surveillance
msgid "Send test heartbeat"
msgstr "Envoyer un heartbeat de test"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Envoyé"
@@ -1399,9 +1440,10 @@ msgstr "Numéro de série"
msgid "Service Details"
msgstr "Détails du service"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Services"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
@@ -1414,8 +1456,10 @@ msgstr "Définissez les variables d'environnement suivantes sur votre hub Beszel
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Paramètres"
@@ -1459,17 +1503,18 @@ msgstr "Statut"
msgid "Sub State"
msgstr "Sous-état"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Espace Swap utilisé par le système"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Utilisation du swap"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Utilisation du swap"
msgid "System"
msgstr "Système"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Charges moyennes du système dans le temps"
@@ -1501,6 +1546,11 @@ msgstr "Les systèmes peuvent être gérés dans un fichier <0>config.yml</0> à
msgid "Table"
msgstr "Tableau"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Tâches"
@@ -1511,7 +1561,7 @@ msgstr "Tâches"
msgid "Temp"
msgstr "Temp."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Température"
@@ -1520,7 +1570,7 @@ msgstr "Température"
msgid "Temperature unit"
msgstr "Unité de température"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Températures des capteurs du système"
@@ -1552,11 +1602,11 @@ msgstr "Cette action ne peut pas être annulée. Cela supprimera définitivement
msgid "This will permanently delete all selected records from the database."
msgstr "Ceci supprimera définitivement tous les enregistrements sélectionnés de la base de données."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Débit de {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Débit du système de fichiers racine"
@@ -1568,11 +1618,6 @@ msgstr "Format d'heure"
msgid "To email(s)"
msgstr "Aux email(s)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Basculer la grille"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Changer le thème"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1600,7 +1645,7 @@ msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connex
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1671,7 +1716,7 @@ msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Type"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
@@ -1728,22 +1773,22 @@ msgstr "Téléverser"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Temps de fonctionnement"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Utilisation"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Utilisation de la partition racine"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Utilisé"
@@ -1773,7 +1818,7 @@ msgstr "Voir vos 200 dernières alertes."
msgid "Visible Fields"
msgstr "Colonnes visibles"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "En attente de suffisamment d'enregistrements à afficher"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Commande Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Écriture"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: he\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "שעה"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "דקה אחת"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 שעות"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 דק'"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 ימים"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 דק'"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "מצב פעיל"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "הוסף {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "הוסף <0>מערכת</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "הוסף מערכת"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "הוסף URL"
@@ -134,6 +135,7 @@ msgstr "התאם את רוחב הפריסה הראשית"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "מנהל"
@@ -163,6 +165,7 @@ msgstr "התראות"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "כל הקונטיינרים"
@@ -188,11 +191,11 @@ msgstr "האם אתה בטוח?"
msgid "Automatic copy requires a secure context."
msgstr "העתקה אוטומטית דורשת הקשר מאובטח."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "ממוצע"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "ניצול ממוצע של CPU בקונטיינרים"
@@ -206,20 +209,20 @@ msgstr "הממוצע יורד מתחת ל-<0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "הממוצע עולה על <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "צריכת חשמל ממוצעת של GPUs"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "ניצול ממוצע כלל-מערכתי של CPU"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "ניצול ממוצע של {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "ניצול ממוצע של מנועי GPU"
@@ -228,7 +231,7 @@ msgstr "ניצול ממוצע של מנועי GPU"
msgid "Backups"
msgstr "גיבויים"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "רוחב פס"
@@ -238,7 +241,7 @@ msgstr "רוחב פס"
msgid "Bat"
msgstr "סוללה"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "סוללה"
@@ -288,7 +291,7 @@ msgstr "מצב אתחול"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "בתים (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "מטמון / חוצצים"
@@ -334,7 +337,7 @@ msgstr "שנה יחידות תצוגה עבור מדדים."
msgid "Change general application options."
msgstr "שנה אפשרויות כלליות של היישום."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "טעינה"
@@ -347,6 +350,10 @@ msgstr "בטעינה"
msgid "Chart options"
msgstr "אפשרויות גרף"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "רוחב תרשים"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "בדוק את {email} לקישור איפוס."
@@ -407,6 +414,10 @@ msgstr "התנגשויות"
msgid "Connection is down"
msgstr "החיבור נפל"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "קונטיינרים"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,6 +473,11 @@ msgstr "העתק את תוכן ה-<0>docker-compose.yml</0> עבור הסוכן
msgid "Copy YAML"
msgstr "העתק YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -484,8 +500,8 @@ msgstr "זמן CPU"
msgid "CPU Time Breakdown"
msgstr "פירוט זמן CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "העלאה מצטברת"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "מצב נוכחי"
@@ -531,6 +547,11 @@ msgstr "מחזורים"
msgid "Daily"
msgstr "יומי"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "תקופת זמן ברירת מחדל"
@@ -563,11 +584,12 @@ msgstr "התקן"
msgid "Discharging"
msgstr "בפריקה"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "דיסק"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "דיסק I/O"
@@ -575,25 +597,31 @@ msgstr "דיסק I/O"
msgid "Disk unit"
msgstr "יחידת דיסק"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "שימוש בדיסק"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "שימוש בדיסק של {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "שימוש CPU של Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "שימוש זיכרון של Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "I/O של רשת Docker"
@@ -770,7 +798,7 @@ msgstr "נכשל: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "פקודת FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "מלא"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "גלובלי"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "מעבד גרפי"
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "מנועי GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "צריכת חשמל GPU"
@@ -826,6 +859,7 @@ msgstr "צריכת חשמל GPU"
msgid "GPU Usage"
msgstr "שימוש GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "רשת"
@@ -912,7 +946,7 @@ msgstr "מחזור חיים"
msgid "limit"
msgstr "גבול"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "ממוצע עומס"
@@ -941,6 +975,7 @@ msgstr "מצב עומס"
msgid "Loading..."
msgstr "טוען..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "התנתק"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "הוראות התקנה ידניות"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "מקס 1 דק'"
@@ -999,15 +1034,16 @@ msgstr "גבול זיכרון"
msgid "Memory Peak"
msgstr "שיא זיכרון"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "שימוש בזיכרון"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "שימוש בזיכרון של קונטיינרים של Docker"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "דגם"
@@ -1025,11 +1061,11 @@ msgstr "שם"
msgid "Net"
msgstr "רשת"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "תעבורת רשת של קונטיינרים של Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "אנא התחבר לחשבון שלך"
msgid "Port"
msgstr "פורט"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "הפעלה"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "ניצול מדויק בזמן הרשום"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "שעות שקט"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "קריאה"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "התקבל"
@@ -1326,6 +1367,10 @@ msgstr "פרטי S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "בדיקה עצמית S.M.A.R.T."
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "שמור {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "שמור כתובת באמצעות מקש enter או פסיק. השאר ריק כדי להשבית התראות אימייל."
@@ -1335,10 +1380,6 @@ msgstr "שמור כתובת באמצעות מקש enter או פסיק. השאר
msgid "Save Settings"
msgstr "שמור הגדרות"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "שמור מערכת"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "נשמר במסד הנתונים ולא פג תוקף עד שתבטל אותו."
@@ -1387,7 +1428,7 @@ msgstr "שלח פינגים יוצאים תקופתיים לשירות ניטו
msgid "Send test heartbeat"
msgstr "שלח פעימת לב לבדיקה"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "נשלח"
@@ -1399,6 +1440,7 @@ msgstr "מספר סידורי"
msgid "Service Details"
msgstr "פרטי שירות"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "שירותים"
@@ -1414,8 +1456,10 @@ msgstr "הגדר את משתני הסביבה הבאים ב-Beszel hub שלך כ
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "הגדרות"
@@ -1459,17 +1503,18 @@ msgstr "סטטוס"
msgid "Sub State"
msgstr "מצב משני"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "שטח swap בשימוש על ידי המערכת"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "שימוש ב-Swap"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "שימוש ב-Swap"
msgid "System"
msgstr "מערכת"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "ממוצעי עומס מערכת לאורך זמן"
@@ -1501,6 +1546,11 @@ msgstr "מערכות יכולות להיות מנוהלות בקובץ <0>config
msgid "Table"
msgstr "טבלה"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "משימות"
@@ -1511,7 +1561,7 @@ msgstr "משימות"
msgid "Temp"
msgstr "טמפ'"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "טמפרטורה"
@@ -1520,7 +1570,7 @@ msgstr "טמפרטורה"
msgid "Temperature unit"
msgstr "יחידת טמפרטורה"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "טמפרטורות של חיישני המערכת"
@@ -1552,11 +1602,11 @@ msgstr "פעולה זו לא ניתנת לביטול. פעולה זו תמחק
msgid "This will permanently delete all selected records from the database."
msgstr "פעולה זו תמחק לצמיתות את כל הרשומות שנבחרו ממסד הנתונים."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "תפוקה של {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "תפוקה של מערכת הקבצים הראשית"
@@ -1568,11 +1618,6 @@ msgstr "פורמט זמן"
msgid "To email(s)"
msgstr "לאימייל(ים)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "החלף רשת"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1730,20 +1775,20 @@ msgstr "העלאה"
msgid "Uptime"
msgstr "זמן פעילות"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "שימוש"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "שימוש במחיצה הראשית"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "בשימוש"
@@ -1773,7 +1818,7 @@ msgstr "צפה ב-200 ההתראות האחרונות שלך."
msgid "Visible Fields"
msgstr "שדות גלויים"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "ממתין לרשומות מספיקות לתצוגה"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "פקודת Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "כתיבה"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: hr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \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"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1 sat"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 minut"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 sati"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 minuta"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 dana"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 minuta"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Aktivno stanje"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Dodaj {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Dodaj <0>Sustav</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Dodaj sustav"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Dodaj URL"
@@ -134,8 +135,9 @@ msgstr "Prilagodite širinu glavnog rasporeda"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -147,7 +149,7 @@ msgstr "Nakon postavljanja varijabli okruženja, ponovno pokrenite svoj Beszel h
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -163,6 +165,7 @@ msgstr "Upozorenja"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Svi spremnici"
@@ -188,11 +191,11 @@ msgstr "Jeste li sigurni?"
msgid "Automatic copy requires a secure context."
msgstr "Automatsko kopiranje zahtijeva siguran kontekst."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Prosjek"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Prosječna iskorištenost procesora u spremnicima"
@@ -206,20 +209,20 @@ msgstr "Prosjek pada ispod <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Prosjek premašuje <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Prosječna potrošnja energije grafičkog procesora"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Prosječna iskorištenost procesora u cijelom sustavu"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Prosječna iskorištenost {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Prosječna iskorištenost grafičkih procesora"
@@ -228,7 +231,7 @@ msgstr "Prosječna iskorištenost grafičkih procesora"
msgid "Backups"
msgstr "Sigurnosne kopije"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Mrežna Propusnost"
@@ -236,9 +239,9 @@ msgstr "Mrežna Propusnost"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Baterija"
@@ -288,7 +291,7 @@ msgstr "Stanje pokretanja"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bajtovi (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Predmemorija / Međuspremnici"
@@ -324,7 +327,7 @@ msgstr "Oprez - mogući gubitak podataka"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Promijenite mjerene jedinice korištene za prikazivanje podataka."
msgid "Change general application options."
msgstr "Promijenite opće opcije aplikacije."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Punjenje"
@@ -347,6 +350,10 @@ msgstr "Puni se"
msgid "Chart options"
msgstr "Postavke grafikona"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Širina grafikona"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Provjerite {email} za pristup poveznici za resetiranje."
@@ -407,6 +414,10 @@ msgstr "Sukobi"
msgid "Connection is down"
msgstr "Veza je pala"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Kontejneri"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,6 +473,11 @@ msgstr "Kopirajte sadržaj <0>docker-compose.yml</0> datoteke za opisanog agenta
msgid "Copy YAML"
msgstr "Kopiraj YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -484,8 +500,8 @@ msgstr "CPU vrijeme"
msgid "CPU Time Breakdown"
msgstr "Raspodjela CPU vremena"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Kumulativno otpremanje"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Trenutno stanje"
@@ -531,6 +547,11 @@ msgstr "Ciklusi"
msgid "Daily"
msgstr "Dnevno"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Zadano vremensko razdoblje"
@@ -563,37 +584,44 @@ msgstr "Uređaj"
msgid "Discharging"
msgstr "Prazni se"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Disk I/O"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
msgstr "Mjerna jedinica za disk"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Iskorištenost Diska"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Iskorištenost diska od {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Iskorištenost Docker procesora"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Iskorištenost Docker memorije"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker mrežni I/O"
@@ -636,7 +664,7 @@ msgstr "Uredi {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -770,7 +798,7 @@ msgstr "Neuspjelo: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD naredba"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Puno"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "Globalno"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "Grafički procesori"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Energetska potrošnja grafičkog procesora"
@@ -826,6 +859,7 @@ msgstr "Energetska potrošnja grafičkog procesora"
msgid "GPU Usage"
msgstr "Iskorištenost GPU-a"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Rešetka"
@@ -836,7 +870,7 @@ msgstr "Zdravlje"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -854,7 +888,7 @@ msgstr "Homebrew naredba"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -884,7 +918,7 @@ msgstr "Neaktivno"
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr "Interval"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
@@ -912,7 +946,7 @@ msgstr "Životni ciklus"
msgid "limit"
msgstr "ograničenje"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Prosječno Opterećenje"
@@ -941,6 +975,7 @@ msgstr "Stanje učitavanja"
msgid "Loading..."
msgstr "Učitavanje..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Odjava"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Upute za ručno postavljanje"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Maksimalno 1 minuta"
@@ -999,18 +1034,19 @@ msgstr "Ograničenje memorije"
msgid "Memory Peak"
msgstr "Vrhunac memorije"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Iskorištenost memorije"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Iskorištenost memorije Docker spremnika"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1025,11 +1061,11 @@ msgstr "Ime"
msgid "Net"
msgstr "Mreža"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Mrežni promet Docker spremnika"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1218,17 +1254,22 @@ msgstr "Molimo prijavite se u svoj račun"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Uključivanje"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Precizno iskorištenje u zabilježenom vremenu"
msgstr "Precizno iskorištenje u zabilježenom trenutku"
#: src/components/routes/settings/general.tsx
msgid "Preferred Language"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Tihi sati"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Pročitaj"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Primljeno"
@@ -1326,19 +1367,19 @@ msgstr "S.M.A.R.T. Detalji"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Samotestiranje"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Spremi {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Spremite adresu pomoću tipke enter ili zareza. Ostavite prazno kako biste onemogućili obavijesti e-poštom."
msgstr "Spremite adresu pomoću tipke enter ili zarez. Ostavite prazno kako biste onemogućili obavijesti e-poštom."
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Save Settings"
msgstr "Spremi Postavke"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Spremi sustav"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Spremljeno u bazi podataka i ne istječe dok ga ne onemogućite."
@@ -1361,7 +1402,7 @@ msgstr "Pretraži"
#: src/components/command-palette.tsx
msgid "Search for systems or settings..."
msgstr "Pretraži za sisteme ili postavke..."
msgstr "Pretraži sustave ili postavke..."
#: src/components/routes/settings/heartbeat.tsx
msgid "Seconds between pings (default: 60)"
@@ -1369,7 +1410,7 @@ msgstr "Sekunde između pingova (zadano: 60)"
#: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Pogledajte <0>postavke obavijesti</0> da biste konfigurirali način primanja upozorenja."
msgstr "Pogledajte <0>postavke obavijesti</0> kako biste konfigurirali način primanja upozorenja."
#: src/components/routes/settings/quiet-hours.tsx
msgid "Select {foo}"
@@ -1387,7 +1428,7 @@ msgstr "Šaljite povremene odlazne pingove vanjskoj usluzi nadzora kako biste mo
msgid "Send test heartbeat"
msgstr "Pošalji testni heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Poslano"
@@ -1399,6 +1440,7 @@ msgstr "Serijski broj"
msgid "Service Details"
msgstr "Detalji usluge"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Usluge"
@@ -1414,8 +1456,10 @@ msgstr "Postavite sljedeće varijable okruženja na svom Beszel hubu kako biste
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Postavke"
@@ -1453,23 +1497,24 @@ msgstr "Stanje"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Podstanje"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Swap prostor uzet od strane sistema"
msgstr "Swap prostor kojeg je zauzeo sustav"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Swap Iskorištenost"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1479,9 +1524,9 @@ msgstr "Swap Iskorištenost"
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "Sistem"
msgstr "Sustav"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Prosječno opterećenje sustava kroz vrijeme"
@@ -1495,12 +1540,17 @@ msgstr "Sustavi"
#: src/components/routes/settings/config-yaml.tsx
msgid "Systems may be managed in a <0>config.yml</0> file inside your data directory."
msgstr "Sistemima se može upravljati u <0>config.yml</0> datoteci unutar data direktorija."
msgstr "Sustavima se može upravljati pomoću <0>config.yml</0> datoteke unutar data mape."
#: src/components/systems-table/systems-table.tsx
msgid "Table"
msgstr "Tablica"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Zadaci"
@@ -1509,9 +1559,9 @@ msgstr "Zadaci"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Temp"
msgstr "Temp"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatura"
@@ -1520,13 +1570,13 @@ msgstr "Temperatura"
msgid "Temperature unit"
msgstr "Mjerna jedinica za temperaturu"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperature sistemskih senzora"
msgstr "Temperature sustavnih mjerila"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Testni <0>URL</0>"
msgstr "Probni <0>URL</0>"
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1534,7 +1584,7 @@ msgstr "Testiraj heartbeat"
#: src/components/routes/settings/notifications.tsx
msgid "Test notification sent"
msgstr "Testna obavijest poslana"
msgstr "Probna obavijest poslana"
#: src/components/routes/settings/heartbeat.tsx
msgid "The overall status is <0>ok</0> when all systems are up, <1>warn</1> when alerts are triggered, and <2>error</2> when any system is down."
@@ -1546,17 +1596,17 @@ msgstr "Zatim se prijavite u backend i resetirajte lozinku korisničkog računa
#: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
msgstr "Ova radnja se ne može poništiti. Ovo će trajno izbrisati sve trenutne zapise za {name} iz baze podataka."
msgstr "Ova radnja ne može se poništiti. Svi trenutni zapisi za {name} bit će trajno izbrisani iz baze podataka."
#: src/components/routes/settings/alerts-history-data-table.tsx
msgid "This will permanently delete all selected records from the database."
msgstr "Ovom radnjom će se trajno izbrisati svi odabrani zapisi iz baze podataka."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Protok {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Protok root datotečnog sustava"
@@ -1566,12 +1616,7 @@ msgstr "Format vremena"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Primaoci e-pošte"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Uključi/isključi rešetku"
msgstr "Primaoci emaila"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
@@ -1581,7 +1626,7 @@ msgstr "Uključi/isključi temu"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1645,7 +1690,7 @@ msgstr "Pokreće se kada razina baterije padne ispod praga"
#: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Pokreće se kada kombinacija gore/dolje premaši prag"
msgstr "Pokreće se kada kumulativna propusnost gore/dolje premaši prag"
#: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold"
@@ -1661,7 +1706,7 @@ msgstr "Pokreće se kada iskorištenost memorije premaši prag"
#: src/lib/alerts.ts
msgid "Triggers when status switches between up and down"
msgstr "Pokreće se kada se status sistema promijeni"
msgstr "Pokreće se kada se status sustava promijeni"
#: src/lib/alerts.ts
msgid "Triggers when usage of any disk exceeds a threshold"
@@ -1690,7 +1735,7 @@ msgstr "Sveopći token"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Nepoznata"
msgstr "Nepoznato"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
@@ -1728,22 +1773,22 @@ msgstr "Otpremi"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Vrijeme rada"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Iskorištenost"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Iskorištenost root datotečnog sustava"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Iskorišteno"
@@ -1773,13 +1818,13 @@ msgstr "Pogledajte posljednjih 200 upozorenja."
msgid "Visible Fields"
msgstr "Vidljiva polja"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Čeka se na više podataka prije prikaza"
msgstr "Potrebno je više podataka za prikaz"
#: src/components/routes/settings/general.tsx
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Želite li nam pomoći da naše prijevode učinimo još boljim? Posjetite <0>Crowdin</0> za više detalja."
msgstr "Želite li nam pomoći poboljšati prijevode? Posjetite <0>Crowdin</0> za više detalja."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows naredba"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Piši"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: hu\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1 óra"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 perc"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 óra"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 perc"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 nap"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 perc"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Aktív állapot"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Hozzáadás {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "<0>Rendszer</0> Hozzáadása"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Rendszer hozzáadása"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "URL hozzáadása"
@@ -134,6 +135,7 @@ msgstr "A fő elrendezés szélességének beállítása"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Adminisztráció"
@@ -163,6 +165,7 @@ msgstr "Riasztások"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Minden konténer"
@@ -188,11 +191,11 @@ msgstr "Biztos vagy benne?"
msgid "Automatic copy requires a secure context."
msgstr "Az automatikus másolás biztonságos környezetet igényel."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Átlag"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Konténerek átlagos CPU kihasználtsága"
@@ -206,20 +209,20 @@ msgstr "Az átlag esik <0>{value}{0}</0> alá"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Az átlag meghaladja a <0>{value}{0}</0> értéket"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "GPU-k átlagos energiafogyasztása"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Rendszerszintű CPU átlagos kihasználtság"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "{0} átlagos kihasználtsága"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "GPU-k átlagos kihasználtsága"
@@ -228,7 +231,7 @@ msgstr "GPU-k átlagos kihasználtsága"
msgid "Backups"
msgstr "Biztonsági mentések"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Sávszélesség"
@@ -238,7 +241,7 @@ msgstr "Sávszélesség"
msgid "Bat"
msgstr "Akku"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Akkumulátor"
@@ -288,7 +291,7 @@ msgstr "Indítási állapot"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Byte-ok (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Gyorsítótár / Pufferelések"
@@ -324,7 +327,7 @@ msgstr "Figyelem - potenciális adatvesztés"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "A mértékegységek megjelenítésének megváltoztatása."
msgid "Change general application options."
msgstr "Általános alkalmazásbeállítások módosítása."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Töltés"
@@ -347,6 +350,10 @@ msgstr "Töltődik"
msgid "Chart options"
msgstr "Diagram beállítások"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Grafikon szélessége"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Ellenőrizd a {email} címet a visszaállító linkért."
@@ -407,6 +414,10 @@ msgstr "Konfliktusok"
msgid "Connection is down"
msgstr "Kapcsolat megszakadt"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Konténerek"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Másold az alábbi ügynök <0>docker-compose.yml</0> tartalmát, vagy r
msgid "Copy YAML"
msgstr "YAML másolása"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "CPU idő"
msgid "CPU Time Breakdown"
msgstr "CPU idő felbontása"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Kumulatív feltöltés"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Jelenlegi állapot"
@@ -531,6 +547,11 @@ msgstr "Ciklusok"
msgid "Daily"
msgstr "Napi"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Alapértelmezett időszak"
@@ -563,11 +584,12 @@ msgstr "Eszköz"
msgid "Discharging"
msgstr "Kisül"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Lemez"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Lemez I/O"
@@ -575,25 +597,31 @@ msgstr "Lemez I/O"
msgid "Disk unit"
msgstr "Lemez mértékegysége"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Lemezhasználat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Lemezhasználat a {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Docker CPU használat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Docker memória használat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker hálózat I/O"
@@ -636,7 +664,7 @@ msgstr "Szerkesztés {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -731,7 +759,7 @@ msgstr "Exportálja a jelenlegi rendszerkonfigurációt."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,7 +798,7 @@ msgstr "Sikertelen: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -783,7 +811,7 @@ msgstr "Ujjlenyomat"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD parancs"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Tele"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "Globális"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU-k"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "GPU áramfelvétele"
@@ -826,6 +859,7 @@ msgstr "GPU áramfelvétele"
msgid "GPU Usage"
msgstr "GPU használat"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Rács"
@@ -836,7 +870,7 @@ msgstr "Egészség"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -912,7 +946,7 @@ msgstr "Életciklus"
msgid "limit"
msgstr "korlát"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Terhelési átlag"
@@ -941,6 +975,7 @@ msgstr "Betöltési állapot"
msgid "Loading..."
msgstr "Betöltés..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Kijelentkezés"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Manuális beállítási lépések"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Maximum 1 perc"
@@ -999,15 +1034,16 @@ msgstr "Memória korlát"
msgid "Memory Peak"
msgstr "Memória csúcs"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Memóriahasználat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Docker konténerek memória használata"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modell"
@@ -1025,11 +1061,11 @@ msgstr "Név"
msgid "Net"
msgstr "Hálózat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Docker konténerek hálózati forgalma"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1218,15 +1254,20 @@ msgstr "Kérjük, jelentkezzen be a fiókjába"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Bekapcsolás"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Pontos kihasználás a rögzített időpontban"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Csendes órák"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Olvasás"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Fogadott"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T. Részletek"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Önteszt"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "{foo} mentése"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Mentse el a címet az Enter billentyű vagy a vessző használatával. Hagyja üresen az e-mail értesítések letiltásához."
@@ -1335,10 +1380,6 @@ msgstr "Mentse el a címet az Enter billentyű vagy a vessző használatával. H
msgid "Save Settings"
msgstr "Beállítások mentése"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Rendszer mentése"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Elmentve az adatbázisban és nem jár le, amíg ki nem kapcsolod."
@@ -1387,7 +1428,7 @@ msgstr "Küldjön időszakos kimenő pingeket egy külső megfigyelő szolgálta
msgid "Send test heartbeat"
msgstr "Teszt heartbeat küldése"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Elküldve"
@@ -1399,6 +1440,7 @@ msgstr "Sorozatszám"
msgid "Service Details"
msgstr "Szolgáltatás részletei"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Szolgáltatások"
@@ -1414,8 +1456,10 @@ msgstr "Állítsa be a következő környezeti változókat a Beszel hubon a hea
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Beállítások"
@@ -1459,17 +1503,18 @@ msgstr "Állapot"
msgid "Sub State"
msgstr "Részállapot"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Rendszer által használt swap terület"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Swap használat"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Swap használat"
msgid "System"
msgstr "Rendszer"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Rendszer terhelési átlaga"
@@ -1501,6 +1546,11 @@ msgstr "A rendszereket egy <0>config.yml</0> fájlban lehet kezelni az adatköny
msgid "Table"
msgstr "Tábla"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Feladatok"
@@ -1511,7 +1561,7 @@ msgstr "Feladatok"
msgid "Temp"
msgstr "Hőmérséklet"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Hőmérséklet"
@@ -1520,7 +1570,7 @@ msgstr "Hőmérséklet"
msgid "Temperature unit"
msgstr "Hőmérséklet mértékegysége"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "A rendszer érzékelőinek hőmérséklete"
@@ -1552,11 +1602,11 @@ msgstr "Ezt a műveletet nem lehet visszavonni! Véglegesen törli a {name} öss
msgid "This will permanently delete all selected records from the database."
msgstr "Ez véglegesen törli az összes kijelölt bejegyzést az adatbázisból."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "A {extraFsName} átviteli teljesítménye"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "A gyökér fájlrendszer átviteli teljesítménye"
@@ -1568,11 +1618,6 @@ msgstr "Időformátum"
msgid "To email(s)"
msgstr "E-mailben"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Rács ki- és bekapcsolása"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Téma váltása"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1730,20 +1775,20 @@ msgstr "Feltöltés"
msgid "Uptime"
msgstr "Üzemidő"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Használat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Root partíció kihasználtsága"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Felhasznált"
@@ -1773,7 +1818,7 @@ msgstr "Legfrissebb 200 riasztásod áttekintése."
msgid "Visible Fields"
msgstr "Látható mezők"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Elegendő rekordra várva a megjelenítéshez"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows parancs"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Írás"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: id\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Indonesian\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1 jam"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 mnt"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 jam"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 menit"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 hari"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 mnt"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Status aktif"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Tambah {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Tambah <0>Sistem</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Tambah sistem"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Tambah URL"
@@ -134,8 +135,9 @@ msgstr "Sesuaikan lebar layar utama"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -163,6 +165,7 @@ msgstr "Peringatan"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Semua Container"
@@ -188,11 +191,11 @@ msgstr "Apakah anda yakin?"
msgid "Automatic copy requires a secure context."
msgstr "Copy memerlukan https."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Rata-rata"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Rata-rata utilisasi CPU untuk semua kontainer"
@@ -206,20 +209,20 @@ msgstr "Rata-rata turun di bawah <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Rata-rata melebihi <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Rata-rata konsumsi daya GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Rata-rata utilisasi CPU seluruh sistem"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Rata-rata utilisasi {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Rata-rata utilisasi GPU"
@@ -228,7 +231,7 @@ msgstr "Rata-rata utilisasi GPU"
msgid "Backups"
msgstr "Cadangan"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Bandwith"
@@ -238,7 +241,7 @@ msgstr "Bandwith"
msgid "Bat"
msgstr "Baterai"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Baterai"
@@ -288,9 +291,9 @@ msgstr "Status memulai"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Byte (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffers"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
@@ -324,7 +327,7 @@ msgstr "Perhatian - potensi kehilangan data"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Ubah tampilan satuan untuk metrik."
msgid "Change general application options."
msgstr "Ubah pengaturan umum aplikasi."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Isi baterai"
@@ -347,6 +350,10 @@ msgstr "Sedang mengisi"
msgid "Chart options"
msgstr "Pilihan grafik"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Lebar grafik"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Periksa {email} untuk tautan atur ulang password."
@@ -407,6 +414,10 @@ msgstr "Konflik"
msgid "Connection is down"
msgstr "Koneksi terputus"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Kontainer"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Salin konten <0>docker-compose.yml</0> untuk agen di bawah, atau daftark
msgid "Copy YAML"
msgstr "Salin YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "Waktu CPU"
msgid "CPU Time Breakdown"
msgstr "Rincian Waktu CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Akumulasi Upload"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Status saat ini"
@@ -531,6 +547,11 @@ msgstr "Siklus"
msgid "Daily"
msgstr "Harian"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Standar waktu"
@@ -552,7 +573,7 @@ msgstr "Deskripsi"
#: src/components/containers-table/containers-table.tsx
msgid "Detail"
msgstr "Detail"
msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Device"
@@ -563,39 +584,46 @@ msgstr "Perangkat"
msgid "Discharging"
msgstr "Sedang tidak di charge"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Disk I/O"
msgstr "I/O Disk"
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
msgstr "Unit disk"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Penggunaan Disk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Penggunaan disk dari {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Penggunaan CPU Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Penggunaan Memori Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker Network I/O"
msgstr "I/O Jaringan Docker"
#: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
@@ -636,7 +664,7 @@ msgstr "Ubah {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -688,7 +716,7 @@ msgstr "Sementara"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error"
msgstr "Error"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Example:"
@@ -731,7 +759,7 @@ msgstr "Export konfigurasi sistem anda saat ini."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,7 +798,7 @@ msgstr "Gagal: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -783,7 +811,7 @@ msgstr "Sidik jari"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "Perintah FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Penuh"
@@ -812,13 +841,17 @@ msgstr "Umum"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Konsumsi Daya GPU"
@@ -826,6 +859,7 @@ msgstr "Konsumsi Daya GPU"
msgid "GPU Usage"
msgstr "Penggunaan GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Kartu"
@@ -836,7 +870,7 @@ msgstr "Kesehatan"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -854,7 +888,7 @@ msgstr "Perintah Homebrew"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -867,7 +901,7 @@ msgstr "Metode HTTP: POST, GET, atau HEAD (default: POST)"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Idle"
msgstr "Idle"
msgstr ""
#: src/components/login/forgot-pass-form.tsx
msgid "If you've lost the password to your admin account, you may reset it using the following command."
@@ -876,7 +910,7 @@ msgstr "Jika anda kehilangan kata sandi untuk akun admin anda, anda dapat merese
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Image"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -884,7 +918,7 @@ msgstr "Tidak aktif"
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr "Interval"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
@@ -912,7 +946,7 @@ msgstr "Siklus hidup"
msgid "limit"
msgstr "batas"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Rata-rata Beban"
@@ -941,6 +975,7 @@ msgstr "Beban saat ini"
msgid "Loading..."
msgstr "Memuat..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Keluar"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Instruksi setup manual"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Maks 1 mnt"
@@ -999,18 +1034,19 @@ msgstr "Batas memori"
msgid "Memory Peak"
msgstr "Puncak Memori"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Penggunaan Memori"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Penggunaan memori kontainer docker"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1025,11 +1061,11 @@ msgstr "Nama"
msgid "Net"
msgstr "Jaringan"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Trafik jaringan kontainer docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1218,15 +1254,20 @@ msgstr "Silakan masuk ke akun anda"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Dihidupkan"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Utilisasi tepat pada waktu yang direkam"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Jam Tenang"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Baca"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Diterima"
@@ -1304,7 +1345,7 @@ msgstr "Lanjutkan"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1326,6 +1367,10 @@ msgstr "Detail S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "Self-Test S.M.A.R.T."
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Simpan {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Simpan alamat menggunakan tombol enter atau koma. Biarkan kosong untuk menonaktifkan notifikasi email."
@@ -1335,10 +1380,6 @@ msgstr "Simpan alamat menggunakan tombol enter atau koma. Biarkan kosong untuk m
msgid "Save Settings"
msgstr "Simpan Pengaturan"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Simpan sistem"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Disimpan di database dan tidak kedaluwarsa sampai Anda menonaktifkannya."
@@ -1387,7 +1428,7 @@ msgstr "Kirim ping keluar secara berkala ke layanan pemantauan eksternal sehingg
msgid "Send test heartbeat"
msgstr "Kirim tes heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Dikirim"
@@ -1399,6 +1440,7 @@ msgstr "Nomor Seri"
msgid "Service Details"
msgstr "Detail Layanan"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Layanan"
@@ -1414,8 +1456,10 @@ msgstr "Setel variabel lingkungan berikut di hub Beszel Anda untuk mengaktifkan
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Pengaturan"
@@ -1453,23 +1497,24 @@ msgstr "Status"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Sub Status"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Ruang swap yang digunakan oleh sistem"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Penggunaan Swap"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Penggunaan Swap"
msgid "System"
msgstr "Sistem"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Rata-rata beban sistem dari waktu ke waktu"
@@ -1501,6 +1546,11 @@ msgstr "Sistem dapat dikelola dalam file <0>config.yml</0> di dalam direktori da
msgid "Table"
msgstr "Tabel"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Tugas"
@@ -1511,7 +1561,7 @@ msgstr "Tugas"
msgid "Temp"
msgstr "Temperatur"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatur"
@@ -1520,7 +1570,7 @@ msgstr "Temperatur"
msgid "Temperature unit"
msgstr "Unit temperatur"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperatur sensor sistem"
@@ -1552,11 +1602,11 @@ msgstr "Aksi ini tidak dapat di kembalikan. ini akan menghapus permanen semua re
msgid "This will permanently delete all selected records from the database."
msgstr "Ini akan menghapus secara permanen semua record yang dipilih dari database."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Throughput dari {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Throughput dari filesystem root"
@@ -1568,11 +1618,6 @@ msgstr "Format waktu"
msgid "To email(s)"
msgstr "Ke email"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Ganti tampilan"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Ganti tema"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1600,7 +1645,7 @@ msgstr "Token dan Fingerprint digunakan untuk mengautentikasi koneksi WebSocket
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1613,7 +1658,7 @@ msgstr "Total data yang dikirim untuk setiap antarmuka"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total: {0}"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
@@ -1730,20 +1775,20 @@ msgstr "Unggah"
msgid "Uptime"
msgstr "Waktu aktif"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Penggunaan"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Penggunaan partisi root"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Digunakan"
@@ -1773,7 +1818,7 @@ msgstr "Lihat 200 peringatan terbaru anda."
msgid "Visible Fields"
msgstr "Metrik yang Terlihat"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Menunggu cukup record untuk ditampilkan"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Perintah Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Tulis"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: it\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,9 +55,9 @@ msgid "1 hour"
msgstr "1 ora"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -66,9 +72,9 @@ msgid "12 hours"
msgstr "12 ore"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -79,9 +85,9 @@ msgid "30 days"
msgstr "30 giorni"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Stato attivo"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Aggiungi {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Aggiungi <0>Sistema</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Aggiungi sistema"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Aggiungi URL"
@@ -134,6 +135,7 @@ msgstr "Regola la larghezza del layout principale"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Amministratore"
@@ -163,6 +165,7 @@ msgstr "Avvisi"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Tutti i contenitori"
@@ -188,11 +191,11 @@ msgstr "Sei sicuro?"
msgid "Automatic copy requires a secure context."
msgstr "La copia automatica richiede un contesto sicuro."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Media"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Utilizzo medio della CPU dei container"
@@ -206,20 +209,20 @@ msgstr "La media scende sotto <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "La media supera <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Consumo energetico medio delle GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Utilizzo medio della CPU a livello di sistema"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Utilizzo medio di {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Utilizzo medio dei motori GPU"
@@ -228,7 +231,7 @@ msgstr "Utilizzo medio dei motori GPU"
msgid "Backups"
msgstr "Backup"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Larghezza di banda"
@@ -236,9 +239,9 @@ msgstr "Larghezza di banda"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Batteria"
@@ -288,7 +291,7 @@ msgstr "Stato di avvio"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Byte (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffer"
@@ -324,7 +327,7 @@ msgstr "Attenzione - possibile perdita di dati"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Modifica le unità di visualizzazione per le metriche."
msgid "Change general application options."
msgstr "Modifica le opzioni generali dell'applicazione."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Carica"
@@ -347,6 +350,10 @@ msgstr "In carica"
msgid "Chart options"
msgstr "Opzioni del grafico"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Larghezza grafico"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Controlla {email} per un link di reset."
@@ -407,6 +414,10 @@ msgstr "Conflitti"
msgid "Connection is down"
msgstr "La connessione è interrotta"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Container"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Copia il contenuto<0>docker-compose.yml</0> per l'agente qui sotto, o re
msgid "Copy YAML"
msgstr "Copia YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "Tempo CPU"
msgid "CPU Time Breakdown"
msgstr "Suddivisione tempo CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Upload cumulativo"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Stato attuale"
@@ -531,6 +547,11 @@ msgstr "Cicli"
msgid "Daily"
msgstr "Giornaliero"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Periodo di tempo predefinito"
@@ -563,11 +584,12 @@ msgstr "Dispositivo"
msgid "Discharging"
msgstr "In scarica"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disco"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "I/O Disco"
@@ -575,25 +597,31 @@ msgstr "I/O Disco"
msgid "Disk unit"
msgstr "Unità disco"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Utilizzo Disco"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Utilizzo del disco di {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Utilizzo CPU Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Utilizzo Memoria Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "I/O di Rete Docker"
@@ -636,7 +664,7 @@ msgstr "Modifica {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -731,7 +759,7 @@ msgstr "Esporta la configurazione attuale dei tuoi sistemi."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,7 +798,7 @@ msgstr "Fallito: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -783,7 +811,7 @@ msgstr "Impronta digitale"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Piena"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "Globale"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "Motori GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Consumo della GPU"
@@ -826,6 +859,7 @@ msgstr "Consumo della GPU"
msgid "GPU Usage"
msgstr "Utilizzo GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Griglia"
@@ -836,7 +870,7 @@ msgstr "Stato"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -854,7 +888,7 @@ msgstr "Comando Homebrew"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -912,7 +946,7 @@ msgstr "Ciclo di vita"
msgid "limit"
msgstr "limite"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Carico medio"
@@ -941,6 +975,7 @@ msgstr "Stato di caricamento"
msgid "Loading..."
msgstr "Caricamento..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Disconnetti"
@@ -978,9 +1013,9 @@ msgid "Manual setup instructions"
msgstr "Istruzioni di configurazione manuale"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 min"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
@@ -999,15 +1034,16 @@ msgstr "Limite memoria"
msgid "Memory Peak"
msgstr "Picco memoria"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Utilizzo Memoria"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Utilizzo della memoria dei container Docker"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modello"
@@ -1025,11 +1061,11 @@ msgstr "Nome"
msgid "Net"
msgstr "Rete"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Traffico di rete dei container Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1045,7 +1081,7 @@ msgstr "Unità rete"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "No"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
@@ -1132,7 +1168,7 @@ msgstr "Pagine / Impostazioni"
#: src/components/login/auth-form.tsx
#: src/components/login/auth-form.tsx
msgid "Password"
msgstr "Password"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Password must be at least 8 characters."
@@ -1220,13 +1256,18 @@ msgstr "Si prega di accedere al proprio account"
msgid "Port"
msgstr "Porta"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Accensione"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Utilizzo preciso al momento registrato"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Ore silenziose"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Lettura"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Ricevuto"
@@ -1304,7 +1345,7 @@ msgstr "Riprendi"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1326,6 +1367,10 @@ msgstr "Dettagli S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "Autotest S.M.A.R.T."
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Salva {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Salva l'indirizzo usando il tasto invio o la virgola. Lascia vuoto per disabilitare le notifiche email."
@@ -1335,10 +1380,6 @@ msgstr "Salva l'indirizzo usando il tasto invio o la virgola. Lascia vuoto per d
msgid "Save Settings"
msgstr "Salva Impostazioni"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Salva sistema"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Salvato nel database e non scade finché non lo disabiliti."
@@ -1387,7 +1428,7 @@ msgstr "Invia ping in uscita periodici a un servizio di monitoraggio esterno in
msgid "Send test heartbeat"
msgstr "Invia heartbeat di prova"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Inviato"
@@ -1399,6 +1440,7 @@ msgstr "Numero di serie"
msgid "Service Details"
msgstr "Dettagli servizio"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Servizi"
@@ -1414,8 +1456,10 @@ msgstr "Imposta le seguenti variabili d'ambiente sul tuo Beszel hub per abilitar
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Impostazioni"
@@ -1459,17 +1503,18 @@ msgstr "Stato"
msgid "Sub State"
msgstr "Sotto-stato"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Spazio di swap utilizzato dal sistema"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Utilizzo Swap"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Utilizzo Swap"
msgid "System"
msgstr "Sistema"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Medie di carico del sistema nel tempo"
@@ -1501,6 +1546,11 @@ msgstr "I sistemi possono essere gestiti in un file <0>config.yml</0> all'intern
msgid "Table"
msgstr "Tabella"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Attività"
@@ -1511,7 +1561,7 @@ msgstr "Attività"
msgid "Temp"
msgstr "Temperatura"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatura"
@@ -1520,17 +1570,17 @@ msgstr "Temperatura"
msgid "Temperature unit"
msgstr "Unità temperatura"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperature dei sensori di sistema"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
msgstr "Test heartbeat"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Test notification sent"
@@ -1552,11 +1602,11 @@ msgstr "Questa azione non può essere annullata. Questo eliminerà permanentemen
msgid "This will permanently delete all selected records from the database."
msgstr "Questo eliminerà permanentemente tutti i record selezionati dal database."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Throughput di {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Throughput del filesystem root"
@@ -1568,11 +1618,6 @@ msgstr "Formato orario"
msgid "To email(s)"
msgstr "A email(s)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Attiva/disattiva griglia"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Attiva/disattiva tema"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1728,22 +1773,22 @@ msgstr "Carica"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Tempo di attività"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Utilizzo"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Utilizzo della partizione root"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Utilizzato"
@@ -1773,7 +1818,7 @@ msgstr "Visualizza i tuoi 200 avvisi più recenti."
msgid "Visible Fields"
msgstr "Colonne visibili"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "In attesa di abbastanza record da visualizzare"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Comando Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Scrittura"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ja\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1時間"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1分"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12時間"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15分"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30日間"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5分"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "アクティブ状態"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "{foo}を追加"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "<0>システム</0>を追加"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "システムを追加"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "URLを追加"
@@ -134,6 +135,7 @@ msgstr "メインレイアウトの幅を調整"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "管理者"
@@ -163,6 +165,7 @@ msgstr "アラート"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "すべてのコンテナ"
@@ -188,11 +191,11 @@ msgstr "よろしいですか?"
msgid "Automatic copy requires a secure context."
msgstr "自動コピーには安全なコンテキストが必要です。"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "平均"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "コンテナの平均CPU使用率"
@@ -206,20 +209,20 @@ msgstr "平均が<0>{value}{0}</0>を下回っています"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "平均が<0>{value}{0}</0>を超えています"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "GPUの平均消費電力"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "システム全体の平均CPU使用率"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "{0}の平均使用率"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "GPUエンジンの平均使用率"
@@ -228,7 +231,7 @@ msgstr "GPUエンジンの平均使用率"
msgid "Backups"
msgstr "バックアップ"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "帯域幅"
@@ -238,7 +241,7 @@ msgstr "帯域幅"
msgid "Bat"
msgstr "バッテリー"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "バッテリー"
@@ -288,7 +291,7 @@ msgstr "ブート状態"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "バイト (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "キャッシュ / バッファ"
@@ -334,7 +337,7 @@ msgstr "メトリックの表示単位を変更します。"
msgid "Change general application options."
msgstr "一般的なアプリケーションオプションを変更します。"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "充電"
@@ -347,6 +350,10 @@ msgstr "充電中"
msgid "Chart options"
msgstr "チャートオプション"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "チャートの幅"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "{email}を確認してリセットリンクを探してください。"
@@ -407,6 +414,10 @@ msgstr "競合"
msgid "Connection is down"
msgstr "接続が切断されました"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "コンテナ"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "下記のエージェントの<0>docker-compose.yml</0>内容をコピ
msgid "Copy YAML"
msgstr "YAMLをコピー"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "CPU時間"
msgid "CPU Time Breakdown"
msgstr "CPU 時間の内訳"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "累積アップロード"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "現在の状態"
@@ -531,6 +547,11 @@ msgstr "サイクル"
msgid "Daily"
msgstr "毎日"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "デフォルトの期間"
@@ -563,11 +584,12 @@ msgstr "デバイス"
msgid "Discharging"
msgstr "放電中"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "ディスク"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "ディスクI/O"
@@ -575,25 +597,31 @@ msgstr "ディスクI/O"
msgid "Disk unit"
msgstr "ディスク単位"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "ディスク使用率"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "{extraFsName}のディスク使用率"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Docker CPU使用率"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Dockerメモリ使用率"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "DockerネットワークI/O"
@@ -770,7 +798,7 @@ msgstr "失敗: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD コマンド"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "満充電"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "グローバル"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPUエンジン"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "GPUの消費電力"
@@ -826,6 +859,7 @@ msgstr "GPUの消費電力"
msgid "GPU Usage"
msgstr "GPU使用率"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "グリッド"
@@ -912,7 +946,7 @@ msgstr "ライフサイクル"
msgid "limit"
msgstr "制限"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "負荷平均"
@@ -941,6 +975,7 @@ msgstr "ロード状態"
msgid "Loading..."
msgstr "読み込み中..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "ログアウト"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "手動セットアップの手順"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "最大1分"
@@ -999,15 +1034,16 @@ msgstr "メモリ制限"
msgid "Memory Peak"
msgstr "メモリピーク"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "メモリ使用率"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Dockerコンテナのメモリ使用率"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "モデル"
@@ -1025,11 +1061,11 @@ msgstr "名前"
msgid "Net"
msgstr "帯域"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Dockerコンテナのネットワークトラフィック"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "アカウントにサインインしてください"
msgid "Port"
msgstr "ポート"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "電源オン"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "記録された時点での正確な利用"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "サイレント時間"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "読み取り"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "受信"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T.詳細"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T.セルフテスト"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "{foo} を保存"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Enterキーまたはカンマを使用してアドレスを保存します。空白のままにするとメール通知が無効になります。"
@@ -1335,10 +1380,6 @@ msgstr "Enterキーまたはカンマを使用してアドレスを保存しま
msgid "Save Settings"
msgstr "設定を保存"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "システムを保存"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "データベースに保存され、無効にするまで有効期限が切れません。"
@@ -1387,7 +1428,7 @@ msgstr "外部監視サービスに定期的にアウトバウンド ping を送
msgid "Send test heartbeat"
msgstr "テストハートビートを送信"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "送信"
@@ -1399,6 +1440,7 @@ msgstr "シリアル番号"
msgid "Service Details"
msgstr "サービス詳細"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "サービス"
@@ -1414,8 +1456,10 @@ msgstr "ハートビート監視を有効にするには、Beszel ハブで次
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "設定"
@@ -1459,17 +1503,18 @@ msgstr "ステータス"
msgid "Sub State"
msgstr "サブ状態"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "システムが使用するスワップ領域"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "スワップ使用量"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "スワップ使用量"
msgid "System"
msgstr "システム"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "システムの負荷平均の推移"
@@ -1501,6 +1546,11 @@ msgstr "システムはデータディレクトリ内の<0>config.yml</0>ファ
msgid "Table"
msgstr "テーブル"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "タスク"
@@ -1511,7 +1561,7 @@ msgstr "タスク"
msgid "Temp"
msgstr "温度"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "温度"
@@ -1520,7 +1570,7 @@ msgstr "温度"
msgid "Temperature unit"
msgstr "温度単位"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "システムセンサーの温度"
@@ -1552,11 +1602,11 @@ msgstr "この操作は元に戻せません。これにより、データベー
msgid "This will permanently delete all selected records from the database."
msgstr "これにより、選択したすべてのレコードがデータベースから完全に削除されます。"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "{extraFsName}のスループット"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "ルートファイルシステムのスループット"
@@ -1568,11 +1618,6 @@ msgstr "時間形式"
msgid "To email(s)"
msgstr "宛先メールアドレス"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "グリッドを切り替え"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1730,20 +1775,20 @@ msgstr "アップロード"
msgid "Uptime"
msgstr "稼働時間"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "使用量"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "ルートパーティションの使用量"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "使用中"
@@ -1773,7 +1818,7 @@ msgstr "直近200件のアラートを表示します。"
msgid "Visible Fields"
msgstr "表示列"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "表示するのに十分なレコードを待っています"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows コマンド"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "書き込み"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ko\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1시간"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1분"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12시간"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15분"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30일"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5분"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "활성 상태"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "{foo} 추가"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "<0>시스템</0> 추가"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "시스템 추가"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "URL 추가"
@@ -134,6 +135,7 @@ msgstr "메인 레이아웃 너비 조정"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "관리자"
@@ -163,6 +165,7 @@ msgstr "알림"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "모든 컨테이너"
@@ -188,11 +191,11 @@ msgstr "확실합니까?"
msgid "Automatic copy requires a secure context."
msgstr "자동 복사는 안전한 컨텍스트가 필요합니다."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "평균"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Docker 컨테이너의 평균 CPU 사용량"
@@ -206,20 +209,20 @@ msgstr "평균이 <0>{value}{0}</0> 아래로 떨어집니다"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "평균이 <0>{value}{0}</0>을(를) 초과합니다"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "GPU들의 평균 전원 사용량"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "시스템 전체의 평균 CPU 사용량"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "평균 {0} 사용량"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "GPU 엔진 평균 사용량"
@@ -228,7 +231,7 @@ msgstr "GPU 엔진 평균 사용량"
msgid "Backups"
msgstr "백업"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "대역폭"
@@ -238,7 +241,7 @@ msgstr "대역폭"
msgid "Bat"
msgstr "배터리"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "배터리"
@@ -288,7 +291,7 @@ msgstr "부팅 상태"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "바이트 (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "캐시 / 버퍼"
@@ -334,7 +337,7 @@ msgstr "메트릭의 표시 단위를 변경합니다."
msgid "Change general application options."
msgstr "일반 애플리케이션 옵션 변경."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "충전"
@@ -347,6 +350,10 @@ msgstr "충전 중"
msgid "Chart options"
msgstr "차트 옵션"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "차트 너비"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "{email}에서 재설정 링크를 확인하세요."
@@ -407,6 +414,10 @@ msgstr "충돌"
msgid "Connection is down"
msgstr "연결이 끊겼습니다"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "컨테이너"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "아래 에이전트의 <0>docker-compose.yml</0> 내용을 복사하거
msgid "Copy YAML"
msgstr "YAML 복사"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "CPU 시간"
msgid "CPU Time Breakdown"
msgstr "CPU 시간 분배"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "누적 업로드"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "현재 상태"
@@ -531,6 +547,11 @@ msgstr "사이클"
msgid "Daily"
msgstr "매일"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "기본 기간"
@@ -563,11 +584,12 @@ msgstr "장치"
msgid "Discharging"
msgstr "방전 중"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "디스크"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "디스크 I/O"
@@ -575,25 +597,31 @@ msgstr "디스크 I/O"
msgid "Disk unit"
msgstr "디스크 단위"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "디스크 사용량"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "{extraFsName}의 디스크 사용량"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Docker CPU 사용량"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Docker 메모리 사용량"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker 네트워크 I/O"
@@ -703,7 +731,7 @@ msgstr "마지막 {2, plural, one {# 분} other {# 분}} 동안 {0}{1} 초과"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "실행 메인 PID"
msgstr "Exec 주 PID"
#: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
@@ -770,7 +798,7 @@ msgstr "실패: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD 명령어"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "가득"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "전역"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU 엔진들"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "GPU 전원 사용량"
@@ -826,6 +859,7 @@ msgstr "GPU 전원 사용량"
msgid "GPU Usage"
msgstr "GPU 사용량"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "그리드"
@@ -912,7 +946,7 @@ msgstr "생명주기"
msgid "limit"
msgstr "제한"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "부하 평균"
@@ -941,6 +975,7 @@ msgstr "로드 상태"
msgid "Loading..."
msgstr "로딩 중..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "로그아웃"
@@ -966,7 +1001,7 @@ msgstr "알림을 생성하려 하시나요? 시스템 테이블의 종 <0/> 아
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "메인 PID"
msgstr " PID"
#: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences."
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "수동 설정 방법"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "1분간 최댓값"
@@ -999,15 +1034,16 @@ msgstr "메모리 제한"
msgid "Memory Peak"
msgstr "메모리 최대값"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "메모리 사용량"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Docker 컨테이너의 메모리 사용량"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "모델"
@@ -1025,11 +1061,11 @@ msgstr "이름"
msgid "Net"
msgstr "네트워크"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Docker 컨테이너의 네트워크 트래픽"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "계정에 로그인하세요."
msgid "Port"
msgstr "포트"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "전원 켜기"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "기록된 시간의 정확한 사용량"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "조용한 시간"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "읽기"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "수신됨"
@@ -1304,7 +1345,7 @@ msgstr "재개"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "루트"
msgstr "최상위"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T. 세부 정보"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. 자체 테스트"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "{foo} 저장"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Enter 키 또는 쉼표를 사용하여 주소를 저장하세요. 이메일 알림을 비활성화하려면 비워 두세요."
@@ -1335,10 +1380,6 @@ msgstr "Enter 키 또는 쉼표를 사용하여 주소를 저장하세요. 이
msgid "Save Settings"
msgstr "설정 저장"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "시스템 저장"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "데이터베이스에 저장되며 비활성화할 때까지 만료되지 않습니다."
@@ -1387,7 +1428,7 @@ msgstr "외부 모니터링 서비스에 주기적으로 아웃바운드 핑을
msgid "Send test heartbeat"
msgstr "테스트 하트비트 전송"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "보냄"
@@ -1399,6 +1440,7 @@ msgstr "시리얼 번호"
msgid "Service Details"
msgstr "서비스 세부 정보"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "서비스"
@@ -1414,8 +1456,10 @@ msgstr "하트비트 모니터링을 활성화하려면 Beszel 허브에 다음
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "설정"
@@ -1459,17 +1503,18 @@ msgstr "상태"
msgid "Sub State"
msgstr "하위 상태"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "시스템에서 사용된 스왑 공간"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "스왑 사용량"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "스왑 사용량"
msgid "System"
msgstr "시스템"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "시간에 따른 시스템 부하 평균"
@@ -1501,6 +1546,11 @@ msgstr "시스템은 데이터 디렉토리 내의 <0>config.yml</0> 파일에
msgid "Table"
msgstr "표"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "작업"
@@ -1511,7 +1561,7 @@ msgstr "작업"
msgid "Temp"
msgstr "온도"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "온도"
@@ -1520,7 +1570,7 @@ msgstr "온도"
msgid "Temperature unit"
msgstr "온도 단위"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "시스템 센서의 온도"
@@ -1552,11 +1602,11 @@ msgstr "이 작업은 되돌릴 수 없습니다. 데이터베이스에서 {name
msgid "This will permanently delete all selected records from the database."
msgstr "선택한 모든 레코드를 데이터베이스에서 영구적으로 삭제합니다."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "{extraFsName}의 처리량"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "루트 파일 시스템의 처리량"
@@ -1568,11 +1618,6 @@ msgstr "시간 형식"
msgid "To email(s)"
msgstr "받는사람(들)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "그리드 전환"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1675,7 +1720,7 @@ msgstr "유형"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "유닛 파일"
msgstr "Unit 파일"
#. Temperature / network units
#: src/components/routes/settings/general.tsx
@@ -1728,22 +1773,22 @@ msgstr "업로드"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "가동시간"
msgstr "가동 시간"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "사용량"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "루트 파티션의 사용량"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "사용됨"
@@ -1773,7 +1818,7 @@ msgstr "최근 200개의 알림을 봅니다."
msgid "Visible Fields"
msgstr "표시할 열"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "표시할 충분한 기록을 기다리는 중"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows 명령어"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "쓰기"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: nl\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-02-19 19:40\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -42,14 +48,14 @@ msgstr "{count, plural, one {{countString} minuut} other {{countString} minuten}
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
msgstr "{threads, plural, one {# thread} other {# threads}}"
msgstr ""
#: src/lib/utils.ts
msgid "1 hour"
msgstr "1 uur"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 minuut"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 uren"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 minuten"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 dagen"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 minuten"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Actieve status"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Voeg {foo} toe"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Voeg <0>Systeem</0> toe"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Voeg systeem toe"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Voeg URL toe"
@@ -134,6 +135,7 @@ msgstr "Breedte van het hoofdlayout aanpassen"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Administrator"
@@ -147,7 +149,7 @@ msgstr "Start na het instellen van de omgevingsvariabelen je Beszel-hub opnieuw
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -163,6 +165,7 @@ msgstr "Waarschuwingen"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Alle containers"
@@ -188,11 +191,11 @@ msgstr "Weet je het zeker?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisch kopiëren vereist een veilige context."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Gemiddelde"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Gemiddeld CPU-gebruik van containers"
@@ -206,20 +209,20 @@ msgstr "Gemiddelde daalt onder <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Gemiddelde overschrijdt <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Gemiddeld stroomverbruik van GPU's"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Gemiddeld systeembrede CPU-gebruik"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Gemiddeld gebruik van {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Gemiddeld gebruik van GPU-engines"
@@ -228,7 +231,7 @@ msgstr "Gemiddeld gebruik van GPU-engines"
msgid "Backups"
msgstr "Back-ups"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Bandbreedte"
@@ -236,9 +239,9 @@ msgstr "Bandbreedte"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Batterij"
@@ -277,7 +280,7 @@ msgstr "Binair"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -286,11 +289,11 @@ msgstr "Opstartstatus"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffers"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
@@ -324,7 +327,7 @@ msgstr "Opgelet - potentieel gegevensverlies"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Verander statistiek eenheden."
msgid "Change general application options."
msgstr "Wijzig algemene applicatie opties."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Lading"
@@ -347,6 +350,10 @@ msgstr "Opladen"
msgid "Chart options"
msgstr "Grafiekopties"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Grafiekbreedte"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Controleer {email} op een reset link."
@@ -407,6 +414,10 @@ msgstr "Conflicten"
msgid "Connection is down"
msgstr "Verbinding is niet actief"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr ""
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Kopieer de<0>docker-compose.yml</0> inhoud voor de agent hieronder, of r
msgid "Copy YAML"
msgstr "YAML kopiëren"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "CPU-tijd"
msgid "CPU Time Breakdown"
msgstr "CPU-tijdverdeling"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Cumulatieve upload"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Huidige status"
@@ -531,6 +547,11 @@ msgstr "Cycli"
msgid "Daily"
msgstr "Dagelijks"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Standaard tijdsduur"
@@ -563,11 +584,12 @@ msgstr "Apparaat"
msgid "Discharging"
msgstr "Ontladen"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Schijf"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Schijf I/O"
@@ -575,25 +597,31 @@ msgstr "Schijf I/O"
msgid "Disk unit"
msgstr "Schijf eenheid"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Schijfgebruik"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Schijfgebruik van {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Docker CPU-gebruik"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Docker geheugengebruik"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker netwerk I/O"
@@ -731,7 +759,7 @@ msgstr "Exporteer je huidige systeemconfiguratie."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,7 +798,7 @@ msgstr "Mislukt: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -783,7 +811,7 @@ msgstr "Vingerafdruk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD commando"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Vol"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "Globaal"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU-engines"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "GPU stroomverbruik"
@@ -826,6 +859,7 @@ msgstr "GPU stroomverbruik"
msgid "GPU Usage"
msgstr "GPU-gebruik"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Raster"
@@ -836,7 +870,7 @@ msgstr "Gezondheid"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -876,7 +910,7 @@ msgstr "Als je het wachtwoord voor je beheerdersaccount bent kwijtgeraakt, kan j
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Afbeelding"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -884,7 +918,7 @@ msgstr "Inactief"
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr "Interval"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
@@ -912,7 +946,7 @@ msgstr "Levenscyclus"
msgid "limit"
msgstr "limiet"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Gemiddelde Belasting"
@@ -941,6 +975,7 @@ msgstr "Laadstatus"
msgid "Loading..."
msgstr "Laden..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Afmelden"
@@ -978,9 +1013,9 @@ msgid "Manual setup instructions"
msgstr "Handmatige installatie-instructies"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 min"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
@@ -999,18 +1034,19 @@ msgstr "Geheugenlimiet"
msgid "Memory Peak"
msgstr "Geheugenpiek"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Geheugengebruik"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Geheugengebruik van docker containers"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1025,11 +1061,11 @@ msgstr "Naam"
msgid "Net"
msgstr "Netwerk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Netwerkverkeer van docker containers"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "Meld je aan bij je account"
msgid "Port"
msgstr "Poort"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Inschakelen"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Nauwkeurig gebruik op de opgenomen tijd"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Stille uren"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Lezen"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Ontvangen"
@@ -1304,7 +1345,7 @@ msgstr "Hervatten"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T.-details"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Zelf-test"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "{foo} opslaan"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Bewaar het adres met de enter-toets of komma. Laat leeg om e-mailmeldingen uit te schakelen."
@@ -1335,10 +1380,6 @@ msgstr "Bewaar het adres met de enter-toets of komma. Laat leeg om e-mailmelding
msgid "Save Settings"
msgstr "Instellingen opslaan"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Systeem bewaren"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Opgeslagen in de database en verloopt niet totdat u het uitschakelt."
@@ -1387,7 +1428,7 @@ msgstr "Stuur periodieke uitgaande pings naar een externe monitoringservice, zod
msgid "Send test heartbeat"
msgstr "Stuur test-heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Verzonden"
@@ -1399,9 +1440,10 @@ msgstr "Serienummer"
msgid "Service Details"
msgstr "Servicedetails"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Services"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
@@ -1414,8 +1456,10 @@ msgstr "Stel de volgende omgevingsvariabelen in op je Beszel-hub om heartbeat-mo
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Instellingen"
@@ -1453,23 +1497,24 @@ msgstr "Status"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Substatus"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Swap ruimte gebruikt door het systeem"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Swap gebruik"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Swap gebruik"
msgid "System"
msgstr "Systeem"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Gemiddelde systeembelasting na verloop van tijd"
@@ -1501,6 +1546,11 @@ msgstr "Systemen kunnen worden beheerd in een <0>config.yml</0> bestand in je da
msgid "Table"
msgstr "Tabel"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Taken"
@@ -1511,7 +1561,7 @@ msgstr "Taken"
msgid "Temp"
msgstr "Temperatuur"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatuur"
@@ -1520,13 +1570,13 @@ msgstr "Temperatuur"
msgid "Temperature unit"
msgstr "Temperatuureenheid"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperatuur van systeem sensoren"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1552,11 +1602,11 @@ msgstr "Deze actie kan niet ongedaan worden gemaakt. Dit zal alle huidige record
msgid "This will permanently delete all selected records from the database."
msgstr "Dit zal alle geselecteerde records verwijderen uit de database."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Doorvoer van {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Doorvoer van het root bestandssysteem"
@@ -1568,11 +1618,6 @@ msgstr "Tijdnotatie"
msgid "To email(s)"
msgstr "Naar e-mail(s)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Schakel raster"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Schakel thema"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1621,7 +1666,7 @@ msgstr "Geactiveerd door"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Triggers"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
@@ -1671,7 +1716,7 @@ msgstr "Triggert wanneer het gebruik van een schijf een drempelwaarde overschrij
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Type"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
@@ -1730,20 +1775,20 @@ msgstr "Uploaden"
msgid "Uptime"
msgstr "Actief"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Gebruik"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Gebruik van root-partitie"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Gebruikt"
@@ -1773,7 +1818,7 @@ msgstr "Bekijk je 200 meest recente meldingen."
msgid "Visible Fields"
msgstr "Zichtbare kolommen"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Wachtend op genoeg records om weer te geven"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows-commando"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Schrijven"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: no\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Norwegian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,9 +55,9 @@ msgid "1 hour"
msgstr "1 time"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -66,9 +72,9 @@ msgid "12 hours"
msgstr "12 timer"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -79,9 +85,9 @@ msgid "30 days"
msgstr "30 dager"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Aktiv tilstand"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Legg til {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Legg til <0>System</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Legg til system"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Legg Til URL"
@@ -134,8 +135,9 @@ msgstr "Juster bredden på hovedlayouten"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -147,7 +149,7 @@ msgstr "Etter å ha angitt miljøvariablene, start Beszel-huben på nytt for at
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -163,6 +165,7 @@ msgstr "Alarmer"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Alle containere"
@@ -188,11 +191,11 @@ msgstr "Er du sikker?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisk kopiering krever en sikker kontekst."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Gjennomsnitt"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Gjennomsnittlig CPU-utnyttelse av konteinere"
@@ -206,20 +209,20 @@ msgstr "Gjennomsnittet faller under <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Gjennomsnittet overstiger <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Gjennomsnittlig strømforbruk for GPU-er"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Gjennomsnittlig CPU-utnyttelse for hele systemet"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Gjennomsnittlig utnyttelse av {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Gjennomsnittlig utnyttelse av GPU-motorer"
@@ -228,7 +231,7 @@ msgstr "Gjennomsnittlig utnyttelse av GPU-motorer"
msgid "Backups"
msgstr "Sikkerhetskopier"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Båndbredde"
@@ -238,7 +241,7 @@ msgstr "Båndbredde"
msgid "Bat"
msgstr "Batteri"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Batteri"
@@ -277,7 +280,7 @@ msgstr "Binær"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -286,9 +289,9 @@ msgstr "Oppstartstilstand"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffere"
@@ -324,7 +327,7 @@ msgstr "Advarsel - potensielt tap av data"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Endre måleenheter for målinger."
msgid "Change general application options."
msgstr "Endre generelle program-innstillinger."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Lading"
@@ -347,6 +350,10 @@ msgstr "Lader"
msgid "Chart options"
msgstr "Diagraminnstillinger"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Grafbredde"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Sjekk {email} for en nullstillings-link."
@@ -407,6 +414,10 @@ msgstr "Konflikter"
msgid "Connection is down"
msgstr "Tilkoblingen er nede"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Containere"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Kopier <0>docker-compose.yml</0> for agenten nedenfor, eller registrer a
msgid "Copy YAML"
msgstr "Kopier YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "CPU-tid"
msgid "CPU Time Breakdown"
msgstr "CPU-tidsoppdeling"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Kumulativ opplasting"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Nåværende tilstand"
@@ -531,6 +547,11 @@ msgstr "Sykluser"
msgid "Daily"
msgstr "Daglig"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Standard tidsperiode"
@@ -563,37 +584,44 @@ msgstr "Enhet"
msgid "Discharging"
msgstr "Lader ut"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Disk I/O"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
msgstr "Diskenhet"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Diskbruk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Diskbruk av {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Docker CPU-bruk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Docker Minnebruk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Docker Nettverks-I/O"
@@ -731,7 +759,7 @@ msgstr "Eksporter din nåværende systemkonfigurasjon"
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,12 +798,12 @@ msgstr "Mislyktes: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
msgstr "Filter..."
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "FreeBSD kommando"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Fullt"
@@ -812,13 +841,17 @@ msgstr "Generelt"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU-motorer"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "GPU Effektforbruk"
@@ -826,6 +859,7 @@ msgstr "GPU Effektforbruk"
msgid "GPU Usage"
msgstr "GPU-bruk"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Rutenett"
@@ -836,7 +870,7 @@ msgstr "Helse"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -876,7 +910,7 @@ msgstr "Dersom du har mistet passordet til admin-kontoen kan du nullstille det m
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Image"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -912,7 +946,7 @@ msgstr "Livssyklus"
msgid "limit"
msgstr "grense"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Snittbelastning Last"
@@ -941,6 +975,7 @@ msgstr "Lastetilstand"
msgid "Loading..."
msgstr "Laster..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Logg Ut"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Instruks for Manuell Installasjon"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Maks 1 min"
@@ -999,15 +1034,16 @@ msgstr "Minnegrense"
msgid "Memory Peak"
msgstr "Minne-topp"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Minnebruk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Minnebruk av docker-konteinere"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modell"
@@ -1025,11 +1061,11 @@ msgstr "Navn"
msgid "Net"
msgstr "Nett"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Nettverkstrafikk av docker-konteinere"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1152,7 +1188,7 @@ msgstr "Fortid"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Pause"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Paused"
@@ -1177,7 +1213,7 @@ msgstr "Prosentandel av tid brukt i hver tilstand"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Permanent"
msgstr "Permanent"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Persistence"
@@ -1218,15 +1254,20 @@ msgstr "Vennligst logg inn på kontoen din"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Påslag"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Nøyaktig utnyttelse på registrert tidspunkt"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Stille timer"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Lesing"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Mottatt"
@@ -1326,6 +1367,10 @@ msgstr "S.M.A.R.T.-detaljer"
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. selvtest"
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Lagre {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Lagre adressen med Enter-tasten eller komma. La feltet være tomt for å deaktivere e-postvarsler."
@@ -1335,10 +1380,6 @@ msgstr "Lagre adressen med Enter-tasten eller komma. La feltet være tomt for å
msgid "Save Settings"
msgstr "Lagre Innstillinger"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Lagre system"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Lagret i databasen og utløper ikke før du deaktiverer det."
@@ -1387,7 +1428,7 @@ msgstr "Send periodiske utgående pinger til en ekstern overvåkingstjeneste sli
msgid "Send test heartbeat"
msgstr "Send test-heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Sendt"
@@ -1399,6 +1440,7 @@ msgstr "Serienummer"
msgid "Service Details"
msgstr "Tjenestedetaljer"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Tjenester"
@@ -1414,8 +1456,10 @@ msgstr "Angi følgende miljøvariabler på Beszel-huben din for å aktivere hear
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Innstillinger"
@@ -1453,23 +1497,24 @@ msgstr "Tilstand"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Undertilstand"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Swap-plass i bruk av systemet"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Swap-bruk"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1479,9 +1524,9 @@ msgstr "Swap-bruk"
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Systembelastning gjennomsnitt over tid"
@@ -1501,6 +1546,11 @@ msgstr "Systemer kan håndteres i en <0>config.yml</0>-fil i din data-katalog."
msgid "Table"
msgstr "Tabell"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Oppgaver"
@@ -1509,9 +1559,9 @@ msgstr "Oppgaver"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Temp"
msgstr "Temp"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatur"
@@ -1520,13 +1570,13 @@ msgstr "Temperatur"
msgid "Temperature unit"
msgstr "Temperaturenhet"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperaturer på system-sensorer"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1552,11 +1602,11 @@ msgstr "Denne handlingen kan ikke omgjøres. Dette vil slette alle poster for {n
msgid "This will permanently delete all selected records from the database."
msgstr "Dette vil permanent slette alle valgte oppføringer fra databasen."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Gjennomstrømning av {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Gjennomstrømning av rot-filsystemet"
@@ -1568,11 +1618,6 @@ msgstr "Tidsformat"
msgid "To email(s)"
msgstr "Til e-postadresse(r)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Rutenett av/på"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Tema av/på"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1671,7 +1716,7 @@ msgstr "Slår inn når forbruk av hvilken som helst disk overstiger en grensever
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Type"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
@@ -1685,7 +1730,7 @@ msgstr "Enhetspreferanser"
#: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Universal token"
msgstr "Universal token"
msgstr ""
#. Context: Battery state
#: src/lib/i18n.ts
@@ -1730,20 +1775,20 @@ msgstr "Last opp"
msgid "Uptime"
msgstr "Oppetid"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Forbruk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Forbruk av rot-partisjon"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Brukt"
@@ -1773,7 +1818,7 @@ msgstr "Vis de 200 siste varslene."
msgid "Visible Fields"
msgstr "Synlige Felter"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Venter på nok registreringer til å vise"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Windows-kommando"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Skriving"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: pl\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \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"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,9 +55,9 @@ msgid "1 hour"
msgstr "1 godzina"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -66,9 +72,9 @@ msgid "12 hours"
msgstr "12 godzin"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -79,9 +85,9 @@ msgid "30 days"
msgstr "30 dni"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Stan aktywny"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Dodaj {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Dodaj <0>system</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Dodaj system"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Dodaj URL"
@@ -134,8 +135,9 @@ msgstr "Dostosuj szerokość widoku"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -147,7 +149,7 @@ msgstr "Po ustawieniu zmiennych środowiskowych zrestartuj hub Beszel, aby zmian
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -163,6 +165,7 @@ msgstr "Alerty"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Wszystkie kontenery"
@@ -188,11 +191,11 @@ msgstr "Czy jesteś pewien?"
msgid "Automatic copy requires a secure context."
msgstr "Automatyczne kopiowanie wymaga bezpiecznego kontekstu."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Średnia"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Średnie wykorzystanie CPU przez kontenery"
@@ -206,20 +209,20 @@ msgstr "Średnia spada poniżej <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Średnia przekracza <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Średnie zużycie energii przez GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Średnie wykorzystanie CPU w całym systemie"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Średnie użycie {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Średnie wykorzystanie silników GPU"
@@ -228,7 +231,7 @@ msgstr "Średnie wykorzystanie silników GPU"
msgid "Backups"
msgstr "Kopie zapasowe"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Przepustowość"
@@ -238,7 +241,7 @@ msgstr "Przepustowość"
msgid "Bat"
msgstr "Bateria"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Bateria"
@@ -288,7 +291,7 @@ msgstr "Stan rozruchu"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bajty (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Pamięć podręczna / Bufory"
@@ -334,7 +337,7 @@ msgstr "Zmień jednostki wyświetlania dla metryk."
msgid "Change general application options."
msgstr "Zmień ogólne ustawienia aplikacji."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Ładowanie"
@@ -347,6 +350,10 @@ msgstr "Ładuje się"
msgid "Chart options"
msgstr "Wykresy"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Szerokość wykresu"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Sprawdź {email}, aby uzyskać link do resetowania."
@@ -407,6 +414,10 @@ msgstr "Konflikty"
msgid "Connection is down"
msgstr "Brak połączenia"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Kontenery"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,6 +473,11 @@ msgstr "Skopiuj poniżej zawartość pliku <0>docker-compose.yml</0> dla agenta
msgid "Copy YAML"
msgstr "Kopiuj YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -484,8 +500,8 @@ msgstr "Czas CPU"
msgid "CPU Time Breakdown"
msgstr "Podział czasu CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Wysyłanie łącznie"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Aktualny stan"
@@ -531,6 +547,11 @@ msgstr "Cykle"
msgid "Daily"
msgstr "Codziennie"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Domyślny przedział czasu"
@@ -563,11 +584,12 @@ msgstr "Urządzenie"
msgid "Discharging"
msgstr "Rozładowuje się"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Dysk"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Dysk I/O"
@@ -575,25 +597,31 @@ msgstr "Dysk I/O"
msgid "Disk unit"
msgstr "Jednostka dysku"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Użycie dysku"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Wykorzystanie dysku {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Użycie CPU przez Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Użycie pamięci przez Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Sieć Docker I/O"
@@ -731,7 +759,7 @@ msgstr "Eksportuj aktualną konfigurację systemów."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,7 +798,7 @@ msgstr "Nieudane: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "Polecenie FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Pełna"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "Globalny"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "Silniki GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Moc GPU"
@@ -826,6 +859,7 @@ msgstr "Moc GPU"
msgid "GPU Usage"
msgstr "Użycie GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Siatka"
@@ -836,7 +870,7 @@ msgstr "Kondycja"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -910,9 +944,9 @@ msgstr "Cykl życia"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "limit"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Średnie obciążenie"
@@ -941,6 +975,7 @@ msgstr "Stan obciążenia"
msgid "Loading..."
msgstr "Ładowanie..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Wyloguj"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Instrukcja ręcznej konfiguracji"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Maks. 1 min"
@@ -999,18 +1034,19 @@ msgstr "Limit pamięci"
msgid "Memory Peak"
msgstr "Szczyt pamięci"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Użycie pamięci"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Użycie pamięci przez kontenery Docker."
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1025,11 +1061,11 @@ msgstr "Nazwa"
msgid "Net"
msgstr "Sieć"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Ruch sieciowy kontenerów Docker."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1218,15 +1254,20 @@ msgstr "Zaloguj się na swoje konto"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Włączony"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Dokładne wykorzystanie w zarejestrowanym czasie"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Godziny ciszy"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Odczyt"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Otrzymane"
@@ -1304,7 +1345,7 @@ msgstr "Wznów"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1326,6 +1367,10 @@ msgstr "Szczegóły S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "Samodiagnostyka S.M.A.R.T."
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Zapisz {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Zapisz adres, używając klawisza enter lub przecinka. Pozostaw puste, aby wyłączyć powiadomienia e-mail."
@@ -1335,10 +1380,6 @@ msgstr "Zapisz adres, używając klawisza enter lub przecinka. Pozostaw puste, a
msgid "Save Settings"
msgstr "Zapisz ustawienia"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Zapisz system"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Zapisany w bazie danych. Nie wygasa, dopóki go nie wyłączysz."
@@ -1387,7 +1428,7 @@ msgstr "Wysyłaj okresowe pingi wychodzące do zewnętrznej usługi monitorowani
msgid "Send test heartbeat"
msgstr "Wyślij testowy heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Wysłane"
@@ -1399,6 +1440,7 @@ msgstr "Numer seryjny"
msgid "Service Details"
msgstr "Szczegóły usługi"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Usługi"
@@ -1414,8 +1456,10 @@ msgstr "Ustaw następujące zmienne środowiskowe w hubie Beszel, aby włączyć
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Ustawienia"
@@ -1453,23 +1497,24 @@ msgstr "Stan"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Stan podrzędny"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Pamięć wymiany używana przez system"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Użycie pamięci wymiany"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1479,9 +1524,9 @@ msgstr "Użycie pamięci wymiany"
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Średnie obciążenie systemu w czasie"
@@ -1501,6 +1546,11 @@ msgstr "Systemy mogą być zarządzane w pliku <0>config.yml</0> znajdującym si
msgid "Table"
msgstr "Tabela"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Zadania"
@@ -1511,7 +1561,7 @@ msgstr "Zadania"
msgid "Temp"
msgstr "Temperatura"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatura"
@@ -1520,13 +1570,13 @@ msgstr "Temperatura"
msgid "Temperature unit"
msgstr "Jednostka temperatury"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperatury czujników systemowych."
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1552,11 +1602,11 @@ msgstr "Tej akcji nie można cofnąć. Spowoduje to trwałe usunięcie wszystkic
msgid "This will permanently delete all selected records from the database."
msgstr "Spowoduje to trwałe usunięcie wszystkich zaznaczonych rekordów z bazy danych."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Przepustowość {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Przepustowość głównego systemu plików"
@@ -1568,11 +1618,6 @@ msgstr "Format czasu"
msgid "To email(s)"
msgstr "Do e-mail(ów)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Przełącz widok"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Zmień motyw"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1728,22 +1773,22 @@ msgstr "Wysyłanie"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Czas pracy"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Wykorzystanie"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Użycie partycji głównej"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Używane"
@@ -1773,7 +1818,7 @@ msgstr "Wyświetl 200 ostatnich alertów."
msgid "Visible Fields"
msgstr "Widoczne kolumny"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Oczekiwanie na wystarczającą liczbę rekordów do wyświetlenia"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Polecenie Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Zapis"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: pt\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-03-27 19:17\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -42,16 +48,16 @@ msgstr "{count, plural, one {{countString} minuto} other {{countString} minutos}
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
msgstr "{threads, plural, one {# thread} other {# threads}}"
msgstr ""
#: src/lib/utils.ts
msgid "1 hour"
msgstr "1 hora"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -66,9 +72,9 @@ msgid "12 hours"
msgstr "12 horas"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -79,9 +85,9 @@ msgid "30 days"
msgstr "30 dias"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Estado ativo"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Adicionar {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Adicionar <0>Sistema</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Adicionar sistema"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Adicionar URL"
@@ -134,8 +135,9 @@ msgstr "Ajustar a largura do layout principal"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -163,6 +165,7 @@ msgstr "Alertas"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Todos os Contêineres"
@@ -188,11 +191,11 @@ msgstr "Tem certeza?"
msgid "Automatic copy requires a secure context."
msgstr "A cópia automática requer um contexto seguro."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Média"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Utilização média de CPU dos contêineres"
@@ -206,20 +209,20 @@ msgstr "A média cai abaixo de <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "A média excede <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Consumo médio de energia pelas GPU's"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Utilização média de CPU em todo o sistema"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Utilização média de {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Utilização média dos motores GPU"
@@ -228,7 +231,7 @@ msgstr "Utilização média dos motores GPU"
msgid "Backups"
msgstr "Cópias de segurança"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Largura de Banda"
@@ -236,9 +239,9 @@ msgstr "Largura de Banda"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Bateria"
@@ -277,7 +280,7 @@ msgstr "Binário"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -286,11 +289,11 @@ msgstr "Estado de inicialização"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffers"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
@@ -324,7 +327,7 @@ msgstr "Cuidado - possível perda de dados"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -334,7 +337,7 @@ msgstr "Alterar unidades de exibição para métricas."
msgid "Change general application options."
msgstr "Alterar opções gerais do aplicativo."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Carga"
@@ -347,6 +350,10 @@ msgstr "Carregando"
msgid "Chart options"
msgstr "Opções de gráfico"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Largura do gráfico"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Verifique {email} para um link de redefinição."
@@ -407,6 +414,10 @@ msgstr "Conflitos"
msgid "Connection is down"
msgstr "A conexão está inativa"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Contentores"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,11 +473,16 @@ msgstr "Copie o conteúdo do <0>docker-compose.yml</0> do agente abaixo, ou regi
msgid "Copy YAML"
msgstr "Copiar YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -484,8 +500,8 @@ msgstr "Tempo de CPU"
msgid "CPU Time Breakdown"
msgstr "Distribuição do Tempo de CPU"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Upload cumulativo"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Estado atual"
@@ -531,6 +547,11 @@ msgstr "Ciclos"
msgid "Daily"
msgstr "Diariamente"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Período de tempo padrão"
@@ -563,11 +584,12 @@ msgstr "Dispositivo"
msgid "Discharging"
msgstr "Descarregando"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disco"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "E/S de Disco"
@@ -575,25 +597,31 @@ msgstr "E/S de Disco"
msgid "Disk unit"
msgstr "Unidade de disco"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Uso de Disco"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Uso de disco de {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Uso de CPU do Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Uso de Memória do Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "E/S de Rede do Docker"
@@ -636,7 +664,7 @@ msgstr "Editar {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -731,7 +759,7 @@ msgstr "Exporte a configuração atual dos seus sistemas."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -770,7 +798,7 @@ msgstr "Falhou: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -783,7 +811,7 @@ msgstr "Impressão digital"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Cheia"
@@ -812,13 +841,17 @@ msgstr "Geral"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "Motores GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Consumo de Energia da GPU"
@@ -826,6 +859,7 @@ msgstr "Consumo de Energia da GPU"
msgid "GPU Usage"
msgstr "Uso de GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Grade"
@@ -836,7 +870,7 @@ msgstr "Saúde"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -854,7 +888,7 @@ msgstr "Comando Homebrew"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -912,7 +946,7 @@ msgstr "Ciclo de vida"
msgid "limit"
msgstr "limite"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Carga Média"
@@ -941,6 +975,7 @@ msgstr "Estado de carga"
msgid "Loading..."
msgstr "Carregando..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Sair"
@@ -958,7 +993,7 @@ msgstr "Tentativa de login falhou"
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
msgid "Logs"
msgstr "Logs"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Instruções de configuração manual"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Máx 1 min"
@@ -999,15 +1034,16 @@ msgstr "Limite de memória"
msgid "Memory Peak"
msgstr "Pico de memória"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Uso de Memória"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Uso de memória dos contêineres Docker"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modelo"
@@ -1025,11 +1061,11 @@ msgstr "Nome"
msgid "Net"
msgstr "Rede"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Tráfego de rede dos contêineres Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "Por favor, entre na sua conta"
msgid "Port"
msgstr "Porta"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Ligado"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Utilização precisa no momento registrado"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Horas Silenciosas"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Ler"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Recebido"
@@ -1304,7 +1345,7 @@ msgstr "Retomar"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1326,6 +1367,10 @@ msgstr "Detalhes S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "Auto-teste S.M.A.R.T."
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Guardar {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Salve o endereço usando a tecla enter ou vírgula. Deixe em branco para desativar notificações por email."
@@ -1335,10 +1380,6 @@ msgstr "Salve o endereço usando a tecla enter ou vírgula. Deixe em branco para
msgid "Save Settings"
msgstr "Guardar Definições"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Guardar Sistema"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Salvo no banco de dados e não expira até você desativá-lo."
@@ -1387,7 +1428,7 @@ msgstr "Envie pings de saída periódicos para um serviço de monitorização ex
msgid "Send test heartbeat"
msgstr "Enviar heartbeat de teste"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Enviado"
@@ -1399,6 +1440,7 @@ msgstr "Número de Série"
msgid "Service Details"
msgstr "Detalhes do serviço"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Serviços"
@@ -1414,8 +1456,10 @@ msgstr "Defina as seguintes variáveis de ambiente no seu hub do Beszel para ati
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Configurações"
@@ -1459,17 +1503,18 @@ msgstr "Estado"
msgid "Sub State"
msgstr "Subestado"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Espaço de swap usado pelo sistema"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Uso de Swap"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Uso de Swap"
msgid "System"
msgstr "Sistema"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Médias de carga do sistema ao longo do tempo"
@@ -1501,6 +1546,11 @@ msgstr "Os sistemas podem ser gerenciados em um arquivo <0>config.yml</0> dentro
msgid "Table"
msgstr "Tabela"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Tarefas"
@@ -1509,9 +1559,9 @@ msgstr "Tarefas"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Temp"
msgstr "Temp"
msgstr ""
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Temperatura"
@@ -1520,7 +1570,7 @@ msgstr "Temperatura"
msgid "Temperature unit"
msgstr "Unidade de temperatura"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Temperaturas dos sensores do sistema"
@@ -1552,11 +1602,11 @@ msgstr "Esta ação não pode ser desfeita. Isso excluirá permanentemente todos
msgid "This will permanently delete all selected records from the database."
msgstr "Isso excluirá permanentemente todos os registros selecionados do banco de dados."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Taxa de transferência de {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Taxa de transferência do sistema de arquivos raiz"
@@ -1568,11 +1618,6 @@ msgstr "Formato de hora"
msgid "To email(s)"
msgstr "Para email(s)"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Alternar grade"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1581,7 +1626,7 @@ msgstr "Alternar tema"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1600,7 +1645,7 @@ msgstr "Tokens e impressões digitais são usados para autenticar conexões WebS
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1613,7 +1658,7 @@ msgstr "Dados totais enviados para cada interface"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total: {0}"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
@@ -1728,22 +1773,22 @@ msgstr "Carregar"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Tempo de Atividade"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Uso"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Uso da partição raiz"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Usado"
@@ -1773,7 +1818,7 @@ msgstr "Veja os seus 200 alertas mais recentes."
msgid "Visible Fields"
msgstr "Campos Visíveis"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Aguardando registros suficientes para exibir"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Comando Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Escrever"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ru\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-01-31 21:16\n"
"PO-Revision-Date: 2026-02-21 09:46\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
@@ -18,6 +18,12 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n"
#. placeholder {0}: newVersion.v
#: src/components/footer-repo-link.tsx
msgctxt "New version available"
msgid "{0} available"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -49,7 +55,7 @@ msgid "1 hour"
msgstr "1 час"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 мин"
@@ -66,7 +72,7 @@ msgid "12 hours"
msgstr "12 часов"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 мин"
@@ -79,7 +85,7 @@ msgid "30 days"
msgstr "30 дней"
#. Load average
#: src/components/charts/load-average-chart.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 мин"
@@ -107,19 +113,14 @@ msgid "Active state"
msgstr "Активное состояние"
#: src/components/add-system.tsx
#: src/components/add-system.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Add {foo}"
msgstr "Добавить {foo}"
#: src/components/add-system.tsx
msgid "Add <0>System</0>"
msgstr "Добавить <0>Систему</0>"
#: src/components/add-system.tsx
msgid "Add system"
msgstr "Добавить систему"
#: src/components/routes/settings/notifications.tsx
msgid "Add URL"
msgstr "Добавить URL"
@@ -134,6 +135,7 @@ msgstr "Настроить ширину основного макета"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Администратор"
@@ -163,6 +165,7 @@ msgstr "Оповещения"
#: src/components/command-palette.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/containers.tsx
msgid "All Containers"
msgstr "Все контейнеры"
@@ -188,11 +191,11 @@ msgstr "Вы уверены?"
msgid "Automatic copy requires a secure context."
msgstr "Автоматическое копирование требует безопасного контекста."
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Average"
msgstr "Среднее"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average CPU utilization of containers"
msgstr "Среднее использование CPU контейнерами"
@@ -206,20 +209,20 @@ msgstr "Среднее опускается ниже <0>{value}{0}</0>"
msgid "Average exceeds <0>{value}{0}</0>"
msgstr "Среднее превышает <0>{value}{0}</0>"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average power consumption of GPUs"
msgstr "Среднее потребление мощности всеми GPU"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Среднее использование CPU по всей системе"
#. placeholder {0}: gpu.n
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of {0}"
msgstr "Среднее использование {0}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "Average utilization of GPU engines"
msgstr "Средняя загрузка GPU движков"
@@ -228,7 +231,7 @@ msgstr "Средняя загрузка GPU движков"
msgid "Backups"
msgstr "Резервные копии"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
msgid "Bandwidth"
msgstr "Пропускная способность"
@@ -238,7 +241,7 @@ msgstr "Пропускная способность"
msgid "Bat"
msgstr "Батарея"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Battery"
msgstr "Батарея"
@@ -288,7 +291,7 @@ msgstr "Состояние загрузки"
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Байты (Кбайт/с, Мбайт/с, Гбайт/с)"
#: src/components/charts/mem-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Кэш / Буферы"
@@ -334,7 +337,7 @@ msgstr "Изменить единицы измерения для метрик."
msgid "Change general application options."
msgstr "Изменить общие параметры приложения."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Заряд"
@@ -347,6 +350,10 @@ msgstr "Заряжается"
msgid "Chart options"
msgstr "Параметры графиков"
#: src/components/routes/system/info-bar.tsx
msgid "Chart width"
msgstr "Ширина графика"
#: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link."
msgstr "Проверьте {email} для получения ссылки на сброс."
@@ -407,6 +414,10 @@ msgstr "Конфликты"
msgid "Connection is down"
msgstr "Нет соединения"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Контейнеры"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Continue"
@@ -462,6 +473,11 @@ msgstr "Скопируйте содержимое <0>docker-compose.yml</0> дл
msgid "Copy YAML"
msgstr "Скопировать YAML"
#: src/components/routes/system.tsx
msgctxt "Core system metrics"
msgid "Core"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -484,8 +500,8 @@ msgstr "Время CPU"
msgid "CPU Time Breakdown"
msgstr "Распределение времени ЦП"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/charts/cpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts
msgid "CPU Usage"
@@ -517,7 +533,7 @@ msgid "Cumulative Upload"
msgstr "Совокупная отдача"
#. Context: Battery state
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Current state"
msgstr "Текущее состояние"
@@ -531,6 +547,11 @@ msgstr "Циклы"
msgid "Daily"
msgstr "Ежедневно"
#: src/components/routes/system/info-bar.tsx
msgctxt "Default system layout option"
msgid "Default"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Default time period"
msgstr "Период по умолчанию"
@@ -563,11 +584,12 @@ msgstr "Устройство"
msgid "Discharging"
msgstr "Разряжается"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Диск"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Disk I/O"
msgstr "Дисковый ввод/вывод"
@@ -575,25 +597,31 @@ msgstr "Дисковый ввод/вывод"
msgid "Disk unit"
msgstr "Единицы измерения дисковой активности"
#: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/lib/alerts.ts
msgid "Disk Usage"
msgstr "Использование диска"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Disk usage of {extraFsName}"
msgstr "Использование диска {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
msgstr "Использование CPU Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Docker Memory Usage"
msgstr "Использование памяти Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Docker Network I/O"
msgstr "Сетевой ввод/вывод Docker"
@@ -770,7 +798,7 @@ msgstr "Неудачно: {0}"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
@@ -800,6 +828,7 @@ msgid "FreeBSD command"
msgstr "Команда FreeBSD"
#. Context: Battery state
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Полная"
@@ -815,10 +844,14 @@ msgid "Global"
msgstr "Глобально"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU движки"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
msgstr "Потребляемая мощность GPU"
@@ -826,6 +859,7 @@ msgstr "Потребляемая мощность GPU"
msgid "GPU Usage"
msgstr "Использование GPU"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Grid"
msgstr "Сетка"
@@ -836,7 +870,7 @@ msgstr "Здоровье"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -912,7 +946,7 @@ msgstr "Жизненный цикл"
msgid "limit"
msgstr "лимит"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
msgstr "Средняя загрузка"
@@ -941,6 +975,7 @@ msgstr "Состояние загрузки"
msgid "Loading..."
msgstr "Загрузка..."
#: src/components/navbar.tsx
#: src/components/navbar.tsx
msgid "Log Out"
msgstr "Выйти"
@@ -978,7 +1013,7 @@ msgid "Manual setup instructions"
msgstr "Инструкции по ручной настройке"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Макс 1 мин"
@@ -999,15 +1034,16 @@ msgstr "Лимит памяти"
msgid "Memory Peak"
msgstr "Пик памяти"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/lib/alerts.ts
msgid "Memory Usage"
msgstr "Использование памяти"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Memory usage of docker containers"
msgstr "Использование памяти контейнерами Docker"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Модель"
@@ -1025,11 +1061,11 @@ msgstr "Имя"
msgid "Net"
msgstr "Сеть"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of docker containers"
msgstr "Сетевой трафик контейнеров Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
@@ -1220,13 +1256,18 @@ msgstr "Пожалуйста, войдите в свою учетную запи
msgid "Port"
msgstr "Порт"
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Включение питания"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Точное использование в записанное время"
@@ -1248,12 +1289,12 @@ msgid "Quiet Hours"
msgstr "Тихие часы"
#. Disk read
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Read"
msgstr "Чтение"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Received"
msgstr "Получено"
@@ -1326,6 +1367,10 @@ msgstr "Детали S.M.A.R.T."
msgid "S.M.A.R.T. Self-Test"
msgstr "Самотестирование S.M.A.R.T."
#: src/components/add-system.tsx
msgid "Save {foo}"
msgstr "Сохранить {foo}"
#: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Сохраните адрес, используя клавишу ввода или запятую. Оставьте пустым, чтобы отключить уведомления по электронной почте."
@@ -1335,10 +1380,6 @@ msgstr "Сохраните адрес, используя клавишу вво
msgid "Save Settings"
msgstr "Сохранить настройки"
#: src/components/add-system.tsx
msgid "Save system"
msgstr "Сохранить систему"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Saved in the database and does not expire until you disable it."
msgstr "Сохранено в базе данных и не истекает, пока вы его не отключите."
@@ -1387,7 +1428,7 @@ msgstr "Отправляйте периодические исходящие п
msgid "Send test heartbeat"
msgstr "Отправить тестовый heartbeat"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/network-charts.tsx
msgid "Sent"
msgstr "Отправлено"
@@ -1399,6 +1440,7 @@ msgstr "Серийный номер"
msgid "Service Details"
msgstr "Детали сервиса"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Службы"
@@ -1414,8 +1456,10 @@ msgstr "Установите следующие переменные окруж
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/layout.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Settings"
msgstr "Настройки"
@@ -1459,17 +1503,18 @@ msgstr "Статус"
msgid "Sub State"
msgstr "Подсостояние"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Используемое системой пространство подкачки"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
msgstr "Использование подкачки"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/navbar.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -1481,7 +1526,7 @@ msgstr "Использование подкачки"
msgid "System"
msgstr "Система"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Средняя загрузка системы за время"
@@ -1501,6 +1546,11 @@ msgstr "Системы могут управляться в файле <0>config
msgid "Table"
msgstr "Таблица"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Задачи"
@@ -1511,7 +1561,7 @@ msgstr "Задачи"
msgid "Temp"
msgstr "Темп"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
msgid "Temperature"
msgstr "Температура"
@@ -1520,7 +1570,7 @@ msgstr "Температура"
msgid "Temperature unit"
msgstr "Единицы измерения температуры"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Температуры датчиков системы"
@@ -1552,11 +1602,11 @@ msgstr "Это действие не может быть отменено. Эт
msgid "This will permanently delete all selected records from the database."
msgstr "Это навсегда удалит все выбранные записи из базы данных."
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Throughput of {extraFsName}"
msgstr "Пропускная способность {extraFsName}"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Throughput of root filesystem"
msgstr "Пропускная способность корневой файловой системы"
@@ -1568,11 +1618,6 @@ msgstr "Формат времени"
msgid "To email(s)"
msgstr "На электронную почту"
#: src/components/routes/system/info-bar.tsx
#: src/components/routes/system/info-bar.tsx
msgid "Toggle grid"
msgstr "Переключить сетку"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Toggle theme"
@@ -1728,22 +1773,22 @@ msgstr "Отдача"
#: src/components/routes/system/info-bar.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Uptime"
msgstr "Uptime"
msgstr "Время работы"
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Использование"
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
msgid "Usage of root partition"
msgstr "Использование корневого раздела"
#: src/components/charts/mem-chart.tsx
#: src/components/charts/swap-chart.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Used"
msgstr "Использовано"
@@ -1773,7 +1818,7 @@ msgstr "Просмотреть 200 последних оповещений."
msgid "Visible Fields"
msgstr "Видимые столбцы"
#: src/components/routes/system.tsx
#: src/components/routes/system/chart-card.tsx
msgid "Waiting for enough records to display"
msgstr "Ожидание достаточного количества записей для отображения"
@@ -1812,8 +1857,8 @@ msgid "Windows command"
msgstr "Команда Windows"
#. Disk write
#: src/components/routes/system.tsx
#: src/components/routes/system.tsx
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/extra-fs-charts.tsx
msgid "Write"
msgstr "Запись"

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