Compare commits

...

24 Commits

Author SHA1 Message Date
henrygd
9d87102e0c working intel gpu stats for one gpu 2025-09-22 16:13:04 -04:00
henrygd
6a406e5206 intel_gpu_top testing 2025-09-22 14:00:01 -04:00
henrygd
240e75f025 add sorted style to home table header buttons 2025-09-21 19:23:34 -04:00
henrygd
ea984844ff update changelog 2025-09-21 17:56:35 -04:00
henrygd
0d157b5857 display agent connection type in hub (ssh, websocket) 2025-09-21 17:49:22 -04:00
henrygd
d0b6e725c8 fix positioning of bandwidth chart button 2025-09-19 12:08:41 -04:00
henrygd
ffe7f8547a fix: update temperature and byte formatting functions to use loose equality checks (#1180) 2025-09-19 11:51:27 -04:00
henrygd
37817b0f15 add --auto-update flag to hub install script 2025-09-18 17:51:22 -04:00
henrygd
a66ac418ae install: remove additional service restart for openwrt 2025-09-18 14:05:19 -04:00
henrygd
2ee2f53267 fix: resolve mipsle architecture detection for install script (#1176)
- Add proper endianness detection using ELF header inspection
- Prevent mipsle devices from downloading incorrect mips binaries
- Maintain backward compatibility for all other architectures
2025-09-18 13:48:24 -04:00
henrygd
e5c766c00b refactoring
- network interface delta
- string concatenation
2025-09-17 21:36:05 -04:00
henrygd
da43ba10e1 add aria-label to button in NetworkSheet for improved accessibility 2025-09-17 16:23:02 -04:00
henrygd
fca13004bd release 0.12.9 2025-09-17 16:06:20 -04:00
henrygd
376a86829c fix divide by zero error (#1175) 2025-09-17 16:00:50 -04:00
henrygd
ef48613f3f improve style of chart sheet button on mobile
- also update changelog
2025-09-17 15:13:10 -04:00
henrygd
49976c6f61 fix nvidia agent dockerfile after project reorganization 2025-09-17 14:10:02 -04:00
henrygd
d68f1f0985 0.12.8 release :) 2025-09-17 14:02:17 -04:00
henrygd
273a090200 update translations 2025-09-17 14:01:20 -04:00
henrygd
59057a2ba4 add check for status alerts which are not properly resolved (#1052) 2025-09-17 13:31:49 -04:00
henrygd
1b9e781d45 refactor deltatracker
- embed mutex
- add example function
2025-09-16 22:09:46 -04:00
henrygd
4e0ca7c2ba formatting (biome) 2025-09-15 18:04:13 -04:00
henrygd
a9e7bcd37f add per-interface and cumulative network traffic charts (#926)
Co-authored-by: Sven van Ginkel <svenvanginkel@icloud.com>
2025-09-15 17:59:21 -04:00
henrygd
4635f24fb2 fix entre arg in makefile dev server 2025-09-15 17:26:07 -04:00
henrygd
3e73399b87 fix battery detection on newer macs (#1170) 2025-09-15 12:02:50 -04:00
90 changed files with 3527 additions and 695 deletions

View File

@@ -77,7 +77,7 @@ dev-hub: export ENV=dev
dev-hub:
mkdir -p ./internal/site/dist && touch ./internal/site/dist/index.html
@if command -v entr >/dev/null 2>&1; then \
find ./internal/cmd/hub/*.go ./internal/{alerts,hub,records,users}/*.go | entr -r -s "cd ./internal/cmd/hub && go run -tags development . serve --http 0.0.0.0:8090"; \
find ./internal -type f -name '*.go' | entr -r -s "cd ./internal/cmd/hub && go run -tags development . serve --http 0.0.0.0:8090"; \
else \
cd ./internal/cmd/hub && go run -tags development . serve --http 0.0.0.0:8090; \
fi

View File

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

View File

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

View File

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

View File

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

View File

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

102
agent/gpu_intel.go Normal file
View File

@@ -0,0 +1,102 @@
package agent
import (
"encoding/json"
"fmt"
"os/exec"
"github.com/henrygd/beszel/internal/entities/system"
)
const (
intelGpuStatsCmd string = "intel_gpu_top"
intelGpuStatsInterval string = "3300" // in milliseconds
)
type intelGpuStats struct {
Power struct {
GPU float64 `json:"GPU"`
} `json:"power"`
Engines map[string]struct {
Busy float64 `json:"busy"`
} `json:"engines"`
}
// updateIntelFromStats updates aggregated GPU data from a single intelGpuStats sample
func (gm *GPUManager) updateIntelFromStats(sample *intelGpuStats) bool {
gm.Lock()
defer gm.Unlock()
// only one gpu for now - cmd doesn't provide all by default
gpuData, ok := gm.GpuDataMap["0"]
if !ok {
gpuData = &system.GPUData{Name: "GPU", Engines: make(map[string]float64)}
gm.GpuDataMap["0"] = gpuData
}
if sample.Power.GPU > 0 {
gpuData.Power += sample.Power.GPU
}
if gpuData.Engines == nil {
gpuData.Engines = make(map[string]float64, len(sample.Engines))
}
for name, engine := range sample.Engines {
gpuData.Engines[name] += engine.Busy
}
gpuData.Count++
return true
}
// collectIntelStats executes intel_gpu_top in JSON mode and stream-decodes the array of samples
func (gm *GPUManager) collectIntelStats() error {
cmd := exec.Command(intelGpuStatsCmd, "-s", intelGpuStatsInterval, "-J")
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
if err := cmd.Start(); err != nil {
return err
}
dec := json.NewDecoder(stdout)
// Expect a JSON array stream: [ { ... }, { ... }, ... ]
tok, err := dec.Token()
if err != nil {
return err
}
if delim, ok := tok.(json.Delim); !ok || delim != '[' {
return fmt.Errorf("unexpected JSON start token: %v", tok)
}
var sample intelGpuStats
for {
if dec.More() {
// Clear the engines map before decoding
if sample.Engines != nil {
for k := range sample.Engines {
delete(sample.Engines, k)
}
}
if err := dec.Decode(&sample); err != nil {
return fmt.Errorf("decode intel gpu: %w", err)
}
gm.updateIntelFromStats(&sample)
continue
}
// Attempt to read closing bracket (will only be present when process exits)
tok, err = dec.Token()
if err != nil {
// When the process is still running, decoder will block in More/Decode; any error here is terminal
return err
}
if delim, ok := tok.(json.Delim); ok && delim == ']' {
break
}
}
return cmd.Wait()
}

View File

@@ -792,3 +792,96 @@ func TestAccumulation(t *testing.T) {
})
}
}
func TestIntelUpdateFromStats(t *testing.T) {
gm := &GPUManager{
GpuDataMap: make(map[string]*system.GPUData),
}
// First sample with power and two engines
sample1 := intelGpuStats{
Engines: map[string]struct {
Busy float64 `json:"busy"`
}{
"Render/3D": {Busy: 20.0},
"Video": {Busy: 5.0},
},
}
sample1.Power.GPU = 10.5
ok := gm.updateIntelFromStats(&sample1)
assert.True(t, ok)
gpu := gm.GpuDataMap["0"]
require.NotNil(t, gpu)
assert.Equal(t, "GPU", gpu.Name)
assert.InDelta(t, 10.5, gpu.Power, 0.001)
assert.InDelta(t, 20.0, gpu.Engines["Render/3D"], 0.001)
assert.InDelta(t, 5.0, gpu.Engines["Video"], 0.001)
assert.Equal(t, float64(1), gpu.Count)
// Second sample with zero power (should not add) and additional engine busy
sample2 := intelGpuStats{
Engines: map[string]struct {
Busy float64 `json:"busy"`
}{
"Render/3D": {Busy: 10.0},
"Video": {Busy: 2.5},
"Blitter": {Busy: 1.0},
},
}
// zero power should not increment power accumulator
sample2.Power.GPU = 0.0
ok = gm.updateIntelFromStats(&sample2)
assert.True(t, ok)
gpu = gm.GpuDataMap["0"]
require.NotNil(t, gpu)
assert.InDelta(t, 10.5, gpu.Power, 0.001)
assert.InDelta(t, 30.0, gpu.Engines["Render/3D"], 0.001) // 20 + 10
assert.InDelta(t, 7.5, gpu.Engines["Video"], 0.001) // 5 + 2.5
assert.InDelta(t, 1.0, gpu.Engines["Blitter"], 0.001)
assert.Equal(t, float64(2), gpu.Count)
}
func TestIntelCollectorStreaming(t *testing.T) {
// Save and override PATH
origPath := os.Getenv("PATH")
defer os.Setenv("PATH", origPath)
dir := t.TempDir()
os.Setenv("PATH", dir)
// Create a fake intel_gpu_top that prints a JSON array with two samples and exits
scriptPath := filepath.Join(dir, "intel_gpu_top")
script := `#!/bin/sh
# Ignore args -s and -J
# Emit a JSON array with two objects, separated by a comma, then exit
(echo '['; \
echo '{"power":{"GPU":1.5},"engines":{"Render/3D":{"busy":12.34}}},'; \
echo '{"power":{"GPU":2.0},"engines":{"Video":{"busy":5}}}'; \
echo ']')`
if err := os.WriteFile(scriptPath, []byte(script), 0755); err != nil {
t.Fatal(err)
}
gm := &GPUManager{
GpuDataMap: make(map[string]*system.GPUData),
}
// Run the collector once; it should read two samples and return
if err := gm.collectIntelStats(); err != nil {
t.Fatalf("collectIntelStats error: %v", err)
}
gpu := gm.GpuDataMap["0"]
require.NotNil(t, gpu)
// Power should be sum of non-zero samples: 1.5 + 2.0 = 3.5
assert.InDelta(t, 3.5, gpu.Power, 0.001)
// Engines aggregated
assert.InDelta(t, 12.34, gpu.Engines["Render/3D"], 0.001)
assert.InDelta(t, 5.0, gpu.Engines["Video"], 0.001)
// Count should be 2 samples
assert.Equal(t, float64(2), gpu.Count)
}

View File

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

View File

@@ -18,7 +18,6 @@ import (
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/load"
"github.com/shirou/gopsutil/v4/mem"
psutilNet "github.com/shirou/gopsutil/v4/net"
)
// Sets initial / non-changing values about the host system
@@ -32,7 +31,7 @@ func (a *Agent) initializeSystemInfo() {
a.systemInfo.KernelVersion = version
a.systemInfo.Os = system.Darwin
} else if strings.Contains(platform, "indows") {
a.systemInfo.KernelVersion = strings.Replace(platform, "Microsoft ", "", 1) + " " + version
a.systemInfo.KernelVersion = fmt.Sprintf("%s %s", strings.Replace(platform, "Microsoft ", "", 1), version)
a.systemInfo.Os = system.Windows
} else if platform == "freebsd" {
a.systemInfo.Os = system.Freebsd
@@ -70,7 +69,7 @@ func (a *Agent) initializeSystemInfo() {
// Returns current info, stats about the host system
func (a *Agent) getSystemStats() system.Stats {
systemStats := system.Stats{}
var systemStats system.Stats
// battery
if battery.HasReadableBattery() {
@@ -173,55 +172,7 @@ func (a *Agent) getSystemStats() system.Stats {
}
// network stats
if len(a.netInterfaces) == 0 {
// if no network interfaces, initialize again
// this is a fix if agent started before network is online (#466)
// maybe refactor this in the future to not cache interface names at all so we
// don't miss an interface that's been added after agent started in any circumstance
a.initializeNetIoStats()
}
if netIO, err := psutilNet.IOCounters(true); err == nil {
msElapsed := uint64(time.Since(a.netIoStats.Time).Milliseconds())
a.netIoStats.Time = time.Now()
totalBytesSent := uint64(0)
totalBytesRecv := uint64(0)
// sum all bytes sent and received
for _, v := range netIO {
// skip if not in valid network interfaces list
if _, exists := a.netInterfaces[v.Name]; !exists {
continue
}
totalBytesSent += v.BytesSent
totalBytesRecv += v.BytesRecv
}
// add to systemStats
var bytesSentPerSecond, bytesRecvPerSecond uint64
if msElapsed > 0 {
bytesSentPerSecond = (totalBytesSent - a.netIoStats.BytesSent) * 1000 / msElapsed
bytesRecvPerSecond = (totalBytesRecv - a.netIoStats.BytesRecv) * 1000 / msElapsed
}
networkSentPs := bytesToMegabytes(float64(bytesSentPerSecond))
networkRecvPs := bytesToMegabytes(float64(bytesRecvPerSecond))
// add check for issue (#150) where sent is a massive number
if networkSentPs > 10_000 || networkRecvPs > 10_000 {
slog.Warn("Invalid net stats. Resetting.", "sent", networkSentPs, "recv", networkRecvPs)
for _, v := range netIO {
if _, exists := a.netInterfaces[v.Name]; !exists {
continue
}
slog.Info(v.Name, "recv", v.BytesRecv, "sent", v.BytesSent)
}
// reset network I/O stats
a.initializeNetIoStats()
} else {
systemStats.NetworkSent = networkSentPs
systemStats.NetworkRecv = networkRecvPs
systemStats.Bandwidth[0], systemStats.Bandwidth[1] = bytesSentPerSecond, bytesRecvPerSecond
// update netIoStats
a.netIoStats.BytesSent = totalBytesSent
a.netIoStats.BytesRecv = totalBytesRecv
}
}
a.updateNetworkStats(&systemStats)
// temperatures
// TODO: maybe refactor to methods on systemStats
@@ -261,6 +212,7 @@ func (a *Agent) getSystemStats() system.Stats {
}
// update base system info
a.systemInfo.ConnectionType = a.connectionManager.ConnectionType
a.systemInfo.Cpu = systemStats.Cpu
a.systemInfo.LoadAvg = systemStats.LoadAvg
// TODO: remove these in future release in favor of load avg array

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,19 +38,21 @@ type Stats struct {
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"26,keyasint,omitzero"` // [sent bytes, recv bytes]
MaxBandwidth [2]uint64 `json:"bm,omitzero" cbor:"27,keyasint,omitzero"` // [sent bytes, recv bytes]
// TODO: remove other load fields in future release in favor of load avg array
LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"`
Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current]
MaxMem float64 `json:"mm,omitempty" cbor:"30,keyasint,omitempty"`
LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"`
Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current]
MaxMem float64 `json:"mm,omitempty" cbor:"30,keyasint,omitempty"`
NetworkInterfaces map[string][4]uint64 `json:"ni,omitempty" cbor:"31,keyasint,omitempty"` // [upload bytes, download bytes, total upload, total download]
}
type GPUData struct {
Name string `json:"n" cbor:"0,keyasint"`
Temperature float64 `json:"-"`
MemoryUsed float64 `json:"mu,omitempty" cbor:"1,keyasint,omitempty"`
MemoryTotal float64 `json:"mt,omitempty" cbor:"2,keyasint,omitempty"`
Usage float64 `json:"u" cbor:"3,keyasint"`
Power float64 `json:"p,omitempty" cbor:"4,keyasint,omitempty"`
Count float64 `json:"-"`
Name string `json:"n" cbor:"0,keyasint"`
Temperature float64 `json:"-"`
MemoryUsed float64 `json:"mu,omitempty,omitzero" cbor:"1,keyasint,omitempty,omitzero"`
MemoryTotal float64 `json:"mt,omitempty,omitzero" cbor:"2,keyasint,omitempty,omitzero"`
Usage float64 `json:"u" cbor:"3,keyasint,omitempty"`
Power float64 `json:"p,omitempty" cbor:"4,keyasint,omitempty"`
Count float64 `json:"-"`
Engines map[string]float64 `json:"e,omitempty" cbor:"5,keyasint,omitempty"`
}
type FsStats struct {
@@ -83,6 +85,14 @@ const (
Freebsd
)
type ConnectionType = uint8
const (
ConnectionTypeNone ConnectionType = iota
ConnectionTypeSSH
ConnectionTypeWebSocket
)
type Info struct {
Hostname string `json:"h" cbor:"0,keyasint"`
KernelVersion string `json:"k,omitempty" cbor:"1,keyasint,omitempty"`
@@ -104,7 +114,8 @@ type Info struct {
LoadAvg15 float64 `json:"l15,omitempty" cbor:"17,keyasint,omitempty"`
BandwidthBytes uint64 `json:"bb" cbor:"18,keyasint"`
// TODO: remove load fields in future release in favor of load avg array
LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"`
LoadAvg [3]float64 `json:"la,omitempty" cbor:"19,keyasint"`
ConnectionType ConnectionType `json:"ct,omitempty" cbor:"20,keyasint,omitempty,omitzero"`
}
// Final data structure to return to the hub

View File

@@ -225,6 +225,19 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.MaxBandwidth[0] = max(sum.MaxBandwidth[0], stats.MaxBandwidth[0], stats.Bandwidth[0])
sum.MaxBandwidth[1] = max(sum.MaxBandwidth[1], stats.MaxBandwidth[1], stats.Bandwidth[1])
// Accumulate network interfaces
if sum.NetworkInterfaces == nil {
sum.NetworkInterfaces = make(map[string][4]uint64, len(stats.NetworkInterfaces))
}
for key, value := range stats.NetworkInterfaces {
sum.NetworkInterfaces[key] = [4]uint64{
sum.NetworkInterfaces[key][0] + value[0],
sum.NetworkInterfaces[key][1] + value[1],
max(sum.NetworkInterfaces[key][2], value[2]),
max(sum.NetworkInterfaces[key][3], value[3]),
}
}
// Accumulate temperatures
if stats.Temperatures != nil {
if sum.Temperatures == nil {
@@ -271,6 +284,16 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
gpu.Usage += value.Usage
gpu.Power += value.Power
gpu.Count += value.Count
if value.Engines != nil {
if gpu.Engines == nil {
gpu.Engines = make(map[string]float64, len(value.Engines))
}
for engineKey, engineValue := range value.Engines {
gpu.Engines[engineKey] += engineValue
}
}
sum.GPUData[id] = gpu
}
}
@@ -299,6 +322,19 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.Bandwidth[0] = sum.Bandwidth[0] / uint64(count)
sum.Bandwidth[1] = sum.Bandwidth[1] / uint64(count)
sum.Battery[0] = uint8(batterySum / int(count))
// Average network interfaces
if sum.NetworkInterfaces != nil {
for key := range sum.NetworkInterfaces {
sum.NetworkInterfaces[key] = [4]uint64{
sum.NetworkInterfaces[key][0] / uint64(count),
sum.NetworkInterfaces[key][1] / uint64(count),
sum.NetworkInterfaces[key][2],
sum.NetworkInterfaces[key][3],
}
}
}
// Average temperatures
if sum.Temperatures != nil && tempCount > 0 {
for key := range sum.Temperatures {
@@ -327,6 +363,13 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
gpu.Usage = twoDecimals(gpu.Usage / count)
gpu.Power = twoDecimals(gpu.Power / count)
gpu.Count = twoDecimals(gpu.Count / count)
if gpu.Engines != nil {
for engineKey := range gpu.Engines {
gpu.Engines[engineKey] = twoDecimals(gpu.Engines[engineKey] / count)
}
}
sum.GPUData[id] = gpu
}
}

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "beszel",
"version": "0.12.7",
"version": "0.12.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "beszel",
"version": "0.12.7",
"version": "0.12.9",
"dependencies": {
"@henrygd/queue": "^1.0.7",
"@henrygd/semaphore": "^0.0.2",
@@ -46,6 +46,7 @@
"valibot": "^0.42.1"
},
"devDependencies": {
"@biomejs/biome": "2.2.3",
"@lingui/cli": "^5.4.1",
"@lingui/swc-plugin": "^5.6.1",
"@lingui/vite-plugin": "^5.4.1",
@@ -330,6 +331,169 @@
"node": ">=6.9.0"
}
},
"node_modules/@biomejs/biome": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.2.3.tgz",
"integrity": "sha512-9w0uMTvPrIdvUrxazZ42Ib7t8Y2yoGLKLdNne93RLICmaHw7mcLv4PPb5LvZLJF3141gQHiCColOh/v6VWlWmg==",
"dev": true,
"license": "MIT OR Apache-2.0",
"bin": {
"biome": "bin/biome"
},
"engines": {
"node": ">=14.21.3"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "2.2.3",
"@biomejs/cli-darwin-x64": "2.2.3",
"@biomejs/cli-linux-arm64": "2.2.3",
"@biomejs/cli-linux-arm64-musl": "2.2.3",
"@biomejs/cli-linux-x64": "2.2.3",
"@biomejs/cli-linux-x64-musl": "2.2.3",
"@biomejs/cli-win32-arm64": "2.2.3",
"@biomejs/cli-win32-x64": "2.2.3"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.2.3.tgz",
"integrity": "sha512-OrqQVBpadB5eqzinXN4+Q6honBz+tTlKVCsbEuEpljK8ASSItzIRZUA02mTikl3H/1nO2BMPFiJ0nkEZNy3B1w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-darwin-x64": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.2.3.tgz",
"integrity": "sha512-OCdBpb1TmyfsTgBAM1kPMXyYKTohQ48WpiN9tkt9xvU6gKVKHY4oVwteBebiOqyfyzCNaSiuKIPjmHjUZ2ZNMg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.2.3.tgz",
"integrity": "sha512-g/Uta2DqYpECxG+vUmTAmUKlVhnGEcY7DXWgKP8ruLRa8Si1QHsWknPY3B/wCo0KgYiFIOAZ9hjsHfNb9L85+g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.3.tgz",
"integrity": "sha512-q3w9jJ6JFPZPeqyvwwPeaiS/6NEszZ+pXKF+IczNo8Xj6fsii45a4gEEicKyKIytalV+s829ACZujQlXAiVLBQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.2.3.tgz",
"integrity": "sha512-LEtyYL1fJsvw35CxrbQ0gZoxOG3oZsAjzfRdvRBRHxOpQ91Q5doRVjvWW/wepgSdgk5hlaNzfeqpyGmfSD0Eyw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.3.tgz",
"integrity": "sha512-y76Dn4vkP1sMRGPFlNc+OTETBhGPJ90jY3il6jAfur8XWrYBQV3swZ1Jo0R2g+JpOeeoA0cOwM7mJG6svDz79w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-arm64": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.2.3.tgz",
"integrity": "sha512-Ms9zFYzjcJK7LV+AOMYnjN3pV3xL8Prxf9aWdDVL74onLn5kcvZ1ZMQswE5XHtnd/r/0bnUd928Rpbs14BzVmA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-x64": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.2.3.tgz",
"integrity": "sha512-gvCpewE7mBwBIpqk1YrUqNR4mCiyJm6UI3YWQQXkedSSEwzRdodRpaKhbdbHw1/hmTWOVXQ+Eih5Qctf4TCVOQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.6",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz",
@@ -5763,14 +5927,14 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
@@ -5957,9 +6121,9 @@
}
},
"node_modules/vite": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz",
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz",
"integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5968,7 +6132,7 @@
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.43.0",
"tinyglobby": "^0.2.14"
"tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,11 @@
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 { cn, formatShortDate, decimalString, chartMargin, formatBytes, toFixedFloat } from "@/lib/utils"
import { ChartData } from "@/types"
import { memo } from "react"
import { $userSettings } from "@/lib/stores"
import { useStore } from "@nanostores/react"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
import type { ChartData } from "@/types"
import { useYAxisWidth } from "./hooks"
export default memo(function SwapChart({ chartData }: { chartData: ChartData }) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,15 @@ import { Plural, Trans, useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router"
import { timeTicks } from "d3-time"
import { ClockArrowUp, CpuIcon, GlobeIcon, LayoutGridIcon, MonitorIcon, XIcon } from "lucide-react"
import {
ChevronRightSquareIcon,
ClockArrowUp,
CpuIcon,
GlobeIcon,
LayoutGridIcon,
MonitorIcon,
XIcon,
} from "lucide-react"
import { subscribeKeys } from "nanostores"
import React, { type JSX, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import AreaChartDefault from "@/components/charts/area-chart"
@@ -16,7 +24,7 @@ import MemChart from "@/components/charts/mem-chart"
import SwapChart from "@/components/charts/swap-chart"
import TemperatureChart from "@/components/charts/temperature-chart"
import { getPbTimestamp, pb } from "@/lib/api"
import { ChartType, Os, SystemStatus, Unit } from "@/lib/enums"
import { ChartType, ConnectionType, Os, SystemStatus, Unit } from "@/lib/enums"
import { batteryStateTranslations } from "@/lib/i18n"
import {
$allSystemsByName,
@@ -47,11 +55,13 @@ import { $router, navigate } from "../router"
import Spinner from "../spinner"
import { Button } from "../ui/button"
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { AppleIcon, ChartAverage, ChartMax, FreeBsdIcon, Rows, TuxIcon, WindowsIcon } from "../ui/icons"
import { AppleIcon, ChartAverage, ChartMax, FreeBsdIcon, Rows, TuxIcon, WebSocketIcon, WindowsIcon } from "../ui/icons"
import { Input } from "../ui/input"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
import { Separator } from "../ui/separator"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
import NetworkSheet from "./system/network-sheet"
import LineChartDefault from "../charts/line-chart"
type ChartTimeData = {
time: number
@@ -129,7 +139,7 @@ async function getStats<T extends SystemStatsRecord | ContainerStatsRecord>(
function dockerOrPodman(str: string, system: SystemRecord) {
if (system.info.p) {
str = str.replace("docker", "podman").replace("Docker", "Podman")
return str.replace("docker", "podman").replace("Docker", "Podman")
}
return str
}
@@ -389,6 +399,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
const lastGpuVals = Object.values(systemStats.at(-1)?.stats.g ?? {})
const hasGpuData = lastGpuVals.length > 0
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined)
const hasGpuEnginesData = lastGpuVals.some((gpu) => gpu.e !== undefined)
let translatedStatus: string = system.status
if (system.status === SystemStatus.Up) {
@@ -406,25 +417,45 @@ export default memo(function SystemDetail({ name }: { name: string }) {
<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="capitalize flex gap-2 items-center">
<span className={cn("relative flex h-3 w-3")}>
{system.status === SystemStatus.Up && (
<span
className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
style={{ animationDuration: "1.5s" }}
></span>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="capitalize flex gap-2 items-center">
<span className={cn("relative flex h-3 w-3")}>
{system.status === SystemStatus.Up && (
<span
className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
style={{ animationDuration: "1.5s" }}
></span>
)}
<span
className={cn("relative inline-flex rounded-full h-3 w-3", {
"bg-green-500": system.status === SystemStatus.Up,
"bg-red-500": system.status === SystemStatus.Down,
"bg-primary/40": system.status === SystemStatus.Paused,
"bg-yellow-500": system.status === SystemStatus.Pending,
})}
></span>
</span>
{translatedStatus}
</div>
</TooltipTrigger>
{system.info.ct && (
<TooltipContent>
{system.info.ct === ConnectionType.WebSocket ? (
<div className="flex gap-1 items-center">
<WebSocketIcon className="size-4" /> WebSocket
</div>
) : (
<div className="flex gap-1 items-center">
<ChevronRightSquareIcon className="size-4" strokeWidth={2} /> SSH
</div>
)}
</TooltipContent>
)}
<span
className={cn("relative inline-flex rounded-full h-3 w-3", {
"bg-green-500": system.status === SystemStatus.Up,
"bg-red-500": system.status === SystemStatus.Down,
"bg-primary/40": system.status === SystemStatus.Paused,
"bg-yellow-500": system.status === SystemStatus.Pending,
})}
></span>
</span>
{translatedStatus}
</div>
</Tooltip>
</TooltipProvider>
{systemInfo.map(({ value, label, Icon, hide }) => {
if (hide || !value) {
return null
@@ -564,13 +595,13 @@ export default memo(function SystemDetail({ name }: { name: string }) {
dataPoints={[
{
label: t({ message: "Write", comment: "Disk write" }),
dataKey: ({ stats }) => (showMax ? stats?.dwm : stats?.dw),
dataKey: ({ stats }: SystemStatsRecord) => (showMax ? stats?.dwm : stats?.dw),
color: 3,
opacity: 0.3,
},
{
label: t({ message: "Read", comment: "Disk read" }),
dataKey: ({ stats }) => (showMax ? stats?.drm : stats?.dr),
dataKey: ({ stats }: SystemStatsRecord) => (showMax ? stats?.drm : stats?.dr),
color: 1,
opacity: 0.3,
},
@@ -590,7 +621,12 @@ export default memo(function SystemDetail({ name }: { name: string }) {
empty={dataEmpty}
grid={grid}
title={t`Bandwidth`}
cornerEl={maxValSelect}
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
@@ -600,7 +636,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
{
label: t`Sent`,
// use bytes if available, otherwise multiply old MB (can remove in future)
dataKey(data) {
dataKey(data: SystemStatsRecord) {
if (showMax) {
return data?.stats?.bm?.[0] ?? (data?.stats?.nsm ?? 0) * 1024 * 1024
}
@@ -611,7 +647,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
},
{
label: t`Received`,
dataKey(data) {
dataKey(data: SystemStatsRecord) {
if (showMax) {
return data?.stats?.bm?.[1] ?? (data?.stats?.nrm ?? 0) * 1024 * 1024
}
@@ -620,7 +656,9 @@ export default memo(function SystemDetail({ name }: { name: string }) {
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}`
@@ -674,6 +712,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
grid={grid}
title={t`Load Average`}
description={t`System load averages over time`}
legend={true}
>
<LoadAverageChart chartData={chartData} />
</ChartCard>
@@ -687,6 +726,7 @@ export default memo(function SystemDetail({ name }: { name: string }) {
title={t`Temperature`}
description={t`Temperatures of system sensors`}
cornerEl={<FilterBar store={$temperatureFilter} />}
legend={Object.keys(systemStats.at(-1)?.stats.t ?? {}).length < 12}
>
<TemperatureChart chartData={chartData} />
</ChartCard>
@@ -721,6 +761,12 @@ export default memo(function SystemDetail({ name }: { name: string }) {
</ChartCard>
)}
</div>
{/* Non-power GPU charts */}
{hasGpuData && (
<div className="grid xl:grid-cols-2 gap-4">
{/* GPU power draw chart */}
{hasGpuPowerData && (
<ChartCard
@@ -732,11 +778,16 @@ export default memo(function SystemDetail({ name }: { name: string }) {
<GpuPowerChart chartData={chartData} />
</ChartCard>
)}
</div>
{/* GPU charts */}
{hasGpuData && (
<div className="grid xl:grid-cols-2 gap-4">
{hasGpuEnginesData && (
<ChartCard
empty={dataEmpty}
grid={grid}
title={t`GPU Engines`}
description={t`Average utilization of GPU engines`}
>
<GpuEnginesChart chartData={chartData} />
</ChartCard>
)}
{Object.keys(systemStats.at(-1)?.stats.g ?? {}).map((id) => {
const gpu = systemStats.at(-1)?.stats.g?.[id] as GPUData
return (
@@ -761,6 +812,8 @@ export default memo(function SystemDetail({ name }: { name: string }) {
contentFormatter={({ value }) => `${decimalString(value)}%`}
/>
</ChartCard>
{(gpu.mt ?? 0) > 0 && (
<ChartCard
empty={dataEmpty}
grid={grid}
@@ -788,7 +841,9 @@ export default memo(function SystemDetail({ name }: { name: string }) {
}}
/>
</ChartCard>
)}
</div>
)
})}
</div>
@@ -859,6 +914,22 @@ export default memo(function SystemDetail({ name }: { name: string }) {
)
})
function GpuEnginesChart({ chartData }: { chartData: ChartData }) {
const dataPoints = []
const engines = Object.keys(chartData.systemStats?.at(-1)?.stats.g?.[0]?.e ?? {}).sort()
for (const engine of engines) {
dataPoints.push({
label: engine,
dataKey: ({ stats }: SystemStatsRecord) => stats?.g?.[0]?.e?.[engine] ?? 0,
color: `hsl(${140 + ((engines.indexOf(engine) * 360) / engines.length) % 360}, 65%, 52%)`,
opacity: 0.35,
})
}
return (
<LineChartDefault legend={true} chartData={chartData} dataPoints={dataPoints} tickFormatter={(val) => `${toFixedFloat(val, 2)}%`} contentFormatter={({ value }) => `${decimalString(value)}%`} />
)
}
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
const containerFilter = useStore(store)
const { t } = useLingui()
@@ -879,7 +950,7 @@ function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilt
return (
<>
<Input placeholder={t`Filter...`} className="ps-4 pe-8" onChange={handleChange} ref={inputRef} />
<Input placeholder={t`Filter...`} className="ps-4 pe-8 w-full sm:w-44" onChange={handleChange} ref={inputRef} />
{containerFilter && (
<Button
type="button"
@@ -905,7 +976,7 @@ 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">
<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>
@@ -921,13 +992,15 @@ const SelectAvgMax = memo(({ max }: { max: boolean }) => {
)
})
function ChartCard({
export function ChartCard({
title,
description,
children,
grid,
empty,
cornerEl,
legend,
className,
}: {
title: string
description: string
@@ -935,17 +1008,22 @@ function ChartCard({
grid?: boolean
empty?: boolean
cornerEl?: JSX.Element | null
legend?: boolean
className?: string
}) {
const { isIntersecting, ref } = useIntersectionObserver()
return (
<Card className={cn("pb-2 sm:pb-4 odd:last-of-type:col-span-full", { "col-span-full": !grid })} ref={ref}>
<Card
className={cn("pb-2 sm:pb-4 odd:last-of-type:col-span-full min-h-full", { "col-span-full": !grid }, className)}
ref={ref}
>
<CardHeader className="pb-5 pt-4 gap-1 relative max-sm:py-3 max-sm:px-4">
<CardTitle className="text-xl sm:text-2xl">{title}</CardTitle>
<CardDescription>{description}</CardDescription>
{cornerEl && <div className="relative py-1 block sm:w-44 sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>}
{cornerEl && <div className="py-1 grid sm:justify-end sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>}
</CardHeader>
<div className="ps-0 w-[calc(100%-1.5em)] h-48 md:h-52 relative group">
<div className={cn("ps-0 w-[calc(100%-1.5em)] relative group", legend ? "h-54 md:h-56" : "h-48 md:h-52")}>
{
<Spinner
msg={empty ? t`Waiting for enough records to display` : undefined}

View File

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

View File

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

View File

@@ -1,8 +1,12 @@
import { SystemRecord } from "@/types"
import { CellContext, ColumnDef, HeaderContext } from "@tanstack/react-table"
import { ClassValue } from "clsx"
import { t } from "@lingui/core/macro"
import { Trans, useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router"
import type { CellContext, ColumnDef, HeaderContext } from "@tanstack/react-table"
import type { ClassValue } from "clsx"
import {
ArrowUpDownIcon,
ChevronRightSquareIcon,
CopyIcon,
CpuIcon,
HardDriveIcon,
@@ -15,7 +19,10 @@ import {
Trash2Icon,
WifiIcon,
} from "lucide-react"
import { Button } from "../ui/button"
import { memo, useMemo, useRef, useState } from "react"
import { isReadOnlyUser, pb } from "@/lib/api"
import { ConnectionType, MeterState, SystemStatus } from "@/lib/enums"
import { $longestSystemNameLen, $userSettings } from "@/lib/stores"
import {
cn,
copyToClipboard,
@@ -25,24 +32,12 @@ import {
getMeterState,
parseSemVer,
} from "@/lib/utils"
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon } from "../ui/icons"
import { useStore } from "@nanostores/react"
import { $longestSystemNameLen, $userSettings } from "@/lib/stores"
import { Trans, useLingui } from "@lingui/react/macro"
import { useMemo, useRef, useState } from "react"
import { memo } from "react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu"
import AlertButton from "../alerts/alert-button"
import { Dialog } from "../ui/dialog"
import type { SystemRecord } from "@/types"
import { SystemDialog } from "../add-system"
import { AlertDialog } from "../ui/alert-dialog"
import AlertButton from "../alerts/alert-button"
import { $router, Link } from "../router"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
@@ -51,12 +46,16 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "../ui/alert-dialog"
import { buttonVariants } from "../ui/button"
import { t } from "@lingui/core/macro"
import { MeterState, SystemStatus } from "@/lib/enums"
import { $router, Link } from "../router"
import { getPagePath } from "@nanostores/router"
import { isReadOnlyUser, pb } from "@/lib/api"
import { Button, buttonVariants } from "../ui/button"
import { Dialog } from "../ui/dialog"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../ui/dropdown-menu"
import { EthernetIcon, GpuIcon, HourglassIcon, ThermometerIcon, WebSocketIcon } from "../ui/icons"
const STATUS_COLORS = {
[SystemStatus.Up]: "bg-green-500",
@@ -273,24 +272,24 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
return null
}
const system = info.row.original
const color = {
"text-green-500": version === globalThis.BESZEL.HUB_VERSION,
"text-yellow-500": version !== globalThis.BESZEL.HUB_VERSION,
"text-red-500": system.status !== SystemStatus.Up,
}
return (
<span className={cn("flex gap-1.5 items-center md:pe-5 tabular-nums", viewMode === "table" && "ps-0.5")}>
<IndicatorDot
system={system}
className={
(system.status !== SystemStatus.Up && STATUS_COLORS[SystemStatus.Paused]) ||
(version === globalThis.BESZEL.HUB_VERSION && STATUS_COLORS[SystemStatus.Up]) ||
STATUS_COLORS[SystemStatus.Pending]
}
/>
<div className={cn("flex gap-1.5 items-center md:pe-5 tabular-nums", viewMode === "table" && "ps-0.5")}>
{system.info.ct === ConnectionType.WebSocket && <WebSocketIcon className={cn("size-3", color)} />}
{system.info.ct === ConnectionType.SSH && <ChevronRightSquareIcon className={cn("size-3", color)} />}
{!system.info.ct && <IndicatorDot system={system} className={cn(color, "bg-current mx-0.5")} />}
<span className="truncate max-w-14">{info.getValue() as string}</span>
</span>
</div>
)
},
},
{
id: "actions",
// @ts-ignore
// @ts-expect-error
name: () => t({ message: "Actions", comment: "Table column" }),
size: 50,
cell: ({ row }) => (
@@ -305,12 +304,13 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
function sortableHeader(context: HeaderContext<SystemRecord, unknown>) {
const { column } = context
// @ts-ignore
// @ts-expect-error
const { Icon, hideSort, name }: { Icon: React.ElementType; name: () => string; hideSort: boolean } = column.columnDef
const isSorted = column.getIsSorted()
return (
<Button
variant="ghost"
className="h-9 px-3 flex"
className={cn("h-9 px-3 flex duration-50", isSorted && "bg-accent/70 light:bg-accent text-accent-foreground/90")}
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
{Icon && <Icon className="me-2 size-4" />}
@@ -353,7 +353,7 @@ export function IndicatorDot({ system, className }: { system: SystemRecord; clas
export const ActionsButton = memo(({ system }: { system: SystemRecord }) => {
const [deleteOpen, setDeleteOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)
let editOpened = useRef(false)
const editOpened = useRef(false)
const { t } = useLingui()
const { id, status, host, name } = system

View File

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

View File

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

View File

@@ -53,3 +53,9 @@ export enum HourFormat {
"12h" = "12h",
"24h" = "24h",
}
/** Connection type */
export enum ConnectionType {
SSH = 1,
WebSocket,
}

View File

@@ -1,6 +1,7 @@
import { t } from "@lingui/core/macro"
import { type ClassValue, clsx } from "clsx"
import { timeDay, timeHour } from "d3-time"
import { listenKeys } from "nanostores"
import { useEffect, useState } from "react"
import { twMerge } from "tailwind-merge"
import { prependBasePath } from "@/components/router"
@@ -8,7 +9,6 @@ import { toast } from "@/components/ui/use-toast"
import type { ChartTimeData, FingerprintRecord, SemVer, SystemRecord } from "@/types"
import { HourFormat, MeterState, Unit } from "./enums"
import { $copyContent, $userSettings } from "./stores"
import { listenKeys } from "nanostores"
export const FAVICON_DEFAULT = "favicon.svg"
export const FAVICON_GREEN = "favicon-green.svg"
@@ -179,8 +179,8 @@ export function formatTemperature(celsius: number, unit?: Unit): { value: number
if (!unit) {
unit = $userSettings.get().unitTemp || Unit.Celsius
}
// need loose equality check due to form data being strings
if (unit === Unit.Fahrenheit) {
// biome-ignore lint/suspicious/noDoubleEquals: need loose equality check due to form data being strings
if (unit == Unit.Fahrenheit) {
return {
value: celsius * 1.8 + 32,
unit: "°F",
@@ -202,8 +202,8 @@ export function formatBytes(
// Convert MB to bytes if isMegabytes is true
if (isMegabytes) size *= 1024 * 1024
// need loose equality check due to form data being strings
if (unit === Unit.Bits) {
// biome-ignore lint/suspicious/noDoubleEquals: need loose equality check due to form data being strings
if (unit == Unit.Bits) {
const bits = size * 8
const suffix = perSecond ? "ps" : ""
if (bits < 1000) return { value: bits, unit: `b${suffix}` }

View File

@@ -363,6 +363,14 @@ msgstr "أنشئت"
msgid "Critical (%)"
msgstr "حرج (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "التنزيل التراكمي"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "الرفع التراكمي"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "معطل"
msgid "Down ({downSystemsLength})"
msgstr "معطل ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "تنزيل"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "المدة"
@@ -452,6 +464,7 @@ msgstr "تعديل"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "البريد الإشباكي"
@@ -472,6 +485,10 @@ msgstr "أدخل عنوان البريد الإشباكي لإعادة تعيي
msgid "Enter email address..."
msgstr "أدخل عنوان البريد الإشباكي..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "أدخل كلمة المرور لمرة واحدة الخاصة بك."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "لمدة <0>{min}</0> {min, plural, one {دقيقة} other {دقائق}}
msgid "Forgot password?"
msgstr "هل نسيت كلمة المرور؟"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "أمر FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "إدارة تفضيلات العرض والإشعارات."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "تعليمات الإعداد اليدوي"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "حركة مرور الشبكة لحاويات الدوكر"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "حركة مرور الشبكة للواجهات العامة"
@@ -715,6 +741,10 @@ msgstr "دعم OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "في كل إعادة تشغيل، سيتم تحديث الأنظمة في قاعدة البيانات لتتطابق مع الأنظمة المعرفة في الملف."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "كلمة مرور لمرة واحدة"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "قراءة"
msgid "Received"
msgstr "تم الاستلام"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "طلب كلمة مرور لمرة واحدة"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "طلب OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "إعادة تعيين كلمة المرور"
@@ -888,10 +926,6 @@ msgstr "تم الإرسال"
msgid "Set percentage thresholds for meter colors."
msgstr "تعيين عتبات النسبة المئوية لألوان العداد."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "يحدد النطاق الزمني الافتراضي للرسوم البيانية عند عرض النظام."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "معدل نقل {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "معدل نقل نظام الملفات الجذر"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "تنسيق الوقت"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "إلى البريد الإشباكي"
@@ -1034,6 +1072,14 @@ msgstr "تسمح الرموز المميزة للوكلاء بالاتصال و
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "إجمالي البيانات المستلمة لكل واجهة"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "إجمالي البيانات المرسلة لكل واجهة"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة دقيقة واحدة عتبة معينة"
@@ -1048,7 +1094,7 @@ msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل
#: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold"
msgstr "يتم التفعيل عندما <EFBFBD><EFBFBD>تجاوز أي مستشعر عتبة معينة"
msgstr "يتم التفعيل عندما يتجاوز أي مستشعر عتبة معينة"
#: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold"
@@ -1095,6 +1141,10 @@ msgstr "قيد التشغيل"
msgid "Up ({upSystemsLength})"
msgstr "قيد التشغيل ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "رفع"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "مدة التشغيل"

View File

@@ -363,6 +363,14 @@ msgstr "Създаден"
msgid "Critical (%)"
msgstr "Критично (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Кумулативно изтегляне"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Кумулативно качване"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Офлайн"
msgid "Down ({downSystemsLength})"
msgstr "Офлайн ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Изтегляне"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Продължителност"
@@ -452,6 +464,7 @@ msgstr "Редактирай"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Имейл"
@@ -472,6 +485,10 @@ msgstr "Въведи имейл адрес за да нулираш парола
msgid "Enter email address..."
msgstr "Въведи имейл адрес..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Въведете Вашата еднократна парола."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "За <0>{min}</0> {min, plural, one {минута} other {минути}}
msgid "Forgot password?"
msgstr "Забравена парола?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD команда"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Управление на предпочитанията за показване и уведомяване."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Инструкции за ръчна настройка"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Мрежов трафик на docker контейнери"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Мрежов трафик на публични интерфейси"
@@ -715,6 +741,10 @@ msgstr "Поддръжка на OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "На всеки рестарт, системите в датабазата ще бъдат обновени да съвпадат със системите зададени във файла."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Еднократна парола"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Прочети"
msgid "Received"
msgstr "Получени"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Заявка за еднократна парола"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Заявка OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Нулиране на парола"
@@ -888,10 +926,6 @@ msgstr "Изпратени"
msgid "Set percentage thresholds for meter colors."
msgstr "Задайте процентни прагове за цветовете на измервателните уреди."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Задава диапазона за време за диаграмите, когато се разглежда система."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Пропускателна способност на {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Пропускателна способност на root файловата система"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Формат на времето"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "До имейл(ите)"
@@ -1034,6 +1072,14 @@ msgstr "Токените позволяват на агентите да се с
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токените и пръстовите отпечатъци се използват за удостоверяване на WebSocket връзките към концентратора."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Общо получени данни за всеки интерфейс"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Общо изпратени данни за всеки интерфейс"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Задейства се, когато употребата на паметта за 1 минута надвиши зададен праг"
@@ -1095,6 +1141,10 @@ msgstr "Нагоре"
msgid "Up ({upSystemsLength})"
msgstr "Нагоре ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Качване"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Време на работа"

View File

@@ -363,6 +363,14 @@ msgstr "Vytvořeno"
msgid "Critical (%)"
msgstr "Kritické (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativní stažení"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativní odeslání"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Nefunkční"
msgid "Down ({downSystemsLength})"
msgstr "Nefunkční ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Stažení"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Doba trvání"
@@ -452,6 +464,7 @@ msgstr "Upravit"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
@@ -472,6 +485,10 @@ msgstr "Zadejte e-mailovou adresu pro obnovu hesla"
msgid "Enter email address..."
msgstr "Zadejte e-mailovou adresu..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Zadejte Vaše jednorázové heslo."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Za <0>{min}</0> {min, plural, one {minutu} few {minuty} other {minut}}"
msgid "Forgot password?"
msgstr "Zapomněli jste heslo?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD příkaz"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Správa nastavení zobrazení a oznámení."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Pokyny k manuálnímu nastavení"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Síťový provoz kontejnerů docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Síťový provoz veřejných rozhraní"
@@ -715,6 +741,10 @@ msgstr "Podpora OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Při každém restartu budou systémy v databázi aktualizovány tak, aby odpovídaly systémům definovaným v souboru."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Jednorázové heslo"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Číst"
msgid "Received"
msgstr "Přijato"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Požádat o jednorázové heslo"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Požádat OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Obnovit heslo"
@@ -888,10 +926,6 @@ msgstr "Odeslat"
msgid "Set percentage thresholds for meter colors."
msgstr "Nastavte procentuální prahové hodnoty pro barvy měřičů."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Nastaví výchozí časový rozsah grafů, když je systém zobrazen."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Propustnost {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Propustnost kořenového souborového systému"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Formát času"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Na email(y)"
@@ -1034,6 +1072,14 @@ msgstr "Tokeny umožňují agentům připojení a registraci. Otisky jsou stabil
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeny a otisky slouží k ověření připojení WebSocket k uzlu."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Celkový přijatý objem dat pro každé rozhraní"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Celkový odeslaný objem dat pro každé rozhraní"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Spustí se, když využití paměti během 1 minuty překročí prahovou hodnotu"
@@ -1095,6 +1141,10 @@ msgstr "Funkční"
msgid "Up ({upSystemsLength})"
msgstr "Funkční ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Odeslání"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Doba provozu"

View File

@@ -363,6 +363,14 @@ msgstr ""
msgid "Critical (%)"
msgstr "Kritisk (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativ download"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativ upload"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Nede"
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Download"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +464,7 @@ msgstr "Rediger"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "E-mail"
@@ -472,6 +485,10 @@ msgstr "Indtast e-mailadresse for at nulstille adgangskoden"
msgid "Enter email address..."
msgstr "Indtast e-mailadresse..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Indtast din engangsadgangskode."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "For <0>{min}</0> {min, plural, one {minut} other {minutter}}"
msgid "Forgot password?"
msgstr "Glemt adgangskode?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD kommando"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Administrer display og notifikationsindstillinger."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Manuel opsætningsvejledning"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Netværkstrafik af dockercontainere"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Netværkstrafik af offentlige grænseflader"
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDC understøttelse"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ved hver genstart vil systemer i databasen blive opdateret til at matche de systemer, der er defineret i filen."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Engangsadgangskode"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Læs"
msgid "Received"
msgstr "Modtaget"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Anmod om engangsadgangskode"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Anmod OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Nulstil adgangskode"
@@ -888,10 +926,6 @@ msgstr "Sendt"
msgid "Set percentage thresholds for meter colors."
msgstr "Indstil procentvise tærskler for målerfarver."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Sætter standardtidsintervallet for diagrammer når et system vises."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Gennemløb af {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Gennemløb af rodfilsystemet"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Tidsformat"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Til email(s)"
@@ -1034,6 +1072,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Samlet modtaget data for hver interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Samlet sendt data for hver interface"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1141,10 @@ msgstr "Oppe"
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Upload"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Oppetid"

View File

@@ -363,6 +363,14 @@ msgstr "Erstellt"
msgid "Critical (%)"
msgstr "Kritisch (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativer Download"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativer Upload"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Offline"
msgid "Down ({downSystemsLength})"
msgstr "Offline ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Download"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Dauer"
@@ -452,6 +464,7 @@ msgstr "Bearbeiten"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "E-Mail"
@@ -472,6 +485,10 @@ msgstr "E-Mail-Adresse eingeben, um das Passwort zurückzusetzen"
msgid "Enter email address..."
msgstr "E-Mail-Adresse eingeben..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Geben Sie Ihr Einmalpasswort ein."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Für <0>{min}</0> {min, plural, one {Minute} other {Minuten}}"
msgid "Forgot password?"
msgstr "Passwort vergessen?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD Befehl"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Anzeige- und Benachrichtigungseinstellungen verwalten."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Anleitung zur manuellen Einrichtung"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Netzwerkverkehr der Docker-Container"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Netzwerkverkehr der öffentlichen Schnittstellen"
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDC-Unterstützung"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Bei jedem Neustart werden die Systeme in der Datenbank aktualisiert, um den in der Datei definierten Systemen zu entsprechen."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Einmalpasswort"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Lesen"
msgid "Received"
msgstr "Empfangen"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Einmalpasswort anfordern"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP anfordern"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Passwort zurücksetzen"
@@ -888,10 +926,6 @@ msgstr "Gesendet"
msgid "Set percentage thresholds for meter colors."
msgstr "Prozentuale Schwellenwerte für Zählerfarben festlegen."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Legt den Standardzeitraum für Diagramme fest, wenn ein System angezeigt wird."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Durchsatz von {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Durchsatz des Root-Dateisystems"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Zeitformat"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "An E-Mail(s)"
@@ -1034,6 +1072,14 @@ msgstr "Tokens ermöglichen es Agents, sich zu verbinden und zu registrieren. Fi
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Gesamtdatenmenge für jede Schnittstelle empfangen"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Gesamtdatenmenge für jede Schnittstelle gesendet"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Löst aus, wenn der Lastdurchschnitt der letzten Minute einen Schwellenwert überschreitet"
@@ -1095,6 +1141,10 @@ msgstr "aktiv"
msgid "Up ({upSystemsLength})"
msgstr "aktiv ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Upload"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Betriebszeit"

View File

@@ -358,6 +358,14 @@ msgstr "Created"
msgid "Critical (%)"
msgstr "Critical (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Cumulative Download"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Cumulative Upload"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -436,6 +444,10 @@ msgstr "Down"
msgid "Down ({downSystemsLength})"
msgstr "Down ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Download"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Duration"
@@ -447,6 +459,7 @@ msgstr "Edit"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
@@ -467,6 +480,10 @@ msgstr "Enter email address to reset password"
msgid "Enter email address..."
msgstr "Enter email address..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Enter your one-time password."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -537,6 +554,12 @@ msgstr "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgid "Forgot password?"
msgstr "Forgot password?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD command"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -640,6 +663,7 @@ msgid "Manage display and notification preferences."
msgstr "Manage display and notification preferences."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Manual setup instructions"
@@ -675,6 +699,8 @@ msgid "Network traffic of docker containers"
msgstr "Network traffic of docker containers"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Network traffic of public interfaces"
@@ -710,6 +736,10 @@ msgstr "OAuth 2 / OIDC support"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "On each restart, systems in the database will be updated to match the systems defined in the file."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "One-time password"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -828,6 +858,14 @@ msgstr "Read"
msgid "Received"
msgstr "Received"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Request a one-time password"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Request OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Reset Password"
@@ -883,10 +921,6 @@ msgstr "Sent"
msgid "Set percentage thresholds for meter colors."
msgstr "Set percentage thresholds for meter colors."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Sets the default time range for charts when a system is viewed."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -997,6 +1031,10 @@ msgstr "Throughput of {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Throughput of root filesystem"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Time format"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "To email(s)"
@@ -1029,6 +1067,14 @@ msgstr "Tokens allow agents to connect and register. Fingerprints are stable ide
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Total data received for each interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Total data sent for each interface"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Triggers when 1 minute load average exceeds a threshold"
@@ -1090,6 +1136,10 @@ msgstr "Up"
msgid "Up ({upSystemsLength})"
msgstr "Up ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Upload"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Uptime"

View File

@@ -363,6 +363,14 @@ msgstr "Creado"
msgid "Critical (%)"
msgstr "Crítico (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Descarga acumulativa"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Carga acumulativa"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Abajo"
msgid "Down ({downSystemsLength})"
msgstr "Abajo ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Descargar"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Duración"
@@ -452,6 +464,7 @@ msgstr "Editar"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Correo electrónico"
@@ -472,6 +485,10 @@ msgstr "Ingrese la dirección de correo electrónico para restablecer la contras
msgid "Enter email address..."
msgstr "Ingrese dirección de correo..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Ingrese su contraseña de un solo uso."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
msgid "Forgot password?"
msgstr "¿Olvidó su contraseña?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Administrar preferencias de visualización y notificaciones."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Instrucciones manuales de configuración"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Tráfico de red de los contenedores de Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Tráfico de red de interfaces públicas"
@@ -715,6 +741,10 @@ msgstr "Soporte para OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "En cada reinicio, los sistemas en la base de datos se actualizarán para coincidir con los sistemas definidos en el archivo."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Contraseña de un solo uso"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Lectura"
msgid "Received"
msgstr "Recibido"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Solicitar contraseña de un solo uso"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Solicitar OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Restablecer Contraseña"
@@ -888,10 +926,6 @@ msgstr "Enviado"
msgid "Set percentage thresholds for meter colors."
msgstr "Establecer umbrales de porcentaje para los colores de los medidores."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Establece el rango de tiempo predeterminado para los gráficos cuando se visualiza un sistema."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Rendimiento de {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Rendimiento del sistema de archivos raíz"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Formato de hora"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "A correo(s)"
@@ -1034,6 +1072,14 @@ msgstr "Los tokens permiten que los agentes se conecten y registren. Las huellas
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Datos totales recibidos por cada interfaz"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Datos totales enviados por cada interfaz"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Se activa cuando la carga media de 1 minuto supera un umbral"
@@ -1095,6 +1141,10 @@ msgstr "Activo"
msgid "Up ({upSystemsLength})"
msgstr "Activo ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Cargar"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Tiempo de actividad"

View File

@@ -363,6 +363,14 @@ msgstr "ایجاد شده"
msgid "Critical (%)"
msgstr "بحرانی (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "دانلود تجمعی"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "آپلود تجمعی"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "قطع"
msgid "Down ({downSystemsLength})"
msgstr "قطع ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "دانلود"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "مدت زمان"
@@ -452,6 +464,7 @@ msgstr "ویرایش"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "ایمیل"
@@ -472,6 +485,10 @@ msgstr "آدرس ایمیل را برای بازنشانی رمز عبور وا
msgid "Enter email address..."
msgstr "آدرس ایمیل را وارد کنید..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "رمز عبور یک‌بار مصرف خود را وارد کنید."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "برای <0>{min}</0> {min, plural, one {دقیقه} other {دقیقه}}
msgid "Forgot password?"
msgstr "رمز عبور را فراموش کرده‌اید؟"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "دستور FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "مدیریت تنظیمات نمایش و اعلان‌ها."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "دستورالعمل‌های راه‌اندازی دستی"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "ترافیک شبکه کانتینرهای داکر"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "ترافیک شبکه رابط‌های عمومی"
@@ -715,6 +741,10 @@ msgstr "پشتیبانی از OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "در هر بار راه‌اندازی مجدد، سیستم‌های موجود در پایگاه داده با سیستم‌های تعریف شده در فایل مطابقت داده می‌شوند."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "رمز عبور یک‌بار مصرف"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "خواندن"
msgid "Received"
msgstr "دریافت شد"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "درخواست رمز عبور یک‌بار مصرف"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "درخواست OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "بازنشانی رمز عبور"
@@ -888,10 +926,6 @@ msgstr "ارسال شد"
msgid "Set percentage thresholds for meter colors."
msgstr "آستانه های درصدی را برای رنگ های متر تنظیم کنید."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "بازه زمانی پیش‌فرض برای نمودارها هنگام مشاهده یک سیستم را تعیین می‌کند."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "توان عملیاتی {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "توان عملیاتی سیستم فایل ریشه"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "فرمت زمان"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "به ایمیل(ها)"
@@ -1034,6 +1072,14 @@ msgstr "توکن‌ها به عامل‌ها اجازه اتصال و ثبت‌
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "داده‌های کل دریافت شده برای هر رابط"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "داده‌های کل ارسال شده برای هر رابط"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "هنگامی که میانگین بار ۱ دقیقه‌ای از یک آستانه فراتر رود، فعال می‌شود"
@@ -1095,6 +1141,10 @@ msgstr "فعال"
msgid "Up ({upSystemsLength})"
msgstr "فعال ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "آپلود"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "آپتایم"

View File

@@ -363,6 +363,14 @@ msgstr ""
msgid "Critical (%)"
msgstr "Critique (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Téléchargement cumulatif"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Téléversement cumulatif"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Injoignable"
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Télécharger"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +464,7 @@ msgstr "Éditer"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
@@ -472,6 +485,10 @@ msgstr "Entrez l'adresse email pour réinitialiser le mot de passe"
msgid "Enter email address..."
msgstr "Entrez l'adresse email..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Entrez votre mot de passe à usage unique."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Pour <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgid "Forgot password?"
msgstr "Mot de passe oublié ?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Commande FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Gérer les préférences d'affichage et de notification."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Guide pour une installation manuelle"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Trafic réseau des conteneurs Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Trafic réseau des interfaces publiques"
@@ -715,6 +741,10 @@ msgstr "Support OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "À chaque redémarrage, les systèmes dans la base de données seront mis à jour pour correspondre aux systèmes définis dans le fichier."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Mot de passe à usage unique"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Lecture"
msgid "Received"
msgstr "Reçu"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Demander un mot de passe à usage unique"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Demander OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Réinitialiser le mot de passe"
@@ -888,10 +926,6 @@ msgstr "Envoyé"
msgid "Set percentage thresholds for meter colors."
msgstr "Définir des seuils de pourcentage pour les couleurs des compteurs."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Définit la plage de temps par défaut pour les graphiques lorsqu'un système est consulté."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Débit de {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Débit du système de fichiers racine"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Format d'heure"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Aux email(s)"
@@ -1034,6 +1072,14 @@ msgstr "Les tokens permettent aux agents de se connecter et de s'enregistrer. Le
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Données totales reçues pour chaque interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Données totales envoyées pour chaque interface"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1141,10 @@ msgstr "Joignable"
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Téléverser"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Temps de fonctionnement"

View File

@@ -363,6 +363,14 @@ msgstr ""
msgid "Critical (%)"
msgstr "Kritično (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativno preuzimanje"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativno otpremanje"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr ""
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Preuzmi"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +464,7 @@ msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
@@ -472,6 +485,10 @@ msgstr "Unesite email adresu za resetiranje lozinke"
msgid "Enter email address..."
msgstr "Unesite email adresu..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Unesite Vašu jednokratnu lozinku."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Za <0>{min}</0> {min, plural, one {minutu} other {minute}}"
msgid "Forgot password?"
msgstr "Zaboravljena lozinka?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD naredba"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Upravljajte postavkama prikaza i obavijesti."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr ""
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Mrežni promet Docker spremnika"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Mrežni promet javnih sučelja"
@@ -715,6 +741,10 @@ msgstr "Podrška za OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Prilikom svakog ponovnog pokretanja, sustavi u bazi podataka biti će ažurirani kako bi odgovarali sustavima definiranim u datoteci."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Jednokratna lozinka"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Pročitaj"
msgid "Received"
msgstr "Primljeno"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Zatraži jednokratnu lozinku"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Zatraži OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Resetiraj Lozinku"
@@ -888,10 +926,6 @@ msgstr "Poslano"
msgid "Set percentage thresholds for meter colors."
msgstr "Postavite pragove postotka za boje mjerača."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Postavlja zadani vremenski raspon za grafikone kada se sustav gleda."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Protok {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Protok root datotečnog sustava"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Format vremena"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Primaoci e-pošte"
@@ -1034,6 +1072,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Ukupni podaci primljeni za svako sučelje"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Ukupni podaci poslani za svako sučelje"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1141,10 @@ msgstr ""
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Otpremi"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Vrijeme rada"

View File

@@ -363,6 +363,14 @@ msgstr ""
msgid "Critical (%)"
msgstr "Kritikus (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulatív letöltés"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulatív feltöltés"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr ""
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Letöltés"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +464,7 @@ msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
@@ -472,6 +485,10 @@ msgstr "E-mail cím megadása a jelszó visszaállításához"
msgid "Enter email address..."
msgstr "Adja meg az e-mail címet..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Adja meg az egyszeri jelszavát."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "A <0>{min}</0> {min, plural, one {perc} other {percek}}"
msgid "Forgot password?"
msgstr "Elfelejtette a jelszavát?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD parancs"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "A megjelenítési és értesítési beállítások kezelése."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr ""
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Docker konténerek hálózati forgalma"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Nyilvános interfészek hálózati forgalma"
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDC támogatás"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Minden újraindításkor az adatbázisban lévő rendszerek frissítésre kerülnek, hogy megfeleljenek a fájlban meghatározott rendszereknek."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Egyszeri jelszó"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Olvasás"
msgid "Received"
msgstr "Fogadott"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Egyszeri jelszó kérése"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP kérése"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Jelszó visszaállítása"
@@ -888,10 +926,6 @@ msgstr "Elküldve"
msgid "Set percentage thresholds for meter colors."
msgstr "Százalékos küszöbértékek beállítása a mérőszínekhez."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Beállítja az alapértelmezett időtartamot a diagramokhoz, amikor egy rendszert néznek."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "A {extraFsName} átviteli teljesítménye"
msgid "Throughput of root filesystem"
msgstr "A gyökér fájlrendszer átviteli teljesítménye"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Időformátum"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "E-mailben"
@@ -1034,6 +1072,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Összes fogadott adat minden interfészenként"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Összes elküldött adat minden interfészenként"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1141,10 @@ msgstr ""
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Feltöltés"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Üzemidő"

View File

@@ -363,6 +363,14 @@ msgstr ""
msgid "Critical (%)"
msgstr "Kritískt (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr ""
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr ""
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr ""
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +464,7 @@ msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Netfang"
@@ -472,6 +485,10 @@ msgstr "Settu netfang til að endursetja lykilorð"
msgid "Enter email address..."
msgstr "Settu inn Netfang..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr ""
msgid "Forgot password?"
msgstr "Gleymt lykilorð?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr ""
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr ""
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr ""
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Net traffík docker kerfa"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr ""
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDC stuðningur"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr ""
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Lesa"
msgid "Received"
msgstr "Móttekið"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr ""
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr ""
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Endurstilla lykilorð"
@@ -888,10 +926,6 @@ msgstr "Sent"
msgid "Set percentage thresholds for meter colors."
msgstr "Stilltu prósentuþröskuld fyrir mælaliti."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr ""
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr ""
msgid "Throughput of root filesystem"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Til tölvupósta"
@@ -1034,6 +1072,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1141,10 @@ msgstr ""
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr ""
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr ""

View File

@@ -363,6 +363,14 @@ msgstr "Creato"
msgid "Critical (%)"
msgstr "Critico (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Download cumulativo"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Upload cumulativo"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Offline"
msgid "Down ({downSystemsLength})"
msgstr "Offline ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Scarica"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Durata"
@@ -452,6 +464,7 @@ msgstr "Modifica"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
@@ -472,6 +485,10 @@ msgstr "Inserisci l'indirizzo email per reimpostare la password"
msgid "Enter email address..."
msgstr "Inserisci l'indirizzo email..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Inserisci la tua password monouso."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Per <0>{min}</0> {min, plural, one {minuto} other {minuti}}"
msgid "Forgot password?"
msgstr "Password dimenticata?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Gestisci le preferenze di visualizzazione e notifica."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Istruzioni di configurazione manuale"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Traffico di rete dei container Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Traffico di rete delle interfacce pubbliche"
@@ -715,6 +741,10 @@ msgstr "Supporto OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ad ogni riavvio, i sistemi nel database verranno aggiornati per corrispondere ai sistemi definiti nel file."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Password monouso"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Lettura"
msgid "Received"
msgstr "Ricevuto"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Richiedi una password monouso"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Richiedi OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Reimposta Password"
@@ -888,10 +926,6 @@ msgstr "Inviato"
msgid "Set percentage thresholds for meter colors."
msgstr "Imposta le soglie percentuali per i colori dei contatori."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Imposta l'intervallo di tempo predefinito per i grafici quando viene visualizzato un sistema."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Throughput di {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Throughput del filesystem root"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Formato orario"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "A email(s)"
@@ -1034,6 +1072,14 @@ msgstr "I token consentono agli agenti di connettersi e registrarsi. Le impronte
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Dati totali ricevuti per ogni interfaccia"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Dati totali inviati per ogni interfaccia"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Si attiva quando la media di carico di 1 minuto supera una soglia"
@@ -1095,6 +1141,10 @@ msgstr "Attivo"
msgid "Up ({upSystemsLength})"
msgstr "Attivo ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Carica"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Tempo di attività"

View File

@@ -363,6 +363,14 @@ msgstr "作成日"
msgid "Critical (%)"
msgstr "致命的 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "累積ダウンロード"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "累積アップロード"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "停止"
msgid "Down ({downSystemsLength})"
msgstr "停止 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "ダウンロード"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "期間"
@@ -452,6 +464,7 @@ msgstr "編集"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "メール"
@@ -472,6 +485,10 @@ msgstr "パスワードをリセットするためにメールアドレスを入
msgid "Enter email address..."
msgstr "メールアドレスを入力..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "ワンタイムパスワードを入力してください。"
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "<0>{min}</0> {min, plural, one {分} other {分}}の間"
msgid "Forgot password?"
msgstr "パスワードをお忘れですか?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD コマンド"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "表示と通知の設定を管理します。"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "手動セットアップの手順"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Dockerコンテナのネットワークトラフィック"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "パブリックインターフェースのネットワークトラフィック"
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDCサポート"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "再起動のたびに、データベース内のシステムはファイルに定義されたシステムに一致するように更新されます。"
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "ワンタイムパスワード"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "読み取り"
msgid "Received"
msgstr "受信"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "ワンタイムパスワードをリクエスト"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP をリクエスト"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "パスワードをリセット"
@@ -888,10 +926,6 @@ msgstr "送信"
msgid "Set percentage thresholds for meter colors."
msgstr "メーターの色を変更するしきい値(パーセンテージ)を設定します。"
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "システムを表示する際のチャートのデフォルトの時間範囲を設定します。"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "{extraFsName}のスループット"
msgid "Throughput of root filesystem"
msgstr "ルートファイルシステムのスループット"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "時間形式"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "宛先メールアドレス"
@@ -1034,6 +1072,14 @@ msgstr "トークンはエージェントの接続と登録を可能にします
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "各インターフェースの総受信データ量"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "各インターフェースの総送信データ量"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "1分間の負荷平均がしきい値を超えたときにトリガーされます"
@@ -1095,6 +1141,10 @@ msgstr "正常"
msgid "Up ({upSystemsLength})"
msgstr "正常 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "アップロード"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "稼働時間"

View File

@@ -363,6 +363,14 @@ msgstr "생성됨"
msgid "Critical (%)"
msgstr "위험 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "누적 다운로드"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "누적 업로드"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "오프라인"
msgid "Down ({downSystemsLength})"
msgstr "오프라인 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "다운로드"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "기간"
@@ -452,6 +464,7 @@ msgstr "수정"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "이메일"
@@ -472,6 +485,10 @@ msgstr "비밀번호를 재설정하려면 이메일 주소를 입력하세요"
msgid "Enter email address..."
msgstr "이메일 주소 입력..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "일회용 비밀번호를 입력하세요."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "<0>{min}</0> {min, plural, one {분} other {분}} 동안"
msgid "Forgot password?"
msgstr "비밀번호를 잊으셨나요?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD 명령어"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "디스플레이 및 알림 설정"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "수동 설정 방법"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Docker 컨테이너의 네트워크 트래픽"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "공용 인터페이스의 네트워크 트래픽"
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDC 지원"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "매 시작 시, 데이터베이스가 파일에 정의된 시스템과 일치하도록 업데이트됩니다."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "일회용 비밀번호"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "읽기"
msgid "Received"
msgstr "수신됨"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "일회용 비밀번호 요청"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP 요청"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "비밀번호 재설정"
@@ -888,10 +926,6 @@ msgstr "보냄"
msgid "Set percentage thresholds for meter colors."
msgstr "그래프 미터 색상의 백분율 임계값을 설정합니다."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "시스템을 볼 때 차트의 기본 시간 범위를 설정합니다."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "{extraFsName}의 처리량"
msgid "Throughput of root filesystem"
msgstr "루트 파일 시스템의 처리량"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "시간 형식"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "받는사람(들)"
@@ -1034,6 +1072,14 @@ msgstr "토큰은 에이전트가 연결하고 등록할 수 있도록 합니다
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "토큰과 지문은 허브에 대한 WebSocket 연결을 인증하는 데 사용됩니다."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "각 인터페이스별 총 수신 데이터량"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "각 인터페이스별 총 발신 데이터량"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "1분 부하 평균이 임계값을 초과하면 트리거됩니다."
@@ -1095,6 +1141,10 @@ msgstr "온라인"
msgid "Up ({upSystemsLength})"
msgstr "온라인 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "업로드"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "가동 시간"

View File

@@ -363,6 +363,14 @@ msgstr "Aangemaakt"
msgid "Critical (%)"
msgstr "Kritiek (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Cumulatieve download"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Cumulatieve upload"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Offline"
msgid "Down ({downSystemsLength})"
msgstr "Offline ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Downloaden"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Duur"
@@ -452,6 +464,7 @@ msgstr "Bewerken"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "E-mail"
@@ -472,6 +485,10 @@ msgstr "Voer een e-mailadres in om het wachtwoord opnieuw in te stellen"
msgid "Enter email address..."
msgstr "Voer een e-mailadres in..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Voer uw eenmalig wachtwoord in."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Voor <0>{min}</0> {min, plural, one {minuut} other {minuten}}"
msgid "Forgot password?"
msgstr "Wachtwoord vergeten?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD commando"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Weergave- en notificatievoorkeuren beheren."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Handmatige installatie-instructies"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Netwerkverkeer van docker containers"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Netwerkverkeer van publieke interfaces"
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDC ondersteuning"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Bij elke herstart zullen systemen in de database worden bijgewerkt om overeen te komen met de systemen die in het bestand zijn gedefinieerd."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Eenmalig wachtwoord"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Lezen"
msgid "Received"
msgstr "Ontvangen"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Eenmalig wachtwoord aanvragen"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP aanvragen"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Wachtwoord resetten"
@@ -888,10 +926,6 @@ msgstr "Verzonden"
msgid "Set percentage thresholds for meter colors."
msgstr "Stel percentagedrempels in voor meterkleuren."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Stelt het standaard tijdsbereik voor grafieken in wanneer een systeem wordt bekeken."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Doorvoer van {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Doorvoer van het root bestandssysteem"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Tijdnotatie"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Naar e-mail(s)"
@@ -1034,6 +1072,14 @@ msgstr "Tokens staan agenten toe om verbinding te maken met en te registreren. V
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens en vingerafdrukken worden gebruikt om WebSocket verbindingen te verifiëren naar de hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Totaal ontvangen gegevens per interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Totaal verzonden gegevens per interface"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Triggert wanneer de gemiddelde belasting een drempelwaarde overschrijdt"
@@ -1095,6 +1141,10 @@ msgstr "Online"
msgid "Up ({upSystemsLength})"
msgstr "Online ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Uploaden"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Actief"

View File

@@ -363,6 +363,14 @@ msgstr ""
msgid "Critical (%)"
msgstr "Kritisk (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativ nedlasting"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativ opplasting"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Nede"
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Last ned"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +464,7 @@ msgstr "Rediger"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "E-post"
@@ -472,6 +485,10 @@ msgstr "Skriv inn e-postadresse for å nullstille passordet"
msgid "Enter email address..."
msgstr "Skriv inn e-postadresse..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Skriv inn ditt engangspassord."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "I <0>{min}</0> {min, plural, one {minutt} other {minutter}}"
msgid "Forgot password?"
msgstr "Glemt passord?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD kommando"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Endre visnings- og varslingsinnstillinger."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Instruks for Manuell Installasjon"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Nettverkstrafikk av docker-konteinere"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Nettverkstrafikk av eksterne nettverksgrensesnitt"
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDC-støtte"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ved hver omstart vil systemer i databasen bli oppdatert til å matche systemene definert i fila."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Engangspassord"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Lesing"
msgid "Received"
msgstr "Mottatt"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Be om engangspassord"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Be om OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Nullstill Passord"
@@ -888,10 +926,6 @@ msgstr "Sendt"
msgid "Set percentage thresholds for meter colors."
msgstr "Angi prosentvise terskler for målerfarger."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Angir standard tidsperiode for diagrammer når et system vises."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Gjennomstrømning av {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Gjennomstrømning av rot-filsystemet"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Tidsformat"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Til e-postadresse(r)"
@@ -1034,6 +1072,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Totalt mottatt data for hvert grensesnitt"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Totalt sendt data for hvert grensesnitt"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1141,10 @@ msgstr "Oppe"
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Last opp"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Oppetid"

View File

@@ -363,6 +363,14 @@ msgstr "Utworzono"
msgid "Critical (%)"
msgstr "Krytyczny (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Pobieranie skumulowane"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Wysyłanie skumulowane"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Nie działa"
msgid "Down ({downSystemsLength})"
msgstr "Nie działa ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Pobierz"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Czas trwania"
@@ -452,6 +464,7 @@ msgstr "Edytuj"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "E-mail"
@@ -472,6 +485,10 @@ msgstr "Wprowadź adres e-mail, aby zresetować hasło"
msgid "Enter email address..."
msgstr "Wprowadź adres e-mail..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Wprowadź swoje jednorazowe hasło."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Na <0>{min}</0> {min, plural, one {minutę} other {minut}}"
msgid "Forgot password?"
msgstr "Zapomniałeś hasła?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Polecenie FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Zarządzaj preferencjami wyświetlania i powiadomień."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Instrukcja ręcznej konfiguracji"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Ruch sieciowy kontenerów Docker."
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Ruch sieciowy interfejsów publicznych"
@@ -715,6 +741,10 @@ msgstr "Wsparcie OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Przy każdym ponownym uruchomieniu systemy w bazie danych będą aktualizowane, aby odpowiadały systemom zdefiniowanym w pliku."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Hasło jednorazowe"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Czytaj"
msgid "Received"
msgstr "Otrzymane"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Zażądaj jednorazowego hasła"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Zażądaj OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Resetuj hasło"
@@ -888,10 +926,6 @@ msgstr "Wysłane"
msgid "Set percentage thresholds for meter colors."
msgstr "Ustaw progi procentowe dla kolorów mierników."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Ustawia domyślny zakres czasowy dla wykresów, gdy system jest wyświetlony."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Przepustowość {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Przepustowość głównego systemu plików"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Format czasu"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Do e-mail(ów)"
@@ -1034,6 +1072,14 @@ msgstr "Tokeny umożliwiają agentom łączenie się i rejestrację. Odciski pal
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeny i odciski palców (fingerprinty) służą do uwierzytelniania połączeń WebSocket z hubem."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Całkowita ilość danych odebranych dla każdego interfejsu"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Całkowita ilość danych wysłanych dla każdego interfejsu"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Uruchamia się, gdy 1-minutowe średnie obciążenie systemu przekroczy ustawiony próg"
@@ -1095,6 +1141,10 @@ msgstr "Działa"
msgid "Up ({upSystemsLength})"
msgstr "Działa ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Wyślij"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Czas pracy"

View File

@@ -363,6 +363,14 @@ msgstr "Criado"
msgid "Critical (%)"
msgstr "Crítico (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Download cumulativo"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Upload cumulativo"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "“Desligado”"
msgid "Down ({downSystemsLength})"
msgstr "Inativo ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Transferir"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Duração"
@@ -452,6 +464,7 @@ msgstr "Editar"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
@@ -472,6 +485,10 @@ msgstr "Digite o endereço de email para redefinir a senha"
msgid "Enter email address..."
msgstr "Digite o endereço de email..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Insira a sua senha de uso único."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
msgid "Forgot password?"
msgstr "Esqueceu a senha?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Comando FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Gerenciar preferências de exibição e notificação."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Instruções de configuração manual"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Tráfego de rede dos contêineres Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Tráfego de rede das interfaces públicas"
@@ -715,6 +741,10 @@ msgstr "Suporte a OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "A cada reinício, os sistemas no banco de dados serão atualizados para corresponder aos sistemas definidos no arquivo."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Senha de uso único"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Ler"
msgid "Received"
msgstr "Recebido"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Solicitar senha de uso único"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Solicitar OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Redefinir Senha"
@@ -888,10 +926,6 @@ msgstr "Enviado"
msgid "Set percentage thresholds for meter colors."
msgstr "Defina os limiares de porcentagem para as cores do medidor."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Define o intervalo de tempo padrão para gráficos quando um sistema é visualizado."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Taxa de transferência de {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Taxa de transferência do sistema de arquivos raiz"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Formato de hora"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Para email(s)"
@@ -1034,6 +1072,14 @@ msgstr "Os tokens permitem que os agentes se conectem e registrem. As impressõe
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens e impressões digitais são usados para autenticar conexões WebSocket ao hub."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Dados totais recebidos para cada interface"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Dados totais enviados para cada interface"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Dispara quando a média de carga de 1 minuto excede um limite"
@@ -1095,6 +1141,10 @@ msgstr "“Ligado”"
msgid "Up ({upSystemsLength})"
msgstr "Ativo ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Carregar"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Tempo de Atividade"

View File

@@ -363,6 +363,14 @@ msgstr "Создано"
msgid "Critical (%)"
msgstr "Критический (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Совокупная загрузка"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Совокупная выгрузка"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Не в сети"
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Скачать"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Длительность"
@@ -452,6 +464,7 @@ msgstr "Редактировать"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Электронная почта"
@@ -472,6 +485,10 @@ msgstr "Введите адрес электронной почты для сб
msgid "Enter email address..."
msgstr "Введите адрес электронной почты..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Введите ваш одноразовый пароль."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "На <0>{min}</0> {min, plural, one {минуту} other {минут}}"
msgid "Forgot password?"
msgstr "Забыли пароль?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Команда FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Управляйте предпочтениями отображения и уведомлений."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Инструкции по ручной настройке"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Сетевой трафик контейнеров Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Сетевой трафик публичных интерфейсов"
@@ -715,6 +741,10 @@ msgstr "Поддержка OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "При каждом перезапуске системы в базе данных будут обновлены в соответствии с системами, определенными в файле."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Одноразовый пароль"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Чтение"
msgid "Received"
msgstr "Получено"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Запросить одноразовый пароль"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Запросить OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Сбросить пароль"
@@ -888,10 +926,6 @@ msgstr "Отправлено"
msgid "Set percentage thresholds for meter colors."
msgstr "Установите процентные пороги для цветов счетчиков."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Устанавливает диапазон времени по умолчанию для графиков при просмотре системы."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Пропускная способность {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Пропускная способность корневой файловой системы"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Формат времени"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "На электронную почту"
@@ -1034,6 +1072,14 @@ msgstr "Токены позволяют агентам подключаться
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токены и отпечатки используются для аутентификации соединений WebSocket с хабом."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Общий объем полученных данных для каждого интерфейса"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Общий объем отправленных данных для каждого интерфейса"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Срабатывает, когда средняя загрузка за 1 минуту превышает порог"
@@ -1095,6 +1141,10 @@ msgstr "В сети"
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Загрузить"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Время работы"

View File

@@ -363,6 +363,14 @@ msgstr ""
msgid "Critical (%)"
msgstr "Kritično (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativno prenos"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativno nalaganje"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr ""
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Prenesi"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +464,7 @@ msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "E-pošta"
@@ -472,6 +485,10 @@ msgstr "Vnesite e-poštni naslov za ponastavitev gesla"
msgid "Enter email address..."
msgstr "Vnesite e-poštni naslov..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Vnesite svoje enkratno geslo."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Za <0>{min}</0> {min, plural, one {minuto} other {minut}}"
msgid "Forgot password?"
msgstr "Pozabljeno geslo?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Ukaz FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Upravljajte nastavitve prikaza in obvestil."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr ""
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Omrežni promet docker kontejnerjev"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Omrežni promet javnih vmesnikov"
@@ -715,6 +741,10 @@ msgstr "Podpora za OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Ob vsakem ponovnem zagonu bodo sistemi v zbirki podatkov posodobljeni, da se bodo ujemali s sistemi, definiranimi v datoteki."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Enkratno geslo"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Preberano"
msgid "Received"
msgstr "Prejeto"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Zahtevaj enkratno geslo"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Zahtevaj OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Ponastavi geslo"
@@ -888,10 +926,6 @@ msgstr "Poslano"
msgid "Set percentage thresholds for meter colors."
msgstr "Nastavite odstotne pragove za barve merilnikov."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Nastavi privzeti časovni obseg za grafikone, ko si ogledujete sistem."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Prepustnost {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Prepustnost korenskega datotečnega sistema"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Oblika časa"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "E-pošta za"
@@ -1034,6 +1072,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Skupni prejeti podatki za vsak vmesnik"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Skupni poslani podatki za vsak vmesnik"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1141,10 @@ msgstr ""
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Naloži"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Čas delovanja"

View File

@@ -363,6 +363,14 @@ msgstr "Skapad"
msgid "Critical (%)"
msgstr "Kritisk (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kumulativ nedladdning"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kumulativ uppladdning"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr ""
msgid "Down ({downSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Ladda ner"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr ""
@@ -452,6 +464,7 @@ msgstr ""
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "E-post"
@@ -472,6 +485,10 @@ msgstr "Ange e-postadress för att återställa lösenord"
msgid "Enter email address..."
msgstr "Ange e-postadress..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Ange ditt engångslösenord."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Under <0>{min}</0> {min, plural, one {minut} other {minuter}}"
msgid "Forgot password?"
msgstr "Glömt lösenordet?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD kommando"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Hantera visnings- och aviseringsinställningar."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr ""
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Nätverkstrafik för dockercontainrar"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Nätverkstrafik för publika gränssnitt"
@@ -715,6 +741,10 @@ msgstr "Stöd för OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Vid varje omstart kommer systemen i databasen att uppdateras för att matcha systemen som definieras i filen."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Engångslösenord"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Läs"
msgid "Received"
msgstr "Mottaget"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Begär engångslösenord"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Begär OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Återställ lösenord"
@@ -888,10 +926,6 @@ msgstr "Skickat"
msgid "Set percentage thresholds for meter colors."
msgstr "Ställ in procentuella tröskelvärden för mätarfärger."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Anger standardtidsintervallet för diagram när ett system visas."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Genomströmning av {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Genomströmning av rotfilsystemet"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Tidsformat"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Till e-postadress(er)"
@@ -1034,6 +1072,14 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Totalt mottagen data för varje gränssnitt"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Totalt skickad data för varje gränssnitt"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr ""
@@ -1095,6 +1141,10 @@ msgstr ""
msgid "Up ({upSystemsLength})"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Ladda upp"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Drifttid"

View File

@@ -363,6 +363,14 @@ msgstr "Oluşturuldu"
msgid "Critical (%)"
msgstr "Kritik (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Kümülatif İndirme"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Kümülatif Yükleme"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Kapalı"
msgid "Down ({downSystemsLength})"
msgstr "Kapalı ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "İndir"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Süre"
@@ -452,6 +464,7 @@ msgstr "Düzenle"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "E-posta"
@@ -472,6 +485,10 @@ msgstr "Şifreyi sıfırlamak için e-posta adresini girin"
msgid "Enter email address..."
msgstr "E-posta adresini girin..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Tek kullanımlık şifrenizi girin."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "<0>{min}</0> {min, plural, one {dakika} other {dakika}} için"
msgid "Forgot password?"
msgstr "Şifrenizi mi unuttunuz?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD komutu"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Görüntüleme ve bildirim tercihlerini yönetin."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Manuel kurulum talimatları"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Docker konteynerlerinin ağ trafiği"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Genel arayüzlerin ağ trafiği"
@@ -715,6 +741,10 @@ msgstr "OAuth 2 / OIDC desteği"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Her yeniden başlatmada, veritabanındaki sistemler dosyada tanımlanan sistemlerle eşleşecek şekilde güncellenecektir."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Tek kullanımlık şifre"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Oku"
msgid "Received"
msgstr "Alındı"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Tek kullanımlık şifre iste"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "OTP iste"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Şifreyi Sıfırla"
@@ -888,10 +926,6 @@ msgstr "Gönderildi"
msgid "Set percentage thresholds for meter colors."
msgstr "Sayaç renkleri için yüzde eşiklerini ayarlayın."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Bir sistem görüntülendiğinde grafikler için varsayılan zaman aralığını ayarlar."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "{extraFsName} verimliliği"
msgid "Throughput of root filesystem"
msgstr "Kök dosya sisteminin verimliliği"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Zaman formatı"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "E-posta(lar)a"
@@ -1034,6 +1072,14 @@ msgstr "Token'lar agentların bağlanıp kaydolmasına izin verir. Parmak izleri
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Token'lar ve parmak izleri hub'a WebSocket bağlantılarını doğrulamak için kullanılır."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Her arayüz için alınan toplam veri"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Her arayüz için gönderilen toplam veri"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "1 dakikalık yük ortalaması bir eşiği aştığında tetiklenir"
@@ -1095,6 +1141,10 @@ msgstr "Açık"
msgid "Up ({upSystemsLength})"
msgstr "Açık ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Yükle"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Çalışma Süresi"

View File

@@ -363,6 +363,14 @@ msgstr "Створено"
msgid "Critical (%)"
msgstr "Критично (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Кумулятивне завантаження"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Кумулятивне відвантаження"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Не працює"
msgid "Down ({downSystemsLength})"
msgstr "Не працює ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Завантажити"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Тривалість"
@@ -452,6 +464,7 @@ msgstr "Редагувати"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Електронна пошта"
@@ -472,6 +485,10 @@ msgstr "Введіть адресу електронної пошти для с
msgid "Enter email address..."
msgstr "Введіть адресу електронної пошти..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Введіть ваш одноразовий пароль."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Протягом <0>{min}</0> {min, plural, one {хвилини} other {
msgid "Forgot password?"
msgstr "Забули пароль?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Команда FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Керуйте параметрами відображення та сповіщень."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Інструкції з ручного налаштування"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Мережевий трафік контейнерів Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Мережевий трафік публічних інтерфейсів"
@@ -715,6 +741,10 @@ msgstr "Підтримка OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "При кожному перезапуску системи в базі даних будуть оновлені, щоб відповідати системам, визначеним у файлі."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Одноразовий пароль"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Читання"
msgid "Received"
msgstr "Отримано"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Запит одноразового пароля"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Запит OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Скинути пароль"
@@ -888,10 +926,6 @@ msgstr "Відправлено"
msgid "Set percentage thresholds for meter colors."
msgstr "Встановіть відсоткові пороги для кольорів лічильників."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Встановлює стандартний діапазон часу для графіків при перегляді системи."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Пропускна здатність {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Пропускна здатність кореневої файлової системи"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Формат часу"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "На електронну пошту"
@@ -1034,6 +1072,14 @@ msgstr "Токени дозволяють агентам підключатис
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токени та відбитки використовуються для автентифікації WebSocket з'єднань до хабу."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Загальний обсяг отриманих даних для кожного інтерфейсу"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Загальний обсяг відправлених даних для кожного інтерфейсу"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Спрацьовує, коли середнє навантаження за 1 хвилину перевищує поріг"
@@ -1095,6 +1141,10 @@ msgstr "Працює"
msgid "Up ({upSystemsLength})"
msgstr "Працює ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Відвантажити"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Час роботи"

View File

@@ -363,6 +363,14 @@ msgstr "Đã tạo"
msgid "Critical (%)"
msgstr "Độ nghiêm trọng (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "Tải xuống tích lũy"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "Tải lên tích lũy"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "Mất kết nối"
msgid "Down ({downSystemsLength})"
msgstr "Mất kết nối ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "Tải xuống"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "Thời lượng"
@@ -452,6 +464,7 @@ msgstr "Chỉnh sửa"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
@@ -472,6 +485,10 @@ msgstr "Nhập địa chỉ email để đặt lại mật khẩu"
msgid "Enter email address..."
msgstr "Nhập địa chỉ email..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "Nhập mật khẩu một lần của bạn."
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "Trong <0>{min}</0> {min, plural, one {phút} other {phút}}"
msgid "Forgot password?"
msgstr "Quên mật khẩu?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "Lệnh FreeBSD"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "Quản lý tùy chọn hiển thị và thông báo."
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "Hướng dẫn cài đặt thủ công"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Lưu lượng mạng của các container Docker"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "Lưu lượng mạng của các giao diện công cộng"
@@ -715,6 +741,10 @@ msgstr "Hỗ trợ OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "Mỗi khi khởi động lại, các hệ thống trong cơ sở dữ liệu sẽ được cập nhật để khớp với các hệ thống được định nghĩa trong tệp."
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "Mật khẩu một lần"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "Đọc"
msgid "Received"
msgstr "Đã nhận"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "Yêu cầu mật khẩu một lần"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "Yêu cầu OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "Đặt lại Mật khẩu"
@@ -888,10 +926,6 @@ msgstr "Đã gửi"
msgid "Set percentage thresholds for meter colors."
msgstr "Đặt ngưỡng cho màu sắc đồng hồ."
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "Đặt phạm vi thời gian mặc định cho biểu đồ khi một hệ thống được xem."
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "Thông lượng của {extraFsName}"
msgid "Throughput of root filesystem"
msgstr "Thông lượng của hệ thống tệp gốc"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "Định dạng thời gian"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "Đến email(s)"
@@ -1034,6 +1072,14 @@ msgstr "Token cho phép các tác nhân kết nối và đăng ký. Vân tay là
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Token và vân tay được sử dụng để xác thực các kết nối WebSocket đến trung tâm."
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "Tổng dữ liệu nhận được cho mỗi giao diện"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "Tổng dữ liệu gửi đi cho mỗi giao diện"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Kích hoạt khi tải trung bình 1 phút vượt quá ngưỡng"
@@ -1095,6 +1141,10 @@ msgstr "Hoạt động"
msgid "Up ({upSystemsLength})"
msgstr "Hoạt động ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "Tải lên"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "Thời gian hoạt động"

View File

@@ -363,6 +363,14 @@ msgstr "创建时间"
msgid "Critical (%)"
msgstr "临界 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "累计下载"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "累计上传"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "离线"
msgid "Down ({downSystemsLength})"
msgstr "离线 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "下载"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "持续时间"
@@ -452,6 +464,7 @@ msgstr "编辑"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "电子邮件"
@@ -472,6 +485,10 @@ msgstr "输入电子邮件地址以重置密码"
msgid "Enter email address..."
msgstr "输入电子邮件地址..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "输入您的一次性密码。"
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "持续<0>{min}</0> {min, plural, one {分钟} other {分钟}}"
msgid "Forgot password?"
msgstr "忘记密码?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD 命令"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "管理显示和通知偏好。"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "手动设置说明"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Docker 容器的网络流量"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "公共接口的网络流量"
@@ -715,6 +741,10 @@ msgstr "支持 OAuth 2/OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "每次重启时,数据库中的系统将更新以匹配文件中定义的系统。"
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "一次性密码"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "读取"
msgid "Received"
msgstr "接收"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "请求一次性密码"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "请求 OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "重置密码"
@@ -888,10 +926,6 @@ msgstr "发送"
msgid "Set percentage thresholds for meter colors."
msgstr "设置仪表颜色的百分比阈值。"
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "设置查看系统时图表的默认时间范围。"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "{extraFsName}的吞吐量"
msgid "Throughput of root filesystem"
msgstr "根文件系统的吞吐量"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "时间格式"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "发送到电子邮件"
@@ -1034,6 +1072,14 @@ msgstr "令牌允许客户端连接和注册。指纹是每个系统唯一的稳
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌与指纹用于验证到中心的 WebSocket 连接。"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "每个接口的总接收数据量"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "每个接口的总发送数据量"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "当 1 分钟负载平均值超过阈值时触发"
@@ -1095,6 +1141,10 @@ msgstr "在线"
msgid "Up ({upSystemsLength})"
msgstr "在线 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "上传"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "正常运行时间"

View File

@@ -363,6 +363,14 @@ msgstr "已建立"
msgid "Critical (%)"
msgstr "嚴重 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "累計下載"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "累計上傳"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "中斷"
msgid "Down ({downSystemsLength})"
msgstr "中斷 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "下載"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "持續時間"
@@ -452,6 +464,7 @@ msgstr "編輯"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "電子郵件"
@@ -472,6 +485,10 @@ msgstr "輸入電子郵件地址以重置密碼"
msgid "Enter email address..."
msgstr "輸入電子郵件地址..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "輸入您的一次性密碼。"
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "持續<0>{min}</0> {min, plural, one {分鐘} other {分鐘}}"
msgid "Forgot password?"
msgstr "忘記密碼?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD 指令"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "管理顯示和通知偏好。"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "手動設定說明"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Docker 容器的網絡流量"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "公共接口的網絡流量"
@@ -715,6 +741,10 @@ msgstr "支援 OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "每次重新啟動時,將會以檔案中的系統定義更新資料庫。"
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "一次性密碼"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "讀取"
msgid "Received"
msgstr "接收"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "請求一次性密碼"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "請求 OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "重設密碼"
@@ -888,10 +926,6 @@ msgstr "發送"
msgid "Set percentage thresholds for meter colors."
msgstr "設定儀表顏色的百分比閾值。"
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "設置查看系統時圖表的默認時間範圍。"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "{extraFsName}的吞吐量"
msgid "Throughput of root filesystem"
msgstr "根文件系統的吞吐量"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "時間格式"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "發送到電子郵件"
@@ -1034,6 +1072,14 @@ msgstr "令牌允許代理程式連接和註冊。指紋是每個系統唯一的
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌和指紋用於驗證到中心的WebSocket連接。"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "每個介面的總接收資料量"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "每個介面的總傳送資料量"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "當 1 分鐘平均負載超過閾值時觸發"
@@ -1095,6 +1141,10 @@ msgstr "上線"
msgid "Up ({upSystemsLength})"
msgstr "上線 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "上傳"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "正常運行時間"

View File

@@ -363,6 +363,14 @@ msgstr "已建立"
msgid "Critical (%)"
msgstr "嚴重 (%)"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download"
msgstr "累計下載"
#: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload"
msgstr "累計上傳"
#. Context: Battery state
#: src/components/routes/system.tsx
msgid "Current state"
@@ -441,6 +449,10 @@ msgstr "離線"
msgid "Down ({downSystemsLength})"
msgstr "離線 ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Download"
msgstr "下載"
#: src/components/alerts-history-columns.tsx
msgid "Duration"
msgstr "持續時間"
@@ -452,6 +464,7 @@ msgstr "編輯"
#: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "電子郵件"
@@ -472,6 +485,10 @@ msgstr "輸入電子郵件地址以重設密碼"
msgid "Enter email address..."
msgstr "輸入電子郵件地址..."
#: src/components/login/otp-forms.tsx
msgid "Enter your one-time password."
msgstr "輸入您的一次性密碼。"
#: src/components/login/auth-form.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/config-yaml.tsx
@@ -542,6 +559,12 @@ msgstr "持續<0>{min}</0> {min, plural, one {分鐘} other {分鐘}}"
msgid "Forgot password?"
msgstr "忘記密碼?"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Button to copy install command"
msgid "FreeBSD command"
msgstr "FreeBSD 指令"
#. Context: Battery state
#: src/lib/i18n.ts
msgid "Full"
@@ -645,6 +668,7 @@ msgid "Manage display and notification preferences."
msgstr "管理顯示和通知偏好。"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions"
msgstr "手動設定說明"
@@ -680,6 +704,8 @@ msgid "Network traffic of docker containers"
msgstr "Docker 容器的網路流量"
#: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces"
msgstr "公開介面的網路流量"
@@ -715,6 +741,10 @@ msgstr "支援 OAuth 2 / OIDC"
msgid "On each restart, systems in the database will be updated to match the systems defined in the file."
msgstr "每次重新啟動時,將會以檔案中的系統定義更新資料庫。"
#: src/components/login/auth-form.tsx
msgid "One-time password"
msgstr "一次性密碼"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -833,6 +863,14 @@ msgstr "讀取"
msgid "Received"
msgstr "接收"
#: src/components/login/login.tsx
msgid "Request a one-time password"
msgstr "請求一次性密碼"
#: src/components/login/otp-forms.tsx
msgid "Request OTP"
msgstr "請求 OTP"
#: src/components/login/forgot-pass-form.tsx
msgid "Reset Password"
msgstr "重設密碼"
@@ -888,10 +926,6 @@ msgstr "傳送"
msgid "Set percentage thresholds for meter colors."
msgstr "設定儀表顏色的百分比閾值。"
#: src/components/routes/settings/general.tsx
msgid "Sets the default time range for charts when a system is viewed."
msgstr "設定顯示系統圖表的預設時間範圍。"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1002,6 +1036,10 @@ msgstr "{extraFsName}的傳輸速率"
msgid "Throughput of root filesystem"
msgstr "Root文件系統的傳輸速率"
#: src/components/routes/settings/general.tsx
msgid "Time format"
msgstr "時間格式"
#: src/components/routes/settings/notifications.tsx
msgid "To email(s)"
msgstr "發送到電子郵件"
@@ -1034,6 +1072,14 @@ msgstr "令牌允許代理程式連線和註冊。指紋是每個系統的唯一
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "令牌和指紋被用於驗證到 Hub 的 WebSocket 連線。"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
msgstr "每個介面的總接收資料量"
#: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface"
msgstr "每個介面的總傳送資料量"
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "當 1 分鐘平均負載超過閾值時觸發"
@@ -1095,6 +1141,10 @@ msgstr "上線"
msgid "Up ({upSystemsLength})"
msgstr "上線 ({upSystemsLength})"
#: src/components/routes/system/network-sheet.tsx
msgid "Upload"
msgstr "上傳"
#: src/components/routes/system.tsx
msgid "Uptime"
msgstr "運行時間"

View File

@@ -1,5 +1,5 @@
import type { RecordModel } from "pocketbase"
import type { Unit, Os, BatteryState, HourFormat } from "./lib/enums"
import type { Unit, Os, BatteryState, HourFormat, ConnectionType } from "@/lib/enums"
// global window properties
declare global {
@@ -75,6 +75,8 @@ export interface SystemInfo {
dt?: number
/** operating system */
os?: Os
/** connection type */
ct?: ConnectionType
}
export interface SystemStats {
@@ -141,6 +143,8 @@ export interface SystemStats {
g?: Record<string, GPUData>
/** battery percent and state */
bat?: [number, BatteryState]
/** network interfaces [upload bytes, download bytes, total upload bytes, total download bytes] */
ni?: Record<string, [number, number, number, number]>
}
export interface GPUData {
@@ -154,6 +158,8 @@ export interface GPUData {
u: number
/** power (w) */
p?: number
/** engines */
e?: Record<string, number>
}
export interface ExtraFsStats {

View File

View File

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

View File

@@ -1,5 +1,20 @@
## 0.12.10
- Show connection type (WebSocket / SSH) in hub UI.
- Fix temperature unit and bytes / bits settings. (#1180)
## 0.12.9
- Fix divide by zero error introduced in 0.12.8 :) (#1175)
## 0.12.8
- Add per-interface network traffic charts. (#926)
- Add cumulative network traffic charts. (#926)
- Add setting for time format (12h / 24h). (#424)
- Add experimental one-time password (OTP) support.
@@ -10,6 +25,8 @@
- Add FreeBSD support for agent install script and update command.
- Fix status alerts not being resolved when system comes up. (#1052)
## 0.12.7
- Make LibreHardwareMonitor opt-in with `LHM=true` environment variable. (#1130)

View File

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

View File

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