Compare commits

..

69 Commits

Author SHA1 Message Date
henrygd
a7ef1235f4 specify latest tag for non-alpine agent image
also change capitalization for gpu alert
2025-11-11 16:18:54 -05:00
henrygd
f64a361c60 add EXCLUDE_SMART env var (#1392) 2025-11-11 16:05:00 -05:00
henrygd
aaa788bc2f add gpu usage alerts 2025-11-11 12:38:47 -05:00
henrygd
3eede6bead fix containers and systemd tables when using system name in URL 2025-11-10 17:43:11 -05:00
henrygd
02abfbcb54 change alert link to use system ID instead of name 2025-11-10 17:31:11 -05:00
henrygd
01d20562f0 add basic systemd service monitoring (#1153)
Co-authored-by: Shelby Tucker <shelby.tucker@gmail.com>
2025-11-10 17:04:51 -05:00
henrygd
cbe6ee6499 fix battery nil pointer error (#1353) 2025-11-10 15:16:50 -05:00
henrygd
9a61ea8356 improve perf of filter bar 2025-11-07 22:29:59 -05:00
henrygd
1af7ff600f embed smartctl in the windows binary (#1362) 2025-11-05 13:03:47 -05:00
henrygd
02d594cc82 release 0.15.4 2025-11-04 17:23:42 -05:00
henrygd
7d0b5c1c67 update language files 2025-11-04 17:18:57 -05:00
Thiago Alves Cavalcante
d3dc8a7af0 new Portuguese translations 2025-11-04 17:18:07 -05:00
henrygd
d67fefe7c5 new spanish translations by dtornerte 2025-11-04 17:17:02 -05:00
henrygd
4d364c5e4d update language files 2025-11-04 17:06:51 -05:00
henrygd
954400ea45 fix intel_gpu_top parsing when engine instance id is in column (#1230) 2025-11-04 16:02:20 -05:00
henrygd
04b6067e64 add a total line to the tooltip of charts with multiple values #1280
Co-authored-by: Titouan V <titouan.verhille@gmail.com>
2025-11-04 15:41:24 -05:00
henrygd
d77ee5554f add fallback paths for smartctl lookup (#1362, #1363) 2025-11-04 14:06:28 -05:00
henrygd
2e034bdead refactor containers table to fix clock issue causing no results (#1337) 2025-11-04 13:18:34 -05:00
henrygd
fc0947aa04 fix windows extra disk backslash issue (#1361) 2025-11-03 17:42:08 -05:00
henrygd
1d546a4091 update nvidia dockerfile to build latest smartmontools (#1335) 2025-11-02 17:13:47 -05:00
henrygd
f60b3bbbfb release 0.15.3 2025-11-01 16:04:02 -04:00
henrygd
8e99b9f1ad update shoutrrr and gopsutil deps 2025-11-01 14:31:41 -04:00
henrygd
fa5ed2bc11 update translations 2025-11-01 14:09:10 -04:00
henrygd
21d961ab97 sync language files 2025-11-01 13:50:53 -04:00
henrygd
aaa93b84d2 add hebrew + new cpu charts refactoring 2025-11-01 13:34:30 -04:00
henrygd
6a562ce03b add more cpu metrics (#1356)
- adds monitoring for cpu state time and per-core usage

Co-authored-by: Sven van Ginkel <svenvanginkel@icloud.com>
2025-11-01 12:57:58 -04:00
henrygd
3dbc48727e add INTEL_GPU_DEVICE env var (#1285) 2025-11-01 11:12:05 -04:00
henrygd
85ac2e5e9a update env var name to EXCLUDE_CONTAINERS #1352 2025-10-30 19:30:01 -04:00
Sven van Ginkel
af6bd4e505 [Feature] Add env var to exclude containers from being monitored (#1352) 2025-10-30 19:02:09 -04:00
Gabay
e54c4b3499 New translations en.po (Hebrew) 2025-10-30 16:50:14 -04:00
henrygd
078c88f825 add hebrew machine translations 2025-10-30 16:45:33 -04:00
henrygd
85169b6c5e improve parsing of edge case smart power on times (#1347) 2025-10-30 16:32:06 -04:00
henrygd
d0ff8ee2c0 fix disk i/o values in longer charts (#1355) 2025-10-30 14:17:56 -04:00
henrygd
e898768997 fix battery nil pointer error #1353 2025-10-30 12:52:33 -04:00
henrygd
0f5b504f23 release 0.15.2 2025-10-29 01:18:15 -04:00
henrygd
365d291393 improve smart device detection (#1345)
also fix virtual device filtering
2025-10-29 01:16:58 -04:00
henrygd
3dbab24c0f improve identification of smart drive types (#1345) 2025-10-28 22:37:47 -04:00
henrygd
1f67fb7c8d release 0.15.1 2025-10-28 19:30:36 -04:00
henrygd
219e09fc78 update language files 2025-10-28 18:41:39 -04:00
henrygd
cd9c2bd9ab update logs in smart.go
also change max execution time to 2 sec
2025-10-28 17:34:49 -04:00
henrygd
9f969d843c update changelog 2025-10-28 16:52:55 -04:00
henrygd
b22a6472fc missed staging this earlier :) 2025-10-28 16:44:34 -04:00
henrygd
d231ace28e fix SHARE_ALL_SYSTEMS not working for Containers
#1334
2025-10-28 16:25:29 -04:00
henrygd
473cb7f437 merge SMART_DEVICES with devices returned from smartctl scan 2025-10-28 15:38:47 -04:00
henrygd
783ed9f456 cache smartctl scan results for 10 min w/ force option
also add support for sntrealtek
2025-10-28 14:01:45 -04:00
henrygd
9a9a89ee50 handle when power on smart attribute is a string like 0h+0m+0.000s 2025-10-28 13:44:31 -04:00
henrygd
5122d0341d fix S.M.A.R.T. wrong disk is renderer in the DiskSheet table #1336 2025-10-28 12:55:38 -04:00
zjkal
81731689da A small translation error has been fixed (#1343) 2025-10-28 11:09:10 -04:00
henrygd
b3e9857448 add SMART_DEVICES env var (#373, #1335)
also iterate through parsers to try to find a match if type is not defined.
2025-10-27 15:26:29 -04:00
henrygd
2eda9eb0e3 add support for scsi and sntasmedia smart data (#373, #1335) 2025-10-27 14:39:12 -04:00
henrygd
82a5df5048 add secondsToString function 2025-10-27 14:14:17 -04:00
Sven van Ginkel
f11564a7ac Skip virtual disks (#1332) 2025-10-27 11:44:21 -04:00
Sven van Ginkel
9df4d29236 Add sorting to the smart table (#1333) 2025-10-27 11:43:23 -04:00
henrygd
1452817423 update readme 2025-10-26 14:09:14 -04:00
AuthorShin
c57e496f5e Added Container to Supported metrics list on readme.md (#1323) 2025-10-26 14:04:42 -04:00
henrygd
6287f7003c fix text contrast when container details disabled (#1324) 2025-10-26 11:41:21 -04:00
henrygd
37037b1f4e update changelog 2025-10-26 11:34:13 -04:00
henrygd
7cf123a99e fix: limit frame and total sizes when reading docker logs (#1322)
- Add per-frame size limit (1MB) to prevent single massive log entries
- Add total log size limit (5MB) for network transfer and browser rendering
- Gracefully truncate logs that exceed limits instead of consuming unbounded memory
2025-10-26 11:02:18 -04:00
henrygd
97394e775f release 0.15.0 2025-10-26 10:47:55 -04:00
henrygd
d5c381188b update go deps 2025-10-26 10:46:30 -04:00
henrygd
b107d12a62 smart support over ssh + change response code for smart failure 2025-10-26 10:33:34 -04:00
henrygd
e646f2c1fc fix inactive tab losing container table data 2025-10-26 10:32:49 -04:00
henrygd
b18528d24a update translations 2025-10-25 18:26:46 -04:00
henrygd
a6e64df399 update language files 2025-10-25 17:20:10 -04:00
Klaus Dandl
66ba21dd41 New German translations 2025-10-25 17:19:14 -04:00
Thor B.
1851e7a111 New Danish translations 2025-10-25 17:00:15 -04:00
henrygd
74b78e96b3 pre release refactoring + update changelog 2025-10-25 16:34:32 -04:00
henrygd
a9657f9c00 add CONTAINER_DETAILS env var (#1305) 2025-10-25 15:33:01 -04:00
henrygd
1dee63a0eb update HasReadableBattery to check all batteries 2025-10-25 14:06:25 -04:00
108 changed files with 15997 additions and 1448 deletions

View File

@@ -33,6 +33,7 @@ jobs:
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
tags: | tags: |
type=raw,value=edge type=raw,value=edge
type=raw,value=latest
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
@@ -99,6 +100,7 @@ jobs:
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
tags: | tags: |
type=raw,value=edge type=raw,value=edge
type=raw,value=latest
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}

View File

@@ -5,6 +5,7 @@ project_name: beszel
before: before:
hooks: hooks:
- go mod tidy - go mod tidy
- go generate -run fetchsmartctl ./agent
builds: builds:
- id: beszel - id: beszel

View File

@@ -7,7 +7,7 @@ SKIP_WEB ?= false
# Set executable extension based on target OS # Set executable extension based on target OS
EXE_EXT := $(if $(filter windows,$(OS)),.exe,) EXE_EXT := $(if $(filter windows,$(OS)),.exe,)
.PHONY: tidy build-agent build-hub build-hub-dev build clean lint dev-server dev-agent dev-hub dev generate-locales .PHONY: tidy build-agent build-hub build-hub-dev build clean lint dev-server dev-agent dev-hub dev generate-locales fetch-smartctl-conditional
.DEFAULT_GOAL := build .DEFAULT_GOAL := build
clean: clean:
@@ -46,8 +46,14 @@ build-dotnet-conditional:
fi; \ fi; \
fi fi
# Download smartctl.exe at build time for Windows (skips if already present)
fetch-smartctl-conditional:
@if [ "$(OS)" = "windows" ]; then \
go generate -run fetchsmartctl ./agent; \
fi
# Update build-agent to include conditional .NET build # Update build-agent to include conditional .NET build
build-agent: tidy build-dotnet-conditional build-agent: tidy build-dotnet-conditional fetch-smartctl-conditional
GOOS=$(OS) GOARCH=$(ARCH) go build -o ./build/beszel-agent_$(OS)_$(ARCH)$(EXE_EXT) -ldflags "-w -s" ./internal/cmd/agent GOOS=$(OS) GOARCH=$(ARCH) go build -o ./build/beszel-agent_$(OS)_$(ARCH)$(EXE_EXT) -ldflags "-w -s" ./internal/cmd/agent
build-hub: tidy $(if $(filter false,$(SKIP_WEB)),build-web-ui) build-hub: tidy $(if $(filter false,$(SKIP_WEB)),build-web-ui)

View File

@@ -43,6 +43,7 @@ type Agent struct {
dataDir string // Directory for persisting data dataDir string // Directory for persisting data
keys []gossh.PublicKey // SSH public keys keys []gossh.PublicKey // SSH public keys
smartManager *SmartManager // Manages SMART data smartManager *SmartManager // Manages SMART data
systemdManager *systemdManager // Manages systemd services
} }
// NewAgent creates a new agent with the given data directory for persisting data. // NewAgent creates a new agent with the given data directory for persisting data.
@@ -101,6 +102,11 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
// initialize docker manager // initialize docker manager
agent.dockerManager = newDockerManager(agent) agent.dockerManager = newDockerManager(agent)
agent.systemdManager, err = newSystemdManager()
if err != nil {
slog.Debug("Systemd", "err", err)
}
agent.smartManager, err = NewSmartManager() agent.smartManager, err = NewSmartManager()
if err != nil { if err != nil {
slog.Debug("SMART", "err", err) slog.Debug("SMART", "err", err)
@@ -154,6 +160,11 @@ func (a *Agent) gatherStats(cacheTimeMs uint16) *system.CombinedData {
} }
} }
// skip updating systemd services if cache time is not the default 60sec interval
if a.systemdManager != nil && cacheTimeMs == 60_000 && a.systemdManager.hasFreshStats {
data.SystemdServices = a.systemdManager.getServiceStats(nil, false)
}
data.Stats.ExtraFs = make(map[string]*system.FsStats) data.Stats.ExtraFs = make(map[string]*system.FsStats)
for name, stats := range a.fsStats { for name, stats := range a.fsStats {
if !stats.Root && stats.DiskTotal > 0 { if !stats.Root && stats.DiskTotal > 0 {

View File

@@ -5,15 +5,16 @@ package battery
import ( import (
"errors" "errors"
"fmt"
"os"
"log/slog" "log/slog"
"math"
"github.com/distatus/battery" "github.com/distatus/battery"
) )
var systemHasBattery = false var (
var haveCheckedBattery = false systemHasBattery = false
haveCheckedBattery = false
)
// HasReadableBattery checks if the system has a battery and returns true if it does. // HasReadableBattery checks if the system has a battery and returns true if it does.
func HasReadableBattery() bool { func HasReadableBattery() bool {
@@ -21,22 +22,20 @@ func HasReadableBattery() bool {
return systemHasBattery return systemHasBattery
} }
haveCheckedBattery = true haveCheckedBattery = true
batteries,err := battery.GetAll() batteries, err := battery.GetAll()
if err != nil { for _, bat := range batteries {
// even if there's errors getting some batteries, the system if bat != nil && (bat.Full > 0 || bat.Design > 0) {
// definitely has a battery if the list is not empty. systemHasBattery = true
// This list will include everything `battery` can find, break
// including things like bluetooth devices. }
fmt.Fprintln(os.Stderr, err)
} }
systemHasBattery = len(batteries) > 0
if !systemHasBattery { if !systemHasBattery {
slog.Debug("No battery found", "err", err) slog.Debug("No battery found", "err", err)
} }
return systemHasBattery return systemHasBattery
} }
// GetBatteryStats returns the current battery percent and charge state // GetBatteryStats returns the current battery percent and charge state
// percent = (current charge of all batteries) / (sum of designed/full capacity of all batteries) // percent = (current charge of all batteries) / (sum of designed/full capacity of all batteries)
func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) { func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
if !HasReadableBattery() { if !HasReadableBattery() {
@@ -53,21 +52,26 @@ func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
totalCharge := float64(0) totalCharge := float64(0)
errs, partialErrs := err.(battery.Errors) errs, partialErrs := err.(battery.Errors)
batteryState = math.MaxUint8
for i, bat := range batteries { for i, bat := range batteries {
if partialErrs && errs[i] != nil { if partialErrs && errs[i] != nil {
// if there were some errors, like missing data, skip it // if there were some errors, like missing data, skip it
continue continue
} }
if bat.Full == 0 { if bat == nil || bat.Full == 0 {
// skip batteries with no capacity. Charge is unlikely to ever be zero, but // skip batteries with no capacity. Charge is unlikely to ever be zero, but
// we can't guarantee that, so don't skip based on charge. // we can't guarantee that, so don't skip based on charge.
continue continue
} }
totalCapacity += bat.Full totalCapacity += bat.Full
totalCharge += bat.Current totalCharge += bat.Current
if bat.State.Raw >= 0 {
batteryState = uint8(bat.State.Raw)
}
} }
if totalCapacity == 0 { if totalCapacity == 0 || batteryState == math.MaxUint8 {
// for macs there's sometimes a ghost battery with 0 capacity // for macs there's sometimes a ghost battery with 0 capacity
// https://github.com/distatus/battery/issues/34 // https://github.com/distatus/battery/issues/34
// Instead of skipping over those batteries, we'll check for total 0 capacity // Instead of skipping over those batteries, we'll check for total 0 capacity
@@ -76,6 +80,5 @@ func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
} }
batteryPercent = uint8(totalCharge / totalCapacity * 100) batteryPercent = uint8(totalCharge / totalCapacity * 100)
batteryState = uint8(batteries[0].State.Raw)
return batteryPercent, batteryState, nil return batteryPercent, batteryState, nil
} }

View File

@@ -17,6 +17,7 @@ import (
"github.com/henrygd/beszel/internal/common" "github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/entities/smart" "github.com/henrygd/beszel/internal/entities/smart"
"github.com/henrygd/beszel/internal/entities/system" "github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/entities/systemd"
"github.com/fxamacker/cbor/v2" "github.com/fxamacker/cbor/v2"
"github.com/lxzan/gws" "github.com/lxzan/gws"
@@ -276,6 +277,8 @@ func (client *WebSocketClient) sendResponse(data any, requestID *uint32) error {
response.String = &v response.String = &v
case map[string]smart.SmartData: case map[string]smart.SmartData:
response.SmartData = v response.SmartData = v
case systemd.ServiceDetails:
response.ServiceInfo = v
// case []byte: // case []byte:
// response.RawBytes = v // response.RawBytes = v
// case string: // case string:

View File

@@ -4,10 +4,12 @@ import (
"math" "math"
"runtime" "runtime"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/shirou/gopsutil/v4/cpu" "github.com/shirou/gopsutil/v4/cpu"
) )
var lastCpuTimes = make(map[uint16]cpu.TimesStat) var lastCpuTimes = make(map[uint16]cpu.TimesStat)
var lastPerCoreCpuTimes = make(map[uint16][]cpu.TimesStat)
// init initializes the CPU monitoring by storing the initial CPU times // init initializes the CPU monitoring by storing the initial CPU times
// for the default 60-second cache interval. // for the default 60-second cache interval.
@@ -15,23 +17,92 @@ func init() {
if times, err := cpu.Times(false); err == nil { if times, err := cpu.Times(false); err == nil {
lastCpuTimes[60000] = times[0] lastCpuTimes[60000] = times[0]
} }
if perCoreTimes, err := cpu.Times(true); err == nil {
lastPerCoreCpuTimes[60000] = perCoreTimes
}
} }
// getCpuPercent calculates the CPU usage percentage using cached previous measurements. // CpuMetrics contains detailed CPU usage breakdown
// It uses the specified cache time interval to determine the time window for calculation. type CpuMetrics struct {
// Returns the CPU usage percentage (0-100) and any error encountered. Total float64
func getCpuPercent(cacheTimeMs uint16) (float64, error) { User float64
System float64
Iowait float64
Steal float64
Idle float64
}
// getCpuMetrics calculates detailed CPU usage metrics using cached previous measurements.
// It returns percentages for total, user, system, iowait, and steal time.
func getCpuMetrics(cacheTimeMs uint16) (CpuMetrics, error) {
times, err := cpu.Times(false) times, err := cpu.Times(false)
if err != nil || len(times) == 0 { if err != nil || len(times) == 0 {
return 0, err return CpuMetrics{}, err
} }
// if cacheTimeMs is not in lastCpuTimes, use 60000 as fallback lastCpuTime // if cacheTimeMs is not in lastCpuTimes, use 60000 as fallback lastCpuTime
if _, ok := lastCpuTimes[cacheTimeMs]; !ok { if _, ok := lastCpuTimes[cacheTimeMs]; !ok {
lastCpuTimes[cacheTimeMs] = lastCpuTimes[60000] lastCpuTimes[cacheTimeMs] = lastCpuTimes[60000]
} }
delta := calculateBusy(lastCpuTimes[cacheTimeMs], times[0])
t1 := lastCpuTimes[cacheTimeMs]
t2 := times[0]
t1All, _ := getAllBusy(t1)
t2All, _ := getAllBusy(t2)
totalDelta := t2All - t1All
if totalDelta <= 0 {
return CpuMetrics{}, nil
}
metrics := CpuMetrics{
Total: calculateBusy(t1, t2),
User: clampPercent((t2.User - t1.User) / totalDelta * 100),
System: clampPercent((t2.System - t1.System) / totalDelta * 100),
Iowait: clampPercent((t2.Iowait - t1.Iowait) / totalDelta * 100),
Steal: clampPercent((t2.Steal - t1.Steal) / totalDelta * 100),
Idle: clampPercent((t2.Idle - t1.Idle) / totalDelta * 100),
}
lastCpuTimes[cacheTimeMs] = times[0] lastCpuTimes[cacheTimeMs] = times[0]
return delta, nil return metrics, nil
}
// clampPercent ensures the percentage is between 0 and 100
func clampPercent(value float64) float64 {
return math.Min(100, math.Max(0, value))
}
// getPerCoreCpuUsage calculates per-core CPU busy usage as integer percentages (0-100).
// It uses cached previous measurements for the provided cache interval.
func getPerCoreCpuUsage(cacheTimeMs uint16) (system.Uint8Slice, error) {
perCoreTimes, err := cpu.Times(true)
if err != nil || len(perCoreTimes) == 0 {
return nil, err
}
// Initialize cache if needed
if _, ok := lastPerCoreCpuTimes[cacheTimeMs]; !ok {
lastPerCoreCpuTimes[cacheTimeMs] = lastPerCoreCpuTimes[60000]
}
lastTimes := lastPerCoreCpuTimes[cacheTimeMs]
// Limit to the number of cores available in both samples
length := len(perCoreTimes)
if len(lastTimes) < length {
length = len(lastTimes)
}
usage := make([]uint8, length)
for i := 0; i < length; i++ {
t1 := lastTimes[i]
t2 := perCoreTimes[i]
usage[i] = uint8(math.Round(calculateBusy(t1, t2)))
}
lastPerCoreCpuTimes[cacheTimeMs] = perCoreTimes
return usage, nil
} }
// calculateBusy calculates the CPU busy percentage between two time points. // calculateBusy calculates the CPU busy percentage between two time points.
@@ -41,13 +112,10 @@ func calculateBusy(t1, t2 cpu.TimesStat) float64 {
t1All, t1Busy := getAllBusy(t1) t1All, t1Busy := getAllBusy(t1)
t2All, t2Busy := getAllBusy(t2) t2All, t2Busy := getAllBusy(t2)
if t2Busy <= t1Busy { if t2All <= t1All || t2Busy <= t1Busy {
return 0 return 0
} }
if t2All <= t1All { return clampPercent((t2Busy - t1Busy) / (t2All - t1All) * 100)
return 100
}
return math.Min(100, math.Max(0, (t2Busy-t1Busy)/(t2All-t1All)*100))
} }
// getAllBusy calculates the total CPU time and busy CPU time from CPU times statistics. // getAllBusy calculates the total CPU time and busy CPU time from CPU times statistics.

View File

@@ -31,6 +31,7 @@ func (a *Agent) initializeDiskInfo() {
filesystem, _ := GetEnv("FILESYSTEM") filesystem, _ := GetEnv("FILESYSTEM")
efPath := "/extra-filesystems" efPath := "/extra-filesystems"
hasRoot := false hasRoot := false
isWindows := runtime.GOOS == "windows"
partitions, err := disk.Partitions(false) partitions, err := disk.Partitions(false)
if err != nil { if err != nil {
@@ -38,6 +39,13 @@ func (a *Agent) initializeDiskInfo() {
} }
slog.Debug("Disk", "partitions", partitions) slog.Debug("Disk", "partitions", partitions)
// trim trailing backslash for Windows devices (#1361)
if isWindows {
for i, p := range partitions {
partitions[i].Device = strings.TrimSuffix(p.Device, "\\")
}
}
// ioContext := context.WithValue(a.sensorsContext, // ioContext := context.WithValue(a.sensorsContext,
// common.EnvKey, common.EnvMap{common.HostProcEnvKey: "/tmp/testproc"}, // common.EnvKey, common.EnvMap{common.HostProcEnvKey: "/tmp/testproc"},
// ) // )
@@ -52,7 +60,7 @@ func (a *Agent) initializeDiskInfo() {
// Helper function to add a filesystem to fsStats if it doesn't exist // Helper function to add a filesystem to fsStats if it doesn't exist
addFsStat := func(device, mountpoint string, root bool, customName ...string) { addFsStat := func(device, mountpoint string, root bool, customName ...string) {
var key string var key string
if runtime.GOOS == "windows" { if isWindows {
key = device key = device
} else { } else {
key = filepath.Base(device) key = filepath.Base(device)

View File

@@ -13,6 +13,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -32,6 +33,12 @@ const (
maxMemoryUsage uint64 = 100 * 1024 * 1024 * 1024 * 1024 maxMemoryUsage uint64 = 100 * 1024 * 1024 * 1024 * 1024
// Number of log lines to request when fetching container logs // Number of log lines to request when fetching container logs
dockerLogsTail = 200 dockerLogsTail = 200
// Maximum size of a single log frame (1MB) to prevent memory exhaustion
// A single log line larger than 1MB is likely an error or misconfiguration
maxLogFrameSize = 1024 * 1024
// Maximum total log content size (5MB) to prevent memory exhaustion
// This provides a reasonable limit for network transfer and browser rendering
maxTotalLogSize = 5 * 1024 * 1024
) )
type dockerManager struct { type dockerManager struct {
@@ -47,6 +54,7 @@ type dockerManager struct {
buf *bytes.Buffer // Buffer to store and read response bodies buf *bytes.Buffer // Buffer to store and read response bodies
decoder *json.Decoder // Reusable JSON decoder that reads from buf decoder *json.Decoder // Reusable JSON decoder that reads from buf
apiStats *container.ApiStats // Reusable API stats object apiStats *container.ApiStats // Reusable API stats object
excludeContainers []string // Patterns to exclude containers by name
// Cache-time-aware tracking for CPU stats (similar to cpu.go) // Cache-time-aware tracking for CPU stats (similar to cpu.go)
// Maps cache time intervals to container-specific CPU usage tracking // Maps cache time intervals to container-specific CPU usage tracking
@@ -88,6 +96,19 @@ func (d *dockerManager) dequeue() {
} }
} }
// shouldExcludeContainer checks if a container name matches any exclusion pattern
func (dm *dockerManager) shouldExcludeContainer(name string) bool {
if len(dm.excludeContainers) == 0 {
return false
}
for _, pattern := range dm.excludeContainers {
if match, _ := path.Match(pattern, name); match {
return true
}
}
return false
}
// Returns stats for all running containers with cache-time-aware delta tracking // Returns stats for all running containers with cache-time-aware delta tracking
func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats, error) { func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats, error) {
resp, err := dm.client.Get("http://localhost/containers/json") resp, err := dm.client.Get("http://localhost/containers/json")
@@ -115,6 +136,13 @@ func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats,
for _, ctr := range dm.apiContainerList { for _, ctr := range dm.apiContainerList {
ctr.IdShort = ctr.Id[:12] ctr.IdShort = ctr.Id[:12]
// Skip this container if it matches the exclusion pattern
if dm.shouldExcludeContainer(ctr.Names[0][1:]) {
slog.Debug("Excluding container", "name", ctr.Names[0][1:])
continue
}
dm.validIds[ctr.IdShort] = struct{}{} dm.validIds[ctr.IdShort] = struct{}{}
// check if container is less than 1 minute old (possible restart) // check if container is less than 1 minute old (possible restart)
// note: can't use Created field because it's not updated on restart // note: can't use Created field because it's not updated on restart
@@ -497,6 +525,19 @@ func newDockerManager(a *Agent) *dockerManager {
userAgent: "Docker-Client/", userAgent: "Docker-Client/",
} }
// Read container exclusion patterns from environment variable
var excludeContainers []string
if excludeStr, set := GetEnv("EXCLUDE_CONTAINERS"); set && excludeStr != "" {
parts := strings.SplitSeq(excludeStr, ",")
for part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
excludeContainers = append(excludeContainers, trimmed)
}
}
slog.Info("EXCLUDE_CONTAINERS", "patterns", excludeContainers)
}
manager := &dockerManager{ manager := &dockerManager{
client: &http.Client{ client: &http.Client{
Timeout: timeout, Timeout: timeout,
@@ -506,6 +547,7 @@ func newDockerManager(a *Agent) *dockerManager {
sem: make(chan struct{}, 5), sem: make(chan struct{}, 5),
apiContainerList: []*container.ApiInfo{}, apiContainerList: []*container.ApiInfo{},
apiStats: &container.ApiStats{}, apiStats: &container.ApiStats{},
excludeContainers: excludeContainers,
// Initialize cache-time-aware tracking structures // Initialize cache-time-aware tracking structures
lastCpuContainer: make(map[uint16]map[string]uint64), lastCpuContainer: make(map[uint16]map[string]uint64),
@@ -657,6 +699,7 @@ func decodeDockerLogStream(reader io.Reader, builder *strings.Builder) error {
const headerSize = 8 const headerSize = 8
var header [headerSize]byte var header [headerSize]byte
buf := make([]byte, 0, dockerLogsTail*200) buf := make([]byte, 0, dockerLogsTail*200)
totalBytesRead := 0
for { for {
if _, err := io.ReadFull(reader, header[:]); err != nil { if _, err := io.ReadFull(reader, header[:]); err != nil {
@@ -671,6 +714,19 @@ func decodeDockerLogStream(reader io.Reader, builder *strings.Builder) error {
continue continue
} }
// Prevent memory exhaustion from excessively large frames
if frameLen > maxLogFrameSize {
return fmt.Errorf("log frame size (%d) exceeds maximum (%d)", frameLen, maxLogFrameSize)
}
// Check if reading this frame would exceed total log size limit
if totalBytesRead+int(frameLen) > maxTotalLogSize {
// Read and discard remaining data to avoid blocking
_, _ = io.Copy(io.Discard, io.LimitReader(reader, int64(frameLen)))
slog.Debug("Truncating logs: limit reached", "read", totalBytesRead, "limit", maxTotalLogSize)
return nil
}
buf = allocateBuffer(buf, int(frameLen)) buf = allocateBuffer(buf, int(frameLen))
if _, err := io.ReadFull(reader, buf[:frameLen]); err != nil { if _, err := io.ReadFull(reader, buf[:frameLen]); err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
@@ -682,6 +738,7 @@ func decodeDockerLogStream(reader io.Reader, builder *strings.Builder) error {
return err return err
} }
builder.Write(buf[:frameLen]) builder.Write(buf[:frameLen])
totalBytesRead += int(frameLen)
} }
} }

View File

@@ -4,8 +4,10 @@
package agent package agent
import ( import (
"bytes"
"encoding/json" "encoding/json"
"os" "os"
"strings"
"testing" "testing"
"time" "time"
@@ -911,6 +913,8 @@ func TestConstantsAndUtilityFunctions(t *testing.T) {
assert.Equal(t, uint16(60000), defaultCacheTimeMs) assert.Equal(t, uint16(60000), defaultCacheTimeMs)
assert.Equal(t, uint64(5e9), maxNetworkSpeedBps) assert.Equal(t, uint64(5e9), maxNetworkSpeedBps)
assert.Equal(t, 2100, dockerTimeoutMs) assert.Equal(t, 2100, dockerTimeoutMs)
assert.Equal(t, uint32(1024*1024), uint32(maxLogFrameSize)) // 1MB
assert.Equal(t, 5*1024*1024, maxTotalLogSize) // 5MB
// Test utility functions // Test utility functions
assert.Equal(t, 1.5, twoDecimals(1.499)) assert.Equal(t, 1.5, twoDecimals(1.499))
@@ -921,3 +925,281 @@ func TestConstantsAndUtilityFunctions(t *testing.T) {
assert.Equal(t, 0.5, bytesToMegabytes(524288)) // 512 KB assert.Equal(t, 0.5, bytesToMegabytes(524288)) // 512 KB
assert.Equal(t, 0.0, bytesToMegabytes(0)) assert.Equal(t, 0.0, bytesToMegabytes(0))
} }
func TestDecodeDockerLogStream(t *testing.T) {
tests := []struct {
name string
input []byte
expected string
expectError bool
}{
{
name: "simple log entry",
input: []byte{
// Frame 1: stdout, 11 bytes
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B,
'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd',
},
expected: "Hello World",
expectError: false,
},
{
name: "multiple frames",
input: []byte{
// Frame 1: stdout, 5 bytes
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
'H', 'e', 'l', 'l', 'o',
// Frame 2: stdout, 5 bytes
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
'W', 'o', 'r', 'l', 'd',
},
expected: "HelloWorld",
expectError: false,
},
{
name: "zero length frame",
input: []byte{
// Frame 1: stdout, 0 bytes
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Frame 2: stdout, 5 bytes
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
'H', 'e', 'l', 'l', 'o',
},
expected: "Hello",
expectError: false,
},
{
name: "empty input",
input: []byte{},
expected: "",
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
reader := bytes.NewReader(tt.input)
var builder strings.Builder
err := decodeDockerLogStream(reader, &builder)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, builder.String())
}
})
}
}
func TestDecodeDockerLogStreamMemoryProtection(t *testing.T) {
t.Run("excessively large frame should error", func(t *testing.T) {
// Create a frame with size exceeding maxLogFrameSize
excessiveSize := uint32(maxLogFrameSize + 1)
input := []byte{
// Frame header with excessive size
0x01, 0x00, 0x00, 0x00,
byte(excessiveSize >> 24), byte(excessiveSize >> 16), byte(excessiveSize >> 8), byte(excessiveSize),
}
reader := bytes.NewReader(input)
var builder strings.Builder
err := decodeDockerLogStream(reader, &builder)
assert.Error(t, err)
assert.Contains(t, err.Error(), "log frame size")
assert.Contains(t, err.Error(), "exceeds maximum")
})
t.Run("total size limit should truncate", func(t *testing.T) {
// Create frames that exceed maxTotalLogSize (5MB)
// Use frames within maxLogFrameSize (1MB) to avoid single-frame rejection
frameSize := uint32(800 * 1024) // 800KB per frame
var input []byte
// Frames 1-6: 800KB each (total 4.8MB - within 5MB limit)
for i := 0; i < 6; i++ {
char := byte('A' + i)
frameHeader := []byte{
0x01, 0x00, 0x00, 0x00,
byte(frameSize >> 24), byte(frameSize >> 16), byte(frameSize >> 8), byte(frameSize),
}
input = append(input, frameHeader...)
input = append(input, bytes.Repeat([]byte{char}, int(frameSize))...)
}
// Frame 7: 800KB (would bring total to 5.6MB, exceeding 5MB limit - should be truncated)
frame7Header := []byte{
0x01, 0x00, 0x00, 0x00,
byte(frameSize >> 24), byte(frameSize >> 16), byte(frameSize >> 8), byte(frameSize),
}
input = append(input, frame7Header...)
input = append(input, bytes.Repeat([]byte{'Z'}, int(frameSize))...)
reader := bytes.NewReader(input)
var builder strings.Builder
err := decodeDockerLogStream(reader, &builder)
// Should complete without error (graceful truncation)
assert.NoError(t, err)
// Should have read 6 frames (4.8MB total, stopping before 7th would exceed 5MB limit)
expectedSize := int(frameSize) * 6
assert.Equal(t, expectedSize, builder.Len())
// Should contain A-F but not Z
result := builder.String()
assert.Contains(t, result, "A")
assert.Contains(t, result, "F")
assert.NotContains(t, result, "Z")
})
}
func TestAllocateBuffer(t *testing.T) {
tests := []struct {
name string
currentCap int
needed int
expectedCap int
shouldRealloc bool
}{
{
name: "buffer has enough capacity",
currentCap: 1024,
needed: 512,
expectedCap: 1024,
shouldRealloc: false,
},
{
name: "buffer needs reallocation",
currentCap: 512,
needed: 1024,
expectedCap: 1024,
shouldRealloc: true,
},
{
name: "buffer needs exact size",
currentCap: 1024,
needed: 1024,
expectedCap: 1024,
shouldRealloc: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
current := make([]byte, 0, tt.currentCap)
result := allocateBuffer(current, tt.needed)
assert.Equal(t, tt.needed, len(result))
assert.GreaterOrEqual(t, cap(result), tt.expectedCap)
if tt.shouldRealloc {
// If reallocation was needed, capacity should be at least the needed size
assert.GreaterOrEqual(t, cap(result), tt.needed)
}
})
}
}
func TestShouldExcludeContainer(t *testing.T) {
tests := []struct {
name string
containerName string
patterns []string
expected bool
}{
{
name: "empty patterns excludes nothing",
containerName: "any-container",
patterns: []string{},
expected: false,
},
{
name: "exact match - excluded",
containerName: "test-web",
patterns: []string{"test-web", "test-api"},
expected: true,
},
{
name: "exact match - not excluded",
containerName: "prod-web",
patterns: []string{"test-web", "test-api"},
expected: false,
},
{
name: "wildcard prefix match - excluded",
containerName: "test-web",
patterns: []string{"test-*"},
expected: true,
},
{
name: "wildcard prefix match - not excluded",
containerName: "prod-web",
patterns: []string{"test-*"},
expected: false,
},
{
name: "wildcard suffix match - excluded",
containerName: "myapp-staging",
patterns: []string{"*-staging"},
expected: true,
},
{
name: "wildcard suffix match - not excluded",
containerName: "myapp-prod",
patterns: []string{"*-staging"},
expected: false,
},
{
name: "wildcard both sides match - excluded",
containerName: "test-myapp-staging",
patterns: []string{"*-myapp-*"},
expected: true,
},
{
name: "wildcard both sides match - not excluded",
containerName: "prod-yourapp-live",
patterns: []string{"*-myapp-*"},
expected: false,
},
{
name: "multiple patterns - matches first",
containerName: "test-container",
patterns: []string{"test-*", "*-staging"},
expected: true,
},
{
name: "multiple patterns - matches second",
containerName: "myapp-staging",
patterns: []string{"test-*", "*-staging"},
expected: true,
},
{
name: "multiple patterns - no match",
containerName: "prod-web",
patterns: []string{"test-*", "*-staging"},
expected: false,
},
{
name: "mixed exact and wildcard - exact match",
containerName: "temp-container",
patterns: []string{"temp-container", "test-*"},
expected: true,
},
{
name: "mixed exact and wildcard - wildcard match",
containerName: "test-web",
patterns: []string{"temp-container", "test-*"},
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dm := &dockerManager{
excludeContainers: tt.patterns,
}
result := dm.shouldExcludeContainer(tt.containerName)
assert.Equal(t, tt.expected, result)
})
}
}

View File

@@ -49,7 +49,12 @@ func (gm *GPUManager) updateIntelFromStats(sample *intelGpuStats) bool {
// collectIntelStats executes intel_gpu_top in text mode (-l) and parses the output // collectIntelStats executes intel_gpu_top in text mode (-l) and parses the output
func (gm *GPUManager) collectIntelStats() (err error) { func (gm *GPUManager) collectIntelStats() (err error) {
cmd := exec.Command(intelGpuStatsCmd, "-s", intelGpuStatsInterval, "-l") // Build command arguments, optionally selecting a device via -d
args := []string{"-s", intelGpuStatsInterval, "-l"}
if dev, ok := GetEnv("INTEL_GPU_DEVICE"); ok && dev != "" {
args = append(args, "-d", dev)
}
cmd := exec.Command(intelGpuStatsCmd, args...)
// Avoid blocking if intel_gpu_top writes to stderr // Avoid blocking if intel_gpu_top writes to stderr
cmd.Stderr = io.Discard cmd.Stderr = io.Discard
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
@@ -129,7 +134,9 @@ func (gm *GPUManager) parseIntelHeaders(header1 string, header2 string) (engineN
powerIndex = -1 // Initialize to -1, will be set to actual index if found powerIndex = -1 // Initialize to -1, will be set to actual index if found
// Collect engine names from header1 // Collect engine names from header1
for _, col := range h1 { for _, col := range h1 {
key := strings.TrimRightFunc(col, func(r rune) bool { return r >= '0' && r <= '9' }) key := strings.TrimRightFunc(col, func(r rune) bool {
return (r >= '0' && r <= '9') || r == '/'
})
var friendly string var friendly string
switch key { switch key {
case "RCS": case "RCS":

View File

@@ -4,8 +4,10 @@
package agent package agent
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"time" "time"
@@ -1437,6 +1439,15 @@ func TestParseIntelHeaders(t *testing.T) {
wantPowerIndex: 4, // "gpu" is at index 4 wantPowerIndex: 4, // "gpu" is at index 4
wantPreEngineCols: 8, // 17 total cols - 3*3 = 8 wantPreEngineCols: 8, // 17 total cols - 3*3 = 8
}, },
{
name: "basic headers with RCS BCS VCS using index in name",
header1: "Freq MHz IRQ RC6 Power W IMC MiB/s RCS/0 BCS/1 VCS/2",
header2: " req act /s % gpu pkg rd wr % se wa % se wa % se wa",
wantEngineNames: []string{"RCS", "BCS", "VCS"},
wantFriendlyNames: []string{"Render/3D", "Blitter", "Video"},
wantPowerIndex: 4, // "gpu" is at index 4
wantPreEngineCols: 8, // 17 total cols - 3*3 = 8
},
{ {
name: "headers with only RCS", name: "headers with only RCS",
header1: "Freq MHz IRQ RC6 Power W IMC MiB/s RCS", header1: "Freq MHz IRQ RC6 Power W IMC MiB/s RCS",
@@ -1624,3 +1635,42 @@ func TestParseIntelData(t *testing.T) {
}) })
} }
} }
func TestIntelCollectorDeviceEnv(t *testing.T) {
dir := t.TempDir()
t.Setenv("PATH", dir)
// Prepare a file to capture args
argsFile := filepath.Join(dir, "args.txt")
// Create a fake intel_gpu_top that records its arguments and prints minimal valid output
scriptPath := filepath.Join(dir, "intel_gpu_top")
script := fmt.Sprintf(`#!/bin/sh
echo "$@" > %s
echo "Freq MHz IRQ RC6 Power W IMC MiB/s RCS VCS"
echo " req act /s %% gpu pkg rd wr %% se wa %% se wa"
echo "226 223 338 58 2.00 2.69 1820 965 0.00 0 0 0.00 0 0"
echo "189 187 412 67 1.80 2.45 1950 823 8.50 2 1 15.00 1 0"
`, argsFile)
if err := os.WriteFile(scriptPath, []byte(script), 0755); err != nil {
t.Fatal(err)
}
// Set device selector via prefixed env var
t.Setenv("BESZEL_AGENT_INTEL_GPU_DEVICE", "sriov")
gm := &GPUManager{GpuDataMap: make(map[string]*system.GPUData)}
if err := gm.collectIntelStats(); err != nil {
t.Fatalf("collectIntelStats error: %v", err)
}
// Verify that -d sriov was passed
data, err := os.ReadFile(argsFile)
if err != nil {
t.Fatalf("failed reading args file: %v", err)
}
argsStr := strings.TrimSpace(string(data))
require.Contains(t, argsStr, "-d sriov")
require.Contains(t, argsStr, "-s ")
require.Contains(t, argsStr, "-l")
}

View File

@@ -50,6 +50,7 @@ func NewHandlerRegistry() *HandlerRegistry {
registry.Register(common.GetContainerLogs, &GetContainerLogsHandler{}) registry.Register(common.GetContainerLogs, &GetContainerLogsHandler{})
registry.Register(common.GetContainerInfo, &GetContainerInfoHandler{}) registry.Register(common.GetContainerInfo, &GetContainerInfoHandler{})
registry.Register(common.GetSmartData, &GetSmartDataHandler{}) registry.Register(common.GetSmartData, &GetSmartDataHandler{})
registry.Register(common.GetSystemdInfo, &GetSystemdInfoHandler{})
return registry return registry
} }
@@ -168,9 +169,37 @@ func (h *GetSmartDataHandler) Handle(hctx *HandlerContext) error {
// return empty map to indicate no data // return empty map to indicate no data
return hctx.SendResponse(map[string]smart.SmartData{}, hctx.RequestID) return hctx.SendResponse(map[string]smart.SmartData{}, hctx.RequestID)
} }
if err := hctx.Agent.smartManager.Refresh(); err != nil { if err := hctx.Agent.smartManager.Refresh(false); err != nil {
slog.Debug("smart refresh failed", "err", err) slog.Debug("smart refresh failed", "err", err)
} }
data := hctx.Agent.smartManager.GetCurrentData() data := hctx.Agent.smartManager.GetCurrentData()
return hctx.SendResponse(data, hctx.RequestID) return hctx.SendResponse(data, hctx.RequestID)
} }
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// GetSystemdInfoHandler handles detailed systemd service info requests
type GetSystemdInfoHandler struct{}
func (h *GetSystemdInfoHandler) Handle(hctx *HandlerContext) error {
if hctx.Agent.systemdManager == nil {
return errors.ErrUnsupported
}
var req common.SystemdInfoRequest
if err := cbor.Unmarshal(hctx.Request.Data, &req); err != nil {
return err
}
if req.ServiceName == "" {
return errors.New("service name is required")
}
details, err := hctx.Agent.systemdManager.getServiceDetails(req.ServiceName)
if err != nil {
return err
}
return hctx.SendResponse(details, hctx.RequestID)
}

View File

@@ -13,7 +13,9 @@ import (
"github.com/henrygd/beszel" "github.com/henrygd/beszel"
"github.com/henrygd/beszel/internal/common" "github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/entities/smart"
"github.com/henrygd/beszel/internal/entities/system" "github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/entities/systemd"
"github.com/blang/semver" "github.com/blang/semver"
"github.com/fxamacker/cbor/v2" "github.com/fxamacker/cbor/v2"
@@ -170,6 +172,10 @@ func (a *Agent) handleSSHRequest(w io.Writer, req *common.HubRequest[cbor.RawMes
response.SystemData = v response.SystemData = v
case string: case string:
response.String = &v response.String = &v
case map[string]smart.SmartData:
response.SmartData = v
case systemd.ServiceDetails:
response.ServiceInfo = v
default: default:
response.Error = fmt.Sprintf("unsupported response type: %T", data) response.Error = fmt.Sprintf("unsupported response type: %T", data)
} }

View File

@@ -1,10 +1,18 @@
//go:generate -command fetchsmartctl go run ./tools/fetchsmartctl
//go:generate fetchsmartctl -out ./smartmontools/smartctl.exe -url https://static.beszel.dev/bin/smartctl/smartctl-nc.exe -sha 3912249c3b329249aa512ce796fd1b64d7cbd8378b68ad2756b39163d9c30b47
package agent package agent
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"runtime"
"strconv"
"strings"
"sync" "sync"
"time" "time"
@@ -16,9 +24,12 @@ import (
// SmartManager manages data collection for SMART devices // SmartManager manages data collection for SMART devices
type SmartManager struct { type SmartManager struct {
sync.Mutex sync.Mutex
SmartDataMap map[string]*smart.SmartData SmartDataMap map[string]*smart.SmartData
SmartDevices []*DeviceInfo SmartDevices []*DeviceInfo
refreshMutex sync.Mutex refreshMutex sync.Mutex
lastScanTime time.Time
binPath string
excludedDevices map[string]struct{}
} }
type scanOutput struct { type scanOutput struct {
@@ -35,18 +46,23 @@ type DeviceInfo struct {
Type string `json:"type"` Type string `json:"type"`
InfoName string `json:"info_name"` InfoName string `json:"info_name"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
// typeVerified reports whether we have already parsed SMART data for this device
// with the stored parserType. When true we can skip re-running the detection logic.
typeVerified bool
// parserType holds the parser type (nvme, sat, scsi) that last succeeded.
parserType string
} }
var errNoValidSmartData = fmt.Errorf("no valid SMART data found") // Error for missing data var errNoValidSmartData = fmt.Errorf("no valid SMART data found") // Error for missing data
// Refresh updates SMART data for all known devices on demand. // Refresh updates SMART data for all known devices
func (sm *SmartManager) Refresh() error { func (sm *SmartManager) Refresh(forceScan bool) error {
sm.refreshMutex.Lock() sm.refreshMutex.Lock()
defer sm.refreshMutex.Unlock() defer sm.refreshMutex.Unlock()
scanErr := sm.ScanDevices() scanErr := sm.ScanDevices(false)
if scanErr != nil { if scanErr != nil {
slog.Warn("smartctl scan failed", "err", scanErr) slog.Debug("smartctl scan failed", "err", scanErr)
} }
devices := sm.devicesSnapshot() devices := sm.devicesSnapshot()
@@ -56,7 +72,7 @@ func (sm *SmartManager) Refresh() error {
continue continue
} }
if err := sm.CollectSmart(deviceInfo); err != nil { if err := sm.CollectSmart(deviceInfo); err != nil {
slog.Info("smartctl collect failed for device, skipping", "device", deviceInfo.Name, "err", err) slog.Debug("smartctl collect failed", "device", deviceInfo.Name, "err", err)
collectErr = err collectErr = err
} }
} }
@@ -126,77 +142,356 @@ func (sm *SmartManager) GetCurrentData() map[string]smart.SmartData {
// Scan devices using `smartctl --scan -j` // Scan devices using `smartctl --scan -j`
// If scan fails, return error // If scan fails, return error
// If scan succeeds, parse the output and update the SmartDevices slice // If scan succeeds, parse the output and update the SmartDevices slice
func (sm *SmartManager) ScanDevices() error { func (sm *SmartManager) ScanDevices(force bool) error {
if !force && time.Since(sm.lastScanTime) < 30*time.Minute {
return nil
}
sm.lastScanTime = time.Now()
currentDevices := sm.devicesSnapshot()
var configuredDevices []*DeviceInfo
if configuredRaw, ok := GetEnv("SMART_DEVICES"); ok {
slog.Info("SMART_DEVICES", "value", configuredRaw)
config := strings.TrimSpace(configuredRaw)
if config == "" {
return errNoValidSmartData
}
parsedDevices, err := sm.parseConfiguredDevices(config)
if err != nil {
return err
}
configuredDevices = parsedDevices
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
cmd := exec.CommandContext(ctx, "smartctl", "--scan", "-j") cmd := exec.CommandContext(ctx, sm.binPath, "--scan", "-j")
output, err := cmd.Output() output, err := cmd.Output()
var (
scanErr error
scannedDevices []*DeviceInfo
hasValidScan bool
)
if err != nil { if err != nil {
return err scanErr = err
} else {
scannedDevices, hasValidScan = sm.parseScan(output)
if !hasValidScan {
scanErr = errNoValidSmartData
}
} }
hasValidData := sm.parseScan(output) finalDevices := mergeDeviceLists(currentDevices, scannedDevices, configuredDevices)
if !hasValidData { finalDevices = sm.filterExcludedDevices(finalDevices)
sm.updateSmartDevices(finalDevices)
if len(finalDevices) == 0 {
if scanErr != nil {
slog.Debug("smartctl scan failed", "err", scanErr)
return scanErr
}
return errNoValidSmartData return errNoValidSmartData
} }
return nil return nil
} }
func (sm *SmartManager) parseConfiguredDevices(config string) ([]*DeviceInfo, error) {
entries := strings.Split(config, ",")
devices := make([]*DeviceInfo, 0, len(entries))
for _, entry := range entries {
entry = strings.TrimSpace(entry)
if entry == "" {
continue
}
parts := strings.SplitN(entry, ":", 2)
name := strings.TrimSpace(parts[0])
if name == "" {
return nil, fmt.Errorf("invalid SMART_DEVICES entry %q", entry)
}
devType := ""
if len(parts) == 2 {
devType = strings.ToLower(strings.TrimSpace(parts[1]))
}
devices = append(devices, &DeviceInfo{
Name: name,
Type: devType,
})
}
if len(devices) == 0 {
return nil, errNoValidSmartData
}
return devices, nil
}
func (sm *SmartManager) refreshExcludedDevices() {
rawValue, _ := GetEnv("EXCLUDE_SMART")
sm.excludedDevices = make(map[string]struct{})
for entry := range strings.SplitSeq(rawValue, ",") {
device := strings.TrimSpace(entry)
if device == "" {
continue
}
sm.excludedDevices[device] = struct{}{}
}
}
func (sm *SmartManager) isExcludedDevice(deviceName string) bool {
_, exists := sm.excludedDevices[deviceName]
return exists
}
func (sm *SmartManager) filterExcludedDevices(devices []*DeviceInfo) []*DeviceInfo {
if devices == nil {
return []*DeviceInfo{}
}
excluded := sm.excludedDevices
if len(excluded) == 0 {
return devices
}
filtered := make([]*DeviceInfo, 0, len(devices))
for _, device := range devices {
if device == nil || device.Name == "" {
continue
}
if _, skip := excluded[device.Name]; skip {
continue
}
filtered = append(filtered, device)
}
return filtered
}
// detectSmartOutputType inspects sections that are unique to each smartctl
// JSON schema (NVMe, ATA/SATA, SCSI) to determine which parser should be used
// when the reported device type is ambiguous or missing.
func detectSmartOutputType(output []byte) string {
var hints struct {
AtaSmartAttributes json.RawMessage `json:"ata_smart_attributes"`
NVMeSmartHealthInformationLog json.RawMessage `json:"nvme_smart_health_information_log"`
ScsiErrorCounterLog json.RawMessage `json:"scsi_error_counter_log"`
}
if err := json.Unmarshal(output, &hints); err != nil {
return ""
}
switch {
case hasJSONValue(hints.NVMeSmartHealthInformationLog):
return "nvme"
case hasJSONValue(hints.AtaSmartAttributes):
return "sat"
case hasJSONValue(hints.ScsiErrorCounterLog):
return "scsi"
default:
return "sat"
}
}
// hasJSONValue reports whether a JSON payload contains a concrete value. The
// smartctl output often emits "null" for sections that do not apply, so we
// only treat non-null content as a hint.
func hasJSONValue(raw json.RawMessage) bool {
if len(raw) == 0 {
return false
}
trimmed := strings.TrimSpace(string(raw))
return trimmed != "" && trimmed != "null"
}
func normalizeParserType(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "nvme", "sntasmedia", "sntrealtek":
return "nvme"
case "sat", "ata":
return "sat"
case "scsi":
return "scsi"
default:
return strings.ToLower(strings.TrimSpace(value))
}
}
// parseSmartOutput attempts each SMART parser, optionally detecting the type when
// it is not provided, and updates the device info when a parser succeeds.
func (sm *SmartManager) parseSmartOutput(deviceInfo *DeviceInfo, output []byte) bool {
parsers := []struct {
Type string
Parse func([]byte) (bool, int)
}{
{Type: "nvme", Parse: sm.parseSmartForNvme},
{Type: "sat", Parse: sm.parseSmartForSata},
{Type: "scsi", Parse: sm.parseSmartForScsi},
}
deviceType := normalizeParserType(deviceInfo.parserType)
if deviceType == "" {
deviceType = normalizeParserType(deviceInfo.Type)
}
if deviceInfo.parserType == "" {
switch deviceType {
case "nvme", "sat", "scsi":
deviceInfo.parserType = deviceType
}
}
// Only run the type detection when we do not yet know which parser works
// or the previous attempt failed.
needsDetection := deviceType == "" || !deviceInfo.typeVerified
if needsDetection {
structureType := detectSmartOutputType(output)
if deviceType != structureType {
deviceType = structureType
deviceInfo.parserType = structureType
deviceInfo.typeVerified = false
}
if deviceInfo.Type == "" || strings.EqualFold(deviceInfo.Type, structureType) {
deviceInfo.Type = structureType
}
}
// Try the most likely parser first, but keep the remaining parsers in reserve
// so an incorrect hint never leaves the device unparsed.
selectedParsers := make([]struct {
Type string
Parse func([]byte) (bool, int)
}, 0, len(parsers))
if deviceType != "" {
for _, parser := range parsers {
if parser.Type == deviceType {
selectedParsers = append(selectedParsers, parser)
break
}
}
}
for _, parser := range parsers {
alreadySelected := false
for _, selected := range selectedParsers {
if selected.Type == parser.Type {
alreadySelected = true
break
}
}
if alreadySelected {
continue
}
selectedParsers = append(selectedParsers, parser)
}
// Try the selected parsers in order until we find one that succeeds.
for _, parser := range selectedParsers {
hasData, _ := parser.Parse(output)
if hasData {
deviceInfo.parserType = parser.Type
if deviceInfo.Type == "" || strings.EqualFold(deviceInfo.Type, parser.Type) {
deviceInfo.Type = parser.Type
}
// Remember that this parser is valid so future refreshes can bypass
// detection entirely.
deviceInfo.typeVerified = true
return true
}
slog.Debug("parser failed", "device", deviceInfo.Name, "parser", parser.Type)
}
// Leave verification false so the next pass will attempt detection again.
deviceInfo.typeVerified = false
slog.Debug("parsing failed", "device", deviceInfo.Name)
return false
}
// CollectSmart collects SMART data for a device // CollectSmart collects SMART data for a device
// Collect data using `smartctl --all -j /dev/sdX` or `smartctl --all -j /dev/nvmeX` // Collect data using `smartctl -d <type> -aj /dev/<device>` when device type is known
// Always attempts to parse output even if command fails, as some data may still be available // Always attempts to parse output even if command fails, as some data may still be available
// If collect fails, return error // If collect fails, return error
// If collect succeeds, parse the output and update the SmartDataMap // If collect succeeds, parse the output and update the SmartDataMap
// Uses -n standby to avoid waking up sleeping disks, but bypasses standby mode // Uses -n standby to avoid waking up sleeping disks, but bypasses standby mode
// for initial data collection when no cached data exists // for initial data collection when no cached data exists
func (sm *SmartManager) CollectSmart(deviceInfo *DeviceInfo) error { func (sm *SmartManager) CollectSmart(deviceInfo *DeviceInfo) error {
if deviceInfo != nil && sm.isExcludedDevice(deviceInfo.Name) {
return errNoValidSmartData
}
// slog.Info("collecting SMART data", "device", deviceInfo.Name, "type", deviceInfo.Type, "has_existing_data", sm.hasDataForDevice(deviceInfo.Name))
// Check if we have any existing data for this device // Check if we have any existing data for this device
hasExistingData := sm.hasDataForDevice(deviceInfo.Name) hasExistingData := sm.hasDataForDevice(deviceInfo.Name)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() defer cancel()
// Try with -n standby first if we have existing data // Try with -n standby first if we have existing data
cmd := exec.CommandContext(ctx, "smartctl", "-aj", "-n", "standby", deviceInfo.Name) args := sm.smartctlArgs(deviceInfo, true)
cmd := exec.CommandContext(ctx, sm.binPath, args...)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
// Check if device is in standby (exit status 2) // Check if device is in standby (exit status 2)
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 2 { if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 2 {
if hasExistingData { if hasExistingData {
// Device is in standby and we have cached data, keep using cache // Device is in standby and we have cached data, keep using cache
slog.Debug("device in standby mode, using cached data", "device", deviceInfo.Name)
return nil return nil
} }
// No cached data, need to collect initial data by bypassing standby // No cached data, need to collect initial data by bypassing standby
slog.Debug("device in standby but no cached data, collecting initial data", "device", deviceInfo.Name) ctx2, cancel2 := context.WithTimeout(context.Background(), 2*time.Second)
ctx2, cancel2 := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel2() defer cancel2()
cmd = exec.CommandContext(ctx2, "smartctl", "-aj", deviceInfo.Name) args = sm.smartctlArgs(deviceInfo, false)
cmd = exec.CommandContext(ctx2, sm.binPath, args...)
output, err = cmd.CombinedOutput() output, err = cmd.CombinedOutput()
} }
hasValidData := false hasValidData := sm.parseSmartOutput(deviceInfo, output)
switch deviceInfo.Type {
case "scsi", "sat", "ata":
// parse SATA/SCSI/ATA devices
hasValidData, _ = sm.parseSmartForSata(output)
case "nvme":
// parse nvme devices
hasValidData, _ = sm.parseSmartForNvme(output)
}
if !hasValidData { if !hasValidData {
if err != nil { if err != nil {
slog.Info("smartctl failed", "device", deviceInfo.Name, "err", err)
return err return err
} }
slog.Info("no valid SMART data found", "device", deviceInfo.Name)
return errNoValidSmartData return errNoValidSmartData
} }
return nil return nil
} }
// smartctlArgs returns the arguments for the smartctl command
// based on the device type and whether to include standby mode
func (sm *SmartManager) smartctlArgs(deviceInfo *DeviceInfo, includeStandby bool) []string {
args := make([]string, 0, 7)
if deviceInfo != nil {
deviceType := strings.ToLower(deviceInfo.Type)
// types sometimes misidentified in scan; see github.com/henrygd/beszel/issues/1345
if deviceType != "" && deviceType != "scsi" && deviceType != "ata" {
args = append(args, "-d", deviceInfo.Type)
}
}
args = append(args, "-a", "--json=c")
if includeStandby {
args = append(args, "-n", "standby")
}
if deviceInfo != nil {
args = append(args, deviceInfo.Name)
}
return args
}
// hasDataForDevice checks if we have cached SMART data for a specific device // hasDataForDevice checks if we have cached SMART data for a specific device
func (sm *SmartManager) hasDataForDevice(deviceName string) bool { func (sm *SmartManager) hasDataForDevice(deviceName string) bool {
sm.Lock() sm.Lock()
@@ -211,43 +506,194 @@ func (sm *SmartManager) hasDataForDevice(deviceName string) bool {
return false return false
} }
// parseScan parses the output of smartctl --scan -j and updates the SmartDevices slice // parseScan parses the output of smartctl --scan -j and returns the discovered devices.
func (sm *SmartManager) parseScan(output []byte) bool { func (sm *SmartManager) parseScan(output []byte) ([]*DeviceInfo, bool) {
sm.Lock()
defer sm.Unlock()
sm.SmartDevices = make([]*DeviceInfo, 0)
scan := &scanOutput{} scan := &scanOutput{}
if err := json.Unmarshal(output, scan); err != nil { if err := json.Unmarshal(output, scan); err != nil {
slog.Warn("Failed to parse smartctl scan JSON", "err", err) return nil, false
return false
} }
if len(scan.Devices) == 0 { if len(scan.Devices) == 0 {
return false slog.Debug("no devices found in smartctl scan")
return nil, false
} }
scannedDeviceNameMap := make(map[string]bool, len(scan.Devices)) devices := make([]*DeviceInfo, 0, len(scan.Devices))
for _, device := range scan.Devices { for _, device := range scan.Devices {
deviceInfo := &DeviceInfo{ slog.Debug("smartctl scan", "name", device.Name, "type", device.Type, "protocol", device.Protocol)
devices = append(devices, &DeviceInfo{
Name: device.Name, Name: device.Name,
Type: device.Type, Type: device.Type,
InfoName: device.InfoName, InfoName: device.InfoName,
Protocol: device.Protocol, Protocol: device.Protocol,
} })
sm.SmartDevices = append(sm.SmartDevices, deviceInfo)
scannedDeviceNameMap[device.Name] = true
}
// remove devices that are not in the scan
for key := range sm.SmartDataMap {
if _, ok := scannedDeviceNameMap[key]; !ok {
delete(sm.SmartDataMap, key)
}
} }
return true return devices, true
}
// mergeDeviceLists combines scanned and configured SMART devices, preferring
// configured SMART_DEVICES when both sources reference the same device.
func mergeDeviceLists(existing, scanned, configured []*DeviceInfo) []*DeviceInfo {
if len(scanned) == 0 && len(configured) == 0 {
return existing
}
// preserveVerifiedType copies the verified type/parser metadata from an existing
// device record so that subsequent scans/config updates never downgrade a
// previously verified device.
preserveVerifiedType := func(target, prev *DeviceInfo) {
if prev == nil || !prev.typeVerified {
return
}
target.Type = prev.Type
target.typeVerified = true
target.parserType = prev.parserType
}
existingIndex := make(map[string]*DeviceInfo, len(existing))
for _, dev := range existing {
if dev == nil || dev.Name == "" {
continue
}
existingIndex[dev.Name] = dev
}
finalDevices := make([]*DeviceInfo, 0, len(scanned)+len(configured))
deviceIndex := make(map[string]*DeviceInfo, len(scanned)+len(configured))
// Start with the newly scanned devices so we always surface fresh metadata,
// but ensure we retain any previously verified parser assignment.
for _, dev := range scanned {
if dev == nil || dev.Name == "" {
continue
}
// Work on a copy so we can safely adjust metadata without mutating the
// input slices that may be reused elsewhere.
copyDev := *dev
if prev := existingIndex[copyDev.Name]; prev != nil {
preserveVerifiedType(&copyDev, prev)
}
finalDevices = append(finalDevices, &copyDev)
deviceIndex[copyDev.Name] = finalDevices[len(finalDevices)-1]
}
// Merge configured devices on top so users can override scan results (except
// for verified type information).
for _, dev := range configured {
if dev == nil || dev.Name == "" {
continue
}
if existingDev, ok := deviceIndex[dev.Name]; ok {
// Only update the type if it has not been verified yet; otherwise we
// keep the existing verified metadata intact.
if dev.Type != "" && !existingDev.typeVerified {
newType := strings.TrimSpace(dev.Type)
existingDev.Type = newType
existingDev.typeVerified = false
existingDev.parserType = normalizeParserType(newType)
}
if dev.InfoName != "" {
existingDev.InfoName = dev.InfoName
}
if dev.Protocol != "" {
existingDev.Protocol = dev.Protocol
}
continue
}
copyDev := *dev
if prev := existingIndex[copyDev.Name]; prev != nil {
preserveVerifiedType(&copyDev, prev)
} else if copyDev.Type != "" {
copyDev.parserType = normalizeParserType(copyDev.Type)
}
finalDevices = append(finalDevices, &copyDev)
deviceIndex[copyDev.Name] = finalDevices[len(finalDevices)-1]
}
return finalDevices
}
// updateSmartDevices replaces the cached device list and prunes SMART data
// entries whose backing device no longer exists.
func (sm *SmartManager) updateSmartDevices(devices []*DeviceInfo) {
sm.Lock()
defer sm.Unlock()
sm.SmartDevices = devices
if len(sm.SmartDataMap) == 0 {
return
}
validNames := make(map[string]struct{}, len(devices))
for _, device := range devices {
if device == nil || device.Name == "" {
continue
}
validNames[device.Name] = struct{}{}
}
for key, data := range sm.SmartDataMap {
if data == nil {
delete(sm.SmartDataMap, key)
continue
}
if _, ok := validNames[data.DiskName]; ok {
continue
}
delete(sm.SmartDataMap, key)
}
}
// isVirtualDevice checks if a device is a virtual disk that should be filtered out
func (sm *SmartManager) isVirtualDevice(data *smart.SmartInfoForSata) bool {
vendorUpper := strings.ToUpper(data.ScsiVendor)
productUpper := strings.ToUpper(data.ScsiProduct)
modelUpper := strings.ToUpper(data.ModelName)
return sm.isVirtualDeviceFromStrings(vendorUpper, productUpper, modelUpper)
}
// isVirtualDeviceNvme checks if an NVMe device is a virtual disk that should be filtered out
func (sm *SmartManager) isVirtualDeviceNvme(data *smart.SmartInfoForNvme) bool {
modelUpper := strings.ToUpper(data.ModelName)
return sm.isVirtualDeviceFromStrings(modelUpper)
}
// isVirtualDeviceScsi checks if a SCSI device is a virtual disk that should be filtered out
func (sm *SmartManager) isVirtualDeviceScsi(data *smart.SmartInfoForScsi) bool {
vendorUpper := strings.ToUpper(data.ScsiVendor)
productUpper := strings.ToUpper(data.ScsiProduct)
modelUpper := strings.ToUpper(data.ScsiModelName)
return sm.isVirtualDeviceFromStrings(vendorUpper, productUpper, modelUpper)
}
// isVirtualDeviceFromStrings checks if any of the provided strings indicate a virtual device
func (sm *SmartManager) isVirtualDeviceFromStrings(fields ...string) bool {
for _, field := range fields {
fieldUpper := strings.ToUpper(field)
switch {
case strings.Contains(fieldUpper, "IET"), // iSCSI Enterprise Target
strings.Contains(fieldUpper, "VIRTUAL"),
strings.Contains(fieldUpper, "QEMU"),
strings.Contains(fieldUpper, "VBOX"),
strings.Contains(fieldUpper, "VMWARE"),
strings.Contains(fieldUpper, "MSFT"): // Microsoft Hyper-V
return true
}
}
return false
} }
// parseSmartForSata parses the output of smartctl --all -j for SATA/ATA devices and updates the SmartDataMap // parseSmartForSata parses the output of smartctl --all -j for SATA/ATA devices and updates the SmartDataMap
@@ -260,14 +706,19 @@ func (sm *SmartManager) parseSmartForSata(output []byte) (bool, int) {
} }
if data.SerialNumber == "" { if data.SerialNumber == "" {
slog.Warn("device has no serial number, skipping", "device", data.Device.Name) slog.Debug("no serial number", "device", data.Device.Name)
return false, data.Smartctl.ExitStatus
}
// Skip virtual devices (e.g., Kubernetes PVCs, QEMU, VirtualBox, etc.)
if sm.isVirtualDevice(&data) {
slog.Debug("skipping smart", "device", data.Device.Name, "model", data.ModelName)
return false, data.Smartctl.ExitStatus return false, data.Smartctl.ExitStatus
} }
sm.Lock() sm.Lock()
defer sm.Unlock() defer sm.Unlock()
// get device name (e.g. /dev/sda)
keyName := data.SerialNumber keyName := data.SerialNumber
// if device does not exist in SmartDataMap, initialize it // if device does not exist in SmartDataMap, initialize it
@@ -290,13 +741,17 @@ func (sm *SmartManager) parseSmartForSata(output []byte) (bool, int) {
// update SmartAttributes // update SmartAttributes
smartData.Attributes = make([]*smart.SmartAttribute, 0, len(data.AtaSmartAttributes.Table)) smartData.Attributes = make([]*smart.SmartAttribute, 0, len(data.AtaSmartAttributes.Table))
for _, attr := range data.AtaSmartAttributes.Table { for _, attr := range data.AtaSmartAttributes.Table {
rawValue := uint64(attr.Raw.Value)
if parsed, ok := smart.ParseSmartRawValueString(attr.Raw.String); ok {
rawValue = parsed
}
smartAttr := &smart.SmartAttribute{ smartAttr := &smart.SmartAttribute{
ID: attr.ID, ID: attr.ID,
Name: attr.Name, Name: attr.Name,
Value: attr.Value, Value: attr.Value,
Worst: attr.Worst, Worst: attr.Worst,
Threshold: attr.Thresh, Threshold: attr.Thresh,
RawValue: attr.Raw.Value, RawValue: rawValue,
RawString: attr.Raw.String, RawString: attr.Raw.String,
WhenFailed: attr.WhenFailed, WhenFailed: attr.WhenFailed,
} }
@@ -317,6 +772,92 @@ func getSmartStatus(temperature uint8, passed bool) string {
} }
} }
func (sm *SmartManager) parseSmartForScsi(output []byte) (bool, int) {
var data smart.SmartInfoForScsi
if err := json.Unmarshal(output, &data); err != nil {
return false, 0
}
if data.SerialNumber == "" {
slog.Debug("no serial number", "device", data.Device.Name)
return false, data.Smartctl.ExitStatus
}
// Skip virtual devices (e.g., Kubernetes PVCs, QEMU, VirtualBox, etc.)
if sm.isVirtualDeviceScsi(&data) {
slog.Debug("skipping smart", "device", data.Device.Name, "model", data.ScsiModelName)
return false, data.Smartctl.ExitStatus
}
sm.Lock()
defer sm.Unlock()
keyName := data.SerialNumber
if _, ok := sm.SmartDataMap[keyName]; !ok {
sm.SmartDataMap[keyName] = &smart.SmartData{}
}
smartData := sm.SmartDataMap[keyName]
smartData.ModelName = data.ScsiModelName
smartData.SerialNumber = data.SerialNumber
smartData.FirmwareVersion = data.ScsiRevision
smartData.Capacity = data.UserCapacity.Bytes
smartData.Temperature = data.Temperature.Current
smartData.SmartStatus = getSmartStatus(smartData.Temperature, data.SmartStatus.Passed)
smartData.DiskName = data.Device.Name
smartData.DiskType = data.Device.Type
attributes := make([]*smart.SmartAttribute, 0, 10)
attributes = append(attributes, &smart.SmartAttribute{Name: "PowerOnHours", RawValue: data.PowerOnTime.Hours})
attributes = append(attributes, &smart.SmartAttribute{Name: "PowerOnMinutes", RawValue: data.PowerOnTime.Minutes})
attributes = append(attributes, &smart.SmartAttribute{Name: "GrownDefectList", RawValue: data.ScsiGrownDefectList})
attributes = append(attributes, &smart.SmartAttribute{Name: "StartStopCycles", RawValue: data.ScsiStartStopCycleCounter.AccumulatedStartStopCycles})
attributes = append(attributes, &smart.SmartAttribute{Name: "LoadUnloadCycles", RawValue: data.ScsiStartStopCycleCounter.AccumulatedLoadUnloadCycles})
attributes = append(attributes, &smart.SmartAttribute{Name: "StartStopSpecified", RawValue: data.ScsiStartStopCycleCounter.SpecifiedCycleCountOverDeviceLifetime})
attributes = append(attributes, &smart.SmartAttribute{Name: "LoadUnloadSpecified", RawValue: data.ScsiStartStopCycleCounter.SpecifiedLoadUnloadCountOverDeviceLifetime})
readStats := data.ScsiErrorCounterLog.Read
writeStats := data.ScsiErrorCounterLog.Write
verifyStats := data.ScsiErrorCounterLog.Verify
attributes = append(attributes, &smart.SmartAttribute{Name: "ReadTotalErrorsCorrected", RawValue: readStats.TotalErrorsCorrected})
attributes = append(attributes, &smart.SmartAttribute{Name: "ReadTotalUncorrectedErrors", RawValue: readStats.TotalUncorrectedErrors})
attributes = append(attributes, &smart.SmartAttribute{Name: "ReadCorrectionAlgorithmInvocations", RawValue: readStats.CorrectionAlgorithmInvocations})
if val := parseScsiGigabytesProcessed(readStats.GigabytesProcessed); val >= 0 {
attributes = append(attributes, &smart.SmartAttribute{Name: "ReadGigabytesProcessed", RawValue: uint64(val)})
}
attributes = append(attributes, &smart.SmartAttribute{Name: "WriteTotalErrorsCorrected", RawValue: writeStats.TotalErrorsCorrected})
attributes = append(attributes, &smart.SmartAttribute{Name: "WriteTotalUncorrectedErrors", RawValue: writeStats.TotalUncorrectedErrors})
attributes = append(attributes, &smart.SmartAttribute{Name: "WriteCorrectionAlgorithmInvocations", RawValue: writeStats.CorrectionAlgorithmInvocations})
if val := parseScsiGigabytesProcessed(writeStats.GigabytesProcessed); val >= 0 {
attributes = append(attributes, &smart.SmartAttribute{Name: "WriteGigabytesProcessed", RawValue: uint64(val)})
}
attributes = append(attributes, &smart.SmartAttribute{Name: "VerifyTotalErrorsCorrected", RawValue: verifyStats.TotalErrorsCorrected})
attributes = append(attributes, &smart.SmartAttribute{Name: "VerifyTotalUncorrectedErrors", RawValue: verifyStats.TotalUncorrectedErrors})
attributes = append(attributes, &smart.SmartAttribute{Name: "VerifyCorrectionAlgorithmInvocations", RawValue: verifyStats.CorrectionAlgorithmInvocations})
if val := parseScsiGigabytesProcessed(verifyStats.GigabytesProcessed); val >= 0 {
attributes = append(attributes, &smart.SmartAttribute{Name: "VerifyGigabytesProcessed", RawValue: uint64(val)})
}
smartData.Attributes = attributes
sm.SmartDataMap[keyName] = smartData
return true, data.Smartctl.ExitStatus
}
func parseScsiGigabytesProcessed(value string) int64 {
if value == "" {
return -1
}
normalized := strings.ReplaceAll(value, ",", "")
parsed, err := strconv.ParseInt(normalized, 10, 64)
if err != nil {
return -1
}
return parsed
}
// parseSmartForNvme parses the output of smartctl --all -j /dev/nvmeX and updates the SmartDataMap // parseSmartForNvme parses the output of smartctl --all -j /dev/nvmeX and updates the SmartDataMap
// Returns hasValidData and exitStatus // Returns hasValidData and exitStatus
func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) { func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) {
@@ -327,14 +868,19 @@ func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) {
} }
if data.SerialNumber == "" { if data.SerialNumber == "" {
slog.Warn("device has no serial number, skipping", "device", data.Device.Name) slog.Debug("no serial number", "device", data.Device.Name)
return false, data.Smartctl.ExitStatus
}
// Skip virtual devices (e.g., Kubernetes PVCs, QEMU, VirtualBox, etc.)
if sm.isVirtualDeviceNvme(data) {
slog.Debug("skipping smart", "device", data.Device.Name, "model", data.ModelName)
return false, data.Smartctl.ExitStatus return false, data.Smartctl.ExitStatus
} }
sm.Lock() sm.Lock()
defer sm.Unlock() defer sm.Unlock()
// get device name (e.g. /dev/nvme0)
keyName := data.SerialNumber keyName := data.SerialNumber
// if device does not exist in SmartDataMap, initialize it // if device does not exist in SmartDataMap, initialize it
@@ -382,11 +928,33 @@ func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) {
} }
// detectSmartctl checks if smartctl is installed, returns an error if not // detectSmartctl checks if smartctl is installed, returns an error if not
func (sm *SmartManager) detectSmartctl() error { func (sm *SmartManager) detectSmartctl() (string, error) {
if _, err := exec.LookPath("smartctl"); err == nil { isWindows := runtime.GOOS == "windows"
return nil
// Load embedded smartctl.exe for Windows amd64 builds.
if isWindows && runtime.GOARCH == "amd64" {
if path, err := ensureEmbeddedSmartctl(); err == nil {
return path, nil
}
} }
return fmt.Errorf("no smartctl found - install smartctl")
if path, err := exec.LookPath("smartctl"); err == nil {
return path, nil
}
locations := []string{}
if isWindows {
locations = append(locations,
"C:\\Program Files\\smartmontools\\bin\\smartctl.exe",
)
} else {
locations = append(locations, "/opt/homebrew/bin/smartctl")
}
for _, location := range locations {
if _, err := os.Stat(location); err == nil {
return location, nil
}
}
return "", errors.New("smartctl not found")
} }
// NewSmartManager creates and initializes a new SmartManager // NewSmartManager creates and initializes a new SmartManager
@@ -394,9 +962,13 @@ func NewSmartManager() (*SmartManager, error) {
sm := &SmartManager{ sm := &SmartManager{
SmartDataMap: make(map[string]*smart.SmartData), SmartDataMap: make(map[string]*smart.SmartData),
} }
if err := sm.detectSmartctl(); err != nil { sm.refreshExcludedDevices()
path, err := sm.detectSmartctl()
if err != nil {
slog.Debug(err.Error())
return nil, err return nil, err
} }
slog.Debug("smartctl", "path", path)
sm.binPath = path
return sm, nil return sm, nil
} }

View File

@@ -0,0 +1,9 @@
//go:build !windows
package agent
import "errors"
func ensureEmbeddedSmartctl() (string, error) {
return "", errors.ErrUnsupported
}

782
agent/smart_test.go Normal file
View File

@@ -0,0 +1,782 @@
//go:build testing
// +build testing
package agent
import (
"errors"
"os"
"path/filepath"
"testing"
"github.com/henrygd/beszel/internal/entities/smart"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseSmartForScsi(t *testing.T) {
fixturePath := filepath.Join("test-data", "smart", "scsi.json")
data, err := os.ReadFile(fixturePath)
if err != nil {
t.Fatalf("failed reading fixture: %v", err)
}
sm := &SmartManager{
SmartDataMap: make(map[string]*smart.SmartData),
}
hasData, exitStatus := sm.parseSmartForScsi(data)
if !hasData {
t.Fatalf("expected SCSI data to parse successfully")
}
if exitStatus != 0 {
t.Fatalf("expected exit status 0, got %d", exitStatus)
}
deviceData, ok := sm.SmartDataMap["9YHSDH9B"]
if !ok {
t.Fatalf("expected smart data entry for serial 9YHSDH9B")
}
assert.Equal(t, deviceData.ModelName, "YADRO WUH721414AL4204")
assert.Equal(t, deviceData.SerialNumber, "9YHSDH9B")
assert.Equal(t, deviceData.FirmwareVersion, "C240")
assert.Equal(t, deviceData.DiskName, "/dev/sde")
assert.Equal(t, deviceData.DiskType, "scsi")
assert.EqualValues(t, deviceData.Temperature, 34)
assert.Equal(t, deviceData.SmartStatus, "PASSED")
assert.EqualValues(t, deviceData.Capacity, 14000519643136)
if len(deviceData.Attributes) == 0 {
t.Fatalf("expected attributes to be populated")
}
assertAttrValue(t, deviceData.Attributes, "PowerOnHours", 458)
assertAttrValue(t, deviceData.Attributes, "PowerOnMinutes", 25)
assertAttrValue(t, deviceData.Attributes, "GrownDefectList", 0)
assertAttrValue(t, deviceData.Attributes, "StartStopCycles", 2)
assertAttrValue(t, deviceData.Attributes, "LoadUnloadCycles", 418)
assertAttrValue(t, deviceData.Attributes, "ReadGigabytesProcessed", 3641)
assertAttrValue(t, deviceData.Attributes, "WriteGigabytesProcessed", 2124590)
assertAttrValue(t, deviceData.Attributes, "VerifyGigabytesProcessed", 0)
}
func TestParseSmartForSata(t *testing.T) {
fixturePath := filepath.Join("test-data", "smart", "sda.json")
data, err := os.ReadFile(fixturePath)
require.NoError(t, err)
sm := &SmartManager{
SmartDataMap: make(map[string]*smart.SmartData),
}
hasData, exitStatus := sm.parseSmartForSata(data)
require.True(t, hasData)
assert.Equal(t, 64, exitStatus)
deviceData, ok := sm.SmartDataMap["9C40918040082"]
require.True(t, ok, "expected smart data entry for serial 9C40918040082")
assert.Equal(t, "P3-2TB", deviceData.ModelName)
assert.Equal(t, "X0104A0", deviceData.FirmwareVersion)
assert.Equal(t, "/dev/sda", deviceData.DiskName)
assert.Equal(t, "sat", deviceData.DiskType)
assert.Equal(t, uint8(31), deviceData.Temperature)
assert.Equal(t, "PASSED", deviceData.SmartStatus)
assert.Equal(t, uint64(2048408248320), deviceData.Capacity)
if assert.NotEmpty(t, deviceData.Attributes) {
assertAttrValue(t, deviceData.Attributes, "Temperature_Celsius", 31)
}
}
func TestParseSmartForSataParentheticalRawValue(t *testing.T) {
jsonPayload := []byte(`{
"smartctl": {"exit_status": 0},
"device": {"name": "/dev/sdz", "type": "sat"},
"model_name": "Example",
"serial_number": "PARENTHESES123",
"firmware_version": "1.0",
"user_capacity": {"bytes": 1024},
"smart_status": {"passed": true},
"temperature": {"current": 25},
"ata_smart_attributes": {
"table": [
{
"id": 9,
"name": "Power_On_Hours",
"value": 93,
"worst": 55,
"thresh": 0,
"when_failed": "",
"raw": {
"value": 57891864217128,
"string": "39925 (212 206 0)"
}
}
]
}
}`)
sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
hasData, exitStatus := sm.parseSmartForSata(jsonPayload)
require.True(t, hasData)
assert.Equal(t, 0, exitStatus)
data, ok := sm.SmartDataMap["PARENTHESES123"]
require.True(t, ok)
require.Len(t, data.Attributes, 1)
attr := data.Attributes[0]
assert.Equal(t, uint64(39925), attr.RawValue)
assert.Equal(t, "39925 (212 206 0)", attr.RawString)
}
func TestParseSmartForNvme(t *testing.T) {
fixturePath := filepath.Join("test-data", "smart", "nvme0.json")
data, err := os.ReadFile(fixturePath)
require.NoError(t, err)
sm := &SmartManager{
SmartDataMap: make(map[string]*smart.SmartData),
}
hasData, exitStatus := sm.parseSmartForNvme(data)
require.True(t, hasData)
assert.Equal(t, 0, exitStatus)
deviceData, ok := sm.SmartDataMap["2024031600129"]
require.True(t, ok, "expected smart data entry for serial 2024031600129")
assert.Equal(t, "PELADN 512GB", deviceData.ModelName)
assert.Equal(t, "VC2S038E", deviceData.FirmwareVersion)
assert.Equal(t, "/dev/nvme0", deviceData.DiskName)
assert.Equal(t, "nvme", deviceData.DiskType)
assert.Equal(t, uint8(61), deviceData.Temperature)
assert.Equal(t, "PASSED", deviceData.SmartStatus)
assert.Equal(t, uint64(512110190592), deviceData.Capacity)
if assert.NotEmpty(t, deviceData.Attributes) {
assertAttrValue(t, deviceData.Attributes, "PercentageUsed", 0)
assertAttrValue(t, deviceData.Attributes, "DataUnitsWritten", 16040567)
}
}
func TestHasDataForDevice(t *testing.T) {
sm := &SmartManager{
SmartDataMap: map[string]*smart.SmartData{
"serial-1": {DiskName: "/dev/sda"},
"serial-2": nil,
},
}
assert.True(t, sm.hasDataForDevice("/dev/sda"))
assert.False(t, sm.hasDataForDevice("/dev/sdb"))
}
func TestDevicesSnapshotReturnsCopy(t *testing.T) {
originalDevice := &DeviceInfo{Name: "/dev/sda"}
sm := &SmartManager{
SmartDevices: []*DeviceInfo{
originalDevice,
{Name: "/dev/sdb"},
},
}
snapshot := sm.devicesSnapshot()
require.Len(t, snapshot, 2)
sm.SmartDevices[0] = &DeviceInfo{Name: "/dev/sdz"}
assert.Equal(t, "/dev/sda", snapshot[0].Name)
snapshot[1] = &DeviceInfo{Name: "/dev/nvme0"}
assert.Equal(t, "/dev/sdb", sm.SmartDevices[1].Name)
sm.SmartDevices = append(sm.SmartDevices, &DeviceInfo{Name: "/dev/nvme1"})
assert.Len(t, snapshot, 2)
}
func TestScanDevicesWithEnvOverride(t *testing.T) {
t.Setenv("SMART_DEVICES", "/dev/sda:sat, /dev/nvme0:nvme")
sm := &SmartManager{
SmartDataMap: make(map[string]*smart.SmartData),
}
err := sm.ScanDevices(true)
require.NoError(t, err)
require.Len(t, sm.SmartDevices, 2)
assert.Equal(t, "/dev/sda", sm.SmartDevices[0].Name)
assert.Equal(t, "sat", sm.SmartDevices[0].Type)
assert.Equal(t, "/dev/nvme0", sm.SmartDevices[1].Name)
assert.Equal(t, "nvme", sm.SmartDevices[1].Type)
}
func TestScanDevicesWithEnvOverrideInvalid(t *testing.T) {
t.Setenv("SMART_DEVICES", ":sat")
sm := &SmartManager{
SmartDataMap: make(map[string]*smart.SmartData),
}
err := sm.ScanDevices(true)
require.Error(t, err)
}
func TestScanDevicesWithEnvOverrideEmpty(t *testing.T) {
t.Setenv("SMART_DEVICES", " ")
sm := &SmartManager{
SmartDataMap: make(map[string]*smart.SmartData),
}
err := sm.ScanDevices(true)
assert.ErrorIs(t, err, errNoValidSmartData)
assert.Empty(t, sm.SmartDevices)
}
func TestSmartctlArgsWithoutType(t *testing.T) {
device := &DeviceInfo{Name: "/dev/sda"}
sm := &SmartManager{}
args := sm.smartctlArgs(device, true)
assert.Equal(t, []string{"-a", "--json=c", "-n", "standby", "/dev/sda"}, args)
}
func TestSmartctlArgs(t *testing.T) {
sm := &SmartManager{}
sataDevice := &DeviceInfo{Name: "/dev/sda", Type: "sat"}
assert.Equal(t,
[]string{"-d", "sat", "-a", "--json=c", "-n", "standby", "/dev/sda"},
sm.smartctlArgs(sataDevice, true),
)
assert.Equal(t,
[]string{"-d", "sat", "-a", "--json=c", "/dev/sda"},
sm.smartctlArgs(sataDevice, false),
)
assert.Equal(t,
[]string{"-a", "--json=c", "-n", "standby"},
sm.smartctlArgs(nil, true),
)
}
func TestResolveRefreshError(t *testing.T) {
scanErr := errors.New("scan failed")
collectErr := errors.New("collect failed")
tests := []struct {
name string
devices []*DeviceInfo
data map[string]*smart.SmartData
scanErr error
collectErr error
expectedErr error
expectNoErr bool
}{
{
name: "no devices returns scan error",
devices: nil,
data: make(map[string]*smart.SmartData),
scanErr: scanErr,
expectedErr: scanErr,
},
{
name: "has data ignores errors",
devices: []*DeviceInfo{{Name: "/dev/sda"}},
data: map[string]*smart.SmartData{"serial": {}},
scanErr: scanErr,
collectErr: collectErr,
expectNoErr: true,
},
{
name: "collect error preferred",
devices: []*DeviceInfo{{Name: "/dev/sda"}},
data: make(map[string]*smart.SmartData),
collectErr: collectErr,
expectedErr: collectErr,
},
{
name: "scan error returned when no data",
devices: []*DeviceInfo{{Name: "/dev/sda"}},
data: make(map[string]*smart.SmartData),
scanErr: scanErr,
expectedErr: scanErr,
},
{
name: "no errors returns sentinel",
devices: []*DeviceInfo{{Name: "/dev/sda"}},
data: make(map[string]*smart.SmartData),
expectedErr: errNoValidSmartData,
},
{
name: "no devices collect error",
devices: nil,
data: make(map[string]*smart.SmartData),
collectErr: collectErr,
expectedErr: collectErr,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := &SmartManager{
SmartDevices: tt.devices,
SmartDataMap: tt.data,
}
err := sm.resolveRefreshError(tt.scanErr, tt.collectErr)
if tt.expectNoErr {
assert.NoError(t, err)
return
}
if tt.expectedErr == nil {
assert.NoError(t, err)
} else {
assert.Equal(t, tt.expectedErr, err)
}
})
}
}
func TestParseScan(t *testing.T) {
sm := &SmartManager{
SmartDataMap: map[string]*smart.SmartData{
"serial-active": {DiskName: "/dev/sda"},
"serial-stale": {DiskName: "/dev/sdb"},
},
}
scanJSON := []byte(`{
"devices": [
{"name": "/dev/sda", "type": "sat", "info_name": "/dev/sda [SAT]", "protocol": "ATA"},
{"name": "/dev/nvme0", "type": "nvme", "info_name": "/dev/nvme0", "protocol": "NVMe"}
]
}`)
devices, hasData := sm.parseScan(scanJSON)
assert.True(t, hasData)
sm.updateSmartDevices(devices)
require.Len(t, sm.SmartDevices, 2)
assert.Equal(t, "/dev/sda", sm.SmartDevices[0].Name)
assert.Equal(t, "sat", sm.SmartDevices[0].Type)
assert.Equal(t, "/dev/nvme0", sm.SmartDevices[1].Name)
assert.Equal(t, "nvme", sm.SmartDevices[1].Type)
_, activeExists := sm.SmartDataMap["serial-active"]
assert.True(t, activeExists, "active smart data should be preserved when device path remains")
_, staleExists := sm.SmartDataMap["serial-stale"]
assert.False(t, staleExists, "stale smart data entry should be removed when device path disappears")
}
func TestMergeDeviceListsPrefersConfigured(t *testing.T) {
scanned := []*DeviceInfo{
{Name: "/dev/sda", Type: "sat", InfoName: "scan-info", Protocol: "ATA"},
{Name: "/dev/nvme0", Type: "nvme"},
}
configured := []*DeviceInfo{
{Name: "/dev/sda", Type: "sat-override"},
{Name: "/dev/sdb", Type: "sat"},
}
merged := mergeDeviceLists(nil, scanned, configured)
require.Len(t, merged, 3)
byName := make(map[string]*DeviceInfo, len(merged))
for _, dev := range merged {
byName[dev.Name] = dev
}
require.Contains(t, byName, "/dev/sda")
assert.Equal(t, "sat-override", byName["/dev/sda"].Type, "configured type should override scanned type")
assert.Equal(t, "scan-info", byName["/dev/sda"].InfoName, "scan metadata should be preserved when config does not provide it")
require.Contains(t, byName, "/dev/nvme0")
assert.Equal(t, "nvme", byName["/dev/nvme0"].Type)
require.Contains(t, byName, "/dev/sdb")
assert.Equal(t, "sat", byName["/dev/sdb"].Type)
}
func TestMergeDeviceListsPreservesVerification(t *testing.T) {
existing := []*DeviceInfo{
{Name: "/dev/sda", Type: "sat+megaraid", parserType: "sat", typeVerified: true},
}
scanned := []*DeviceInfo{
{Name: "/dev/sda", Type: "nvme"},
}
merged := mergeDeviceLists(existing, scanned, nil)
require.Len(t, merged, 1)
device := merged[0]
assert.True(t, device.typeVerified)
assert.Equal(t, "sat", device.parserType)
assert.Equal(t, "sat+megaraid", device.Type)
}
func TestMergeDeviceListsUpdatesTypeWhenUnverified(t *testing.T) {
existing := []*DeviceInfo{
{Name: "/dev/sda", Type: "sat", parserType: "sat", typeVerified: false},
}
scanned := []*DeviceInfo{
{Name: "/dev/sda", Type: "nvme"},
}
merged := mergeDeviceLists(existing, scanned, nil)
require.Len(t, merged, 1)
device := merged[0]
assert.False(t, device.typeVerified)
assert.Equal(t, "nvme", device.Type)
assert.Equal(t, "", device.parserType)
}
func TestParseSmartOutputMarksVerified(t *testing.T) {
fixturePath := filepath.Join("test-data", "smart", "nvme0.json")
data, err := os.ReadFile(fixturePath)
require.NoError(t, err)
sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
device := &DeviceInfo{Name: "/dev/nvme0"}
require.True(t, sm.parseSmartOutput(device, data))
assert.Equal(t, "nvme", device.Type)
assert.Equal(t, "nvme", device.parserType)
assert.True(t, device.typeVerified)
}
func TestParseSmartOutputKeepsCustomType(t *testing.T) {
fixturePath := filepath.Join("test-data", "smart", "sda.json")
data, err := os.ReadFile(fixturePath)
require.NoError(t, err)
sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
device := &DeviceInfo{Name: "/dev/sda", Type: "sat+megaraid"}
require.True(t, sm.parseSmartOutput(device, data))
assert.Equal(t, "sat+megaraid", device.Type)
assert.Equal(t, "sat", device.parserType)
assert.True(t, device.typeVerified)
}
func TestParseSmartOutputResetsVerificationOnFailure(t *testing.T) {
sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
device := &DeviceInfo{Name: "/dev/sda", Type: "sat", parserType: "sat", typeVerified: true}
assert.False(t, sm.parseSmartOutput(device, []byte("not json")))
assert.False(t, device.typeVerified)
assert.Equal(t, "sat", device.parserType)
}
func assertAttrValue(t *testing.T, attributes []*smart.SmartAttribute, name string, expected uint64) {
t.Helper()
attr := findAttr(attributes, name)
if attr == nil {
t.Fatalf("expected attribute %s to be present", name)
}
if attr.RawValue != expected {
t.Fatalf("unexpected attribute %s value: got %d, want %d", name, attr.RawValue, expected)
}
}
func findAttr(attributes []*smart.SmartAttribute, name string) *smart.SmartAttribute {
for _, attr := range attributes {
if attr != nil && attr.Name == name {
return attr
}
}
return nil
}
func TestIsVirtualDevice(t *testing.T) {
sm := &SmartManager{}
tests := []struct {
name string
vendor string
product string
model string
expected bool
}{
{"regular drive", "SEAGATE", "ST1000DM003", "ST1000DM003-1CH162", false},
{"qemu virtual", "QEMU", "QEMU HARDDISK", "QEMU HARDDISK", true},
{"virtualbox virtual", "VBOX", "HARDDISK", "VBOX HARDDISK", true},
{"vmware virtual", "VMWARE", "Virtual disk", "VMWARE Virtual disk", true},
{"virtual in model", "ATA", "VIRTUAL", "VIRTUAL DISK", true},
{"iet virtual", "IET", "VIRTUAL-DISK", "VIRTUAL-DISK", true},
{"hyper-v virtual", "MSFT", "VIRTUAL HD", "VIRTUAL HD", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data := &smart.SmartInfoForSata{
ScsiVendor: tt.vendor,
ScsiProduct: tt.product,
ModelName: tt.model,
}
result := sm.isVirtualDevice(data)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsVirtualDeviceNvme(t *testing.T) {
sm := &SmartManager{}
tests := []struct {
name string
model string
expected bool
}{
{"regular nvme", "Samsung SSD 970 EVO Plus 1TB", false},
{"qemu virtual", "QEMU NVMe Ctrl", true},
{"virtualbox virtual", "VBOX NVMe", true},
{"vmware virtual", "VMWARE NVMe", true},
{"virtual in model", "Virtual NVMe Device", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data := &smart.SmartInfoForNvme{
ModelName: tt.model,
}
result := sm.isVirtualDeviceNvme(data)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsVirtualDeviceScsi(t *testing.T) {
sm := &SmartManager{}
tests := []struct {
name string
vendor string
product string
model string
expected bool
}{
{"regular scsi", "SEAGATE", "ST1000DM003", "ST1000DM003-1CH162", false},
{"qemu virtual", "QEMU", "QEMU HARDDISK", "QEMU HARDDISK", true},
{"virtualbox virtual", "VBOX", "HARDDISK", "VBOX HARDDISK", true},
{"vmware virtual", "VMWARE", "Virtual disk", "VMWARE Virtual disk", true},
{"virtual in model", "ATA", "VIRTUAL", "VIRTUAL DISK", true},
{"iet virtual", "IET", "VIRTUAL-DISK", "VIRTUAL-DISK", true},
{"hyper-v virtual", "MSFT", "VIRTUAL HD", "VIRTUAL HD", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data := &smart.SmartInfoForScsi{
ScsiVendor: tt.vendor,
ScsiProduct: tt.product,
ScsiModelName: tt.model,
}
result := sm.isVirtualDeviceScsi(data)
assert.Equal(t, tt.expected, result)
})
}
}
func TestRefreshExcludedDevices(t *testing.T) {
tests := []struct {
name string
envValue string
expectedDevs map[string]struct{}
}{
{
name: "empty env",
envValue: "",
expectedDevs: map[string]struct{}{},
},
{
name: "single device",
envValue: "/dev/sda",
expectedDevs: map[string]struct{}{
"/dev/sda": {},
},
},
{
name: "multiple devices",
envValue: "/dev/sda,/dev/sdb,/dev/nvme0",
expectedDevs: map[string]struct{}{
"/dev/sda": {},
"/dev/sdb": {},
"/dev/nvme0": {},
},
},
{
name: "devices with whitespace",
envValue: " /dev/sda , /dev/sdb , /dev/nvme0 ",
expectedDevs: map[string]struct{}{
"/dev/sda": {},
"/dev/sdb": {},
"/dev/nvme0": {},
},
},
{
name: "duplicate devices",
envValue: "/dev/sda,/dev/sdb,/dev/sda",
expectedDevs: map[string]struct{}{
"/dev/sda": {},
"/dev/sdb": {},
},
},
{
name: "empty entries and whitespace",
envValue: "/dev/sda,, /dev/sdb , , ",
expectedDevs: map[string]struct{}{
"/dev/sda": {},
"/dev/sdb": {},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.envValue != "" {
t.Setenv("EXCLUDE_SMART", tt.envValue)
} else {
// Ensure env var is not set for empty test
os.Unsetenv("EXCLUDE_SMART")
}
sm := &SmartManager{}
sm.refreshExcludedDevices()
assert.Equal(t, tt.expectedDevs, sm.excludedDevices)
})
}
}
func TestIsExcludedDevice(t *testing.T) {
sm := &SmartManager{
excludedDevices: map[string]struct{}{
"/dev/sda": {},
"/dev/nvme0": {},
},
}
tests := []struct {
name string
deviceName string
expectedBool bool
}{
{"excluded device sda", "/dev/sda", true},
{"excluded device nvme0", "/dev/nvme0", true},
{"non-excluded device sdb", "/dev/sdb", false},
{"non-excluded device nvme1", "/dev/nvme1", false},
{"empty device name", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := sm.isExcludedDevice(tt.deviceName)
assert.Equal(t, tt.expectedBool, result)
})
}
}
func TestFilterExcludedDevices(t *testing.T) {
tests := []struct {
name string
excludedDevs map[string]struct{}
inputDevices []*DeviceInfo
expectedDevs []*DeviceInfo
expectedLength int
}{
{
name: "no exclusions",
excludedDevs: map[string]struct{}{},
inputDevices: []*DeviceInfo{
{Name: "/dev/sda"},
{Name: "/dev/sdb"},
{Name: "/dev/nvme0"},
},
expectedDevs: []*DeviceInfo{
{Name: "/dev/sda"},
{Name: "/dev/sdb"},
{Name: "/dev/nvme0"},
},
expectedLength: 3,
},
{
name: "some devices excluded",
excludedDevs: map[string]struct{}{
"/dev/sda": {},
"/dev/nvme0": {},
},
inputDevices: []*DeviceInfo{
{Name: "/dev/sda"},
{Name: "/dev/sdb"},
{Name: "/dev/nvme0"},
{Name: "/dev/nvme1"},
},
expectedDevs: []*DeviceInfo{
{Name: "/dev/sdb"},
{Name: "/dev/nvme1"},
},
expectedLength: 2,
},
{
name: "all devices excluded",
excludedDevs: map[string]struct{}{
"/dev/sda": {},
"/dev/sdb": {},
},
inputDevices: []*DeviceInfo{
{Name: "/dev/sda"},
{Name: "/dev/sdb"},
},
expectedDevs: []*DeviceInfo{},
expectedLength: 0,
},
{
name: "nil devices",
excludedDevs: map[string]struct{}{},
inputDevices: nil,
expectedDevs: []*DeviceInfo{},
expectedLength: 0,
},
{
name: "filter nil and empty name devices",
excludedDevs: map[string]struct{}{
"/dev/sda": {},
},
inputDevices: []*DeviceInfo{
{Name: "/dev/sda"},
nil,
{Name: ""},
{Name: "/dev/sdb"},
},
expectedDevs: []*DeviceInfo{
{Name: "/dev/sdb"},
},
expectedLength: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sm := &SmartManager{
excludedDevices: tt.excludedDevs,
}
result := sm.filterExcludedDevices(tt.inputDevices)
assert.Len(t, result, tt.expectedLength)
assert.Equal(t, tt.expectedDevs, result)
})
}
}

40
agent/smart_windows.go Normal file
View File

@@ -0,0 +1,40 @@
//go:build windows
package agent
import (
_ "embed"
"fmt"
"os"
"path/filepath"
"sync"
)
//go:embed smartmontools/smartctl.exe
var embeddedSmartctl []byte
var (
smartctlOnce sync.Once
smartctlPath string
smartctlErr error
)
func ensureEmbeddedSmartctl() (string, error) {
smartctlOnce.Do(func() {
destDir := filepath.Join(os.TempDir(), "beszel", "smartmontools")
if err := os.MkdirAll(destDir, 0o755); err != nil {
smartctlErr = fmt.Errorf("failed to create smartctl directory: %w", err)
return
}
destPath := filepath.Join(destDir, "smartctl.exe")
if err := os.WriteFile(destPath, embeddedSmartctl, 0o755); err != nil {
smartctlErr = fmt.Errorf("failed to write embedded smartctl: %w", err)
return
}
smartctlPath = destPath
})
return smartctlPath, smartctlErr
}

View File

@@ -83,12 +83,24 @@ func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats {
systemStats.Battery[1] = batteryState systemStats.Battery[1] = batteryState
} }
// cpu percent // cpu metrics
cpuPercent, err := getCpuPercent(cacheTimeMs) cpuMetrics, err := getCpuMetrics(cacheTimeMs)
if err == nil { if err == nil {
systemStats.Cpu = twoDecimals(cpuPercent) systemStats.Cpu = twoDecimals(cpuMetrics.Total)
systemStats.CpuBreakdown = []float64{
twoDecimals(cpuMetrics.User),
twoDecimals(cpuMetrics.System),
twoDecimals(cpuMetrics.Iowait),
twoDecimals(cpuMetrics.Steal),
twoDecimals(cpuMetrics.Idle),
}
} else { } else {
slog.Error("Error getting cpu percent", "err", err) slog.Error("Error getting cpu metrics", "err", err)
}
// per-core cpu usage
if perCoreUsage, err := getPerCoreCpuUsage(cacheTimeMs); err == nil {
systemStats.CpuCoresUsage = perCoreUsage
} }
// load average // load average

229
agent/systemd.go Normal file
View File

@@ -0,0 +1,229 @@
//go:build linux
package agent
import (
"context"
"errors"
"log/slog"
"maps"
"math"
"strconv"
"strings"
"sync"
"time"
"github.com/coreos/go-systemd/v22/dbus"
"github.com/henrygd/beszel/internal/entities/systemd"
)
var (
errNoActiveTime = errors.New("no active time")
)
// systemdManager manages the collection of systemd service statistics.
type systemdManager struct {
sync.Mutex
serviceStatsMap map[string]*systemd.Service
isRunning bool
hasFreshStats bool
}
// newSystemdManager creates a new systemdManager.
func newSystemdManager() (*systemdManager, error) {
conn, err := dbus.NewSystemConnectionContext(context.Background())
if err != nil {
slog.Warn("Error connecting to systemd", "err", err, "ref", "https://beszel.dev/guide/systemd")
return nil, err
}
manager := &systemdManager{
serviceStatsMap: make(map[string]*systemd.Service),
}
manager.startWorker(conn)
return manager, nil
}
func (sm *systemdManager) startWorker(conn *dbus.Conn) {
if sm.isRunning {
return
}
sm.isRunning = true
// prime the service stats map with the current services
_ = sm.getServiceStats(conn, true)
// update the services every 10 minutes
go func() {
for {
time.Sleep(time.Minute * 10)
_ = sm.getServiceStats(nil, true)
}
}()
}
// getServiceStats collects statistics for all running systemd services.
func (sm *systemdManager) getServiceStats(conn *dbus.Conn, refresh bool) []*systemd.Service {
// start := time.Now()
// defer func() {
// slog.Info("systemdManager.getServiceStats", "duration", time.Since(start))
// }()
var services []*systemd.Service
var err error
if !refresh {
// return nil
sm.Lock()
defer sm.Unlock()
for _, service := range sm.serviceStatsMap {
services = append(services, service)
}
sm.hasFreshStats = false
return services
}
if conn == nil || !conn.Connected() {
conn, err = dbus.NewSystemConnectionContext(context.Background())
if err != nil {
return nil
}
defer conn.Close()
}
units, err := conn.ListUnitsByPatternsContext(context.Background(), []string{"loaded"}, []string{"*.service"})
if err != nil {
slog.Error("Error listing systemd service units", "err", err)
return nil
}
for _, unit := range units {
service, err := sm.updateServiceStats(conn, unit)
if err != nil {
continue
}
services = append(services, service)
}
sm.hasFreshStats = true
return services
}
// updateServiceStats updates the statistics for a single systemd service.
func (sm *systemdManager) updateServiceStats(conn *dbus.Conn, unit dbus.UnitStatus) (*systemd.Service, error) {
sm.Lock()
defer sm.Unlock()
ctx := context.Background()
// if service has never been active (no active since time), skip it
if activeEnterTsProp, err := conn.GetUnitTypePropertyContext(ctx, unit.Name, "Unit", "ActiveEnterTimestamp"); err == nil {
if ts, ok := activeEnterTsProp.Value.Value().(uint64); !ok || ts == 0 || ts == math.MaxUint64 {
return nil, errNoActiveTime
}
} else {
return nil, err
}
service, serviceExists := sm.serviceStatsMap[unit.Name]
if !serviceExists {
service = &systemd.Service{Name: unescapeServiceName(strings.TrimSuffix(unit.Name, ".service"))}
sm.serviceStatsMap[unit.Name] = service
}
memPeak := service.MemPeak
if memPeakProp, err := conn.GetUnitTypePropertyContext(ctx, unit.Name, "Service", "MemoryPeak"); err == nil {
// If memPeak is MaxUint64 the api is saying it's not available
if v, ok := memPeakProp.Value.Value().(uint64); ok && v != math.MaxUint64 {
memPeak = v
}
}
var memUsage uint64
if memProp, err := conn.GetUnitTypePropertyContext(ctx, unit.Name, "Service", "MemoryCurrent"); err == nil {
// If memUsage is MaxUint64 the api is saying it's not available
if v, ok := memProp.Value.Value().(uint64); ok && v != math.MaxUint64 {
memUsage = v
}
}
service.State = systemd.ParseServiceStatus(unit.ActiveState)
service.Sub = systemd.ParseServiceSubState(unit.SubState)
// some systems always return 0 for mem peak, so we should update the peak if the current usage is greater
if memUsage > memPeak {
memPeak = memUsage
}
var cpuUsage uint64
if cpuProp, err := conn.GetUnitTypePropertyContext(ctx, unit.Name, "Service", "CPUUsageNSec"); err == nil {
if v, ok := cpuProp.Value.Value().(uint64); ok {
cpuUsage = v
}
}
service.Mem = memUsage
if memPeak > service.MemPeak {
service.MemPeak = memPeak
}
service.UpdateCPUPercent(cpuUsage)
return service, nil
}
// getServiceDetails collects extended information for a specific systemd service.
func (sm *systemdManager) getServiceDetails(serviceName string) (systemd.ServiceDetails, error) {
conn, err := dbus.NewSystemConnectionContext(context.Background())
if err != nil {
return nil, err
}
defer conn.Close()
unitName := serviceName
if !strings.HasSuffix(unitName, ".service") {
unitName += ".service"
}
ctx := context.Background()
props, err := conn.GetUnitPropertiesContext(ctx, unitName)
if err != nil {
return nil, err
}
// Start with all unit properties
details := make(systemd.ServiceDetails)
maps.Copy(details, props)
// // Add service-specific properties
servicePropNames := []string{
"MainPID", "ExecMainPID", "TasksCurrent", "TasksMax",
"MemoryCurrent", "MemoryPeak", "MemoryLimit", "CPUUsageNSec",
"NRestarts", "ExecMainStartTimestampRealtime", "Result",
}
for _, propName := range servicePropNames {
if variant, err := conn.GetUnitTypePropertyContext(ctx, unitName, "Service", propName); err == nil {
value := variant.Value.Value()
// Check if the value is MaxUint64, which indicates unlimited/infinite
if uint64Value, ok := value.(uint64); ok && uint64Value == math.MaxUint64 {
// Set to nil to indicate unlimited - frontend will handle this appropriately
details[propName] = nil
} else {
details[propName] = value
}
}
}
return details, nil
}
// unescapeServiceName unescapes systemd service names that contain C-style escape sequences like \x2d
func unescapeServiceName(name string) string {
if !strings.Contains(name, "\\x") {
return name
}
unescaped, err := strconv.Unquote("\"" + name + "\"")
if err != nil {
return name
}
return unescaped
}

28
agent/systemd_nonlinux.go Normal file
View File

@@ -0,0 +1,28 @@
//go:build !linux
package agent
import (
"errors"
"github.com/henrygd/beszel/internal/entities/systemd"
)
// systemdManager manages the collection of systemd service statistics.
type systemdManager struct {
hasFreshStats bool
}
// newSystemdManager creates a new systemdManager.
func newSystemdManager() (*systemdManager, error) {
return &systemdManager{}, nil
}
// getServiceStats returns nil for non-linux systems.
func (sm *systemdManager) getServiceStats(conn any, refresh bool) []*systemd.Service {
return nil
}
func (sm *systemdManager) getServiceDetails(string) (systemd.ServiceDetails, error) {
return nil, errors.New("systemd manager unavailable")
}

View File

@@ -0,0 +1,53 @@
//go:build !linux && testing
package agent
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNewSystemdManager(t *testing.T) {
manager, err := newSystemdManager()
assert.NoError(t, err)
assert.NotNil(t, manager)
}
func TestSystemdManagerGetServiceStats(t *testing.T) {
manager, err := newSystemdManager()
assert.NoError(t, err)
// Test with refresh = true
result := manager.getServiceStats(true)
assert.Nil(t, result)
// Test with refresh = false
result = manager.getServiceStats(false)
assert.Nil(t, result)
}
func TestSystemdManagerGetServiceDetails(t *testing.T) {
manager, err := newSystemdManager()
assert.NoError(t, err)
result, err := manager.getServiceDetails("any-service")
assert.Error(t, err)
assert.Equal(t, "systemd manager unavailable", err.Error())
assert.Nil(t, result)
// Test with empty service name
result, err = manager.getServiceDetails("")
assert.Error(t, err)
assert.Equal(t, "systemd manager unavailable", err.Error())
assert.Nil(t, result)
}
func TestSystemdManagerFields(t *testing.T) {
manager, err := newSystemdManager()
assert.NoError(t, err)
// The non-linux manager should be a simple struct with no special fields
// We can't test private fields directly, but we can test the methods work
assert.NotNil(t, manager)
}

48
agent/systemd_test.go Normal file
View File

@@ -0,0 +1,48 @@
//go:build linux && testing
package agent
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnescapeServiceName(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"nginx.service", "nginx.service"}, // No escaping needed
{"test\\x2dwith\\x2ddashes.service", "test-with-dashes.service"}, // \x2d is dash
{"service\\x20with\\x20spaces.service", "service with spaces.service"}, // \x20 is space
{"mixed\\x2dand\\x2dnormal", "mixed-and-normal"}, // Mixed escaped and normal
{"no-escape-here", "no-escape-here"}, // No escape sequences
{"", ""}, // Empty string
{"\\x2d\\x2d", "--"}, // Multiple escapes
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
result := unescapeServiceName(test.input)
assert.Equal(t, test.expected, result)
})
}
}
func TestUnescapeServiceNameInvalid(t *testing.T) {
// Test invalid escape sequences - should return original string
invalidInputs := []string{
"invalid\\x", // Incomplete escape
"invalid\\xZZ", // Invalid hex
"invalid\\x2", // Incomplete hex
"invalid\\xyz", // Not a valid escape
}
for _, input := range invalidInputs {
t.Run(input, func(t *testing.T) {
result := unescapeServiceName(input)
assert.Equal(t, input, result, "Invalid escape sequences should return original string")
})
}
}

View File

@@ -0,0 +1,272 @@
{
"json_format_version": [
1,
0
],
"smartctl": {
"version": [
7,
5
],
"pre_release": false,
"svn_revision": "5714",
"platform_info": "x86_64-linux-6.17.1-2-cachyos",
"build_info": "(local build)",
"argv": [
"smartctl",
"-aj",
"/dev/nvme0"
],
"exit_status": 0
},
"local_time": {
"time_t": 1761507494,
"asctime": "Sun Oct 26 15:38:14 2025 EDT"
},
"device": {
"name": "/dev/nvme0",
"info_name": "/dev/nvme0",
"type": "nvme",
"protocol": "NVMe"
},
"model_name": "PELADN 512GB",
"serial_number": "2024031600129",
"firmware_version": "VC2S038E",
"nvme_pci_vendor": {
"id": 4332,
"subsystem_id": 4332
},
"nvme_ieee_oui_identifier": 57420,
"nvme_controller_id": 1,
"nvme_version": {
"string": "1.4",
"value": 66560
},
"nvme_number_of_namespaces": 1,
"nvme_namespaces": [
{
"id": 1,
"size": {
"blocks": 1000215216,
"bytes": 512110190592
},
"capacity": {
"blocks": 1000215216,
"bytes": 512110190592
},
"utilization": {
"blocks": 1000215216,
"bytes": 512110190592
},
"formatted_lba_size": 512,
"eui64": {
"oui": 57420,
"ext_id": 112094110470
},
"features": {
"value": 0,
"thin_provisioning": false,
"na_fields": false,
"dealloc_or_unwritten_block_error": false,
"uid_reuse": false,
"np_fields": false,
"other": 0
},
"lba_formats": [
{
"formatted": true,
"data_bytes": 512,
"metadata_bytes": 0,
"relative_performance": 0
}
]
}
],
"user_capacity": {
"blocks": 1000215216,
"bytes": 512110190592
},
"logical_block_size": 512,
"smart_support": {
"available": true,
"enabled": true
},
"nvme_firmware_update_capabilities": {
"value": 2,
"slots": 1,
"first_slot_is_read_only": false,
"activiation_without_reset": false,
"multiple_update_detection": false,
"other": 0
},
"nvme_optional_admin_commands": {
"value": 23,
"security_send_receive": true,
"format_nvm": true,
"firmware_download": true,
"namespace_management": false,
"self_test": true,
"directives": false,
"mi_send_receive": false,
"virtualization_management": false,
"doorbell_buffer_config": false,
"get_lba_status": false,
"command_and_feature_lockdown": false,
"other": 0
},
"nvme_optional_nvm_commands": {
"value": 94,
"compare": false,
"write_uncorrectable": true,
"dataset_management": true,
"write_zeroes": true,
"save_select_feature_nonzero": true,
"reservations": false,
"timestamp": true,
"verify": false,
"copy": false,
"other": 0
},
"nvme_log_page_attributes": {
"value": 2,
"smart_health_per_namespace": false,
"commands_effects_log": true,
"extended_get_log_page_cmd": false,
"telemetry_log": false,
"persistent_event_log": false,
"supported_log_pages_log": false,
"telemetry_data_area_4": false,
"other": 0
},
"nvme_maximum_data_transfer_pages": 32,
"nvme_composite_temperature_threshold": {
"warning": 100,
"critical": 110
},
"temperature": {
"op_limit_max": 100,
"critical_limit_max": 110,
"current": 61
},
"nvme_power_states": [
{
"non_operational_state": false,
"relative_read_latency": 0,
"relative_read_throughput": 0,
"relative_write_latency": 0,
"relative_write_throughput": 0,
"entry_latency_us": 230000,
"exit_latency_us": 50000,
"max_power": {
"value": 800,
"scale": 2,
"units_per_watt": 100
}
},
{
"non_operational_state": false,
"relative_read_latency": 1,
"relative_read_throughput": 1,
"relative_write_latency": 1,
"relative_write_throughput": 1,
"entry_latency_us": 4000,
"exit_latency_us": 50000,
"max_power": {
"value": 400,
"scale": 2,
"units_per_watt": 100
}
},
{
"non_operational_state": false,
"relative_read_latency": 2,
"relative_read_throughput": 2,
"relative_write_latency": 2,
"relative_write_throughput": 2,
"entry_latency_us": 4000,
"exit_latency_us": 250000,
"max_power": {
"value": 300,
"scale": 2,
"units_per_watt": 100
}
},
{
"non_operational_state": true,
"relative_read_latency": 3,
"relative_read_throughput": 3,
"relative_write_latency": 3,
"relative_write_throughput": 3,
"entry_latency_us": 5000,
"exit_latency_us": 10000,
"max_power": {
"value": 300,
"scale": 1,
"units_per_watt": 10000
}
},
{
"non_operational_state": true,
"relative_read_latency": 4,
"relative_read_throughput": 4,
"relative_write_latency": 4,
"relative_write_throughput": 4,
"entry_latency_us": 54000,
"exit_latency_us": 45000,
"max_power": {
"value": 50,
"scale": 1,
"units_per_watt": 10000
}
}
],
"smart_status": {
"passed": true,
"nvme": {
"value": 0
}
},
"nvme_smart_health_information_log": {
"nsid": -1,
"critical_warning": 0,
"temperature": 61,
"available_spare": 100,
"available_spare_threshold": 32,
"percentage_used": 0,
"data_units_read": 6573104,
"data_units_written": 16040567,
"host_reads": 63241130,
"host_writes": 253050006,
"controller_busy_time": 0,
"power_cycles": 430,
"power_on_hours": 4399,
"unsafe_shutdowns": 44,
"media_errors": 0,
"num_err_log_entries": 0,
"warning_temp_time": 0,
"critical_comp_time": 0
},
"spare_available": {
"current_percent": 100,
"threshold_percent": 32
},
"endurance_used": {
"current_percent": 0
},
"power_cycle_count": 430,
"power_on_time": {
"hours": 4399
},
"nvme_error_information_log": {
"size": 8,
"read": 8,
"unread": 0
},
"nvme_self_test_log": {
"nsid": -1,
"current_self_test_operation": {
"value": 0,
"string": "No self-test in progress"
}
}
}

View File

@@ -0,0 +1,36 @@
{
"json_format_version": [
1,
0
],
"smartctl": {
"version": [
7,
5
],
"pre_release": false,
"svn_revision": "5714",
"platform_info": "x86_64-linux-6.17.1-2-cachyos",
"build_info": "(local build)",
"argv": [
"smartctl",
"--scan",
"-j"
],
"exit_status": 0
},
"devices": [
{
"name": "/dev/sda",
"info_name": "/dev/sda [SAT]",
"type": "sat",
"protocol": "ATA"
},
{
"name": "/dev/nvme0",
"info_name": "/dev/nvme0",
"type": "nvme",
"protocol": "NVMe"
}
]
}

View File

@@ -0,0 +1,125 @@
{
"json_format_version": [
1,
0
],
"smartctl": {
"version": [
7,
3
],
"svn_revision": "5338",
"platform_info": "x86_64-linux-6.12.43+deb12-amd64",
"build_info": "(local build)",
"argv": [
"smartctl",
"-aj",
"/dev/sde"
],
"exit_status": 0
},
"local_time": {
"time_t": 1761502142,
"asctime": "Sun Oct 21 21:09:02 2025 MSK"
},
"device": {
"name": "/dev/sde",
"info_name": "/dev/sde",
"type": "scsi",
"protocol": "SCSI"
},
"scsi_vendor": "YADRO",
"scsi_product": "WUH721414AL4204",
"scsi_model_name": "YADRO WUH721414AL4204",
"scsi_revision": "C240",
"scsi_version": "SPC-4",
"user_capacity": {
"blocks": 3418095616,
"bytes": 14000519643136
},
"logical_block_size": 4096,
"scsi_lb_provisioning": {
"name": "fully provisioned",
"value": 0,
"management_enabled": {
"name": "LBPME",
"value": 0
},
"read_zeros": {
"name": "LBPRZ",
"value": 0
}
},
"rotation_rate": 7200,
"form_factor": {
"scsi_value": 2,
"name": "3.5 inches"
},
"logical_unit_id": "0x5000cca29063dc00",
"serial_number": "9YHSDH9B",
"device_type": {
"scsi_terminology": "Peripheral Device Type [PDT]",
"scsi_value": 0,
"name": "disk"
},
"scsi_transport_protocol": {
"name": "SAS (SPL-4)",
"value": 6
},
"smart_support": {
"available": true,
"enabled": true
},
"temperature_warning": {
"enabled": true
},
"smart_status": {
"passed": true
},
"temperature": {
"current": 34,
"drive_trip": 85
},
"power_on_time": {
"hours": 458,
"minutes": 25
},
"scsi_start_stop_cycle_counter": {
"year_of_manufacture": "2022",
"week_of_manufacture": "41",
"specified_cycle_count_over_device_lifetime": 50000,
"accumulated_start_stop_cycles": 2,
"specified_load_unload_count_over_device_lifetime": 600000,
"accumulated_load_unload_cycles": 418
},
"scsi_grown_defect_list": 0,
"scsi_error_counter_log": {
"read": {
"errors_corrected_by_eccfast": 0,
"errors_corrected_by_eccdelayed": 0,
"errors_corrected_by_rereads_rewrites": 0,
"total_errors_corrected": 0,
"correction_algorithm_invocations": 346,
"gigabytes_processed": "3,641",
"total_uncorrected_errors": 0
},
"write": {
"errors_corrected_by_eccfast": 0,
"errors_corrected_by_eccdelayed": 0,
"errors_corrected_by_rereads_rewrites": 0,
"total_errors_corrected": 0,
"correction_algorithm_invocations": 4052,
"gigabytes_processed": "2124,590",
"total_uncorrected_errors": 0
},
"verify": {
"errors_corrected_by_eccfast": 0,
"errors_corrected_by_eccdelayed": 0,
"errors_corrected_by_rereads_rewrites": 0,
"total_errors_corrected": 0,
"correction_algorithm_invocations": 223,
"gigabytes_processed": "0,000",
"total_uncorrected_errors": 0
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
package main
import (
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"flag"
"fmt"
"hash"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
// Download smartctl.exe from the given URL and save it to the given destination.
// This is used to embed smartctl.exe in the Windows build.
func main() {
url := flag.String("url", "", "URL to download smartctl.exe from (required)")
out := flag.String("out", "", "Destination path for smartctl.exe (required)")
sha := flag.String("sha", "", "Optional SHA1/SHA256 checksum for integrity validation")
force := flag.Bool("force", false, "Force re-download even if destination exists")
flag.Parse()
if *url == "" || *out == "" {
fatalf("-url and -out are required")
}
if !*force {
if info, err := os.Stat(*out); err == nil && info.Size() > 0 {
fmt.Println("smartctl.exe already present, skipping download")
return
}
}
if err := downloadFile(*url, *out, *sha); err != nil {
fatalf("download failed: %v", err)
}
}
func downloadFile(url, dest, shaHex string) error {
// Prepare destination
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
return fmt.Errorf("create dir: %w", err)
}
// HTTP client
client := &http.Client{Timeout: 60 * time.Second}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return fmt.Errorf("new request: %w", err)
}
req.Header.Set("User-Agent", "beszel-fetchsmartctl/1.0")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("http get: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("unexpected HTTP status: %s", resp.Status)
}
tmp := dest + ".tmp"
f, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
if err != nil {
return fmt.Errorf("open tmp: %w", err)
}
// Determine hash algorithm based on length (SHA1=40, SHA256=64)
var hasher hash.Hash
if shaHex := strings.TrimSpace(shaHex); shaHex != "" {
cleanSha := strings.ToLower(strings.ReplaceAll(shaHex, " ", ""))
switch len(cleanSha) {
case 40:
hasher = sha1.New()
case 64:
hasher = sha256.New()
default:
f.Close()
os.Remove(tmp)
return fmt.Errorf("unsupported hash length: %d (expected 40 for SHA1 or 64 for SHA256)", len(cleanSha))
}
}
var mw io.Writer = f
if hasher != nil {
mw = io.MultiWriter(f, hasher)
}
if _, err := io.Copy(mw, resp.Body); err != nil {
f.Close()
os.Remove(tmp)
return fmt.Errorf("write tmp: %w", err)
}
if err := f.Close(); err != nil {
os.Remove(tmp)
return fmt.Errorf("close tmp: %w", err)
}
if hasher != nil && shaHex != "" {
cleanSha := strings.ToLower(strings.ReplaceAll(strings.TrimSpace(shaHex), " ", ""))
got := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
if got != cleanSha {
os.Remove(tmp)
return fmt.Errorf("hash mismatch: got %s want %s", got, cleanSha)
}
}
// Make executable and move into place
if err := os.Chmod(tmp, 0o755); err != nil {
os.Remove(tmp)
return fmt.Errorf("chmod: %w", err)
}
if err := os.Rename(tmp, dest); err != nil {
os.Remove(tmp)
return fmt.Errorf("rename: %w", err)
}
fmt.Println("smartctl.exe downloaded to", dest)
return nil
}
func fatalf(format string, a ...any) {
fmt.Fprintf(os.Stderr, format+"\n", a...)
os.Exit(1)
}

View File

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

41
go.mod
View File

@@ -1,27 +1,25 @@
module github.com/henrygd/beszel module github.com/henrygd/beszel
go 1.25.1 go 1.25.3
// lock shoutrrr to specific version to allow review before updating
replace github.com/nicholas-fedor/shoutrrr => github.com/nicholas-fedor/shoutrrr v0.9.1
require ( require (
github.com/blang/semver v3.5.1+incompatible github.com/blang/semver v3.5.1+incompatible
github.com/coreos/go-systemd/v22 v22.6.0
github.com/distatus/battery v0.11.0 github.com/distatus/battery v0.11.0
github.com/fxamacker/cbor/v2 v2.9.0 github.com/fxamacker/cbor/v2 v2.9.0
github.com/gliderlabs/ssh v0.3.8 github.com/gliderlabs/ssh v0.3.8
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lxzan/gws v1.8.9 github.com/lxzan/gws v1.8.9
github.com/nicholas-fedor/shoutrrr v0.10.0 github.com/nicholas-fedor/shoutrrr v0.11.1
github.com/pocketbase/dbx v1.11.0 github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/pocketbase v0.30.1 github.com/pocketbase/pocketbase v0.31.0
github.com/shirou/gopsutil/v4 v4.25.9 github.com/shirou/gopsutil/v4 v4.25.10
github.com/spf13/cast v1.10.0 github.com/spf13/cast v1.10.0
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.42.0 golang.org/x/crypto v0.43.0
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -35,18 +33,19 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.9.0 // indirect github.com/ebitengine/purego v0.9.0 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gabriel-vasile/mimetype v1.4.11 // indirect
github.com/ganigeorgiev/fexpr v0.5.0 // indirect github.com/ganigeorgiev/fexpr v0.5.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/go-sql-driver/mysql v1.9.1 // indirect github.com/go-sql-driver/mysql v1.9.1 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.1 // indirect
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
@@ -54,16 +53,16 @@ require (
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/image v0.31.0 // indirect golang.org/x/image v0.32.0 // indirect
golang.org/x/net v0.44.0 // indirect golang.org/x/net v0.46.0 // indirect
golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/term v0.36.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect golang.org/x/text v0.30.0 // indirect
howett.net/plist v1.0.1 // indirect howett.net/plist v1.0.1 // indirect
modernc.org/libc v1.66.3 // indirect modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.39.0 // indirect modernc.org/sqlite v1.39.1 // indirect
) )

110
go.sum
View File

@@ -9,6 +9,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -31,8 +33,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk= github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@@ -49,13 +51,15 @@ github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtS
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d h1:KJIErDwbSHjnp/SGzE5ed8Aol7JsKiI5X7yWKAtzhM0=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -63,26 +67,26 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM= github.com/lxzan/gws v1.8.9 h1:VU3SGUeWlQrEwfUSfokcZep8mdg/BrUF+y73YYshdBM=
github.com/lxzan/gws v1.8.9/go.mod h1:d9yHaR1eDTBHagQC6KY7ycUOaz5KWeqQtP3xu7aMK8Y= github.com/lxzan/gws v1.8.9/go.mod h1:d9yHaR1eDTBHagQC6KY7ycUOaz5KWeqQtP3xu7aMK8Y=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nicholas-fedor/shoutrrr v0.9.1 h1:SEBhM6P1favzILO0f55CY3P9JwvM9RZ7B1ZMCl+Injs= github.com/nicholas-fedor/shoutrrr v0.11.1 h1:DND1gW8UM8GYG8c0bUZ5fPFAnm3id8noPdfaFBUmezk=
github.com/nicholas-fedor/shoutrrr v0.9.1/go.mod h1:khue5m8LYyMzdPWuJxDTJeT89l9gjwjA+a+r0e8qxxk= github.com/nicholas-fedor/shoutrrr v0.11.1/go.mod h1:RZuSZSEaSimS47zTOLXb6HJDwLjDHiuJ9SrzxsDcWaQ=
github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -90,8 +94,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU= github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.30.1 h1:8lgfhH+HiSw1PyKVMq2sjtC4ZNvda2f/envTAzWMLOA= github.com/pocketbase/pocketbase v0.31.0 h1:JaOtSDytdA+a0r4689Mrjda4rmq+BaHgEJkPeOIydms=
github.com/pocketbase/pocketbase v0.30.1/go.mod h1:sUI+uekXZam5Wa0eh+DClc+HieKMCeqsHA7Ydd9vwyE= github.com/pocketbase/pocketbase v0.31.0/go.mod h1:p4a83n+DlBcTvvqhC7QDy0KDmQ2la2c6dgxdIBWwKiE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -99,8 +103,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA=
github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
@@ -120,25 +124,23 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA= golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA= golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -146,41 +148,41 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM= modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM= modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ= modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8= modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -189,8 +191,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY= modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E= modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -40,13 +40,18 @@ type UserNotificationSettings struct {
} }
type SystemAlertStats struct { type SystemAlertStats struct {
Cpu float64 `json:"cpu"` Cpu float64 `json:"cpu"`
Mem float64 `json:"mp"` Mem float64 `json:"mp"`
Disk float64 `json:"dp"` Disk float64 `json:"dp"`
NetSent float64 `json:"ns"` NetSent float64 `json:"ns"`
NetRecv float64 `json:"nr"` NetRecv float64 `json:"nr"`
Temperatures map[string]float32 `json:"t"` GPU map[string]SystemAlertGPUData `json:"g"`
LoadAvg [3]float64 `json:"la"` Temperatures map[string]float32 `json:"t"`
LoadAvg [3]float64 `json:"la"`
}
type SystemAlertGPUData struct {
Usage float64 `json:"u"`
} }
type SystemAlertData struct { type SystemAlertData struct {

View File

@@ -161,19 +161,14 @@ func (am *AlertManager) sendStatusAlert(alertStatus string, systemName string, a
title := fmt.Sprintf("Connection to %s is %s %v", systemName, alertStatus, emoji) title := fmt.Sprintf("Connection to %s is %s %v", systemName, alertStatus, emoji)
message := strings.TrimSuffix(title, emoji) message := strings.TrimSuffix(title, emoji)
// if errs := am.hub.ExpandRecord(alertRecord, []string{"user"}, nil); len(errs) > 0 { // Get system ID for the link
// return errs["user"] systemID := alertRecord.GetString("system")
// }
// user := alertRecord.ExpandedOne("user")
// if user == nil {
// return nil
// }
return am.SendAlert(AlertMessageData{ return am.SendAlert(AlertMessageData{
UserID: alertRecord.GetString("user"), UserID: alertRecord.GetString("user"),
Title: title, Title: title,
Message: message, Message: message,
Link: am.hub.MakeLink("system", systemName), Link: am.hub.MakeLink("system", systemID),
LinkText: "View " + systemName, LinkText: "View " + systemName,
}) })
} }

View File

@@ -64,6 +64,8 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
case "LoadAvg15": case "LoadAvg15":
val = data.Info.LoadAvg[2] val = data.Info.LoadAvg[2]
unit = "" unit = ""
case "GPU":
val = data.Info.GpuPct
} }
triggered := alertRecord.GetBool("triggered") triggered := alertRecord.GetBool("triggered")
@@ -206,6 +208,17 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
alert.val += stats.LoadAvg[1] alert.val += stats.LoadAvg[1]
case "LoadAvg15": case "LoadAvg15":
alert.val += stats.LoadAvg[2] alert.val += stats.LoadAvg[2]
case "GPU":
if len(stats.GPU) == 0 {
continue
}
maxUsage := 0.0
for _, gpu := range stats.GPU {
if gpu.Usage > maxUsage {
maxUsage = gpu.Usage
}
}
alert.val += maxUsage
default: default:
continue continue
} }
@@ -268,9 +281,9 @@ func (am *AlertManager) sendSystemAlert(alert SystemAlertData) {
alert.name = after + "m Load" alert.name = after + "m Load"
} }
// make title alert name lowercase if not CPU // make title alert name lowercase if not CPU or GPU
titleAlertName := alert.name titleAlertName := alert.name
if titleAlertName != "CPU" { if titleAlertName != "CPU" && titleAlertName != "GPU" {
titleAlertName = strings.ToLower(titleAlertName) titleAlertName = strings.ToLower(titleAlertName)
} }
@@ -298,7 +311,7 @@ func (am *AlertManager) sendSystemAlert(alert SystemAlertData) {
UserID: alert.alertRecord.GetString("user"), UserID: alert.alertRecord.GetString("user"),
Title: subject, Title: subject,
Message: body, Message: body,
Link: am.hub.MakeLink("system", systemName), Link: am.hub.MakeLink("system", alert.systemRecord.Id),
LinkText: "View " + systemName, LinkText: "View " + systemName,
}) })
} }

View File

@@ -3,6 +3,7 @@ package common
import ( import (
"github.com/henrygd/beszel/internal/entities/smart" "github.com/henrygd/beszel/internal/entities/smart"
"github.com/henrygd/beszel/internal/entities/system" "github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/entities/systemd"
) )
type WebSocketAction = uint8 type WebSocketAction = uint8
@@ -18,6 +19,8 @@ const (
GetContainerInfo GetContainerInfo
// Request SMART data from agent // Request SMART data from agent
GetSmartData GetSmartData
// Request detailed systemd service info from agent
GetSystemdInfo
// Add new actions here... // Add new actions here...
) )
@@ -36,6 +39,7 @@ type AgentResponse struct {
Error string `cbor:"3,keyasint,omitempty,omitzero"` Error string `cbor:"3,keyasint,omitempty,omitzero"`
String *string `cbor:"4,keyasint,omitempty,omitzero"` String *string `cbor:"4,keyasint,omitempty,omitzero"`
SmartData map[string]smart.SmartData `cbor:"5,keyasint,omitempty,omitzero"` SmartData map[string]smart.SmartData `cbor:"5,keyasint,omitempty,omitzero"`
ServiceInfo systemd.ServiceDetails `cbor:"6,keyasint,omitempty,omitzero"`
// Logs *LogsPayload `cbor:"4,keyasint,omitempty,omitzero"` // Logs *LogsPayload `cbor:"4,keyasint,omitempty,omitzero"`
// RawBytes []byte `cbor:"4,keyasint,omitempty,omitzero"` // RawBytes []byte `cbor:"4,keyasint,omitempty,omitzero"`
} }
@@ -65,3 +69,7 @@ type ContainerLogsRequest struct {
type ContainerInfoRequest struct { type ContainerInfoRequest struct {
ContainerID string `cbor:"0,keyasint"` ContainerID string `cbor:"0,keyasint"`
} }
type SystemdInfoRequest struct {
ServiceName string `cbor:"0,keyasint"`
}

View File

@@ -2,7 +2,6 @@ FROM --platform=$BUILDPLATFORM golang:alpine AS builder
WORKDIR /app WORKDIR /app
COPY ../go.mod ../go.sum ./ COPY ../go.mod ../go.sum ./
RUN go mod download RUN go mod download
@@ -13,7 +12,24 @@ COPY . ./
ARG TARGETOS TARGETARCH ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 GOGC=75 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags "-w -s" -o /agent ./internal/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/* # --------------------------
# Smartmontools builder stage
# --------------------------
FROM nvidia/cuda:12.2.2-base-ubuntu22.04 AS smartmontools-builder
RUN apt-get update && apt-get install -y \
wget \
build-essential \
&& wget https://downloads.sourceforge.net/project/smartmontools/smartmontools/7.5/smartmontools-7.5.tar.gz \
&& tar zxvf smartmontools-7.5.tar.gz \
&& cd smartmontools-7.5 \
&& ./configure --prefix=/usr --sysconfdir=/etc \
&& make \
&& make install \
&& rm -rf /smartmontools-7.5* \
&& apt-get remove -y wget build-essential \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
# -------------------------- # --------------------------
# Final image: GPU-enabled agent with nvidia-smi # Final image: GPU-enabled agent with nvidia-smi
@@ -21,10 +37,8 @@ RUN rm -rf /tmp/*
FROM nvidia/cuda:12.2.2-base-ubuntu22.04 FROM nvidia/cuda:12.2.2-base-ubuntu22.04
COPY --from=builder /agent /agent COPY --from=builder /agent /agent
# this is so we don't need to create the /tmp directory in the scratch container # Copy smartmontools binaries and config files
COPY --from=builder /tmp /tmp COPY --from=smartmontools-builder /usr/sbin/smartctl /usr/sbin/smartctl
RUN apt-get update && apt-get install -y smartmontools && rm -rf /var/lib/apt/lists/*
# Ensure data persistence across container recreations # Ensure data persistence across container recreations
VOLUME ["/var/lib/beszel-agent"] VOLUME ["/var/lib/beszel-agent"]

View File

@@ -1,5 +1,11 @@
package smart package smart
import (
"encoding/json"
"strconv"
"strings"
)
// Common types // Common types
type VersionInfo [2]int type VersionInfo [2]int
@@ -129,30 +135,136 @@ type AtaSmartAttributes struct {
} }
type AtaSmartAttribute struct { type AtaSmartAttribute struct {
ID uint16 `json:"id"` ID uint16 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Value uint16 `json:"value"` Value uint16 `json:"value"`
Worst uint16 `json:"worst"` Worst uint16 `json:"worst"`
Thresh uint16 `json:"thresh"` Thresh uint16 `json:"thresh"`
WhenFailed string `json:"when_failed"` WhenFailed string `json:"when_failed"`
Flags AttributeFlags `json:"flags"` // Flags AttributeFlags `json:"flags"`
Raw RawValue `json:"raw"` Raw RawValue `json:"raw"`
} }
type AttributeFlags struct { // type AttributeFlags struct {
Value int `json:"value"` // Value int `json:"value"`
String string `json:"string"` // String string `json:"string"`
Prefailure bool `json:"prefailure"` // Prefailure bool `json:"prefailure"`
UpdatedOnline bool `json:"updated_online"` // UpdatedOnline bool `json:"updated_online"`
Performance bool `json:"performance"` // Performance bool `json:"performance"`
ErrorRate bool `json:"error_rate"` // ErrorRate bool `json:"error_rate"`
EventCount bool `json:"event_count"` // EventCount bool `json:"event_count"`
AutoKeep bool `json:"auto_keep"` // AutoKeep bool `json:"auto_keep"`
} // }
type RawValue struct { type RawValue struct {
Value uint64 `json:"value"` Value SmartRawValue `json:"value"`
String string `json:"string"` String string `json:"string"`
}
func (r *RawValue) UnmarshalJSON(data []byte) error {
var tmp struct {
Value json.RawMessage `json:"value"`
String string `json:"string"`
}
if err := json.Unmarshal(data, &tmp); err != nil {
return err
}
if len(tmp.Value) > 0 {
if err := r.Value.UnmarshalJSON(tmp.Value); err != nil {
return err
}
} else {
r.Value = 0
}
r.String = tmp.String
if parsed, ok := ParseSmartRawValueString(tmp.String); ok {
r.Value = SmartRawValue(parsed)
}
return nil
}
type SmartRawValue uint64
// handles when drives report strings like "0h+0m+0.000s" or "7344 (253d 8h)" for power on hours
func (v *SmartRawValue) UnmarshalJSON(data []byte) error {
trimmed := strings.TrimSpace(string(data))
if len(trimmed) == 0 || trimmed == "null" {
*v = 0
return nil
}
if trimmed[0] == '"' {
valueStr, err := strconv.Unquote(trimmed)
if err != nil {
return err
}
parsed, ok := ParseSmartRawValueString(valueStr)
if ok {
*v = SmartRawValue(parsed)
return nil
}
*v = 0
return nil
}
if parsed, err := strconv.ParseUint(trimmed, 0, 64); err == nil {
*v = SmartRawValue(parsed)
return nil
}
if parsed, ok := ParseSmartRawValueString(trimmed); ok {
*v = SmartRawValue(parsed)
return nil
}
*v = 0
return nil
}
// ParseSmartRawValueString attempts to extract a numeric value from the raw value
// strings emitted by smartctl, which sometimes include human-friendly annotations
// like "7344 (253d 8h)" or "0h+0m+0.000s". It returns the parsed value and a
// boolean indicating success.
func ParseSmartRawValueString(value string) (uint64, bool) {
value = strings.TrimSpace(value)
if value == "" {
return 0, false
}
if parsed, err := strconv.ParseUint(value, 0, 64); err == nil {
return parsed, true
}
if idx := strings.IndexRune(value, 'h'); idx > 0 {
hoursPart := strings.TrimSpace(value[:idx])
if hoursPart != "" {
if parsed, err := strconv.ParseFloat(hoursPart, 64); err == nil {
return uint64(parsed), true
}
}
}
for i := 0; i < len(value); i++ {
if value[i] < '0' || value[i] > '9' {
continue
}
end := i + 1
for end < len(value) && value[end] >= '0' && value[end] <= '9' {
end++
}
digits := value[i:end]
if parsed, err := strconv.ParseUint(digits, 10, 64); err == nil {
return parsed, true
}
i = end
}
return 0, false
} }
// type PowerOnTimeInfo struct { // type PowerOnTimeInfo struct {
@@ -163,6 +275,11 @@ type TemperatureInfo struct {
Current uint8 `json:"current"` Current uint8 `json:"current"`
} }
type TemperatureInfoScsi struct {
Current uint8 `json:"current"`
DriveTrip uint8 `json:"drive_trip"`
}
// type SelectiveSelfTestTable struct { // type SelectiveSelfTestTable struct {
// LbaMin int `json:"lba_min"` // LbaMin int `json:"lba_min"`
// LbaMax int `json:"lba_max"` // LbaMax int `json:"lba_max"`
@@ -211,6 +328,8 @@ type SmartInfoForSata struct {
// Wwn WwnInfo `json:"wwn"` // Wwn WwnInfo `json:"wwn"`
FirmwareVersion string `json:"firmware_version"` FirmwareVersion string `json:"firmware_version"`
UserCapacity UserCapacity `json:"user_capacity"` UserCapacity UserCapacity `json:"user_capacity"`
ScsiVendor string `json:"scsi_vendor"`
ScsiProduct string `json:"scsi_product"`
// LogicalBlockSize int `json:"logical_block_size"` // LogicalBlockSize int `json:"logical_block_size"`
// PhysicalBlockSize int `json:"physical_block_size"` // PhysicalBlockSize int `json:"physical_block_size"`
// RotationRate int `json:"rotation_rate"` // RotationRate int `json:"rotation_rate"`
@@ -233,6 +352,54 @@ type SmartInfoForSata struct {
// AtaSmartSelectiveSelfTestLog AtaSmartSelectiveSelfTestLog `json:"ata_smart_selective_self_test_log"` // AtaSmartSelectiveSelfTestLog AtaSmartSelectiveSelfTestLog `json:"ata_smart_selective_self_test_log"`
} }
type ScsiErrorCounter struct {
ErrorsCorrectedByECCFast uint64 `json:"errors_corrected_by_eccfast"`
ErrorsCorrectedByECCDelayed uint64 `json:"errors_corrected_by_eccdelayed"`
ErrorsCorrectedByRereadsRewrites uint64 `json:"errors_corrected_by_rereads_rewrites"`
TotalErrorsCorrected uint64 `json:"total_errors_corrected"`
CorrectionAlgorithmInvocations uint64 `json:"correction_algorithm_invocations"`
GigabytesProcessed string `json:"gigabytes_processed"`
TotalUncorrectedErrors uint64 `json:"total_uncorrected_errors"`
}
type ScsiErrorCounterLog struct {
Read ScsiErrorCounter `json:"read"`
Write ScsiErrorCounter `json:"write"`
Verify ScsiErrorCounter `json:"verify"`
}
type ScsiStartStopCycleCounter struct {
YearOfManufacture string `json:"year_of_manufacture"`
WeekOfManufacture string `json:"week_of_manufacture"`
SpecifiedCycleCountOverDeviceLifetime uint64 `json:"specified_cycle_count_over_device_lifetime"`
AccumulatedStartStopCycles uint64 `json:"accumulated_start_stop_cycles"`
SpecifiedLoadUnloadCountOverDeviceLifetime uint64 `json:"specified_load_unload_count_over_device_lifetime"`
AccumulatedLoadUnloadCycles uint64 `json:"accumulated_load_unload_cycles"`
}
type PowerOnTimeScsi struct {
Hours uint64 `json:"hours"`
Minutes uint64 `json:"minutes"`
}
type SmartInfoForScsi struct {
Smartctl SmartctlInfoLegacy `json:"smartctl"`
Device DeviceInfo `json:"device"`
ScsiVendor string `json:"scsi_vendor"`
ScsiProduct string `json:"scsi_product"`
ScsiModelName string `json:"scsi_model_name"`
ScsiRevision string `json:"scsi_revision"`
ScsiVersion string `json:"scsi_version"`
SerialNumber string `json:"serial_number"`
UserCapacity UserCapacity `json:"user_capacity"`
Temperature TemperatureInfoScsi `json:"temperature"`
SmartStatus SmartStatusInfo `json:"smart_status"`
PowerOnTime PowerOnTimeScsi `json:"power_on_time"`
ScsiStartStopCycleCounter ScsiStartStopCycleCounter `json:"scsi_start_stop_cycle_counter"`
ScsiGrownDefectList uint64 `json:"scsi_grown_defect_list"`
ScsiErrorCounterLog ScsiErrorCounterLog `json:"scsi_error_counter_log"`
}
// type AtaSmartErrorLog struct { // type AtaSmartErrorLog struct {
// Summary SummaryInfo `json:"summary"` // Summary SummaryInfo `json:"summary"`
// } // }

View File

@@ -0,0 +1,62 @@
package smart
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSmartRawValueUnmarshalDuration(t *testing.T) {
input := []byte(`{"value":"62312h+33m+50.907s","string":"62312h+33m+50.907s"}`)
var raw RawValue
err := json.Unmarshal(input, &raw)
assert.NoError(t, err)
assert.EqualValues(t, 62312, raw.Value)
}
func TestSmartRawValueUnmarshalNumericString(t *testing.T) {
input := []byte(`{"value":"7344","string":"7344"}`)
var raw RawValue
err := json.Unmarshal(input, &raw)
assert.NoError(t, err)
assert.EqualValues(t, 7344, raw.Value)
}
func TestSmartRawValueUnmarshalParenthetical(t *testing.T) {
input := []byte(`{"value":"39925 (212 206 0)","string":"39925 (212 206 0)"}`)
var raw RawValue
err := json.Unmarshal(input, &raw)
assert.NoError(t, err)
assert.EqualValues(t, 39925, raw.Value)
}
func TestSmartRawValueUnmarshalDurationWithFractions(t *testing.T) {
input := []byte(`{"value":"2748h+31m+49.560s","string":"2748h+31m+49.560s"}`)
var raw RawValue
err := json.Unmarshal(input, &raw)
assert.NoError(t, err)
assert.EqualValues(t, 2748, raw.Value)
}
func TestSmartRawValueUnmarshalParentheticalRawValue(t *testing.T) {
input := []byte(`{"value":57891864217128,"string":"39925 (212 206 0)"}`)
var raw RawValue
err := json.Unmarshal(input, &raw)
assert.NoError(t, err)
assert.EqualValues(t, 39925, raw.Value)
}
func TestSmartRawValueUnmarshalDurationRawValue(t *testing.T) {
input := []byte(`{"value":57891864217128,"string":"2748h+31m+49.560s"}`)
var raw RawValue
err := json.Unmarshal(input, &raw)
assert.NoError(t, err)
assert.EqualValues(t, 2748, raw.Value)
}

View File

@@ -3,9 +3,11 @@ package system
// TODO: this is confusing, make common package with common/types common/helpers etc // TODO: this is confusing, make common package with common/types common/helpers etc
import ( import (
"encoding/json"
"time" "time"
"github.com/henrygd/beszel/internal/entities/container" "github.com/henrygd/beszel/internal/entities/container"
"github.com/henrygd/beszel/internal/entities/systemd"
) )
type Stats struct { type Stats struct {
@@ -41,9 +43,28 @@ type Stats struct {
LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"` LoadAvg [3]float64 `json:"la,omitempty" cbor:"28,keyasint"`
Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current] Battery [2]uint8 `json:"bat,omitzero" cbor:"29,keyasint,omitzero"` // [percent, charge state, current]
MaxMem float64 `json:"mm,omitempty" cbor:"30,keyasint,omitempty"` 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] NetworkInterfaces map[string][4]uint64 `json:"ni,omitempty" cbor:"31,keyasint,omitempty"` // [upload bytes, download bytes, total upload, total download]
DiskIO [2]uint64 `json:"dio,omitzero" cbor:"32,keyasint,omitzero"` // [read bytes, write bytes] DiskIO [2]uint64 `json:"dio,omitzero" cbor:"32,keyasint,omitzero"` // [read bytes, write bytes]
MaxDiskIO [2]uint64 `json:"diom,omitzero" cbor:"-"` // [max read bytes, max write bytes] MaxDiskIO [2]uint64 `json:"diom,omitzero" cbor:"-"` // [max read bytes, max write bytes]
CpuBreakdown []float64 `json:"cpub,omitempty" cbor:"33,keyasint,omitempty"` // [user, system, iowait, steal, idle]
CpuCoresUsage Uint8Slice `json:"cpus,omitempty" cbor:"34,keyasint,omitempty"` // per-core busy usage [CPU0..]
}
// Uint8Slice wraps []uint8 to customize JSON encoding while keeping CBOR efficient.
// JSON: encodes as array of numbers (avoids base64 string).
// CBOR: falls back to default handling for []uint8 (byte string), keeping payload small.
type Uint8Slice []uint8
func (s Uint8Slice) MarshalJSON() ([]byte, error) {
if s == nil {
return []byte("null"), nil
}
// Convert to wider ints to force array-of-numbers encoding.
arr := make([]uint16, len(s))
for i, v := range s {
arr[i] = uint16(v)
}
return json.Marshal(arr)
} }
type GPUData struct { type GPUData struct {
@@ -129,7 +150,8 @@ type Info struct {
// Final data structure to return to the hub // Final data structure to return to the hub
type CombinedData struct { type CombinedData struct {
Stats Stats `json:"stats" cbor:"0,keyasint"` Stats Stats `json:"stats" cbor:"0,keyasint"`
Info Info `json:"info" cbor:"1,keyasint"` Info Info `json:"info" cbor:"1,keyasint"`
Containers []*container.Stats `json:"container" cbor:"2,keyasint"` Containers []*container.Stats `json:"container" cbor:"2,keyasint"`
SystemdServices []*systemd.Service `json:"systemd,omitempty" cbor:"3,keyasint,omitempty"`
} }

View File

@@ -0,0 +1,127 @@
package systemd
import (
"math"
"runtime"
"time"
)
// ServiceState represents the status of a systemd service
type ServiceState uint8
const (
StatusActive ServiceState = iota
StatusInactive
StatusFailed
StatusActivating
StatusDeactivating
StatusReloading
)
// ServiceSubState represents the sub status of a systemd service
type ServiceSubState uint8
const (
SubStateDead ServiceSubState = iota
SubStateRunning
SubStateExited
SubStateFailed
SubStateUnknown
)
// ParseServiceStatus converts a string status to a ServiceStatus enum value
func ParseServiceStatus(status string) ServiceState {
switch status {
case "active":
return StatusActive
case "inactive":
return StatusInactive
case "failed":
return StatusFailed
case "activating":
return StatusActivating
case "deactivating":
return StatusDeactivating
case "reloading":
return StatusReloading
default:
return StatusInactive
}
}
// ParseServiceSubState converts a string sub status to a ServiceSubState enum value
func ParseServiceSubState(subState string) ServiceSubState {
switch subState {
case "dead":
return SubStateDead
case "running":
return SubStateRunning
case "exited":
return SubStateExited
case "failed":
return SubStateFailed
default:
return SubStateUnknown
}
}
// Service represents a single systemd service with its stats.
type Service struct {
Name string `json:"n" cbor:"0,keyasint"`
State ServiceState `json:"s" cbor:"1,keyasint"`
Cpu float64 `json:"c" cbor:"2,keyasint"`
Mem uint64 `json:"m" cbor:"3,keyasint"`
MemPeak uint64 `json:"mp" cbor:"4,keyasint"`
Sub ServiceSubState `json:"ss" cbor:"5,keyasint"`
CpuPeak float64 `json:"cp" cbor:"6,keyasint"`
PrevCpuUsage uint64 `json:"-"`
PrevReadTime time.Time `json:"-"`
}
// UpdateCPUPercent calculates the CPU usage percentage for the service.
func (s *Service) UpdateCPUPercent(cpuUsage uint64) {
now := time.Now()
if s.PrevReadTime.IsZero() || cpuUsage < s.PrevCpuUsage {
s.Cpu = 0
s.PrevCpuUsage = cpuUsage
s.PrevReadTime = now
return
}
duration := now.Sub(s.PrevReadTime).Nanoseconds()
if duration <= 0 {
s.PrevCpuUsage = cpuUsage
s.PrevReadTime = now
return
}
coreCount := int64(runtime.NumCPU())
duration *= coreCount
usageDelta := cpuUsage - s.PrevCpuUsage
cpuPercent := float64(usageDelta) / float64(duration)
s.Cpu = twoDecimals(cpuPercent * 100)
if s.Cpu > s.CpuPeak {
s.CpuPeak = s.Cpu
}
s.PrevCpuUsage = cpuUsage
s.PrevReadTime = now
}
func twoDecimals(value float64) float64 {
return math.Round(value*100) / 100
}
// ServiceDependency represents a unit that the service depends on.
type ServiceDependency struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
ActiveState string `json:"activeState,omitempty"`
SubState string `json:"subState,omitempty"`
}
// ServiceDetails contains extended information about a systemd service.
type ServiceDetails map[string]any

View File

@@ -0,0 +1,113 @@
//go:build testing
package systemd_test
import (
"testing"
"time"
"github.com/henrygd/beszel/internal/entities/systemd"
"github.com/stretchr/testify/assert"
)
func TestParseServiceStatus(t *testing.T) {
tests := []struct {
input string
expected systemd.ServiceState
}{
{"active", systemd.StatusActive},
{"inactive", systemd.StatusInactive},
{"failed", systemd.StatusFailed},
{"activating", systemd.StatusActivating},
{"deactivating", systemd.StatusDeactivating},
{"reloading", systemd.StatusReloading},
{"unknown", systemd.StatusInactive}, // default case
{"", systemd.StatusInactive}, // default case
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
result := systemd.ParseServiceStatus(test.input)
assert.Equal(t, test.expected, result)
})
}
}
func TestParseServiceSubState(t *testing.T) {
tests := []struct {
input string
expected systemd.ServiceSubState
}{
{"dead", systemd.SubStateDead},
{"running", systemd.SubStateRunning},
{"exited", systemd.SubStateExited},
{"failed", systemd.SubStateFailed},
{"unknown", systemd.SubStateUnknown},
{"other", systemd.SubStateUnknown}, // default case
{"", systemd.SubStateUnknown}, // default case
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
result := systemd.ParseServiceSubState(test.input)
assert.Equal(t, test.expected, result)
})
}
}
func TestServiceUpdateCPUPercent(t *testing.T) {
t.Run("initial call sets CPU to 0", func(t *testing.T) {
service := &systemd.Service{}
service.UpdateCPUPercent(1000)
assert.Equal(t, 0.0, service.Cpu)
assert.Equal(t, uint64(1000), service.PrevCpuUsage)
assert.False(t, service.PrevReadTime.IsZero())
})
t.Run("subsequent call calculates CPU percentage", func(t *testing.T) {
service := &systemd.Service{}
service.PrevCpuUsage = 1000
service.PrevReadTime = time.Now().Add(-time.Second)
service.UpdateCPUPercent(8000000000) // 8 seconds of CPU time
// CPU usage should be positive and reasonable
assert.Greater(t, service.Cpu, 0.0, "CPU usage should be positive")
assert.LessOrEqual(t, service.Cpu, 100.0, "CPU usage should not exceed 100%")
assert.Equal(t, uint64(8000000000), service.PrevCpuUsage)
assert.Greater(t, service.CpuPeak, 0.0, "CPU peak should be set")
})
t.Run("CPU peak updates only when higher", func(t *testing.T) {
service := &systemd.Service{}
service.PrevCpuUsage = 1000
service.PrevReadTime = time.Now().Add(-time.Second)
service.UpdateCPUPercent(8000000000) // Set initial peak to ~50%
initialPeak := service.CpuPeak
// Now try with much lower CPU usage - should not update peak
service.PrevReadTime = time.Now().Add(-time.Second)
service.UpdateCPUPercent(1000000) // Much lower usage
assert.Equal(t, initialPeak, service.CpuPeak, "Peak should not update for lower CPU usage")
})
t.Run("handles zero duration", func(t *testing.T) {
service := &systemd.Service{}
service.PrevCpuUsage = 1000
now := time.Now()
service.PrevReadTime = now
// Mock time.Now() to return the same time to ensure zero duration
// Since we can't mock time in Go easily, we'll check the logic manually
// The zero duration case happens when duration <= 0
assert.Equal(t, 0.0, service.Cpu, "CPU should start at 0")
})
t.Run("handles CPU usage wraparound", func(t *testing.T) {
service := &systemd.Service{}
// Simulate wraparound where new usage is less than previous
service.PrevCpuUsage = 1000
service.PrevReadTime = time.Now().Add(-time.Second)
service.UpdateCPUPercent(500) // Less than previous, should reset
assert.Equal(t, 0.0, service.Cpu)
})
}

View File

@@ -136,6 +136,7 @@ func setCollectionAuthSettings(app core.App) error {
if err != nil { if err != nil {
return err return err
} }
// disable email auth if DISABLE_PASSWORD_AUTH env var is set // disable email auth if DISABLE_PASSWORD_AUTH env var is set
disablePasswordAuth, _ := GetEnv("DISABLE_PASSWORD_AUTH") disablePasswordAuth, _ := GetEnv("DISABLE_PASSWORD_AUTH")
usersCollection.PasswordAuth.Enabled = disablePasswordAuth != "true" usersCollection.PasswordAuth.Enabled = disablePasswordAuth != "true"
@@ -147,6 +148,7 @@ func setCollectionAuthSettings(app core.App) error {
} else { } else {
usersCollection.CreateRule = nil usersCollection.CreateRule = nil
} }
// enable mfaOtp mfa if MFA_OTP env var is set // enable mfaOtp mfa if MFA_OTP env var is set
mfaOtp, _ := GetEnv("MFA_OTP") mfaOtp, _ := GetEnv("MFA_OTP")
usersCollection.OTP.Length = 6 usersCollection.OTP.Length = 6
@@ -161,23 +163,37 @@ func setCollectionAuthSettings(app core.App) error {
if err := app.Save(usersCollection); err != nil { if err := app.Save(usersCollection); err != nil {
return err return err
} }
shareAllSystems, _ := GetEnv("SHARE_ALL_SYSTEMS")
// allow all users to access systems if SHARE_ALL_SYSTEMS is set // allow all users to access systems if SHARE_ALL_SYSTEMS is set
systemsCollection, err := app.FindCollectionByNameOrId("systems") systemsCollection, err := app.FindCollectionByNameOrId("systems")
if err != nil { if err != nil {
return err return err
} }
shareAllSystems, _ := GetEnv("SHARE_ALL_SYSTEMS") var systemsReadRule string
systemsReadRule := "@request.auth.id != \"\"" if shareAllSystems == "true" {
if shareAllSystems != "true" { systemsReadRule = "@request.auth.id != \"\""
// default is to only show systems that the user id is assigned to } else {
systemsReadRule += " && users.id ?= @request.auth.id" systemsReadRule = "@request.auth.id != \"\" && users.id ?= @request.auth.id"
} }
updateDeleteRule := systemsReadRule + " && @request.auth.role != \"readonly\"" updateDeleteRule := systemsReadRule + " && @request.auth.role != \"readonly\""
systemsCollection.ListRule = &systemsReadRule systemsCollection.ListRule = &systemsReadRule
systemsCollection.ViewRule = &systemsReadRule systemsCollection.ViewRule = &systemsReadRule
systemsCollection.UpdateRule = &updateDeleteRule systemsCollection.UpdateRule = &updateDeleteRule
systemsCollection.DeleteRule = &updateDeleteRule systemsCollection.DeleteRule = &updateDeleteRule
return app.Save(systemsCollection) if err := app.Save(systemsCollection); err != nil {
return err
}
// allow all users to access all containers if SHARE_ALL_SYSTEMS is set
containersCollection, err := app.FindCollectionByNameOrId("containers")
if err != nil {
return err
}
containersListRule := strings.Replace(systemsReadRule, "users.id", "system.users.id", 1)
containersCollection.ListRule = &containersListRule
return app.Save(containersCollection)
} }
// registerCronJobs sets up scheduled tasks // registerCronJobs sets up scheduled tasks
@@ -252,12 +268,17 @@ func (h *Hub) registerApiRoutes(se *core.ServeEvent) error {
// update / delete user alerts // update / delete user alerts
apiAuth.POST("/user-alerts", alerts.UpsertUserAlerts) apiAuth.POST("/user-alerts", alerts.UpsertUserAlerts)
apiAuth.DELETE("/user-alerts", alerts.DeleteUserAlerts) apiAuth.DELETE("/user-alerts", alerts.DeleteUserAlerts)
// get container logs
apiAuth.GET("/containers/logs", h.getContainerLogs)
// get container info
apiAuth.GET("/containers/info", h.getContainerInfo)
// get SMART data // get SMART data
apiAuth.GET("/smart", h.getSmartData) apiAuth.GET("/smart", h.getSmartData)
// get systemd service details
apiAuth.GET("/systemd/info", h.getSystemdInfo)
// /containers routes
if enabled, _ := GetEnv("CONTAINER_DETAILS"); enabled != "false" {
// get container logs
apiAuth.GET("/containers/logs", h.getContainerLogs)
// get container info
apiAuth.GET("/containers/info", h.getContainerInfo)
}
return nil return nil
} }
@@ -304,7 +325,7 @@ func (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*syst
data, err := fetchFunc(system, containerID) data, err := fetchFunc(system, containerID)
if err != nil { if err != nil {
return e.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
} }
return e.JSON(http.StatusOK, map[string]string{responseKey: data}) return e.JSON(http.StatusOK, map[string]string{responseKey: data})
@@ -323,6 +344,27 @@ func (h *Hub) getContainerInfo(e *core.RequestEvent) error {
}, "info") }, "info")
} }
// getSystemdInfo handles GET /api/beszel/systemd/info requests
func (h *Hub) getSystemdInfo(e *core.RequestEvent) error {
query := e.Request.URL.Query()
systemID := query.Get("system")
serviceName := query.Get("service")
if systemID == "" || serviceName == "" {
return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and service parameters are required"})
}
system, err := h.sm.GetSystem(systemID)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
}
details, err := system.FetchSystemdInfoFromAgent(serviceName)
if err != nil {
return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
}
e.Response.Header().Set("Cache-Control", "public, max-age=60")
return e.JSON(http.StatusOK, map[string]any{"details": details})
}
// getSmartData handles GET /api/beszel/smart requests // getSmartData handles GET /api/beszel/smart requests
func (h *Hub) getSmartData(e *core.RequestEvent) error { func (h *Hub) getSmartData(e *core.RequestEvent) error {
systemID := e.Request.URL.Query().Get("system") systemID := e.Request.URL.Query().Get("system")
@@ -335,7 +377,7 @@ func (h *Hub) getSmartData(e *core.RequestEvent) error {
} }
data, err := system.FetchSmartDataFromAgent() data, err := system.FetchSmartDataFromAgent()
if err != nil { if err != nil {
return e.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}) return e.JSON(http.StatusNotFound, map[string]string{"error": err.Error()})
} }
e.Response.Header().Set("Cache-Control", "public, max-age=60") e.Response.Header().Set("Cache-Control", "public, max-age=60")
return e.JSON(http.StatusOK, data) return e.JSON(http.StatusOK, data)

View File

@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"hash/fnv"
"math/rand" "math/rand"
"net" "net"
"strings" "strings"
@@ -15,6 +16,7 @@ import (
"github.com/henrygd/beszel/internal/entities/container" "github.com/henrygd/beszel/internal/entities/container"
"github.com/henrygd/beszel/internal/entities/system" "github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/entities/systemd"
"github.com/henrygd/beszel" "github.com/henrygd/beszel"
@@ -171,6 +173,14 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
return err return err
} }
} }
// add new systemd_stats record
if len(data.SystemdServices) > 0 {
if err := createSystemdStatsRecords(txApp, data.SystemdServices, sys.Id); err != nil {
return err
}
}
// update system record (do this last because it triggers alerts and we need above records to be inserted first) // update system record (do this last because it triggers alerts and we need above records to be inserted first)
systemRecord.Set("status", up) systemRecord.Set("status", up)
@@ -184,11 +194,50 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
return systemRecord, err return systemRecord, err
} }
func createSystemdStatsRecords(app core.App, data []*systemd.Service, systemId string) error {
if len(data) == 0 {
return nil
}
// shared params for all records
params := dbx.Params{
"system": systemId,
"updated": time.Now().UTC().UnixMilli(),
}
valueStrings := make([]string, 0, len(data))
for i, service := range data {
suffix := fmt.Sprintf("%d", i)
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:state%[1]s}, {:sub%[1]s}, {:cpu%[1]s}, {:cpuPeak%[1]s}, {:memory%[1]s}, {:memPeak%[1]s}, {:updated})", suffix))
params["id"+suffix] = getSystemdServiceId(systemId, service.Name)
params["name"+suffix] = service.Name
params["state"+suffix] = service.State
params["sub"+suffix] = service.Sub
params["cpu"+suffix] = service.Cpu
params["cpuPeak"+suffix] = service.CpuPeak
params["memory"+suffix] = service.Mem
params["memPeak"+suffix] = service.MemPeak
}
queryString := fmt.Sprintf(
"INSERT INTO systemd_services (id, system, name, state, sub, cpu, cpuPeak, memory, memPeak, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, state = excluded.state, sub = excluded.sub, cpu = excluded.cpu, cpuPeak = excluded.cpuPeak, memory = excluded.memory, memPeak = excluded.memPeak, updated = excluded.updated",
strings.Join(valueStrings, ","),
)
_, err := app.DB().NewQuery(queryString).Bind(params).Execute()
return err
}
// getSystemdServiceId generates a deterministic unique id for a systemd service
func getSystemdServiceId(systemId string, serviceName string) string {
hash := fnv.New32a()
hash.Write([]byte(systemId + serviceName))
return fmt.Sprintf("%x", hash.Sum32())
}
// createContainerRecords creates container records // createContainerRecords creates container records
func createContainerRecords(app core.App, data []*container.Stats, systemId string) error { func createContainerRecords(app core.App, data []*container.Stats, systemId string) error {
if len(data) == 0 { if len(data) == 0 {
return nil return nil
} }
// shared params for all records
params := dbx.Params{ params := dbx.Params{
"system": systemId, "system": systemId,
"updated": time.Now().UTC().UnixMilli(), "updated": time.Now().UTC().UnixMilli(),
@@ -340,6 +389,52 @@ func (sys *System) FetchContainerLogsFromAgent(containerID string) (string, erro
return sys.fetchStringFromAgentViaSSH(common.GetContainerLogs, common.ContainerLogsRequest{ContainerID: containerID}, "no logs in response") return sys.fetchStringFromAgentViaSSH(common.GetContainerLogs, common.ContainerLogsRequest{ContainerID: containerID}, "no logs in response")
} }
// FetchSystemdInfoFromAgent fetches detailed systemd service information from the agent
func (sys *System) FetchSystemdInfoFromAgent(serviceName string) (systemd.ServiceDetails, error) {
// fetch via websocket
if sys.WsConn != nil && sys.WsConn.IsConnected() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return sys.WsConn.RequestSystemdInfo(ctx, serviceName)
}
var result systemd.ServiceDetails
err := sys.runSSHOperation(5*time.Second, 1, func(session *ssh.Session) (bool, error) {
stdout, err := session.StdoutPipe()
if err != nil {
return false, err
}
stdin, stdinErr := session.StdinPipe()
if stdinErr != nil {
return false, stdinErr
}
if err := session.Shell(); err != nil {
return false, err
}
req := common.HubRequest[any]{Action: common.GetSystemdInfo, Data: common.SystemdInfoRequest{ServiceName: serviceName}}
if err := cbor.NewEncoder(stdin).Encode(req); err != nil {
return false, err
}
_ = stdin.Close()
var resp common.AgentResponse
if err := cbor.NewDecoder(stdout).Decode(&resp); err != nil {
return false, err
}
if resp.ServiceInfo == nil {
if resp.Error != "" {
return false, errors.New(resp.Error)
}
return false, errors.New("no systemd info in response")
}
result = resp.ServiceInfo
return false, nil
})
return result, err
}
// FetchSmartDataFromAgent fetches SMART data from the agent // FetchSmartDataFromAgent fetches SMART data from the agent
func (sys *System) FetchSmartDataFromAgent() (map[string]any, error) { func (sys *System) FetchSmartDataFromAgent() (map[string]any, error) {
// fetch via websocket // fetch via websocket

View File

@@ -0,0 +1,75 @@
//go:build testing
package systems
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetSystemdServiceId(t *testing.T) {
t.Run("deterministic output", func(t *testing.T) {
systemId := "sys-123"
serviceName := "nginx.service"
// Call multiple times and ensure same result
id1 := getSystemdServiceId(systemId, serviceName)
id2 := getSystemdServiceId(systemId, serviceName)
id3 := getSystemdServiceId(systemId, serviceName)
assert.Equal(t, id1, id2)
assert.Equal(t, id2, id3)
assert.NotEmpty(t, id1)
})
t.Run("different inputs produce different ids", func(t *testing.T) {
systemId1 := "sys-123"
systemId2 := "sys-456"
serviceName1 := "nginx.service"
serviceName2 := "apache.service"
id1 := getSystemdServiceId(systemId1, serviceName1)
id2 := getSystemdServiceId(systemId2, serviceName1)
id3 := getSystemdServiceId(systemId1, serviceName2)
id4 := getSystemdServiceId(systemId2, serviceName2)
// All IDs should be different
assert.NotEqual(t, id1, id2)
assert.NotEqual(t, id1, id3)
assert.NotEqual(t, id1, id4)
assert.NotEqual(t, id2, id3)
assert.NotEqual(t, id2, id4)
assert.NotEqual(t, id3, id4)
})
t.Run("consistent length", func(t *testing.T) {
testCases := []struct {
systemId string
serviceName string
}{
{"short", "short.service"},
{"very-long-system-id-that-might-be-used-in-practice", "very-long-service-name.service"},
{"", "empty-system.service"},
{"empty-service", ""},
{"", ""},
}
for _, tc := range testCases {
id := getSystemdServiceId(tc.systemId, tc.serviceName)
// FNV-32 produces 8 hex characters
assert.Len(t, id, 8, "ID should be 8 characters for systemId='%s', serviceName='%s'", tc.systemId, tc.serviceName)
}
})
t.Run("hexadecimal output", func(t *testing.T) {
id := getSystemdServiceId("test-system", "test-service")
assert.NotEmpty(t, id)
// Should only contain hexadecimal characters
for _, char := range id {
assert.True(t, (char >= '0' && char <= '9') || (char >= 'a' && char <= 'f'),
"ID should only contain hexadecimal characters, got: %s", id)
}
})
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/fxamacker/cbor/v2" "github.com/fxamacker/cbor/v2"
"github.com/henrygd/beszel/internal/common" "github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/entities/system" "github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/entities/systemd"
"github.com/lxzan/gws" "github.com/lxzan/gws"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@@ -115,6 +116,44 @@ func (ws *WsConn) RequestContainerInfo(ctx context.Context, containerID string)
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
// RequestSystemdInfo requests detailed information about a systemd service via WebSocket.
func (ws *WsConn) RequestSystemdInfo(ctx context.Context, serviceName string) (systemd.ServiceDetails, error) {
if !ws.IsConnected() {
return nil, gws.ErrConnClosed
}
req, err := ws.requestManager.SendRequest(ctx, common.GetSystemdInfo, common.SystemdInfoRequest{ServiceName: serviceName})
if err != nil {
return nil, err
}
var result systemd.ServiceDetails
handler := &systemdInfoHandler{result: &result}
if err := ws.handleAgentRequest(req, handler); err != nil {
return nil, err
}
return result, nil
}
// systemdInfoHandler parses ServiceDetails from AgentResponse
type systemdInfoHandler struct {
BaseHandler
result *systemd.ServiceDetails
}
func (h *systemdInfoHandler) Handle(agentResponse common.AgentResponse) error {
if agentResponse.ServiceInfo == nil {
return errors.New("no systemd info in response")
}
*h.result = agentResponse.ServiceInfo
return nil
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// RequestSmartData requests SMART data via WebSocket. // RequestSmartData requests SMART data via WebSocket.
func (ws *WsConn) RequestSmartData(ctx context.Context) (map[string]any, error) { func (ws *WsConn) RequestSmartData(ctx context.Context) (map[string]any, error) {
if !ws.IsConnected() { if !ws.IsConnected() {

View File

@@ -0,0 +1,75 @@
//go:build testing
package ws
import (
"testing"
"github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/entities/systemd"
"github.com/stretchr/testify/assert"
)
func TestSystemdInfoHandlerSuccess(t *testing.T) {
handler := &systemdInfoHandler{
result: &systemd.ServiceDetails{},
}
// Test successful handling with valid ServiceInfo
testDetails := systemd.ServiceDetails{
"Id": "nginx.service",
"ActiveState": "active",
"SubState": "running",
"Description": "A high performance web server",
"ExecMainPID": 1234,
"MemoryCurrent": 1024000,
}
response := common.AgentResponse{
ServiceInfo: testDetails,
}
err := handler.Handle(response)
assert.NoError(t, err)
assert.Equal(t, testDetails, *handler.result)
}
func TestSystemdInfoHandlerError(t *testing.T) {
handler := &systemdInfoHandler{
result: &systemd.ServiceDetails{},
}
// Test error handling when ServiceInfo is nil
response := common.AgentResponse{
ServiceInfo: nil,
Error: "service not found",
}
err := handler.Handle(response)
assert.Error(t, err)
assert.Equal(t, "no systemd info in response", err.Error())
}
func TestSystemdInfoHandlerEmptyResponse(t *testing.T) {
handler := &systemdInfoHandler{
result: &systemd.ServiceDetails{},
}
// Test with completely empty response
response := common.AgentResponse{}
err := handler.Handle(response)
assert.Error(t, err)
assert.Equal(t, "no systemd info in response", err.Error())
}
func TestSystemdInfoHandlerLegacyNotSupported(t *testing.T) {
handler := &systemdInfoHandler{
result: &systemd.ServiceDetails{},
}
// Test that legacy format is not supported
err := handler.HandleLegacy([]byte("some data"))
assert.Error(t, err)
assert.Equal(t, "legacy format not supported", err.Error())
}

View File

@@ -75,6 +75,7 @@ func init() {
"Disk", "Disk",
"Temperature", "Temperature",
"Bandwidth", "Bandwidth",
"GPU",
"LoadAvg1", "LoadAvg1",
"LoadAvg5", "LoadAvg5",
"LoadAvg15" "LoadAvg15"
@@ -718,7 +719,9 @@ func init() {
"type": "autodate" "type": "autodate"
} }
], ],
"indexes": [], "indexes": [
"CREATE INDEX ` + "`" + `idx_systems_status` + "`" + ` ON ` + "`" + `systems` + "`" + ` (` + "`" + `status` + "`" + `)"
],
"system": false "system": false
}, },
{ {
@@ -1005,6 +1008,148 @@ func init() {
"CREATE INDEX ` + "`" + `idx_r3Ja0rs102` + "`" + ` ON ` + "`" + `containers` + "`" + ` (` + "`" + `system` + "`" + `)" "CREATE INDEX ` + "`" + `idx_r3Ja0rs102` + "`" + ` ON ` + "`" + `containers` + "`" + ` (` + "`" + `system` + "`" + `)"
], ],
"system": false "system": false
},
{
"createRule": null,
"deleteRule": null,
"fields": [
{
"autogeneratePattern": "[a-z0-9]{10}",
"hidden": false,
"id": "text3208210256",
"max": 10,
"min": 6,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 0,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"cascadeDelete": true,
"collectionId": "2hz5ncl8tizk5nx",
"hidden": false,
"id": "relation3377271179",
"maxSelect": 1,
"minSelect": 0,
"name": "system",
"presentable": false,
"required": false,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "number2063623452",
"max": null,
"min": null,
"name": "state",
"onlyInt": true,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number1476559580",
"max": null,
"min": null,
"name": "sub",
"onlyInt": true,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number3128971310",
"max": null,
"min": null,
"name": "cpu",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number1052053287",
"max": null,
"min": null,
"name": "cpuPeak",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number3933025333",
"max": null,
"min": null,
"name": "memory",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number1828797201",
"max": null,
"min": null,
"name": "memPeak",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
},
{
"hidden": false,
"id": "number3332085495",
"max": null,
"min": null,
"name": "updated",
"onlyInt": false,
"presentable": false,
"required": false,
"system": false,
"type": "number"
}
],
"id": "pbc_3494996990",
"indexes": [
"CREATE INDEX ` + "`" + `idx_4Z7LuLNdQb` + "`" + ` ON ` + "`" + `systemd_services` + "`" + ` (` + "`" + `system` + "`" + `)",
"CREATE INDEX ` + "`" + `idx_pBp1fF837e` + "`" + ` ON ` + "`" + `systemd_services` + "`" + ` (` + "`" + `updated` + "`" + `)"
],
"listRule": "@request.auth.id != \"\" && system.users.id ?= @request.auth.id",
"name": "systemd_services",
"system": false,
"type": "base",
"updateRule": null,
"viewRule": null
} }
]` ]`

View File

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

View File

@@ -177,6 +177,10 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
stats := &tempStats stats := &tempStats
// necessary because uint8 is not big enough for the sum // necessary because uint8 is not big enough for the sum
batterySum := 0 batterySum := 0
// accumulate per-core usage across records
var cpuCoresSums []uint64
// accumulate cpu breakdown [user, system, iowait, steal, idle]
var cpuBreakdownSums []float64
count := float64(len(records)) count := float64(len(records))
tempCount := float64(0) tempCount := float64(0)
@@ -194,6 +198,15 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
} }
sum.Cpu += stats.Cpu sum.Cpu += stats.Cpu
// accumulate cpu time breakdowns if present
if stats.CpuBreakdown != nil {
if len(cpuBreakdownSums) < len(stats.CpuBreakdown) {
cpuBreakdownSums = append(cpuBreakdownSums, make([]float64, len(stats.CpuBreakdown)-len(cpuBreakdownSums))...)
}
for i, v := range stats.CpuBreakdown {
cpuBreakdownSums[i] += v
}
}
sum.Mem += stats.Mem sum.Mem += stats.Mem
sum.MemUsed += stats.MemUsed sum.MemUsed += stats.MemUsed
sum.MemPct += stats.MemPct sum.MemPct += stats.MemPct
@@ -217,6 +230,17 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.DiskIO[1] += stats.DiskIO[1] sum.DiskIO[1] += stats.DiskIO[1]
batterySum += int(stats.Battery[0]) batterySum += int(stats.Battery[0])
sum.Battery[1] = stats.Battery[1] sum.Battery[1] = stats.Battery[1]
// accumulate per-core usage if present
if stats.CpuCoresUsage != nil {
if len(cpuCoresSums) < len(stats.CpuCoresUsage) {
// extend slices to accommodate core count
cpuCoresSums = append(cpuCoresSums, make([]uint64, len(stats.CpuCoresUsage)-len(cpuCoresSums))...)
}
for i, v := range stats.CpuCoresUsage {
cpuCoresSums[i] += uint64(v)
}
}
// Set peak values // Set peak values
sum.MaxCpu = max(sum.MaxCpu, stats.MaxCpu, stats.Cpu) sum.MaxCpu = max(sum.MaxCpu, stats.MaxCpu, stats.Cpu)
sum.MaxMem = max(sum.MaxMem, stats.MaxMem, stats.MemUsed) sum.MaxMem = max(sum.MaxMem, stats.MaxMem, stats.MemUsed)
@@ -269,6 +293,10 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
fs.DiskReadPs += value.DiskReadPs fs.DiskReadPs += value.DiskReadPs
fs.MaxDiskReadPS = max(fs.MaxDiskReadPS, value.MaxDiskReadPS, value.DiskReadPs) fs.MaxDiskReadPS = max(fs.MaxDiskReadPS, value.MaxDiskReadPS, value.DiskReadPs)
fs.MaxDiskWritePS = max(fs.MaxDiskWritePS, value.MaxDiskWritePS, value.DiskWritePs) fs.MaxDiskWritePS = max(fs.MaxDiskWritePS, value.MaxDiskWritePS, value.DiskWritePs)
fs.DiskReadBytes += value.DiskReadBytes
fs.DiskWriteBytes += value.DiskWriteBytes
fs.MaxDiskReadBytes = max(fs.MaxDiskReadBytes, value.MaxDiskReadBytes, value.DiskReadBytes)
fs.MaxDiskWriteBytes = max(fs.MaxDiskWriteBytes, value.MaxDiskWriteBytes, value.DiskWriteBytes)
} }
} }
@@ -356,6 +384,8 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
fs.DiskUsed = twoDecimals(fs.DiskUsed / count) fs.DiskUsed = twoDecimals(fs.DiskUsed / count)
fs.DiskWritePs = twoDecimals(fs.DiskWritePs / count) fs.DiskWritePs = twoDecimals(fs.DiskWritePs / count)
fs.DiskReadPs = twoDecimals(fs.DiskReadPs / count) fs.DiskReadPs = twoDecimals(fs.DiskReadPs / count)
fs.DiskReadBytes = fs.DiskReadBytes / uint64(count)
fs.DiskWriteBytes = fs.DiskWriteBytes / uint64(count)
} }
} }
@@ -379,6 +409,25 @@ func (rm *RecordManager) AverageSystemStats(db dbx.Builder, records RecordIds) *
sum.GPUData[id] = gpu sum.GPUData[id] = gpu
} }
} }
// Average per-core usage
if len(cpuCoresSums) > 0 {
avg := make(system.Uint8Slice, len(cpuCoresSums))
for i := range cpuCoresSums {
v := math.Round(float64(cpuCoresSums[i]) / count)
avg[i] = uint8(v)
}
sum.CpuCoresUsage = avg
}
// Average CPU breakdown
if len(cpuBreakdownSums) > 0 {
avg := make([]float64, len(cpuBreakdownSums))
for i := range cpuBreakdownSums {
avg[i] = twoDecimals(cpuBreakdownSums[i] / count)
}
sum.CpuBreakdown = avg
}
} }
return sum return sum
@@ -441,6 +490,10 @@ func (rm *RecordManager) DeleteOldRecords() {
if err != nil { if err != nil {
return err return err
} }
err = deleteOldSystemdServiceRecords(txApp)
if err != nil {
return err
}
err = deleteOldAlertsHistory(txApp, 200, 250) err = deleteOldAlertsHistory(txApp, 200, 250)
if err != nil { if err != nil {
return err return err
@@ -510,6 +563,20 @@ func deleteOldSystemStats(app core.App) error {
return nil return nil
} }
// Deletes systemd service records that haven't been updated in the last 20 minutes
func deleteOldSystemdServiceRecords(app core.App) error {
now := time.Now().UTC()
twentyMinutesAgo := now.Add(-20 * time.Minute)
// Delete systemd service records where updated < twentyMinutesAgo
_, err := app.DB().NewQuery("DELETE FROM systemd_services WHERE updated < {:updated}").Bind(dbx.Params{"updated": twentyMinutesAgo.UnixMilli()}).Execute()
if err != nil {
return fmt.Errorf("failed to delete old systemd service records: %v", err)
}
return nil
}
// Deletes container records that haven't been updated in the last 10 minutes // Deletes container records that haven't been updated in the last 10 minutes
func deleteOldContainerRecords(app core.App) error { func deleteOldContainerRecords(app core.App) error {
now := time.Now().UTC() now := time.Now().UTC()

View File

@@ -351,6 +351,83 @@ func TestDeleteOldAlertsHistoryEdgeCases(t *testing.T) {
}) })
} }
// TestDeleteOldSystemdServiceRecords tests systemd service cleanup via DeleteOldRecords
func TestDeleteOldSystemdServiceRecords(t *testing.T) {
hub, err := tests.NewTestHub(t.TempDir())
require.NoError(t, err)
defer hub.Cleanup()
rm := records.NewRecordManager(hub)
// Create test user and system
user, err := tests.CreateUser(hub, "test@example.com", "testtesttest")
require.NoError(t, err)
system, err := tests.CreateRecord(hub, "systems", map[string]any{
"name": "test-system",
"host": "localhost",
"port": "45876",
"status": "up",
"users": []string{user.Id},
})
require.NoError(t, err)
now := time.Now().UTC()
// Create old systemd service records that should be deleted (older than 20 minutes)
oldRecord, err := tests.CreateRecord(hub, "systemd_services", map[string]any{
"system": system.Id,
"name": "nginx.service",
"state": 0, // Active
"sub": 1, // Running
"cpu": 5.0,
"cpuPeak": 10.0,
"memory": 1024000,
"memPeak": 2048000,
})
require.NoError(t, err)
// Set updated time to 25 minutes ago (should be deleted)
oldRecord.SetRaw("updated", now.Add(-25*time.Minute).UnixMilli())
err = hub.SaveNoValidate(oldRecord)
require.NoError(t, err)
// Create recent systemd service record that should be kept (within 20 minutes)
recentRecord, err := tests.CreateRecord(hub, "systemd_services", map[string]any{
"system": system.Id,
"name": "apache.service",
"state": 1, // Inactive
"sub": 0, // Dead
"cpu": 2.0,
"cpuPeak": 3.0,
"memory": 512000,
"memPeak": 1024000,
})
require.NoError(t, err)
// Set updated time to 10 minutes ago (should be kept)
recentRecord.SetRaw("updated", now.Add(-10*time.Minute).UnixMilli())
err = hub.SaveNoValidate(recentRecord)
require.NoError(t, err)
// Count records before deletion
countBefore, err := hub.CountRecords("systemd_services")
require.NoError(t, err)
assert.Equal(t, int64(2), countBefore, "Should have 2 systemd service records initially")
// Run deletion via RecordManager
rm.DeleteOldRecords()
// Count records after deletion
countAfter, err := hub.CountRecords("systemd_services")
require.NoError(t, err)
assert.Equal(t, int64(1), countAfter, "Should have 1 systemd service record after deletion")
// Verify the correct record was kept
remainingRecords, err := hub.FindRecordsByFilter("systemd_services", "", "", 10, 0, nil)
require.NoError(t, err)
assert.Len(t, remainingRecords, 1, "Should have exactly 1 record remaining")
assert.Equal(t, "apache.service", remainingRecords[0].Get("name"), "The recent record should be kept")
}
// TestRecordManagerCreation tests RecordManager creation // TestRecordManagerCreation tests RecordManager creation
func TestRecordManagerCreation(t *testing.T) { func TestRecordManagerCreation(t *testing.T) {
hub, err := tests.NewTestHub(t.TempDir()) hub, err := tests.NewTestHub(t.TempDir())

View File

@@ -11,10 +11,10 @@ export default defineConfig({
"es", "es",
"fa", "fa",
"fr", "fr",
"he",
"hr", "hr",
"hu", "hu",
"it", "it",
"is",
"ja", "ja",
"ko", "ko",
"nl", "nl",

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ export default memo(function AlertsButton({ system }: { system: SystemRecord })
/> />
</Button> </Button>
</SheetTrigger> </SheetTrigger>
<SheetContent className="max-h-full overflow-auto w-145 !max-w-full p-4 sm:p-6"> <SheetContent className="max-h-full overflow-auto w-150 !max-w-full p-4 sm:p-6">
{opened && <AlertDialogContent system={system} />} {opened && <AlertDialogContent system={system} />}
</SheetContent> </SheetContent>
</Sheet> </Sheet>

View File

@@ -17,6 +17,7 @@ export type DataPoint = {
dataKey: (data: SystemStatsRecord) => number | undefined dataKey: (data: SystemStatsRecord) => number | undefined
color: number | string color: number | string
opacity: number opacity: number
stackId?: string | number
} }
export default function AreaChartDefault({ export default function AreaChartDefault({
@@ -29,19 +30,25 @@ export default function AreaChartDefault({
domain, domain,
legend, legend,
itemSorter, itemSorter,
showTotal = false,
reverseStackOrder = false,
hideYAxis = false,
}: // logRender = false, }: // logRender = false,
{ {
chartData: ChartData chartData: ChartData
max?: number max?: number
maxToggled?: boolean maxToggled?: boolean
tickFormatter: (value: number, index: number) => string tickFormatter: (value: number, index: number) => string
contentFormatter: ({ value, payload }: { value: number; payload: SystemStatsRecord }) => string contentFormatter: ({ value, payload }: { value: number; payload: SystemStatsRecord }) => string
dataPoints?: DataPoint[] dataPoints?: DataPoint[]
domain?: [number, number] domain?: [number, number]
legend?: boolean legend?: boolean
itemSorter?: (a: any, b: any) => number showTotal?: boolean
// logRender?: boolean itemSorter?: (a: any, b: any) => number
}) { reverseStackOrder?: boolean
hideYAxis?: boolean
// logRender?: boolean
}) {
const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() const { yAxisWidth, updateYAxisWidth } = useYAxisWidth()
// biome-ignore lint/correctness/useExhaustiveDependencies: ignore // biome-ignore lint/correctness/useExhaustiveDependencies: ignore
@@ -56,21 +63,29 @@ export default function AreaChartDefault({
<div> <div>
<ChartContainer <ChartContainer
className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", { className={cn("h-full w-full absolute aspect-auto bg-card opacity-0 transition-opacity", {
"opacity-100": yAxisWidth, "opacity-100": yAxisWidth || hideYAxis,
"ps-4": hideYAxis,
})} })}
> >
<AreaChart accessibilityLayer data={chartData.systemStats} margin={chartMargin}> <AreaChart
reverseStackOrder={reverseStackOrder}
accessibilityLayer
data={chartData.systemStats}
margin={hideYAxis ? { ...chartMargin, left: 5 } : chartMargin}
>
<CartesianGrid vertical={false} /> <CartesianGrid vertical={false} />
<YAxis {!hideYAxis && (
direction="ltr" <YAxis
orientation={chartData.orientation} direction="ltr"
className="tracking-tighter" orientation={chartData.orientation}
width={yAxisWidth} className="tracking-tighter"
domain={domain ?? [0, max ?? "auto"]} width={yAxisWidth}
tickFormatter={(value, index) => updateYAxisWidth(tickFormatter(value, index))} domain={domain ?? [0, max ?? "auto"]}
tickLine={false} tickFormatter={(value, index) => updateYAxisWidth(tickFormatter(value, index))}
axisLine={false} tickLine={false}
/> axisLine={false}
/>
)}
{xAxis(chartData)} {xAxis(chartData)}
<ChartTooltip <ChartTooltip
animationEasing="ease-out" animationEasing="ease-out"
@@ -81,6 +96,7 @@ export default function AreaChartDefault({
<ChartTooltipContent <ChartTooltipContent
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
contentFormatter={contentFormatter} contentFormatter={contentFormatter}
showTotal={showTotal}
/> />
} }
/> />
@@ -99,13 +115,14 @@ export default function AreaChartDefault({
fillOpacity={dataPoint.opacity} fillOpacity={dataPoint.opacity}
stroke={color} stroke={color}
isAnimationActive={false} isAnimationActive={false}
stackId={dataPoint.stackId}
/> />
) )
})} })}
{legend && <ChartLegend content={<ChartLegendContent />} />} {legend && <ChartLegend content={<ChartLegendContent reverse={reverseStackOrder} />} />}
</AreaChart> </AreaChart>
</ChartContainer> </ChartContainer>
</div> </div>
) )
}, [chartData.systemStats.at(-1), yAxisWidth, maxToggled]) }, [chartData.systemStats.at(-1), yAxisWidth, maxToggled, showTotal])
} }

View File

@@ -41,7 +41,7 @@ export default memo(function ContainerChart({
// tick formatter // tick formatter
if (chartType === ChartType.CPU) { if (chartType === ChartType.CPU) {
obj.tickFormatter = (value) => { obj.tickFormatter = (value) => {
const val = toFixedFloat(value, 2) + unit const val = `${toFixedFloat(value, 2)}%`
return updateYAxisWidth(val) return updateYAxisWidth(val)
} }
} else { } else {
@@ -78,7 +78,7 @@ export default memo(function ContainerChart({
return `${decimalString(value)} ${unit}` return `${decimalString(value)} ${unit}`
} }
} else { } else {
obj.toolTipFormatter = (item: any) => `${decimalString(item.value)} ${unit}` obj.toolTipFormatter = (item: any) => `${decimalString(item.value)}${unit}`
} }
// data function // data function
if (isNetChart) { if (isNetChart) {
@@ -139,7 +139,7 @@ export default memo(function ContainerChart({
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
// @ts-expect-error // @ts-expect-error
itemSorter={(a, b) => b.value - a.value} itemSorter={(a, b) => b.value - a.value}
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />} content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} showTotal={true} />}
/> />
{Object.keys(chartConfig).map((key) => { {Object.keys(chartConfig).map((key) => {
const filtered = filteredKeys.has(key) const filtered = filteredKeys.has(key)

View File

@@ -69,7 +69,7 @@ export function useContainerChartConfigs(containerData: ChartData["containerData
const hue = ((i * 360) / count) % 360 const hue = ((i * 360) / count) % 360
chartConfig[containerName] = { chartConfig[containerName] = {
label: containerName, label: containerName,
color: `hsl(${hue}, 60%, 55%)`, color: `hsl(${hue}, var(--chart-saturation), var(--chart-lightness))`,
} }
} }

View File

@@ -61,6 +61,7 @@ export default memo(function MemChart({ chartData, showMax }: { chartData: Chart
const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true) const { value: convertedValue, unit } = formatBytes(value * 1024, false, Unit.Bytes, true)
return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit return decimalString(convertedValue, convertedValue >= 100 ? 1 : 2) + " " + unit
}} }}
showTotal={true}
/> />
} }
/> />

View File

@@ -35,6 +35,7 @@ import { getPagePath } from "@nanostores/router"
const syntaxTheme = "github-dark-dimmed" const syntaxTheme = "github-dark-dimmed"
export default function ContainersTable({ systemId }: { systemId?: string }) { export default function ContainersTable({ systemId }: { systemId?: string }) {
const loadTime = Date.now()
const [data, setData] = useState<ContainerRecord[]>([]) const [data, setData] = useState<ContainerRecord[]>([])
const [sorting, setSorting] = useBrowserStorage<SortingState>( const [sorting, setSorting] = useBrowserStorage<SortingState>(
`sort-c-${systemId ? 1 : 0}`, `sort-c-${systemId ? 1 : 0}`,
@@ -47,55 +48,53 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
const [globalFilter, setGlobalFilter] = useState("") const [globalFilter, setGlobalFilter] = useState("")
useEffect(() => { useEffect(() => {
const pbOptions = { function fetchData(systemId?: string) {
fields: "id,name,image,cpu,memory,net,health,status,system,updated",
}
const fetchData = (lastXMs: number) => {
const updated = Date.now() - lastXMs
let filter: string
if (systemId) {
filter = pb.filter("system={:system} && updated > {:updated}", { system: systemId, updated })
} else {
filter = pb.filter("updated > {:updated}", { updated })
}
pb.collection<ContainerRecord>("containers") pb.collection<ContainerRecord>("containers")
.getList(0, 2000, { .getList(0, 2000, {
...pbOptions, fields: "id,name,image,cpu,memory,net,health,status,system,updated",
filter, filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
}) })
.then(({ items }) => setData((curItems) => { .then(({ items }) => items.length && setData((curItems) => {
const containerIds = new Set(items.map(item => item.id)) const lastUpdated = Math.max(items[0].updated, items.at(-1)?.updated ?? 0)
const now = Date.now() const containerIds = new Set()
for (const item of curItems) { const newItems = []
if (!containerIds.has(item.id) && now - item.updated < 70_000) { for (const item of items) {
items.push(item) if (Math.abs(lastUpdated - item.updated) < 70_000) {
containerIds.add(item.id)
newItems.push(item)
} }
} }
return items for (const item of curItems) {
if (!containerIds.has(item.id) && lastUpdated - item.updated < 70_000) {
newItems.push(item)
}
}
return newItems
})) }))
} }
// initial load // initial load
fetchData(70_000) fetchData(systemId)
// if no systemId, poll every 10 seconds // if no systemId, pull system containers after every system update
if (!systemId) { if (!systemId) {
// poll every 10 seconds return $allSystemsById.listen((_value, _oldValue, systemId) => {
const intervalId = setInterval(() => fetchData(10_500), 10_000) // exclude initial load of systems
// clear interval on unmount if (Date.now() - loadTime > 500) {
return () => clearInterval(intervalId) fetchData(systemId)
}
})
} }
// if systemId, fetch containers after the system is updated // if systemId, fetch containers after the system is updated
return listenKeys($allSystemsById, [systemId], (_newSystems) => { return listenKeys($allSystemsById, [systemId], (_newSystems) => {
setTimeout(() => fetchData(1000), 100) fetchData(systemId)
}) })
}, []) }, [])
const table = useReactTable({ const table = useReactTable({
data, data,
columns: containerChartCols.filter(col => systemId ? col.id !== "system" : true), columns: containerChartCols.filter((col) => (systemId ? col.id !== "system" : true)),
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
@@ -163,78 +162,79 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
) )
} }
const AllContainersTable = memo( const AllContainersTable = memo(function AllContainersTable({
function AllContainersTable({ table, rows, colLength }: { table: TableType<ContainerRecord>; rows: Row<ContainerRecord>[]; colLength: number }) { table,
// The virtualizer will need a reference to the scrollable container element rows,
const scrollRef = useRef<HTMLDivElement>(null) colLength,
const activeContainer = useRef<ContainerRecord | null>(null) }: {
const [sheetOpen, setSheetOpen] = useState(false) table: TableType<ContainerRecord>
const openSheet = (container: ContainerRecord) => { rows: Row<ContainerRecord>[]
activeContainer.current = container colLength: number
setSheetOpen(true) }) {
} // The virtualizer will need a reference to the scrollable container element
const scrollRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({ const activeContainer = useRef<ContainerRecord | null>(null)
count: rows.length, const [sheetOpen, setSheetOpen] = useState(false)
estimateSize: () => 54, const openSheet = (container: ContainerRecord) => {
getScrollElement: () => scrollRef.current, activeContainer.current = container
overscan: 5, setSheetOpen(true)
})
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))
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() + 48}px`, paddingTop, paddingBottom }}>
<table className="text-sm w-full h-full text-nowrap">
<ContainersTableHead table={table} />
<TableBody>
{rows.length ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index]
return (
<ContainerTableRow
key={row.id}
row={row}
virtualRow={virtualRow}
openSheet={openSheet}
/>
)
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
<Trans>No results.</Trans>
</TableCell>
</TableRow>
)}
</TableBody>
</table>
</div>
<ContainerSheet sheetOpen={sheetOpen} setSheetOpen={setSheetOpen} activeContainer={activeContainer} />
</div>
)
} }
)
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
count: rows.length,
estimateSize: () => 54,
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))
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() + 48}px`, paddingTop, paddingBottom }}>
<table className="text-sm w-full h-full text-nowrap">
<ContainersTableHead table={table} />
<TableBody>
{rows.length ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index]
return <ContainerTableRow key={row.id} row={row} virtualRow={virtualRow} openSheet={openSheet} />
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
<Trans>No results.</Trans>
</TableCell>
</TableRow>
)}
</TableBody>
</table>
</div>
<ContainerSheet sheetOpen={sheetOpen} setSheetOpen={setSheetOpen} activeContainer={activeContainer} />
</div>
)
})
async function getLogsHtml(container: ContainerRecord): Promise<string> { async function getLogsHtml(container: ContainerRecord): Promise<string> {
try { try {
const [{ highlighter }, logsHtml] = await Promise.all([import('@/lib/shiki'), pb.send<{ logs: string }>("/api/beszel/containers/logs", { const [{ highlighter }, logsHtml] = await Promise.all([
system: container.system, import("@/lib/shiki"),
container: container.id, pb.send<{ logs: string }>("/api/beszel/containers/logs", {
})]) system: container.system,
return highlighter.codeToHtml(logsHtml.logs, { lang: "log", theme: syntaxTheme }) container: container.id,
}),
])
return logsHtml.logs ? highlighter.codeToHtml(logsHtml.logs, { lang: "log", theme: syntaxTheme }) : t`No results.`
} catch (error) { } catch (error) {
console.error(error) console.error(error)
return "" return ""
@@ -243,21 +243,32 @@ async function getLogsHtml(container: ContainerRecord): Promise<string> {
async function getInfoHtml(container: ContainerRecord): Promise<string> { async function getInfoHtml(container: ContainerRecord): Promise<string> {
try { try {
let [{ highlighter }, { info }] = await Promise.all([import('@/lib/shiki'), pb.send<{ info: string }>("/api/beszel/containers/info", { let [{ highlighter }, { info }] = await Promise.all([
system: container.system, import("@/lib/shiki"),
container: container.id, pb.send<{ info: string }>("/api/beszel/containers/info", {
})]) system: container.system,
container: container.id,
}),
])
try { try {
info = JSON.stringify(JSON.parse(info), null, 2) info = JSON.stringify(JSON.parse(info), null, 2)
} catch (_) { } } catch (_) { }
return highlighter.codeToHtml(info, { lang: "json", theme: syntaxTheme }) return info ? highlighter.codeToHtml(info, { lang: "json", theme: syntaxTheme }) : t`No results.`
} catch (error) { } catch (error) {
console.error(error) console.error(error)
return "" return ""
} }
} }
function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpen: boolean, setSheetOpen: (open: boolean) => void, activeContainer: RefObject<ContainerRecord | null> }) { function ContainerSheet({
sheetOpen,
setSheetOpen,
activeContainer,
}: {
sheetOpen: boolean
setSheetOpen: (open: boolean) => void
activeContainer: RefObject<ContainerRecord | null>
}) {
const container = activeContainer.current const container = activeContainer.current
if (!container) return null if (!container) return null
@@ -296,14 +307,14 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
useEffect(() => { useEffect(() => {
setLogsDisplay("") setLogsDisplay("")
setInfoDisplay(""); setInfoDisplay("")
if (!container) return if (!container) return
(async () => { ; (async () => {
const [logsHtml, infoHtml] = await Promise.all([getLogsHtml(container), getInfoHtml(container)]) const [logsHtml, infoHtml] = await Promise.all([getLogsHtml(container), getInfoHtml(container)])
setLogsDisplay(logsHtml) setLogsDisplay(logsHtml)
setInfoDisplay(infoHtml) setInfoDisplay(infoHtml)
setTimeout(scrollLogsToBottom, 20) setTimeout(scrollLogsToBottom, 20)
})() })()
}, [container]) }, [container])
return ( return (
@@ -327,7 +338,9 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
<SheetHeader> <SheetHeader>
<SheetTitle>{container.name}</SheetTitle> <SheetTitle>{container.name}</SheetTitle>
<SheetDescription className="flex flex-wrap items-center gap-x-2 gap-y-1"> <SheetDescription className="flex flex-wrap items-center gap-x-2 gap-y-1">
<Link className="hover:underline" href={getPagePath($router, "system", { id: container.system })}>{$allSystemsById.get()[container.system]?.name ?? ""}</Link> <Link className="hover:underline" href={getPagePath($router, "system", { id: container.system })}>
{$allSystemsById.get()[container.system]?.name ?? ""}
</Link>
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" /> <Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
{container.status} {container.status}
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" /> <Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
@@ -349,19 +362,20 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
disabled={isRefreshingLogs} disabled={isRefreshingLogs}
> >
<RefreshCwIcon <RefreshCwIcon
className={`size-4 transition-transform duration-300 ${isRefreshingLogs ? 'animate-spin' : ''}`} className={`size-4 transition-transform duration-300 ${isRefreshingLogs ? "animate-spin" : ""}`}
/> />
</Button> </Button>
<Button <Button variant="ghost" size="sm" onClick={() => setLogsFullscreenOpen(true)} className="h-8 w-8 p-0">
variant="ghost"
size="sm"
onClick={() => setLogsFullscreenOpen(true)}
className="h-8 w-8 p-0"
>
<MaximizeIcon className="size-4" /> <MaximizeIcon className="size-4" />
</Button> </Button>
</div> </div>
<div ref={logsContainerRef} className={cn("max-h-[calc(50dvh-10rem)] w-full overflow-auto p-3 rounded-md bg-gh-dark text-sm", !logsDisplay && ["animate-pulse", "h-full"])}> <div
ref={logsContainerRef}
className={cn(
"max-h-[calc(50dvh-10rem)] w-full overflow-auto p-3 rounded-md bg-gh-dark text-white text-sm",
!logsDisplay && ["animate-pulse", "h-full"]
)}
>
<div dangerouslySetInnerHTML={{ __html: logsDisplay }} /> <div dangerouslySetInnerHTML={{ __html: logsDisplay }} />
</div> </div>
<div className="flex items-center w-full"> <div className="flex items-center w-full">
@@ -375,15 +389,18 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
<MaximizeIcon className="size-4" /> <MaximizeIcon className="size-4" />
</Button> </Button>
</div> </div>
<div className={cn("grow h-[calc(50dvh-4rem)] w-full overflow-auto p-3 rounded-md bg-gh-dark text-sm", !infoDisplay && "animate-pulse")}> <div
className={cn(
"grow h-[calc(50dvh-4rem)] w-full overflow-auto p-3 rounded-md bg-gh-dark text-white text-sm",
!infoDisplay && "animate-pulse"
)}
>
<div dangerouslySetInnerHTML={{ __html: infoDisplay }} /> <div dangerouslySetInnerHTML={{ __html: infoDisplay }} />
</div> </div>
</div> </div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
</> </>
) )
} }
@@ -405,39 +422,51 @@ function ContainersTableHead({ table }: { table: TableType<ContainerRecord> }) {
) )
} }
const ContainerTableRow = memo( const ContainerTableRow = memo(function ContainerTableRow({
function ContainerTableRow({ row,
row, virtualRow,
virtualRow, openSheet,
openSheet, }: {
}: { row: Row<ContainerRecord>
row: Row<ContainerRecord> virtualRow: VirtualItem
virtualRow: VirtualItem openSheet: (container: ContainerRecord) => void
openSheet: (container: ContainerRecord) => void }) {
}) { return (
return ( <TableRow
<TableRow data-state={row.getIsSelected() && "selected"}
data-state={row.getIsSelected() && "selected"} className="cursor-pointer transition-opacity"
className="cursor-pointer transition-opacity" onClick={() => openSheet(row.original)}
onClick={() => openSheet(row.original)} >
> {row.getVisibleCells().map((cell) => (
{row.getVisibleCells().map((cell) => ( <TableCell
<TableCell key={cell.id}
key={cell.id} className="py-0"
className="py-0" style={{
style={{ height: virtualRow.size,
height: virtualRow.size, }}
}} >
> {flexRender(cell.column.columnDef.cell, cell.getContext())}
{flexRender(cell.column.columnDef.cell, cell.getContext())} </TableCell>
</TableCell> ))}
))} </TableRow>
</TableRow> )
) })
}
)
function LogsFullscreenDialog({ open, onOpenChange, logsDisplay, containerName, onRefresh, isRefreshing }: { open: boolean, onOpenChange: (open: boolean) => void, logsDisplay: string, containerName: string, onRefresh: () => void | Promise<void>, isRefreshing: boolean }) { function LogsFullscreenDialog({
open,
onOpenChange,
logsDisplay,
containerName,
onRefresh,
isRefreshing,
}: {
open: boolean
onOpenChange: (open: boolean) => void
logsDisplay: string
containerName: string
onRefresh: () => void | Promise<void>
isRefreshing: boolean
}) {
const outerContainerRef = useRef<HTMLDivElement>(null) const outerContainerRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
@@ -470,16 +499,24 @@ function LogsFullscreenDialog({ open, onOpenChange, logsDisplay, containerName,
title={t`Refresh`} title={t`Refresh`}
aria-label={t`Refresh`} aria-label={t`Refresh`}
> >
<RefreshCwIcon <RefreshCwIcon className={`size-4 transition-transform duration-300 ${isRefreshing ? "animate-spin" : ""}`} />
className={`size-4 transition-transform duration-300 ${isRefreshing ? 'animate-spin' : ''}`}
/>
</button> </button>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) )
} }
function InfoFullscreenDialog({ open, onOpenChange, infoDisplay, containerName }: { open: boolean, onOpenChange: (open: boolean) => void, infoDisplay: string, containerName: string }) { function InfoFullscreenDialog({
open,
onOpenChange,
infoDisplay,
containerName,
}: {
open: boolean
onOpenChange: (open: boolean) => void
infoDisplay: string
containerName: string
}) {
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[calc(100vw-20px)] h-[calc(100dvh-20px)] max-w-none p-0 bg-gh-dark border-0 text-white"> <DialogContent className="w-[calc(100vw-20px)] h-[calc(100dvh-20px)] max-w-none p-0 bg-gh-dark border-0 text-white">

View File

@@ -1,5 +1,5 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { Plural, Trans, useLingui } from "@lingui/react/macro" import { Trans, useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { getPagePath } from "@nanostores/router" import { getPagePath } from "@nanostores/router"
import { timeTicks } from "d3-time" import { timeTicks } from "d3-time"
@@ -42,9 +42,9 @@ import {
chartTimeData, chartTimeData,
cn, cn,
compareSemVer, compareSemVer,
debounce,
decimalString, decimalString,
formatBytes, formatBytes,
secondsToString,
getHostDisplayValue, getHostDisplayValue,
listen, listen,
parseSemVer, parseSemVer,
@@ -72,10 +72,9 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
import { Separator } from "../ui/separator" import { Separator } from "../ui/separator"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
import NetworkSheet from "./system/network-sheet" import NetworkSheet from "./system/network-sheet"
import CpuCoresSheet from "./system/cpu-sheet"
import LineChartDefault from "../charts/line-chart" import LineChartDefault from "../charts/line-chart"
type ChartTimeData = { type ChartTimeData = {
time: number time: number
data: { data: {
@@ -96,8 +95,8 @@ function getTimeData(chartTime: ChartTimes, lastCreated: number) {
} }
} }
const buffer = chartTime === "1m" ? 400 : 20_000 // const buffer = chartTime === "1m" ? 400 : 20_000
const now = new Date(Date.now() + buffer) const now = new Date(Date.now())
const startTime = chartTimeData[chartTime].getOffset(now) const startTime = chartTimeData[chartTime].getOffset(now)
const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime()) const ticks = timeTicks(startTime, now, chartTimeData[chartTime].ticks ?? 12).map((date) => date.getTime())
const data = { const data = {
@@ -358,21 +357,13 @@ export default memo(function SystemDetail({ id }: { id: string }) {
value: system.info.k, value: system.info.k,
}, },
} }
let uptime: React.ReactNode let uptime: string
if (system.info.u < 3600) { if (system.info.u < 3600) {
uptime = ( uptime = secondsToString(system.info.u, "minute")
<Plural } else if (system.info.u < 360000) {
value={Math.trunc(system.info.u / 60)} uptime = secondsToString(system.info.u, "hour")
one="# minute"
few="# minutes"
many="# minutes"
other="# minutes"
/>
)
} else if (system.info.u < 172800) {
uptime = <Plural value={Math.trunc(system.info.u / 3600)} one="# hour" other="# hours" />
} else { } else {
uptime = <Plural value={Math.trunc(system.info?.u / 86400)} one="# day" other="# days" /> uptime = secondsToString(system.info.u, "day")
} }
return [ return [
{ value: getHostDisplayValue(system), Icon: GlobeIcon }, { value: getHostDisplayValue(system), Icon: GlobeIcon },
@@ -592,7 +583,12 @@ export default memo(function SystemDetail({ id }: { id: string }) {
grid={grid} grid={grid}
title={t`CPU Usage`} title={t`CPU Usage`}
description={t`Average system-wide CPU utilization`} description={t`Average system-wide CPU utilization`}
cornerEl={maxValSelect} cornerEl={
<div className="flex gap-2">
{maxValSelect}
<CpuCoresSheet chartData={chartData} dataEmpty={dataEmpty} grid={grid} maxValues={maxValues} />
</div>
}
> >
<AreaChartDefault <AreaChartDefault
chartData={chartData} chartData={chartData}
@@ -700,6 +696,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, false) const { value: convertedValue, unit } = formatBytes(value, true, userSettings.unitDisk, false)
return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}` return `${decimalString(convertedValue, convertedValue >= 100 ? 1 : 2)} ${unit}`
}} }}
showTotal={true}
/> />
</ChartCard> </ChartCard>
@@ -753,6 +750,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
const { value, unit } = formatBytes(data.value, true, userSettings.unitNet, false) const { value, unit } = formatBytes(data.value, true, userSettings.unitNet, false)
return `${decimalString(value, value >= 100 ? 1 : 2)} ${unit}` return `${decimalString(value, value >= 100 ? 1 : 2)} ${unit}`
}} }}
showTotal={true}
/> />
</ChartCard> </ChartCard>
@@ -965,9 +963,9 @@ export default memo(function SystemDetail({ id }: { id: string }) {
label: t`Write`, label: t`Write`,
dataKey: ({ stats }) => { dataKey: ({ stats }) => {
if (showMax) { if (showMax) {
return stats?.efs?.[extraFsName]?.wb ?? (stats?.efs?.[extraFsName]?.wm ?? 0) * 1024 * 1024 return stats?.efs?.[extraFsName]?.wbm || (stats?.efs?.[extraFsName]?.wm ?? 0) * 1024 * 1024
} }
return stats?.efs?.[extraFsName]?.wb ?? (stats?.efs?.[extraFsName]?.w ?? 0) * 1024 * 1024 return stats?.efs?.[extraFsName]?.wb || (stats?.efs?.[extraFsName]?.w ?? 0) * 1024 * 1024
}, },
color: 3, color: 3,
opacity: 0.3, opacity: 0.3,
@@ -1003,15 +1001,20 @@ export default memo(function SystemDetail({ id }: { id: string }) {
</div> </div>
)} )}
{compareSemVer(chartData.agentVersion, parseSemVer("0.15.0")) >= 0 && (
{containerData.length > 0 && compareSemVer(chartData.agentVersion, parseSemVer("0.14.0")) >= 0 && ( <LazySmartTable systemId={system.id} />
<LazyContainersTable systemId={id} />
)} )}
<LazySmartTable systemId={system.id} /> {containerData.length > 0 && compareSemVer(chartData.agentVersion, parseSemVer("0.14.0")) >= 0 && (
<LazyContainersTable systemId={system.id} />
)}
{system.info?.os === Os.Linux && compareSemVer(chartData.agentVersion, parseSemVer("0.16.0")) >= 0 && (
<LazySystemdTable systemId={system.id} />
)}
</div> </div>
{/* add space for tooltip if more than 12 containers */} {/* add space for tooltip if lots of sensors */}
{bottomSpacing > 0 && <span className="block" style={{ height: bottomSpacing }} />} {bottomSpacing > 0 && <span className="block" style={{ height: bottomSpacing }} />}
</> </>
) )
@@ -1040,32 +1043,51 @@ function GpuEnginesChart({ chartData }: { chartData: ChartData }) {
} }
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) { function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
const containerFilter = useStore(store) const storeValue = useStore(store)
const [inputValue, setInputValue] = useState(storeValue)
const { t } = useLingui() const { t } = useLingui()
const debouncedStoreSet = useMemo(() => debounce((value: string) => store.set(value), 80), [store]) useEffect(() => {
setInputValue(storeValue)
}, [storeValue])
useEffect(() => {
if (inputValue === storeValue) {
return
}
const handle = window.setTimeout(() => store.set(inputValue), 80)
return () => clearTimeout(handle)
}, [inputValue, storeValue, store])
const handleChange = useCallback( const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => debouncedStoreSet(e.target.value), (e: React.ChangeEvent<HTMLInputElement>) => {
[debouncedStoreSet] const value = e.target.value
setInputValue(value)
},
[]
) )
const handleClear = useCallback(() => {
setInputValue("")
store.set("")
}, [store])
return ( return (
<> <>
<Input <Input
placeholder={t`Filter...`} placeholder={t`Filter...`}
className="ps-4 pe-8 w-full sm:w-44" className="ps-4 pe-8 w-full sm:w-44"
onChange={handleChange} onChange={handleChange}
value={containerFilter} value={inputValue}
/> />
{containerFilter && ( {inputValue && (
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
size="icon" size="icon"
aria-label="Clear" aria-label="Clear"
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100" className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
onClick={() => store.set("")} onClick={handleClear}
> >
<XIcon className="h-4 w-4" /> <XIcon className="h-4 w-4" />
</Button> </Button>
@@ -1125,7 +1147,7 @@ export function ChartCard({
<CardDescription>{description}</CardDescription> <CardDescription>{description}</CardDescription>
{cornerEl && <div className="py-1 grid sm:justify-end sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>} {cornerEl && <div className="py-1 grid sm:justify-end sm:absolute sm:top-3.5 sm:end-3.5">{cornerEl}</div>}
</CardHeader> </CardHeader>
<div className={cn("ps-0 w-[calc(100%-1.5em)] relative group", legend ? "h-54 md:h-56" : "h-48 md:h-52")}> <div className={cn("ps-0 w-[calc(100%-1.3em)] relative group", legend ? "h-54 md:h-56" : "h-48 md:h-52")}>
{ {
<Spinner <Spinner
msg={empty ? t`Waiting for enough records to display` : undefined} msg={empty ? t`Waiting for enough records to display` : undefined}
@@ -1144,7 +1166,7 @@ const ContainersTable = lazy(() => import("../containers-table/containers-table"
function LazyContainersTable({ systemId }: { systemId: string }) { function LazyContainersTable({ systemId }: { systemId: string }) {
const { isIntersecting, ref } = useIntersectionObserver({ rootMargin: "90px" }) const { isIntersecting, ref } = useIntersectionObserver({ rootMargin: "90px" })
return ( return (
<div ref={ref}> <div ref={ref} className={cn(isIntersecting && "contents")}>
{isIntersecting && <ContainersTable systemId={systemId} />} {isIntersecting && <ContainersTable systemId={systemId} />}
</div> </div>
) )
@@ -1153,10 +1175,21 @@ function LazyContainersTable({ systemId }: { systemId: string }) {
const SmartTable = lazy(() => import("./system/smart-table")) const SmartTable = lazy(() => import("./system/smart-table"))
function LazySmartTable({ systemId }: { systemId: string }) { function LazySmartTable({ systemId }: { systemId: string }) {
const { isIntersecting, ref } = useIntersectionObserver() const { isIntersecting, ref } = useIntersectionObserver({ rootMargin: "90px" })
return ( return (
<div ref={ref}> <div ref={ref} className={cn(isIntersecting && "contents")}>
{isIntersecting && <SmartTable systemId={systemId} />} {isIntersecting && <SmartTable systemId={systemId} />}
</div> </div>
) )
}
const SystemdTable = lazy(() => import("../systemd-table/systemd-table"))
function LazySystemdTable({ systemId }: { systemId: string }) {
const { isIntersecting, ref } = useIntersectionObserver()
return (
<div ref={ref} className={cn(isIntersecting && "contents")}>
{isIntersecting && <SystemdTable systemId={systemId} />}
</div>
)
} }

View File

@@ -0,0 +1,195 @@
import { t } from "@lingui/core/macro"
import { MoreHorizontalIcon } from "lucide-react"
import { memo, useRef, useState } from "react"
import AreaChartDefault, { DataPoint } from "@/components/charts/area-chart"
import ChartTimeSelect from "@/components/charts/chart-time-select"
import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { DialogTitle } from "@/components/ui/dialog"
import { compareSemVer, decimalString, parseSemVer, toFixedFloat } from "@/lib/utils"
import type { ChartData, SystemStatsRecord } from "@/types"
import { ChartCard } from "../system"
const minAgentVersion = parseSemVer("0.15.3")
export default memo(function CpuCoresSheet({
chartData,
dataEmpty,
grid,
maxValues,
}: {
chartData: ChartData
dataEmpty: boolean
grid: boolean
maxValues: boolean
}) {
const [cpuCoresOpen, setCpuCoresOpen] = useState(false)
const hasOpened = useRef(false)
const supportsBreakdown = compareSemVer(chartData.agentVersion, minAgentVersion) >= 0
if (!supportsBreakdown) {
return null
}
if (cpuCoresOpen && !hasOpened.current) {
hasOpened.current = true
}
// Latest stats snapshot
const latest = chartData.systemStats.at(-1)?.stats
const cpus = latest?.cpus ?? []
const numCores = cpus.length
const hasBreakdown = (latest?.cpub?.length ?? 0) > 0
const breakdownDataPoints = [
{
label: "System",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[1],
color: 3,
opacity: 0.35,
stackId: "a"
},
{
label: "User",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[0],
color: 1,
opacity: 0.35,
stackId: "a"
},
{
label: "IOWait",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[2],
color: 4,
opacity: 0.35,
stackId: "a"
},
{
label: "Steal",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[3],
color: 5,
opacity: 0.35,
stackId: "a"
},
{
label: "Idle",
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpub?.[4],
color: 2,
opacity: 0.35,
stackId: "a"
},
{
label: t`Other`,
dataKey: ({ stats }: SystemStatsRecord) => {
const total = stats?.cpub?.reduce((acc, curr) => acc + curr, 0) ?? 0
return total > 0 ? 100 - total : null
},
color: `hsl(80, 65%, 52%)`,
opacity: 0.35,
stackId: "a"
},
] as DataPoint[]
return (
<Sheet open={cpuCoresOpen} onOpenChange={setCpuCoresOpen}>
<DialogTitle className="sr-only">{t`CPU Usage`}</DialogTitle>
<SheetTrigger asChild>
<Button
title={t`View more`}
variant="outline"
size="icon"
className="shrink-0 max-sm:absolute max-sm:top-3 max-sm:end-3"
>
<MoreHorizontalIcon />
</Button>
</SheetTrigger>
{hasOpened.current && (
<SheetContent aria-describedby={undefined} className="overflow-auto w-200 !max-w-full p-4 sm:p-6">
<ChartTimeSelect className="w-[calc(100%-2em)] bg-card" agentVersion={chartData.agentVersion} />
{hasBreakdown && (
<ChartCard
key="cpu-breakdown"
empty={dataEmpty}
grid={grid}
title={t`CPU Time Breakdown`}
description={t`Percentage of time spent in each state`}
legend={true}
className="min-h-auto"
>
<AreaChartDefault
chartData={chartData}
maxToggled={maxValues}
legend={true}
dataPoints={breakdownDataPoints}
tickFormatter={(val) => `${toFixedFloat(val, 2)}%`}
contentFormatter={({ value }) => `${decimalString(value)}%`}
reverseStackOrder={true}
itemSorter={() => 1}
domain={[0, 100]}
/>
</ChartCard>
)}
{numCores > 0 && (
<ChartCard
key="cpu-cores-all"
empty={dataEmpty}
grid={grid}
title={t`CPU Cores`}
legend={numCores < 10}
description={t`Per-core average utilization`}
className="min-h-auto"
>
<AreaChartDefault
hideYAxis={true}
chartData={chartData}
maxToggled={maxValues}
legend={numCores < 10}
dataPoints={Array.from({ length: numCores }).map((_, i) => ({
label: `CPU ${i}`,
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpus?.[i] ?? 1 / (stats?.cpus?.length ?? 1),
color: `hsl(${226 + (((i * 360) / Math.max(1, numCores)) % 360)}, var(--chart-saturation), var(--chart-lightness))`,
opacity: 0.35,
stackId: "a"
}))}
tickFormatter={(val) => `${val}%`}
contentFormatter={({ value }) => `${value}%`}
reverseStackOrder={true}
itemSorter={() => 1}
/>
</ChartCard>
)}
{Array.from({ length: numCores }).map((_, i) => (
<ChartCard
key={`cpu-core-${i}`}
empty={dataEmpty}
grid={grid}
title={`CPU ${i}`}
description={t`Per-core average utilization`}
legend={false}
className="min-h-auto"
>
<AreaChartDefault
chartData={chartData}
maxToggled={maxValues}
legend={false}
dataPoints={[
{
label: t`Usage`,
dataKey: ({ stats }: SystemStatsRecord) => stats?.cpus?.[i],
color: `hsl(${226 + (((i * 360) / Math.max(1, numCores)) % 360)}, 65%, 52%)`,
opacity: 0.35,
},
]}
tickFormatter={(val) => `${val}%`}
contentFormatter={({ value }) => `${value}%`}
/>
</ChartCard>
))}
</SheetContent>
)}
</Sheet>
)
})

View File

@@ -53,7 +53,7 @@ export default memo(function NetworkSheet({
</SheetTrigger> </SheetTrigger>
{hasOpened.current && ( {hasOpened.current && (
<SheetContent aria-describedby={undefined} className="overflow-auto w-200 !max-w-full p-4 sm:p-6"> <SheetContent aria-describedby={undefined} className="overflow-auto w-200 !max-w-full p-4 sm:p-6">
<ChartTimeSelect className="w-[calc(100%-2em)]" agentVersion={chartData.agentVersion} /> <ChartTimeSelect className="w-[calc(100%-2em)] bg-card" agentVersion={chartData.agentVersion} />
<ChartCard <ChartCard
empty={dataEmpty} empty={dataEmpty}
grid={grid} grid={grid}

View File

@@ -3,6 +3,7 @@ import { t } from "@lingui/core/macro"
import { import {
ColumnDef, ColumnDef,
ColumnFiltersState, ColumnFiltersState,
Column,
flexRender, flexRender,
getCoreRowModel, getCoreRowModel,
getFilteredRowModel, getFilteredRowModel,
@@ -23,9 +24,10 @@ import {
TableRow, TableRow,
} from "@/components/ui/table" } from "@/components/ui/table"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { pb } from "@/lib/api" import { pb } from "@/lib/api"
import { SmartData, SmartAttribute } from "@/types" import { SmartData, SmartAttribute } from "@/types"
import { formatBytes, toFixedFloat, formatTemperature } from "@/lib/utils" import { formatBytes, toFixedFloat, formatTemperature, cn, secondsToString } from "@/lib/utils"
import { Trans } from "@lingui/react/macro" import { Trans } from "@lingui/react/macro"
import { ThermometerIcon } from "@/components/ui/icons" import { ThermometerIcon } from "@/components/ui/icons"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
@@ -87,18 +89,25 @@ function formatCapacity(bytes: number): string {
// Function to convert SmartData to DiskInfo // Function to convert SmartData to DiskInfo
function convertSmartDataToDiskInfo(smartDataRecord: Record<string, SmartData>): DiskInfo[] { function convertSmartDataToDiskInfo(smartDataRecord: Record<string, SmartData>): DiskInfo[] {
const unknown = "Unknown"
return Object.entries(smartDataRecord).map(([key, smartData]) => ({ return Object.entries(smartDataRecord).map(([key, smartData]) => ({
device: smartData.dn || key, device: smartData.dn || key,
model: smartData.mn || "Unknown", model: smartData.mn || unknown,
serialNumber: smartData.sn || "Unknown", serialNumber: smartData.sn || unknown,
firmwareVersion: smartData.fv || "Unknown", firmwareVersion: smartData.fv || unknown,
capacity: smartData.c ? formatCapacity(smartData.c) : "Unknown", capacity: smartData.c ? formatCapacity(smartData.c) : unknown,
status: smartData.s || "Unknown", status: smartData.s || unknown,
temperature: smartData.t || 0, temperature: smartData.t || 0,
deviceType: smartData.dt || "Unknown", deviceType: smartData.dt || unknown,
// These fields need to be extracted from SmartAttribute if available // These fields need to be extracted from SmartAttribute if available
powerOnHours: smartData.a?.find(attr => attr.n.toLowerCase().includes("poweronhours") || attr.n.toLowerCase().includes("power_on_hours"))?.rv, powerOnHours: smartData.a?.find(attr => {
powerCycles: smartData.a?.find(attr => attr.n.toLowerCase().includes("power") && attr.n.toLowerCase().includes("cycle"))?.rv, const name = attr.n.toLowerCase();
return name.includes("poweronhours") || name.includes("power_on_hours");
})?.rv,
powerCycles: smartData.a?.find(attr => {
const name = attr.n.toLowerCase();
return (name.includes("power") && name.includes("cycle")) || name.includes("startstopcycles");
})?.rv,
})) }))
} }
@@ -106,151 +115,136 @@ function convertSmartDataToDiskInfo(smartDataRecord: Record<string, SmartData>):
export const columns: ColumnDef<DiskInfo>[] = [ export const columns: ColumnDef<DiskInfo>[] = [
{ {
accessorKey: "device", accessorKey: "device",
header: () => ( sortingFn: (a, b) => a.original.device.localeCompare(b.original.device),
<div className="flex items-center gap-1.5"> header: ({ column }) => <HeaderButton column={column} name={t`Device`} Icon={HardDrive} />,
<HardDrive className="size-4" />
<Trans>Device</Trans>
</div>
),
cell: ({ row }) => ( cell: ({ row }) => (
<div className="font-medium">{row.getValue("device")}</div> <div className="font-medium ms-1.5">{row.getValue("device")}</div>
), ),
}, },
{ {
accessorKey: "model", accessorKey: "model",
header: () => ( sortingFn: (a, b) => a.original.model.localeCompare(b.original.model),
<div className="flex items-center gap-1.5"> header: ({ column }) => <HeaderButton column={column} name={t`Model`} Icon={Box} />,
<Box className="size-4" />
<Trans>Model</Trans>
</div>
),
cell: ({ row }) => ( cell: ({ row }) => (
<div className="max-w-50 truncate" title={row.getValue("model")}> <div className="max-w-50 truncate ms-1.5" title={row.getValue("model")}>
{row.getValue("model")} {row.getValue("model")}
</div> </div>
), ),
}, },
{ {
accessorKey: "capacity", accessorKey: "capacity",
header: () => ( header: ({ column }) => <HeaderButton column={column} name={t`Capacity`} Icon={BinaryIcon} />,
<div className="flex items-center gap-1.5"> cell: ({ getValue }) => (
<BinaryIcon className="size-4" /> <span className="ms-1.5">{getValue() as string}</span>
<Trans>Capacity</Trans>
</div>
), ),
}, },
{ {
accessorKey: "temperature", accessorKey: "temperature",
header: () => ( invertSorting: true,
<div className="flex items-center gap-2"> header: ({ column }) => <HeaderButton column={column} name={t`Temp`} Icon={ThermometerIcon} />,
<ThermometerIcon className="size-4" />
<Trans>Temp</Trans>
</div>
),
cell: ({ getValue }) => { cell: ({ getValue }) => {
const { value, unit } = formatTemperature(getValue() as number) const { value, unit } = formatTemperature(getValue() as number)
return `${value} ${unit}` return <span className="ms-1.5">{`${value} ${unit}`}</span>
}, },
}, },
{ {
accessorKey: "status", accessorKey: "status",
header: () => ( header: ({ column }) => <HeaderButton column={column} name={t`Status`} Icon={Activity} />,
<div className="flex items-center gap-2">
<Activity className="size-4" />
<Trans>Status</Trans>
</div>
),
cell: ({ getValue }) => { cell: ({ getValue }) => {
const status = getValue() as string const status = getValue() as string
return ( return (
<Badge <div className="ms-1.5">
variant={status === "PASSED" ? "success" : status === "FAILED" ? "danger" : "warning"} <Badge
> variant={status === "PASSED" ? "success" : status === "FAILED" ? "danger" : "warning"}
{status} >
</Badge> {status}
</Badge>
</div>
) )
}, },
}, },
{ {
accessorKey: "deviceType", accessorKey: "deviceType",
header: () => ( sortingFn: (a, b) => a.original.deviceType.localeCompare(b.original.deviceType),
<div className="flex items-center gap-1.5"> header: ({ column }) => <HeaderButton column={column} name={t`Type`} Icon={ArrowLeftRightIcon} />,
<ArrowLeftRightIcon className="size-4" />
<Trans>Type</Trans>
</div>
),
cell: ({ getValue }) => ( cell: ({ getValue }) => (
<Badge variant="outline" className="uppercase"> <div className="ms-1.5">
{getValue() as string} <Badge variant="outline" className="uppercase">
</Badge> {getValue() as string}
</Badge>
</div>
), ),
}, },
{ {
accessorKey: "powerOnHours", accessorKey: "powerOnHours",
header: () => ( invertSorting: true,
<div className="flex items-center gap-1.5"> header: ({ column }) => <HeaderButton column={column} name={t({ message: "Power On", comment: "Power On Time" })} Icon={Clock} />,
<Clock className="size-4" /> cell: ({ getValue }) => {
<Trans comment="Power On Time">Power On</Trans> const hours = (getValue() ?? 0) as number
</div>
),
cell: ({ row }) => {
const hours = row.getValue("powerOnHours") as number | undefined
if (!hours && hours !== 0) { if (!hours && hours !== 0) {
return ( return (
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground ms-1.5">
N/A N/A
</div> </div>
) )
} }
const days = Math.floor(hours / 24) const seconds = hours * 3600
return ( return (
<div className="text-sm"> <div className="text-sm ms-1.5">
<div>{hours.toLocaleString()} hours</div> <div>{secondsToString(seconds, "hour")}</div>
<div className="text-muted-foreground text-xs">{days} days</div> <div className="text-muted-foreground text-xs">{secondsToString(seconds, "day")}</div>
</div> </div>
) )
}, },
}, },
{ {
accessorKey: "powerCycles", accessorKey: "powerCycles",
header: () => ( invertSorting: true,
<div className="flex items-center gap-1.5"> header: ({ column }) => <HeaderButton column={column} name={t({ message: "Cycles", comment: "Power Cycles" })} Icon={RotateCwIcon} />,
<RotateCwIcon className="size-4" />
<Trans comment="Power Cycles">Cycles</Trans>
</div>
),
cell: ({ getValue }) => { cell: ({ getValue }) => {
const cycles = getValue() as number | undefined const cycles = getValue() as number | undefined
if (!cycles && cycles !== 0) { if (!cycles && cycles !== 0) {
return ( return (
<div className="text-muted-foreground"> <div className="text-muted-foreground ms-1.5">
N/A N/A
</div> </div>
) )
} }
return cycles return <span className="ms-1.5">{cycles}</span>
}, },
}, },
{ {
accessorKey: "serialNumber", accessorKey: "serialNumber",
header: () => ( sortingFn: (a, b) => a.original.serialNumber.localeCompare(b.original.serialNumber),
<div className="flex items-center gap-1.5"> header: ({ column }) => <HeaderButton column={column} name={t`Serial Number`} Icon={HashIcon} />,
<HashIcon className="size-4" /> cell: ({ getValue }) => (
<Trans>Serial Number</Trans> <span className="ms-1.5">{getValue() as string}</span>
</div>
), ),
}, },
{ {
accessorKey: "firmwareVersion", accessorKey: "firmwareVersion",
header: () => ( sortingFn: (a, b) => a.original.firmwareVersion.localeCompare(b.original.firmwareVersion),
<div className="flex items-center gap-1.5"> header: ({ column }) => <HeaderButton column={column} name={t`Firmware`} Icon={CpuIcon} />,
<CpuIcon className="size-4" /> cell: ({ getValue }) => (
<Trans>Firmware</Trans> <span className="ms-1.5">{getValue() as string}</span>
</div>
), ),
}, },
] ]
function HeaderButton({ column, name, Icon }: { column: Column<DiskInfo>; name: string; Icon: React.ElementType }) {
const isSorted = column.getIsSorted()
return (
<Button
className={cn("h-9 px-3 flex items-center gap-2 duration-50", isSorted && "bg-accent/70 light:bg-accent text-accent-foreground/90")}
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
{Icon && <Icon className="size-4" />}
{name}
</Button>
)
}
export default function DisksTable({ systemId }: { systemId: string }) { export default function DisksTable({ systemId }: { systemId: string }) {
const [sorting, setSorting] = React.useState<SortingState>([{ id: "device", desc: false }]) const [sorting, setSorting] = React.useState<SortingState>([{ id: "device", desc: false }])
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]) const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
@@ -297,6 +291,10 @@ export default function DisksTable({ systemId }: { systemId: string }) {
}, },
}) })
if (!diskData.length && !columnFilters.length) {
return null
}
return ( return (
<div> <div>
<Card className="p-6 @container w-full"> <Card className="p-6 @container w-full">
@@ -327,7 +325,7 @@ export default function DisksTable({ systemId }: { systemId: string }) {
<TableRow key={headerGroup.id}> <TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => { {headerGroup.headers.map((header) => {
return ( return (
<TableHead key={header.id}> <TableHead key={header.id} className="px-2">
{header.isPlaceholder {header.isPlaceholder
? null ? null
: flexRender( : flexRender(
@@ -350,7 +348,7 @@ export default function DisksTable({ systemId }: { systemId: string }) {
onClick={() => openSheet(row.original)} onClick={() => openSheet(row.original)}
> >
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}> <TableCell key={cell.id} className="md:ps-5">
{flexRender( {flexRender(
cell.column.columnDef.cell, cell.column.columnDef.cell,
cell.getContext() cell.getContext()
@@ -374,7 +372,7 @@ export default function DisksTable({ systemId }: { systemId: string }) {
</Table> </Table>
</div> </div>
</Card> </Card>
<DiskSheet disk={activeDisk} smartData={activeDisk && smartData ? Object.values(smartData).find(sd => sd.dn === activeDisk.device || sd.mn === activeDisk.model) : undefined} open={sheetOpen} onOpenChange={setSheetOpen} /> <DiskSheet disk={activeDisk} smartData={smartData?.[activeDisk?.serialNumber ?? ""]} open={sheetOpen} onOpenChange={setSheetOpen} />
</div> </div>
) )
} }

View File

@@ -0,0 +1,200 @@
import type { Column, ColumnDef } from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { cn, decimalString, formatBytes, hourWithSeconds } from "@/lib/utils"
import type { SystemdRecord } from "@/types"
import { ServiceStatus, ServiceStatusLabels, ServiceSubState, ServiceSubStateLabels } from "@/lib/enums"
import {
ActivityIcon,
ArrowUpDownIcon,
ClockIcon,
CpuIcon,
MemoryStickIcon,
TerminalSquareIcon,
} from "lucide-react"
import { Badge } from "../ui/badge"
import { t } from "@lingui/core/macro"
// import { $allSystemsById } from "@/lib/stores"
// import { useStore } from "@nanostores/react"
function getSubStateColor(subState: ServiceSubState) {
switch (subState) {
case ServiceSubState.Running:
return "bg-green-500"
case ServiceSubState.Failed:
return "bg-red-500"
case ServiceSubState.Dead:
return "bg-yellow-500"
default:
return "bg-zinc-500"
}
}
export const systemdTableCols: ColumnDef<SystemdRecord>[] = [
{
id: "name",
sortingFn: (a, b) => a.original.name.localeCompare(b.original.name),
accessorFn: (record) => record.name,
header: ({ column }) => <HeaderButton column={column} name={t`Name`} Icon={TerminalSquareIcon} />,
cell: ({ getValue }) => {
return <span className="ms-1.5 xl:w-50 block truncate">{getValue() as string}</span>
},
},
// {
// id: "system",
// accessorFn: (record) => record.system,
// sortingFn: (a, b) => {
// const allSystems = $allSystemsById.get()
// const systemNameA = allSystems[a.original.system]?.name ?? ""
// const systemNameB = allSystems[b.original.system]?.name ?? ""
// return systemNameA.localeCompare(systemNameB)
// },
// header: ({ column }) => <HeaderButton column={column} name={t`System`} Icon={ServerIcon} />,
// cell: ({ getValue }) => {
// const allSystems = useStore($allSystemsById)
// return <span className="ms-1.5 xl:w-34 block truncate">{allSystems[getValue() as string]?.name ?? ""}</span>
// },
// },
{
id: "state",
accessorFn: (record) => record.state,
header: ({ column }) => <HeaderButton column={column} name={t`State`} Icon={ActivityIcon} />,
cell: ({ getValue }) => {
const statusValue = getValue() as ServiceStatus
const statusLabel = ServiceStatusLabels[statusValue] || "Unknown"
return (
<Badge variant="outline" className="dark:border-white/12">
<span className={cn("size-2 me-1.5 rounded-full", getStatusColor(statusValue))} />
{statusLabel}
</Badge>
)
},
},
{
id: "sub",
accessorFn: (record) => record.sub,
header: ({ column }) => <HeaderButton column={column} name={t`Sub State`} Icon={ActivityIcon} />,
cell: ({ getValue }) => {
const subState = getValue() as ServiceSubState
const subStateLabel = ServiceSubStateLabels[subState] || "Unknown"
return (
<Badge variant="outline" className="dark:border-white/12 text-xs capitalize">
<span className={cn("size-2 me-1.5 rounded-full", getSubStateColor(subState))} />
{subStateLabel}
</Badge>
)
},
},
{
id: "cpu",
accessorFn: (record) => {
if (record.sub !== ServiceSubState.Running) {
return -1
}
return record.cpu
},
invertSorting: true,
header: ({ column }) => <HeaderButton column={column} name={`${t`CPU`} (10m)`} Icon={CpuIcon} />,
cell: ({ getValue }) => {
const val = getValue() as number
if (val < 0) {
return <span className="ms-1.5 text-muted-foreground">N/A</span>
}
return <span className="ms-1.5 tabular-nums">{`${decimalString(val, val >= 10 ? 1 : 2)}%`}</span>
},
},
{
id: "cpuPeak",
accessorFn: (record) => {
if (record.sub !== ServiceSubState.Running) {
return -1
}
return record.cpuPeak ?? 0
},
invertSorting: true,
header: ({ column }) => <HeaderButton column={column} name={t`CPU Peak`} Icon={CpuIcon} />,
cell: ({ getValue }) => {
const val = getValue() as number
if (val < 0) {
return <span className="ms-1.5 text-muted-foreground">N/A</span>
}
return <span className="ms-1.5 tabular-nums">{`${decimalString(val, val >= 10 ? 1 : 2)}%`}</span>
},
},
{
id: "memory",
accessorFn: (record) => record.memory,
invertSorting: true,
header: ({ column }) => <HeaderButton column={column} name={t`Memory`} Icon={MemoryStickIcon} />,
cell: ({ getValue }) => {
const val = getValue() as number
if (!val) {
return <span className="ms-1.5 text-muted-foreground">N/A</span>
}
const formatted = formatBytes(val, false, undefined, false)
return (
<span className="ms-1.5 tabular-nums">{`${decimalString(formatted.value, formatted.value >= 10 ? 1 : 2)} ${formatted.unit}`}</span>
)
},
},
{
id: "memPeak",
accessorFn: (record) => record.memPeak,
invertSorting: true,
header: ({ column }) => <HeaderButton column={column} name={t`Memory Peak`} Icon={MemoryStickIcon} />,
cell: ({ getValue }) => {
const val = getValue() as number
if (!val) {
return <span className="ms-1.5 text-muted-foreground">N/A</span>
}
const formatted = formatBytes(val, false, undefined, false)
return (
<span className="ms-1.5 tabular-nums">{`${decimalString(formatted.value, formatted.value >= 10 ? 1 : 2)} ${formatted.unit}`}</span>
)
},
},
{
id: "updated",
invertSorting: true,
accessorFn: (record) => record.updated,
header: ({ column }) => <HeaderButton column={column} name={t`Updated`} Icon={ClockIcon} />,
cell: ({ getValue }) => {
const timestamp = getValue() as number
return (
<span className="ms-1.5 tabular-nums">
{hourWithSeconds(new Date(timestamp).toISOString())}
</span>
)
},
},
]
function HeaderButton({ column, name, Icon }: { column: Column<SystemdRecord>; name: string; Icon: React.ElementType }) {
const isSorted = column.getIsSorted()
return (
<Button
className={cn("h-9 px-3 flex items-center gap-2 duration-50", isSorted && "bg-accent/70 light:bg-accent text-accent-foreground/90")}
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
{Icon && <Icon className="size-4" />}
{name}
<ArrowUpDownIcon className="size-4" />
</Button>
)
}
export function getStatusColor(status: ServiceStatus) {
switch (status) {
case ServiceStatus.Active:
return "bg-green-500"
case ServiceStatus.Failed:
return "bg-red-500"
case ServiceStatus.Reloading:
case ServiceStatus.Activating:
case ServiceStatus.Deactivating:
return "bg-yellow-500"
default:
return "bg-zinc-500"
}
}

View File

@@ -0,0 +1,665 @@
import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
import {
type ColumnFiltersState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getSortedRowModel,
type Row,
type SortingState,
type Table as TableType,
useReactTable,
type VisibilityState,
} from "@tanstack/react-table"
import { useVirtualizer, type VirtualItem } from "@tanstack/react-virtual"
import { LoaderCircleIcon } from "lucide-react"
import { listenKeys } from "nanostores"
import { memo, type ReactNode, useEffect, useMemo, useRef, useState } from "react"
import { getStatusColor, systemdTableCols } from "@/components/systemd-table/systemd-table-columns"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { pb } from "@/lib/api"
import { ServiceStatus, ServiceStatusLabels, type ServiceSubState, ServiceSubStateLabels } from "@/lib/enums"
import { $allSystemsById } from "@/lib/stores"
import { cn, decimalString, formatBytes, useBrowserStorage } from "@/lib/utils"
import type { SystemdRecord, SystemdServiceDetails } from "@/types"
import { Separator } from "../ui/separator"
export default function SystemdTable({ systemId }: { systemId?: string }) {
const loadTime = Date.now()
const [data, setData] = useState<SystemdRecord[]>([])
const [sorting, setSorting] = useBrowserStorage<SortingState>(
`sort-sd-${systemId ? 1 : 0}`,
[{ id: systemId ? "name" : "system", desc: false }],
sessionStorage
)
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
const [globalFilter, setGlobalFilter] = useState("")
// clear old data when systemId changes
useEffect(() => {
return setData([])
}, [systemId])
useEffect(() => {
const lastUpdated = data[0]?.updated ?? 0
function fetchData(systemId?: string) {
pb.collection<SystemdRecord>("systemd_services")
.getList(0, 2000, {
fields: "name,state,sub,cpu,cpuPeak,memory,memPeak,updated",
filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
})
.then(
({ items }) =>
items.length &&
setData((curItems) => {
const lastUpdated = Math.max(items[0].updated, items.at(-1)?.updated ?? 0)
const systemdNames = new Set()
const newItems: SystemdRecord[] = []
for (const item of items) {
if (Math.abs(lastUpdated - item.updated) < 70_000) {
systemdNames.add(item.name)
newItems.push(item)
}
}
for (const item of curItems) {
if (!systemdNames.has(item.name) && lastUpdated - item.updated < 70_000) {
newItems.push(item)
}
}
return newItems
})
)
}
// initial load
fetchData(systemId)
// if no systemId, pull system containers after every system update
if (!systemId) {
return $allSystemsById.listen((_value, _oldValue, systemId) => {
// exclude initial load of systems
if (Date.now() - loadTime > 500) {
fetchData(systemId)
}
})
}
// if systemId, fetch containers after the system is updated
return listenKeys($allSystemsById, [systemId], (_newSystems) => {
// don't fetch data if the last update is less than 9.5 minutes
if (lastUpdated > Date.now() - 9.5 * 60 * 1000) {
return
}
fetchData(systemId)
})
}, [systemId])
const table = useReactTable({
data,
// columns: systemdTableCols.filter((col) => (systemId ? col.id !== "system" : true)),
columns: systemdTableCols,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
defaultColumn: {
sortUndefined: "last",
size: 100,
minSize: 0,
},
state: {
sorting,
columnFilters,
columnVisibility,
globalFilter,
},
onGlobalFilterChange: setGlobalFilter,
globalFilterFn: (row, _columnId, filterValue) => {
const service = row.original
const systemName = $allSystemsById.get()[service.system]?.name ?? ""
const name = service.name ?? ""
const statusLabel = ServiceStatusLabels[service.state as ServiceStatus] ?? ""
const subState = service.sub ?? ""
const searchString = `${systemName} ${name} ${statusLabel} ${subState}`.toLowerCase()
return (filterValue as string)
.toLowerCase()
.split(" ")
.every((term) => searchString.includes(term))
},
})
const rows = table.getRowModel().rows
const visibleColumns = table.getVisibleLeafColumns()
const statusTotals = useMemo(() => {
const totals = [0, 0, 0, 0, 0, 0]
for (const service of data) {
totals[service.state]++
}
return totals
}, [data])
if (!data.length && !globalFilter) {
return null
}
return (
<Card className="p-6 @container w-full">
<CardHeader className="p-0 mb-4">
<div className="grid md:flex gap-5 w-full items-end">
<div className="px-2 sm:px-1">
<CardTitle className="mb-2">
<Trans>Systemd Services</Trans>
</CardTitle>
<CardDescription className="flex items-center">
<Trans>Total: {data.length}</Trans>
<Separator orientation="vertical" className="h-4 mx-2 bg-primary/40" />
<Trans>Failed: {statusTotals[ServiceStatus.Failed]}</Trans>
<Separator orientation="vertical" className="h-4 mx-2 bg-primary/40" />
<Trans>Updated every 10 minutes.</Trans>
</CardDescription>
</div>
<Input
placeholder={t`Filter...`}
value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)}
className="ms-auto px-4 w-full max-w-full md:w-64"
/>
</div>
</CardHeader>
<div className="rounded-md">
<AllSystemdTable table={table} rows={rows} colLength={visibleColumns.length} systemId={systemId} />
</div>
</Card>
)
}
const AllSystemdTable = memo(function AllSystemdTable({
table,
rows,
colLength,
systemId,
}: {
table: TableType<SystemdRecord>
rows: Row<SystemdRecord>[]
colLength: number
systemId?: string
}) {
// The virtualizer will need a reference to the scrollable container element
const scrollRef = useRef<HTMLDivElement>(null)
const activeService = useRef<SystemdRecord | null>(null)
const [sheetOpen, setSheetOpen] = useState(false)
const openSheet = (service: SystemdRecord) => {
activeService.current = service
setSheetOpen(true)
}
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
count: rows.length,
estimateSize: () => 54,
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))
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() + 48}px`, paddingTop, paddingBottom }}>
<table className="text-sm w-full h-full text-nowrap">
<SystemdTableHead table={table} />
<TableBody>
{rows.length ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index]
return <SystemdTableRow key={row.id} row={row} virtualRow={virtualRow} openSheet={openSheet} />
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
<Trans>No results.</Trans>
</TableCell>
</TableRow>
)}
</TableBody>
</table>
</div>
<SystemdSheet
sheetOpen={sheetOpen}
setSheetOpen={setSheetOpen}
activeService={activeService}
systemId={systemId}
/>
</div>
)
})
function SystemdSheet({
sheetOpen,
setSheetOpen,
activeService,
systemId,
}: {
sheetOpen: boolean
setSheetOpen: (open: boolean) => void
activeService: React.RefObject<SystemdRecord | null>
systemId?: string
}) {
const service = activeService.current
const [details, setDetails] = useState<SystemdServiceDetails | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!sheetOpen || !service) {
return
}
setError(null)
let cancelled = false
setDetails(null)
setIsLoading(true)
pb.send<{ details: SystemdServiceDetails }>("/api/beszel/systemd/info", {
query: {
system: systemId,
service: service.name,
},
})
.then(({ details }) => {
if (cancelled) return
if (details) {
setDetails(details)
} else {
setDetails(null)
setError(t`No systemd details returned`)
}
})
.catch((err) => {
if (cancelled) return
setError(err?.message ?? "Failed to load service details")
setDetails(null)
})
.finally(() => {
if (!cancelled) {
setIsLoading(false)
}
})
return () => {
cancelled = true
}
}, [sheetOpen, service, systemId])
if (!service) return null
const statusLabel = ServiceStatusLabels[service.state as ServiceStatus] ?? ""
const subStateLabel = ServiceSubStateLabels[service.sub as ServiceSubState] ?? ""
const notAvailable = <span className="text-muted-foreground">N/A</span>
const formatMemory = (value?: number | null) => {
if (value === undefined || value === null) {
return value === null ? t`Unlimited` : undefined
}
const { value: convertedValue, unit } = formatBytes(value, false, undefined, false)
const digits = convertedValue >= 10 ? 1 : 2
return `${decimalString(convertedValue, digits)} ${unit}`
}
const formatCpuTime = (ns?: number) => {
if (!ns) return undefined
const seconds = ns / 1_000_000_000
if (seconds >= 3600) {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = Math.floor(seconds % 60)
return [hours ? `${hours}h` : null, minutes ? `${minutes}m` : null, secs ? `${secs}s` : null]
.filter(Boolean)
.join(" ")
}
if (seconds >= 60) {
const minutes = Math.floor(seconds / 60)
const secs = Math.floor(seconds % 60)
return `${minutes}m ${secs}s`
}
if (seconds >= 1) {
return `${decimalString(seconds, 2)}s`
}
return `${decimalString(seconds * 1000, 2)}ms`
}
const formatTasks = (current?: number, max?: number) => {
const hasCurrent = typeof current === "number" && current >= 0
const hasMax = typeof max === "number" && max > 0 && max !== null
if (!hasCurrent && !hasMax) {
return undefined
}
return (
<>
{hasCurrent ? current : notAvailable}
{hasMax && (
<span className="text-muted-foreground ms-1.5">
{t`(limit: ${max})`}
</span>
)}
{max === null && (
<span className="text-muted-foreground ms-1.5">
{t`(limit: unlimited)`}
</span>
)}
</>
)
}
const formatTimestamp = (timestamp?: number) => {
if (!timestamp) return undefined
// systemd timestamps are in microseconds, convert to milliseconds for JavaScript Date
const date = new Date(timestamp / 1000)
if (Number.isNaN(date.getTime())) return undefined
return date.toLocaleString()
}
const activeStateValue = (() => {
const stateText = details?.ActiveState
? details.SubState
? `${details.ActiveState} (${details.SubState})`
: details.ActiveState
: subStateLabel
? `${statusLabel} (${subStateLabel})`
: statusLabel
for (const [index, status] of ServiceStatusLabels.entries()) {
if (details?.ActiveState?.toLowerCase() === status.toLowerCase()) {
service.state = index as ServiceStatus
break
}
}
return (
<div className="flex items-center gap-2">
<div className={cn("w-2 h-2 rounded-full flex-shrink-0", getStatusColor(service.state))} />
{stateText}
</div>
)
})()
const statusTextValue = details?.Result
const cpuTime = formatCpuTime(details?.CPUUsageNSec)
const tasks = formatTasks(details?.TasksCurrent, details?.TasksMax)
const memoryCurrent = formatMemory(details?.MemoryCurrent)
const memoryPeak = formatMemory(details?.MemoryPeak)
const memoryLimit = formatMemory(details?.MemoryLimit)
const restartsValue = typeof details?.NRestarts === "number" ? details.NRestarts : undefined
const mainPidValue = typeof details?.MainPID === "number" && details.MainPID > 0 ? details.MainPID : undefined
const execMainPidValue =
typeof details?.ExecMainPID === "number" && details.ExecMainPID > 0 && details.ExecMainPID !== details?.MainPID
? details.ExecMainPID
: undefined
const activeEnterTimestamp = formatTimestamp(details?.ActiveEnterTimestamp)
const activeExitTimestamp = formatTimestamp(details?.ActiveExitTimestamp)
const inactiveEnterTimestamp = formatTimestamp(details?.InactiveEnterTimestamp)
const execMainStartTimestamp = undefined // Property not available in current systemd interface
const renderRow = (key: string, label: ReactNode, value?: ReactNode, alwaysShow = false) => {
if (!alwaysShow && (value === undefined || value === null || value === "")) {
return null
}
return (
<tr key={key} className="border-b last:border-b-0">
<td className="px-3 py-2 font-medium bg-muted dark:bg-muted/40 align-top w-35">{label}</td>
<td className="px-3 py-2">{value ?? notAvailable}</td>
</tr>
)
}
return (
<Sheet open={sheetOpen} onOpenChange={setSheetOpen}>
<SheetContent className="w-full sm:max-w-220 p-6 overflow-y-auto">
<SheetHeader className="p-0">
<SheetTitle>
<Trans>Service Details</Trans>
</SheetTitle>
</SheetHeader>
<div className="grid gap-6">
{isLoading && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<LoaderCircleIcon className="size-4 animate-spin" />
<Trans>Loading...</Trans>
</div>
)}
{error && (
<Alert className="border-destructive/50 text-destructive dark:border-destructive/60 dark:text-destructive">
<AlertTitle>
<Trans>Error</Trans>
</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<div>
<div className="border rounded-md">
<table className="w-full text-sm">
<tbody>
{renderRow("name", t`Name`, service.name, true)}
{renderRow("description", t`Description`, details?.Description, true)}
{renderRow("loadState", t`Load State`, details?.LoadState, true)}
{renderRow(
"bootState",
t`Boot State`,
<div className="flex items-center">
{details?.UnitFileState}
{details?.UnitFilePreset && (
<span className="text-muted-foreground ms-1.5">(preset: {details?.UnitFilePreset})</span>
)}
</div>,
true
)}
{renderRow("unitFile", t`Unit File`, details?.FragmentPath, true)}
{renderRow("active", t`Active State`, activeStateValue, true)}
{renderRow("status", t`Status`, statusTextValue, true)}
{renderRow(
"documentation",
t`Documentation`,
Array.isArray(details?.Documentation) && details.Documentation.length > 0
? details.Documentation.join(", ")
: undefined
)}
</tbody>
</table>
</div>
</div>
<div>
<h3 className="text-sm font-medium mb-3">
<Trans>Runtime Metrics</Trans>
</h3>
<div className="border rounded-md">
<table className="w-full text-sm">
<tbody>
{renderRow("mainPid", t`Main PID`, mainPidValue, true)}
{renderRow("execMainPid", t`Exec Main PID`, execMainPidValue)}
{renderRow("tasks", t`Tasks`, tasks, true)}
{renderRow("cpuTime", t`CPU Time`, cpuTime)}
{renderRow("memory", t`Memory`, memoryCurrent, true)}
{renderRow("memoryPeak", t`Memory Peak`, memoryPeak)}
{renderRow("memoryLimit", t`Memory Limit`, memoryLimit)}
{renderRow("restarts", t`Restarts`, restartsValue, true)}
</tbody>
</table>
</div>
</div>
<div className="hidden has-[tr]:block">
<h3 className="text-sm font-medium mb-3">
<Trans>Relationships</Trans>
</h3>
<div className="border rounded-md">
<table className="w-full text-sm">
<tbody>
{renderRow(
"wants",
t`Wants`,
Array.isArray(details?.Wants) && details.Wants.length > 0 ? details.Wants.join(", ") : undefined
)}
{renderRow(
"requires",
t`Requires`,
Array.isArray(details?.Requires) && details.Requires.length > 0
? details.Requires.join(", ")
: undefined
)}
{renderRow(
"requiredBy",
t`Required By`,
Array.isArray(details?.RequiredBy) && details.RequiredBy.length > 0
? details.RequiredBy.join(", ")
: undefined
)}
{renderRow(
"conflicts",
t`Conflicts`,
Array.isArray(details?.Conflicts) && details.Conflicts.length > 0
? details.Conflicts.join(", ")
: undefined
)}
{renderRow(
"before",
t`Before`,
Array.isArray(details?.Before) && details.Before.length > 0 ? details.Before.join(", ") : undefined
)}
{renderRow(
"after",
t`After`,
Array.isArray(details?.After) && details.After.length > 0 ? details.After.join(", ") : undefined
)}
{renderRow(
"triggers",
t`Triggers`,
Array.isArray(details?.Triggers) && details.Triggers.length > 0
? details.Triggers.join(", ")
: undefined
)}
{renderRow(
"triggeredBy",
t`Triggered By`,
Array.isArray(details?.TriggeredBy) && details.TriggeredBy.length > 0
? details.TriggeredBy.join(", ")
: undefined
)}
</tbody>
</table>
</div>
</div>
<div className="hidden has-[tr]:block">
<h3 className="text-sm font-medium mb-3">
<Trans>Lifecycle</Trans>
</h3>
<div className="border rounded-md">
<table className="w-full text-sm">
<tbody>
{renderRow("activeSince", t`Became Active`, activeEnterTimestamp)}
{service.state !== ServiceStatus.Active &&
renderRow("lastActive", t`Exited Active`, activeExitTimestamp)}
{renderRow("inactiveSince", t`Became Inactive`, inactiveEnterTimestamp)}
{renderRow("execMainStart", t`Process Started`, execMainStartTimestamp)}
{/* {renderRow("invocationId", t`Invocation ID`, details?.InvocationID)} */}
{/* {renderRow("freezerState", t`Freezer State`, details?.FreezerState)} */}
</tbody>
</table>
</div>
</div>
<div className="hidden has-[tr]:block">
<h3 className="text-sm font-medium mb-3">
<Trans>Capabilities</Trans>
</h3>
<div className="border rounded-md">
<table className="w-full text-sm">
<tbody>
{renderRow("canStart", t`Can Start`, details?.CanStart ? t`Yes` : t`No`)}
{renderRow("canStop", t`Can Stop`, details?.CanStop ? t`Yes` : t`No`)}
{renderRow("canReload", t`Can Reload`, details?.CanReload ? t`Yes` : t`No`)}
{/* {renderRow("refuseManualStart", t`Refuse Manual Start`, details?.RefuseManualStart ? t`Yes` : t`No`)}
{renderRow("refuseManualStop", t`Refuse Manual Stop`, details?.RefuseManualStop ? t`Yes` : t`No`)} */}
</tbody>
</table>
</div>
</div>
</div>
</SheetContent>
</Sheet>
)
}
function SystemdTableHead({ table }: { table: TableType<SystemdRecord> }) {
return (
<TableHeader className="sticky top-0 z-50 w-full border-b-2">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead className="px-2" key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
)
})}
</tr>
))}
</TableHeader>
)
}
const SystemdTableRow = memo(function SystemdTableRow({
row,
virtualRow,
openSheet,
}: {
row: Row<SystemdRecord>
virtualRow: VirtualItem
openSheet: (service: SystemdRecord) => void
}) {
return (
<TableRow
data-state={row.getIsSelected() && "selected"}
className="cursor-pointer transition-opacity"
onClick={() => openSheet(row.original)}
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className="py-0"
style={{
height: virtualRow.size,
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
)
})

View File

@@ -1,8 +1,10 @@
import type { JSX } from "react" import type { JSX } from "react"
import { useLingui } from "@lingui/react/macro"
import * as React from "react" import * as React from "react"
import * as RechartsPrimitive from "recharts" import * as RechartsPrimitive from "recharts"
import { chartTimeData, cn } from "@/lib/utils" import { chartTimeData, cn } from "@/lib/utils"
import type { ChartData } from "@/types" import type { ChartData } from "@/types"
import { Separator } from "./separator"
// Format: { THEME_NAME: CSS_SELECTOR } // Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const const THEMES = { light: "", dark: ".dark" } as const
@@ -100,6 +102,8 @@ const ChartTooltipContent = React.forwardRef<
filter?: string filter?: string
contentFormatter?: (item: any, key: string) => React.ReactNode | string contentFormatter?: (item: any, key: string) => React.ReactNode | string
truncate?: boolean truncate?: boolean
showTotal?: boolean
totalLabel?: React.ReactNode
} }
>( >(
( (
@@ -121,11 +125,16 @@ const ChartTooltipContent = React.forwardRef<
itemSorter, itemSorter,
contentFormatter: content = undefined, contentFormatter: content = undefined,
truncate = false, truncate = false,
showTotal = false,
totalLabel,
}, },
ref ref
) => { ) => {
// const { config } = useChart() // const { config } = useChart()
const config = {} const config = {}
const { t } = useLingui()
const totalLabelNode = totalLabel ?? t`Total`
const totalName = typeof totalLabelNode === "string" ? totalLabelNode : t`Total`
React.useMemo(() => { React.useMemo(() => {
if (filter) { if (filter) {
@@ -141,6 +150,76 @@ const ChartTooltipContent = React.forwardRef<
} }
}, [itemSorter, payload]) }, [itemSorter, payload])
const totalValueDisplay = React.useMemo(() => {
if (!showTotal || !payload?.length) {
return null
}
let totalValue = 0
let hasNumericValue = false
const aggregatedNestedValues: Record<string, number> = {}
for (const item of payload) {
const numericValue = typeof item.value === "number" ? item.value : Number(item.value)
if (Number.isFinite(numericValue)) {
totalValue += numericValue
hasNumericValue = true
}
if (content && item?.payload) {
const payloadKey = `${nameKey || item.name || item.dataKey || "value"}`
const nestedPayload = (item.payload as Record<string, unknown> | undefined)?.[payloadKey]
if (nestedPayload && typeof nestedPayload === "object") {
for (const [nestedKey, nestedValue] of Object.entries(nestedPayload)) {
if (typeof nestedValue === "number" && Number.isFinite(nestedValue)) {
aggregatedNestedValues[nestedKey] = (aggregatedNestedValues[nestedKey] ?? 0) + nestedValue
}
}
}
}
}
if (!hasNumericValue) {
return null
}
const totalKey = "__total__"
const totalItem: any = {
value: totalValue,
name: totalName,
dataKey: totalKey,
color,
}
if (content) {
const basePayload =
payload[0]?.payload && typeof payload[0].payload === "object"
? { ...(payload[0].payload as Record<string, unknown>) }
: {}
totalItem.payload = {
...basePayload,
[totalKey]: aggregatedNestedValues,
}
}
if (typeof formatter === "function") {
return formatter(
totalValue,
totalName,
totalItem,
payload.length,
totalItem.payload ?? payload[0]?.payload
)
}
if (content) {
return content(totalItem, totalKey)
}
return `${totalValue.toLocaleString()}${unit ?? ""}`
}, [color, content, formatter, nameKey, payload, showTotal, totalName, unit])
const tooltipLabel = React.useMemo(() => { const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) { if (hideLabel || !payload?.length) {
return null return null
@@ -242,6 +321,15 @@ const ChartTooltipContent = React.forwardRef<
</div> </div>
) )
})} })}
{totalValueDisplay ? (
<>
<Separator className="mt-0.5" />
<div className="flex items-center justify-between gap-2 -mt-0.75 font-medium">
<span className="text-muted-foreground ps-3">{totalLabelNode}</span>
<span>{totalValueDisplay}</span>
</div>
</>
) : null}
</div> </div>
</div> </div>
) )
@@ -257,14 +345,17 @@ const ChartLegendContent = React.forwardRef<
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & { Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean hideIcon?: boolean
nameKey?: string nameKey?: string
reverse?: boolean
} }
>(({ className, payload, verticalAlign = "bottom" }, ref) => { >(({ className, payload, verticalAlign = "bottom", reverse = false }, ref) => {
// const { config } = useChart() // const { config } = useChart()
if (!payload?.length) { if (!payload?.length) {
return null return null
} }
const reversedPayload = reverse ? [...payload].reverse() : payload
return ( return (
<div <div
ref={ref} ref={ref}
@@ -274,7 +365,7 @@ const ChartLegendContent = React.forwardRef<
className className
)} )}
> >
{payload.map((item) => { {reversedPayload.map((item) => {
// const key = `${nameKey || item.dataKey || 'value'}` // const key = `${nameKey || item.dataKey || 'value'}`
// const itemConfig = getPayloadConfigFromPayload(config, item, key) // const itemConfig = getPayloadConfigFromPayload(config, item, key)

View File

@@ -32,6 +32,8 @@
--chart-4: hsl(280 65% 60%); --chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%); --chart-5: hsl(340 75% 55%);
--table-header: hsl(225, 6%, 97%); --table-header: hsl(225, 6%, 97%);
--chart-saturation: 65%;
--chart-lightness: 50%;
} }
.dark { .dark {
@@ -51,11 +53,13 @@
--accent: hsl(220 5% 15.5%); --accent: hsl(220 5% 15.5%);
--accent-foreground: hsl(220 2% 98%); --accent-foreground: hsl(220 2% 98%);
--destructive: hsl(0 62% 46%); --destructive: hsl(0 62% 46%);
--border: hsl(220 3% 16%); --border: hsl(220 3% 17%);
--input: hsl(220 4% 22%); --input: hsl(220 4% 22%);
--ring: hsl(220 4% 80%); --ring: hsl(220 4% 80%);
--table-header: hsl(220, 6%, 13%); --table-header: hsl(220, 6%, 13%);
--radius: 0.8rem; --radius: 0.8rem;
--chart-saturation: 60%;
--chart-lightness: 55%;
} }
@theme inline { @theme inline {

View File

@@ -1,7 +1,7 @@
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { CpuIcon, HardDriveIcon, HourglassIcon, MemoryStickIcon, ServerIcon, ThermometerIcon } from "lucide-react" import { CpuIcon, HardDriveIcon, HourglassIcon, MemoryStickIcon, ServerIcon, ThermometerIcon } from "lucide-react"
import type { RecordSubscription } from "pocketbase" import type { RecordSubscription } from "pocketbase"
import { EthernetIcon } from "@/components/ui/icons" import { EthernetIcon, GpuIcon } from "@/components/ui/icons"
import { $alerts } from "@/lib/stores" import { $alerts } from "@/lib/stores"
import type { AlertInfo, AlertRecord } from "@/types" import type { AlertInfo, AlertRecord } from "@/types"
import { pb } from "./api" import { pb } from "./api"
@@ -41,6 +41,12 @@ export const alertInfo: Record<string, AlertInfo> = {
desc: () => t`Triggers when combined up/down exceeds a threshold`, desc: () => t`Triggers when combined up/down exceeds a threshold`,
max: 125, max: 125,
}, },
GPU: {
name: () => t`GPU Usage`,
unit: "%",
icon: GpuIcon,
desc: () => t`Triggers when GPU usage exceeds a threshold`,
},
Temperature: { Temperature: {
name: () => t`Temperature`, name: () => t`Temperature`,
unit: "°C", unit: "°C",

View File

@@ -71,3 +71,26 @@ export enum ConnectionType {
} }
export const connectionTypeLabels = ["", "SSH", "WebSocket"] as const export const connectionTypeLabels = ["", "SSH", "WebSocket"] as const
/** Systemd service state */
export enum ServiceStatus {
Active,
Inactive,
Failed,
Activating,
Deactivating,
Reloading,
}
export const ServiceStatusLabels = ["Active", "Inactive", "Failed", "Activating", "Deactivating", "Reloading"] as const
/** Systemd service sub state */
export enum ServiceSubState {
Dead,
Running,
Exited,
Failed,
Unknown,
}
export const ServiceSubStateLabels = ["Dead", "Running", "Exited", "Failed", "Unknown"] as const

View File

@@ -7,13 +7,15 @@ import { messages as enMessages } from "@/locales/en/en"
import { BatteryState } from "./enums" import { BatteryState } from "./enums"
import { $direction } from "./stores" import { $direction } from "./stores"
const rtlLanguages = new Set(["ar", "fa", "he"])
// activates locale // activates locale
function activateLocale(locale: string, messages: Messages = enMessages) { function activateLocale(locale: string, messages: Messages = enMessages) {
i18n.load(locale, messages) i18n.load(locale, messages)
i18n.activate(locale) i18n.activate(locale)
document.documentElement.lang = locale document.documentElement.lang = locale
localStorage.setItem("lang", locale) localStorage.setItem("lang", locale)
$direction.set(locale.startsWith("ar") || locale.startsWith("fa") ? "rtl" : "ltr") $direction.set(rtlLanguages.has(locale) ? "rtl" : "ltr")
} }
// dynamically loads translations for the given locale // dynamically loads translations for the given locale

View File

@@ -44,6 +44,11 @@ export default [
label: "Français", label: "Français",
e: "🇫🇷", e: "🇫🇷",
}, },
{
lang: "he",
label: "עברית",
e: "🕎",
},
{ {
lang: "hr", lang: "hr",
label: "Hrvatski", label: "Hrvatski",

View File

@@ -1,4 +1,4 @@
import { t } from "@lingui/core/macro" import { plural, t } from "@lingui/core/macro"
import { type ClassValue, clsx } from "clsx" import { type ClassValue, clsx } from "clsx"
import { listenKeys } from "nanostores" import { listenKeys } from "nanostores"
import { timeDay, timeHour, timeMinute } from "d3-time" import { timeDay, timeHour, timeMinute } from "d3-time"
@@ -111,18 +111,17 @@ export const updateFavicon = (() => {
</linearGradient> </linearGradient>
</defs> </defs>
<path fill="url(#gradient)" d="M35 70H0V0h35q4.4 0 8.2 1.7a21.4 21.4 0 0 1 6.6 4.5q2.9 2.8 4.5 6.6Q56 16.7 56 21a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5Q53 39.1 54 41a18.3 18.3 0 0 1 1.5 4 17.4 17.4 0 0 1 .5 3 15.3 15.3 0 0 1 0 1q0 4.4-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.6Q39.4 70 35 70ZM14 14v14h21a7 7 0 0 0 2.3-.3 6.6 6.6 0 0 0 .4-.2Q39 27 40 26a6.9 6.9 0 0 0 1.5-2.2q.5-1.3.5-2.8a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 17 40 16a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm0 28v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 54.9 40 54a7 7 0 0 0 1.5-2.2 6.9 6.9 0 0 0 .5-2.6 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 45 40 44a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Z"/> <path fill="url(#gradient)" d="M35 70H0V0h35q4.4 0 8.2 1.7a21.4 21.4 0 0 1 6.6 4.5q2.9 2.8 4.5 6.6Q56 16.7 56 21a15.4 15.4 0 0 1-.3 3.2 17.6 17.6 0 0 1-.2.8 19.4 19.4 0 0 1-1.5 4 17 17 0 0 1-2.4 3.4 13.5 13.5 0 0 1-2.6 2.3 12.5 12.5 0 0 1-.4.3q1.7 1 3 2.5Q53 39.1 54 41a18.3 18.3 0 0 1 1.5 4 17.4 17.4 0 0 1 .5 3 15.3 15.3 0 0 1 0 1q0 4.4-1.7 8.2a21.4 21.4 0 0 1-4.5 6.6q-2.8 2.9-6.6 4.6Q39.4 70 35 70ZM14 14v14h21a7 7 0 0 0 2.3-.3 6.6 6.6 0 0 0 .4-.2Q39 27 40 26a6.9 6.9 0 0 0 1.5-2.2q.5-1.3.5-2.8a7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 17 40 16a7 7 0 0 0-2.3-1.4 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Zm0 28v14h21a7 7 0 0 0 2.3-.4 6.6 6.6 0 0 0 .4-.1Q39 54.9 40 54a7 7 0 0 0 1.5-2.2 6.9 6.9 0 0 0 .5-2.6 7.9 7.9 0 0 0 0-.2 7 7 0 0 0-.4-2.3 6.6 6.6 0 0 0-.1-.4Q40.9 45 40 44a7 7 0 0 0-2.3-1.5 6.9 6.9 0 0 0-2.5-.6 7.9 7.9 0 0 0-.2 0H14Z"/>
${ ${downCount > 0 &&
downCount > 0 && `
`
<circle cx="40" cy="50" r="22" fill="#f00"/> <circle cx="40" cy="50" r="22" fill="#f00"/>
<text x="40" y="60" font-size="34" text-anchor="middle" fill="#fff" font-family="Arial" font-weight="bold">${downCount}</text> <text x="40" y="60" font-size="34" text-anchor="middle" fill="#fff" font-family="Arial" font-weight="bold">${downCount}</text>
` `
} }
</svg> </svg>
` `
const blob = new Blob([svg], { type: "image/svg+xml" }) const blob = new Blob([svg], { type: "image/svg+xml" })
const url = URL.createObjectURL(blob) const url = URL.createObjectURL(blob)
;(document.querySelector("link[rel='icon']") as HTMLLinkElement).href = url ; (document.querySelector("link[rel='icon']") as HTMLLinkElement).href = url
} }
})() })()
@@ -288,7 +287,7 @@ export function formatBytes(
} }
} }
export const chartMargin = { top: 12 } export const chartMargin = { top: 12, right: 5 }
/** /**
* Retuns value of system host, truncating full path if socket. * Retuns value of system host, truncating full path if socket.
@@ -367,6 +366,12 @@ export function formatDuration(
.join(" ") .join(" ")
} }
/** Parse semver string into major, minor, and patch numbers
* @example
* const semVer = "1.2.3"
* const { major, minor, patch } = parseSemVer(semVer)
* console.log(major, minor, patch) // 1, 2, 3
*/
export const parseSemVer = (semVer = ""): SemVer => { export const parseSemVer = (semVer = ""): SemVer => {
// if (semVer.startsWith("v")) { // if (semVer.startsWith("v")) {
// semVer = semVer.slice(1) // semVer = semVer.slice(1)
@@ -423,3 +428,17 @@ export function runOnce<T extends (...args: any[]) => any>(fn: T): T {
return state.result return state.result
}) as T }) as T
} }
/** Format seconds to hours, minutes, or seconds */
export function secondsToString(seconds: number, unit: "hour" | "minute" | "day"): string {
const count = Math.floor(seconds / (unit === "hour" ? 3600 : unit === "minute" ? 60 : 86400))
const countString = count.toLocaleString()
switch (unit) {
case "minute":
return plural(count, { one: `${countString} minute`, few: `${countString} minutes`, many: `${countString} minutes`, other: `${countString} minutes` })
case "hour":
return plural(count, { one: `${countString} hour`, other: `${countString} hours` })
case "day":
return plural(count, { one: `${countString} day`, other: `${countString} days` })
}
}

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: ar\n" "Language: ar\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-30 21:52\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Arabic\n" "Language-Team: Arabic\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n" "Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: ar\n" "X-Crowdin-Language: ar\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# يوم} other {# أيام}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# ساعة} other {# ساعات}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# دقيقة} few {# دقائق} many {# دقيقة} other {# دقيقة}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# دقيقة} few {# دقائق} many {# دقيقة} ot
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "تم تحديد {0} من {1} صف" msgstr "تم تحديد {0} من {1} صف"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} يوم} other {{countString} أيام}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} ساعة} other {{countString} ساعات}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} دقيقة} few {{countString} دقائق} many {{countString} دقيقة} other {{countString} دقيقة}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 ساعة" msgstr "1 ساعة"
@@ -234,6 +231,10 @@ msgstr "ذاكرة التخزين المؤقت / المخازن المؤقتة"
msgid "Cancel" msgid "Cancel"
msgstr "إلغاء" msgstr "إلغاء"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "السعة"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "تحذير - فقدان محتمل للبيانات" msgstr "تحذير - فقدان محتمل للبيانات"
@@ -279,6 +280,10 @@ msgstr "تحقق من خدمة الإشعارات الخاصة بك"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "انقر على حاوية لعرض مزيد من المعلومات." msgstr "انقر على حاوية لعرض مزيد من المعلومات."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "انقر على جهاز لعرض مزيد من المعلومات."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "انقر على نظام لعرض مزيد من المعلومات." msgstr "انقر على نظام لعرض مزيد من المعلومات."
@@ -365,8 +370,17 @@ msgstr "نسخ YAML"
msgid "CPU" msgid "CPU"
msgstr "المعالج" msgstr "المعالج"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "نوى المعالج"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "تفصيل وقت المعالج"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "استخدام وحدة المعالجة المركزية" msgstr "استخدام وحدة المعالجة المركزية"
@@ -397,6 +411,11 @@ msgstr "الرفع التراكمي"
msgid "Current state" msgid "Current state"
msgstr "الحالة الحالية" msgstr "الحالة الحالية"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "الدورات"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "لوحة التحكم" msgstr "لوحة التحكم"
@@ -418,6 +437,10 @@ msgstr "حذف البصمة"
msgid "Detail" msgid "Detail"
msgstr "التفاصيل" msgstr "التفاصيل"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "الجهاز"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "تصدير تكوين الأنظمة الحالية الخاصة بك."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "فهرنهايت (°ف)" msgstr "فهرنهايت (°ف)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "السمات الفاشلة:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "فشل في المصادقة" msgstr "فشل في المصادقة"
@@ -568,6 +595,7 @@ msgstr "فشل في تحديث التنبيه"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "تصفية..." msgstr "تصفية..."
@@ -576,6 +604,10 @@ msgstr "تصفية..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "البصمة" msgstr "البصمة"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "البرمجيات الثابتة"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "لمدة <0>{min}</0> {min, plural, one {دقيقة} other {دقائق}}" msgstr "لمدة <0>{min}</0> {min, plural, one {دقيقة} other {دقائق}}"
@@ -730,6 +762,10 @@ msgstr "استخدام الذاكرة"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "استخدام الذاكرة لحاويات دوكر" msgstr "استخدام الذاكرة لحاويات دوكر"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "الموديل"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "وحدة الشبكة"
msgid "No results found." msgid "No results found."
msgstr "لم يتم العثور على نتائج." msgstr "لم يتم العثور على نتائج."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "لا توجد نتائج." msgstr "لا توجد نتائج."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "لا توجد سمات S.M.A.R.T. متاحة لهذا الجهاز."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "فتح القائمة"
msgid "Or continue with" msgid "Or continue with"
msgstr "أو المتابعة باستخدام" msgstr "أو المتابعة باستخدام"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "أخرى"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "الكتابة فوق التنبيهات الحالية" msgstr "الكتابة فوق التنبيهات الحالية"
@@ -847,6 +894,15 @@ msgstr "متوقف مؤقتا"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "متوقف مؤقتا ({pausedSystemsLength})" msgstr "متوقف مؤقتا ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "متوسط الاستخدام لكل نواة"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "النسبة المئوية للوقت المقضي في كل حالة"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "يرجى <0>تكوين خادم SMTP</0> لضمان تسليم التنبيهات." msgstr "يرجى <0>تكوين خادم SMTP</0> لضمان تسليم التنبيهات."
@@ -884,6 +940,11 @@ msgstr "يرجى تسجيل الدخول إلى حسابك"
msgid "Port" msgid "Port"
msgstr "المنفذ" msgstr "المنفذ"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "تشغيل الطاقة"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "تدوير الرمز المميز"
msgid "Rows per page" msgid "Rows per page"
msgstr "صفوف لكل صفحة" msgstr "صفوف لكل صفحة"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "تفاصيل S.M.A.R.T."
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "اختبار S.M.A.R.T. الذاتي"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "احفظ العنوان باستخدام مفتاح الإدخال أو الفاصلة. اتركه فارغًا لتعطيل إشعارات البريد الإشباكي." msgstr "احفظ العنوان باستخدام مفتاح الإدخال أو الفاصلة. اتركه فارغًا لتعطيل إشعارات البريد الإشباكي."
@@ -972,6 +1041,10 @@ msgstr "راجع <0>إعدادات الإشعارات</0> لتكوين كيفي
msgid "Sent" msgid "Sent"
msgstr "تم الإرسال" msgstr "تم الإرسال"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "الرقم التسلسلي"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "تعيين عتبات النسبة المئوية لألوان العداد." msgstr "تعيين عتبات النسبة المئوية لألوان العداد."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "الحالة" msgstr "الحالة"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "جدول" msgstr "جدول"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "درجة الحرارة" msgstr "درجة الحرارة"
@@ -1124,6 +1199,11 @@ msgstr "تسمح الرموز المميزة للوكلاء بالاتصال و
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور." msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "الإجمالي"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "إجمالي البيانات المستلمة لكل واجهة" msgstr "إجمالي البيانات المستلمة لكل واجهة"
@@ -1168,6 +1248,10 @@ msgstr "يتم التفعيل عندما يتغير الحالة بين التش
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز استخدام أي قرص عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز استخدام أي قرص عتبة معينة"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "النوع"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "مدة التشغيل"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "الاستخدام" msgstr "الاستخدام"
@@ -1234,6 +1319,7 @@ msgstr "القيمة"
msgid "View" msgid "View"
msgstr "عرض" msgstr "عرض"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "عرض المزيد" msgstr "عرض المزيد"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: bg\n" "Language: bg\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-20 21:37\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Bulgarian\n" "Language-Team: Bulgarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: bg\n" "X-Crowdin-Language: bg\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# ден} other {# дни}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# час} other {# часа}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# минута} few {# минути} many {# минути} other {# минути}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# минута} few {# минути} many {# минут
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} от {1} селектирани." msgstr "{0} от {1} селектирани."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} ден} other {{countString} дни}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} час} other {{countString} часа}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} минута} few {{countString} минути} many {{countString} минути} other {{countString} минути}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 час" msgstr "1 час"
@@ -234,6 +231,10 @@ msgstr "Кеш / Буфери"
msgid "Cancel" msgid "Cancel"
msgstr "Откажи" msgstr "Откажи"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Капацитет"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Внимание - възможност за загуба на данни" msgstr "Внимание - възможност за загуба на данни"
@@ -279,6 +280,10 @@ msgstr "Провери услугата си за удостоверяване"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Кликнете върху контейнер, за да видите повече информация." msgstr "Кликнете върху контейнер, за да видите повече информация."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Кликнете върху устройство, за да видите повече информация."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Кликнете върху система, за да видите повече информация." msgstr "Кликнете върху система, за да видите повече информация."
@@ -365,8 +370,17 @@ msgstr "Копирай YAML"
msgid "CPU" msgid "CPU"
msgstr "Процесор" msgstr "Процесор"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU ядра"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Разбивка на времето на CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Употреба на процесор" msgstr "Употреба на процесор"
@@ -397,6 +411,11 @@ msgstr "Кумулативно качване"
msgid "Current state" msgid "Current state"
msgstr "Текущо състояние" msgstr "Текущо състояние"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Цикли"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Табло" msgstr "Табло"
@@ -418,6 +437,10 @@ msgstr "Изтрий пръстов отпечатък"
msgid "Detail" msgid "Detail"
msgstr "Подробности" msgstr "Подробности"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Устройство"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "Експортирай конфигурацията на системи
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Фаренхайт (°F)" msgstr "Фаренхайт (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Неуспешни атрибути:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Неуспешно удостоверяване" msgstr "Неуспешно удостоверяване"
@@ -568,6 +595,7 @@ msgstr "Неуспешно обнови тревога"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Филтрирай..." msgstr "Филтрирай..."
@@ -576,6 +604,10 @@ msgstr "Филтрирай..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Пръстов отпечатък" msgstr "Пръстов отпечатък"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Фърмуер"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "За <0>{min}</0> {min, plural, one {минута} other {минути}}" msgstr "За <0>{min}</0> {min, plural, one {минута} other {минути}}"
@@ -730,6 +762,10 @@ msgstr "Употреба на паметта"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Използването на памет от docker контейнерите" msgstr "Използването на памет от docker контейнерите"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Модел"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Единица за измерване на скорост"
msgid "No results found." msgid "No results found."
msgstr "Няма намерени резултати." msgstr "Няма намерени резултати."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Няма резултати." msgstr "Няма резултати."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Няма налични S.M.A.R.T. атрибути за това устройство."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Отвори менюто"
msgid "Or continue with" msgid "Or continue with"
msgstr "Или продължи с" msgstr "Или продължи с"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Други"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Презапиши съществуващи тревоги" msgstr "Презапиши съществуващи тревоги"
@@ -847,6 +894,15 @@ msgstr "На пауза"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "На пауза ({pausedSystemsLength})" msgstr "На пауза ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Средно използване на ядро"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Процент време, прекарано във всяко състояние"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Моля <0>конфигурурай SMTP сървър</0> за да се подсигуриш, че тревогите са доставени." msgstr "Моля <0>конфигурурай SMTP сървър</0> за да се подсигуриш, че тревогите са доставени."
@@ -884,6 +940,11 @@ msgstr "Моля влез в акаунта ти"
msgid "Port" msgid "Port"
msgstr "Порт" msgstr "Порт"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Включване"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Пресъздаване на идентификатора"
msgid "Rows per page" msgid "Rows per page"
msgstr "Редове на страница" msgstr "Редове на страница"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Детайли"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Самотест"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Запази адреса с enter или запетая. Остави празно за да изключиш нотификациите чрез имейл." msgstr "Запази адреса с enter или запетая. Остави празно за да изключиш нотификациите чрез имейл."
@@ -972,6 +1041,10 @@ msgstr "Виж <0>настройките за нотификациите</0> з
msgid "Sent" msgid "Sent"
msgstr "Изпратени" msgstr "Изпратени"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Сериен номер"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Задайте процентни прагове за цветовете на измервателните уреди." msgstr "Задайте процентни прагове за цветовете на измервателните уреди."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Състояние" msgstr "Състояние"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Таблица" msgstr "Таблица"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Температура" msgstr "Температура"
@@ -1124,6 +1199,11 @@ msgstr "Токените позволяват на агентите да се с
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токените и пръстовите отпечатъци се използват за удостоверяване на WebSocket връзките към концентратора." msgstr "Токените и пръстовите отпечатъци се използват за удостоверяване на WebSocket връзките към концентратора."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Общо"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Общо получени данни за всеки интерфейс" msgstr "Общо получени данни за всеки интерфейс"
@@ -1168,6 +1248,10 @@ msgstr "Задейства се, когато статуса превключв
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Задейства се, когато употребата на някой диск надивши зададен праг" msgstr "Задейства се, когато употребата на някой диск надивши зададен праг"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Тип"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Време на работа"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Употреба" msgstr "Употреба"
@@ -1234,6 +1319,7 @@ msgstr "Стойност"
msgid "View" msgid "View"
msgstr "Изглед" msgstr "Изглед"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Виж повече" msgstr "Виж повече"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: cs\n" "Language: cs\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Czech\n" "Language-Team: Czech\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n" "Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: cs\n" "X-Crowdin-Language: cs\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# den} few {# dny} other {# dní}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# Hodina} few {# Hodiny} many {# Hodin} other {# Hodin}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} z {1} vybraných řádků." msgstr "{0} z {1} vybraných řádků."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} den} few {{countString} dny} other {{countString} dní}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} Hodina} few {{countString} Hodiny} many {{countString} Hodin} other {{countString} Hodin}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuta} few {{countString} minuty} many {{countString} minut} other {{countString} minut}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 hodina" msgstr "1 hodina"
@@ -46,7 +43,7 @@ msgstr "1 hodina"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "1 min" msgid "1 min"
msgstr "1 min" msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 minute" msgid "1 minute"
@@ -63,7 +60,7 @@ msgstr "12 hodin"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "15 min" msgid "15 min"
msgstr "15 min" msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "24 hours" msgid "24 hours"
@@ -76,7 +73,7 @@ msgstr "30 dní"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "5 min" msgid "5 min"
msgstr "5 min" msgstr ""
#. Table column #. Table column
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -116,11 +113,11 @@ msgstr "Upravit možnosti zobrazení pro grafy."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Administrátor"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
@@ -213,17 +210,17 @@ msgstr "Beszel používá <0>Shoutrrr</0> k integraci s populárními notifikač
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Binary" msgid "Binary"
msgstr "Binary" msgstr "Binární"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)" msgstr "Bity (Kbps, Mbps, Gbps)"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)" msgstr "Byty (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
msgid "Cache / Buffers" msgid "Cache / Buffers"
@@ -234,6 +231,10 @@ msgstr "Cache / vyrovnávací paměť"
msgid "Cancel" msgid "Cancel"
msgstr "Zrušit" msgstr "Zrušit"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapacita"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Upozornění - možná ztráta dat" msgstr "Upozornění - možná ztráta dat"
@@ -279,6 +280,10 @@ msgstr "Zkontrolujte službu upozornění"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Klikněte na kontejner pro zobrazení dalších informací." msgstr "Klikněte na kontejner pro zobrazení dalších informací."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Klikněte na zařízení pro zobrazení dalších informací."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Klikněte na systém pro zobrazení více informací." msgstr "Klikněte na systém pro zobrazení více informací."
@@ -365,8 +370,17 @@ msgstr "Kopírovat YAML"
msgid "CPU" msgid "CPU"
msgstr "Procesor" msgstr "Procesor"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU jádra"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Rozdělení času CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Využití procesoru" msgstr "Využití procesoru"
@@ -397,6 +411,11 @@ msgstr "Kumulativní odeslání"
msgid "Current state" msgid "Current state"
msgstr "Aktuální stav" msgstr "Aktuální stav"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cykly"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Přehled" msgstr "Přehled"
@@ -416,7 +435,11 @@ msgstr "Smazat identifikátor"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detail" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Zařízení"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -425,11 +448,11 @@ msgstr "Vybíjení"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Disk I/O" msgid "Disk I/O"
msgstr "Disk I/O" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Disk unit" msgid "Disk unit"
@@ -490,7 +513,7 @@ msgstr "Upravit"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx #: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr ""
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Email notifications" msgid "Email notifications"
@@ -534,7 +557,7 @@ msgstr "Stávající systémy, které nejsou definovány v <0>config.yml</0>, bu
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Export" msgstr "Exportovat"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Export configuration" msgid "Export configuration"
@@ -548,6 +571,10 @@ msgstr "Exportovat aktuální konfiguraci systémů."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheita (°F)" msgstr "Fahrenheita (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Neúspěšné atributy:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Ověření se nezdařilo" msgstr "Ověření se nezdařilo"
@@ -568,6 +595,7 @@ msgstr "Nepodařilo se aktualizovat upozornění"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtr..." msgstr "Filtr..."
@@ -576,6 +604,10 @@ msgstr "Filtr..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Otisk" msgstr "Otisk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Za <0>{min}</0> {min, plural, one {minutu} few {minuty} other {minut}}" msgstr "Za <0>{min}</0> {min, plural, one {minutu} few {minuty} other {minut}}"
@@ -648,7 +680,7 @@ msgstr "Neplatná e-mailová adresa."
#. Linux kernel #. Linux kernel
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Jádro"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
@@ -730,6 +762,10 @@ msgstr "Využití paměti"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Využití paměti docker kontejnerů" msgstr "Využití paměti docker kontejnerů"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Síťová jednotka"
msgid "No results found." msgid "No results found."
msgstr "Nenalezeny žádné výskyty." msgstr "Nenalezeny žádné výskyty."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Žádné výsledky." msgstr "Žádné výsledky."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Pro toto zařízení nejsou k dispozici žádné atributy S.M.A.R.T."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Otevřít menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Nebo pokračujte s" msgstr "Nebo pokračujte s"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Jiné"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Přepsat existující upozornění" msgstr "Přepsat existující upozornění"
@@ -847,6 +894,15 @@ msgstr "Pozastaveno"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pozastaveno ({pausedSystemsLength})" msgstr "Pozastaveno ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Průměrné využití na jádro"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Procento času strávěného v každém stavu"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "<0>nakonfigurujte SMTP server</0> pro zajištění toho, aby byla upozornění doručena." msgstr "<0>nakonfigurujte SMTP server</0> pro zajištění toho, aby byla upozornění doručena."
@@ -882,7 +938,12 @@ msgstr "Přihlaste se prosím k vašemu účtu"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Port" msgid "Port"
msgstr "Port" msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Zapnutí"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -943,6 +1004,14 @@ msgstr "Změnit token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Řádků na stránku" msgstr "Řádků na stránku"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Detaily"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Vlastní test"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Adresu uložte pomocí klávesy enter nebo čárky. Pro deaktivaci e-mailových oznámení ponechte prázdné pole." msgstr "Adresu uložte pomocí klávesy enter nebo čárky. Pro deaktivaci e-mailových oznámení ponechte prázdné pole."
@@ -972,6 +1041,10 @@ msgstr "Podívejte se na <0>nastavení upozornění</0> pro nastavení toho, jak
msgid "Sent" msgid "Sent"
msgstr "Odeslat" msgstr "Odeslat"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Sériové číslo"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Nastavte procentuální prahové hodnoty pro barvy měřičů." msgstr "Nastavte procentuální prahové hodnoty pro barvy měřičů."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Stav" msgstr "Stav"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tabulka" msgstr "Tabulka"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Teplota" msgstr "Teplota"
@@ -1062,7 +1137,7 @@ msgstr "Teploty systémových senzorů"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>" msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>" msgstr "Testovat <0>URL</0>"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Test notification sent" msgid "Test notification sent"
@@ -1108,7 +1183,7 @@ msgstr "Přepnout motiv"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token" msgid "Token"
msgstr "Token" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1124,6 +1199,11 @@ msgstr "Tokeny umožňují agentům připojení a registraci. Otisky jsou stabil
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeny a otisky slouží k ověření připojení WebSocket k uzlu." msgstr "Tokeny a otisky slouží k ověření připojení WebSocket k uzlu."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Celkem"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Celkový přijatý objem dat pro každé rozhraní" msgstr "Celkový přijatý objem dat pro každé rozhraní"
@@ -1168,6 +1248,10 @@ msgstr "Spouští se, když se změní dostupnost"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Spustí se, když využití disku překročí prahovou hodnotu" msgstr "Spustí se, když využití disku překročí prahovou hodnotu"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Typ"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Doba provozu"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Využití" msgstr "Využití"
@@ -1234,6 +1319,7 @@ msgstr "Hodnota"
msgid "View" msgid "View"
msgstr "Zobrazení" msgstr "Zobrazení"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Zobrazit více" msgstr "Zobrazit více"

View File

@@ -8,36 +8,33 @@ msgstr ""
"Language: da\n" "Language: da\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Danish\n" "Language-Team: Danish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: da\n" "X-Crowdin-Language: da\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# day} other {# days}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# hour} other {# hours}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "" msgstr "{0} af {1} række(r) valgt."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dag} other {{countString} dage}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} time} other {{countString} timer}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minut} other {{countString} minutter}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
@@ -46,7 +43,7 @@ msgstr "1 time"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "1 min" msgid "1 min"
msgstr "" msgstr "1 minut"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 minute" msgid "1 minute"
@@ -63,7 +60,7 @@ msgstr "12 timer"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "15 min" msgid "15 min"
msgstr "" msgstr "15 minutter"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "24 hours" msgid "24 hours"
@@ -76,7 +73,7 @@ msgstr "30 dage"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "5 min" msgid "5 min"
msgstr "" msgstr "5 minutter"
#. Table column #. Table column
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -87,7 +84,7 @@ msgstr "Handlinger"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Active" msgid "Active"
msgstr "" msgstr "Aktiv"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Active Alerts" msgid "Active Alerts"
@@ -116,7 +113,7 @@ msgstr "Juster visningsindstillinger for diagrammer."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Administrator"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
@@ -126,7 +123,7 @@ msgstr "Agent"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "" msgstr "Advarselshistorik"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
@@ -153,7 +150,7 @@ msgstr "Er du sikker på, at du vil slette {name}?"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?" msgid "Are you sure?"
msgstr "" msgstr "Er du sikker?"
#: src/components/copy-to-clipboard.tsx #: src/components/copy-to-clipboard.tsx
msgid "Automatic copy requires a secure context." msgid "Automatic copy requires a secure context."
@@ -187,7 +184,7 @@ msgstr "Gennemsnitlig udnyttelse af {0}"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Average utilization of GPU engines" msgid "Average utilization of GPU engines"
msgstr "Gennemsnitlig udnyttelse af GPU-motorer" msgstr "Gennemsnitlig udnyttelse af GPU-enheder"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
@@ -218,12 +215,12 @@ msgstr "Binær"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "" msgstr "Bits (Kbps, Mbps, Gbps)"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "" msgstr "Bytes (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
msgid "Cache / Buffers" msgid "Cache / Buffers"
@@ -234,17 +231,21 @@ msgstr "Cache / Buffere"
msgid "Cancel" msgid "Cancel"
msgstr "Fortryd" msgstr "Fortryd"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapacitet"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Forsigtig - muligt tab af data" msgstr "Forsigtig - muligt tab af data"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Celsius (°C)" msgid "Celsius (°C)"
msgstr "" msgstr "Celsius (°C)"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Change display units for metrics." msgid "Change display units for metrics."
msgstr "" msgstr "Ændre viste enheder for målinger."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Change general application options." msgid "Change general application options."
@@ -279,9 +280,13 @@ msgstr "Tjek din notifikationstjeneste"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Klik på en container for at se mere information." msgstr "Klik på en container for at se mere information."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Klik på en enhed for at se flere oplysninger."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "" msgstr "Klik på et system for at se mere information."
#: src/components/ui/input-copy.tsx #: src/components/ui/input-copy.tsx
msgid "Click to copy" msgid "Click to copy"
@@ -303,7 +308,7 @@ msgstr "Bekræft adgangskode"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "" msgstr "Forbindelsen er nede"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -329,7 +334,7 @@ msgstr "Kopiér docker run"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Environment variables" msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "" msgstr "Kopier miljø"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
@@ -350,23 +355,32 @@ msgstr "Kopier tekst"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>." msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>."
msgstr "" msgstr "Kopier installationskommandoen for agenten nedenfor, eller registrer agenter automatisk med en <0>universalnøgle</0>."
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>." msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr "" msgstr "Kopier <0>docker-compose.yml</0> indholdet for agenten nedenfor, eller registrer agenter automatisk med en <1>universalnøgle</1>."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML" msgid "Copy YAML"
msgstr "" msgstr "Kopier YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU-kerner"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU-tidsfordeling"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU forbrug" msgstr "CPU forbrug"
@@ -378,7 +392,7 @@ msgstr "Opret konto"
#. Context: date created #. Context: date created
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Created" msgid "Created"
msgstr "" msgstr "Oprettet"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Critical (%)" msgid "Critical (%)"
@@ -397,6 +411,11 @@ msgstr "Kumulativ upload"
msgid "Current state" msgid "Current state"
msgstr "Nuværende tilstand" msgstr "Nuværende tilstand"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cykler"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Oversigtspanel" msgstr "Oversigtspanel"
@@ -412,12 +431,16 @@ msgstr "Slet"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "" msgstr "Slet fingeraftryk"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detalje" msgstr "Detalje"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Enhed"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -433,7 +456,7 @@ msgstr "Disk I/O"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Disk unit" msgid "Disk unit"
msgstr "" msgstr "Diskenhed"
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -471,15 +494,15 @@ msgstr "Nede"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr "Nede ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Download" msgid "Download"
msgstr "Download" msgstr "Hent ned"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr "Varighed"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -534,7 +557,7 @@ msgstr "Eksisterende systemer ikke defineret i <0>config.yml</0> vil blive slett
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "" msgstr "Eksporter"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Export configuration" msgid "Export configuration"
@@ -546,7 +569,11 @@ msgstr "Eksporter din nuværende systemkonfiguration."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Mislykkede attributter:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
@@ -568,12 +595,17 @@ msgstr "Kunne ikke opdatere alarm"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Fingeraftryk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "" msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
@@ -603,7 +635,7 @@ msgstr "Generelt"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Engines" msgid "GPU Engines"
msgstr "GPU-motorer" msgstr "GPU-enheder"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
@@ -639,7 +671,7 @@ msgstr "Hvis du har mistet adgangskoden til din administratorkonto, kan du nulst
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image" msgctxt "Docker image"
msgid "Image" msgid "Image"
msgstr "Image" msgstr "Billede"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Invalid email address." msgid "Invalid email address."
@@ -648,7 +680,7 @@ msgstr "Ugyldig email adresse."
#. Linux kernel #. Linux kernel
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr "Kerne"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
@@ -656,28 +688,28 @@ msgstr "Sprog"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Layout" msgid "Layout"
msgstr "Layout" msgstr "Opstilling"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "" msgstr "Belastning Gennemsnitlig"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "" msgstr "Belastning Gennemsnitlig 15m"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "" msgstr "Belastning Gennemsnitlig 1m"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "" msgstr "Belastning Gennemsnitlig 5m"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr "Belastning gns."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
@@ -730,6 +762,10 @@ msgstr "Hukommelsesforbrug"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Hukommelsesforbrug af dockercontainere" msgstr "Hukommelsesforbrug af dockercontainere"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -739,7 +775,7 @@ msgstr "Navn"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Net" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Network traffic of docker containers" msgid "Network traffic of docker containers"
@@ -755,16 +791,23 @@ msgstr "Netværkstrafik af offentlige grænseflader"
#. Context: Bytes or bits #. Context: Bytes or bits
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Network unit" msgid "Network unit"
msgstr "" msgstr "Netværksenhed"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "No results found." msgid "No results found."
msgstr "Ingen resultater fundet." msgstr "Ingen resultater fundet."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "" msgstr "Ingen resultater."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Ingen S.M.A.R.T.-attributter tilgængelige for denne enhed."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
@@ -799,6 +842,10 @@ msgstr "Åbn menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Eller fortsæt med" msgstr "Eller fortsæt med"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Andre"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Overskriv eksisterende alarmer" msgstr "Overskriv eksisterende alarmer"
@@ -812,7 +859,7 @@ msgstr "Side"
#. placeholder {1}: table.getPageCount() #. placeholder {1}: table.getPageCount()
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Page {0} of {1}" msgid "Page {0} of {1}"
msgstr "" msgstr "Side {0} af {1}"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Pages / Settings" msgid "Pages / Settings"
@@ -845,7 +892,16 @@ msgstr "Sat på pause"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "" msgstr "Sat på pause ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Gennemsnitlig udnyttelse pr. kerne"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Procentdel af tid brugt i hver tilstand"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
@@ -884,6 +940,11 @@ msgstr "Log venligst ind på din konto"
msgid "Port" msgid "Port"
msgstr "Port" msgstr "Port"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Tænd"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -929,7 +990,7 @@ msgstr "Nulstil adgangskode"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Resolved" msgid "Resolved"
msgstr "" msgstr "Løst"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
@@ -937,11 +998,19 @@ msgstr "Genoptag"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "" msgstr "Roter nøgle"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Rows per page" msgid "Rows per page"
msgstr "" msgstr "Rækker per side"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T.-detaljer"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. selvtest"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
@@ -972,6 +1041,10 @@ msgstr "Se <0>meddelelsesindstillinger</0> for at konfigurere, hvordan du modtag
msgid "Sent" msgid "Sent"
msgstr "Sendt" msgstr "Sendt"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Serienummer"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Indstil procentvise tærskler for målerfarver." msgstr "Indstil procentvise tærskler for målerfarver."
@@ -1002,9 +1075,10 @@ msgstr "Sorter efter"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "State" msgid "State"
msgstr "" msgstr "Tilstand"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1028,7 +1102,7 @@ msgstr "System"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "System load averages over time" msgid "System load averages over time"
msgstr "" msgstr "Gennemsnitlig system belastning over tid"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tabel" msgstr "Tabel"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatur" msgstr "Temperatur"
@@ -1054,7 +1129,7 @@ msgstr "Temperatur"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Temperature unit" msgid "Temperature unit"
msgstr "" msgstr "Temperaturenhed"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Temperatures of system sensors" msgid "Temperatures of system sensors"
@@ -1078,7 +1153,7 @@ msgstr "Denne handling kan ikke fortrydes. Dette vil permanent slette alle aktue
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "This will permanently delete all selected records from the database." msgid "This will permanently delete all selected records from the database."
msgstr "" msgstr "Dette vil permanent slette alle poster fra databasen."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Throughput of {extraFsName}" msgid "Throughput of {extraFsName}"
@@ -1108,21 +1183,26 @@ msgstr "Skift tema"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token" msgid "Token"
msgstr "" msgstr "Nøgle"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "" msgstr "Nøgler & fingeraftryk"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection." msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection."
msgstr "" msgstr "Nøgler tillader agenter at oprette forbindelse og registrere. Fingeraftryk er stabile identifikatorer unikke for hvert system, indstillet ved første forbindelse."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr "Nøgler og fingeraftryk bruges til at godkende WebSocket-forbindelser til hubben."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Samlet"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
@@ -1134,15 +1214,15 @@ msgstr "Samlet sendt data for hver interface"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "" msgstr "Udløser når 1 minut belastning gennemsnit overstiger en tærskel"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 15 minute load average exceeds a threshold" msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr "" msgstr "Udløser når 15 minut belastning gennemsnit overstiger en tærskel"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 5 minute load average exceeds a threshold" msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr "" msgstr "Udløser når 5 minut belastning gennemsnit overstiger en tærskel"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
@@ -1168,15 +1248,19 @@ msgstr "Udløser når status skifter mellem op og ned"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Udløser når brugen af en disk overstiger en tærskel" msgstr "Udløser når brugen af en disk overstiger en tærskel"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Type"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
msgstr "" msgstr "Enhedspræferencer"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Universal token" msgid "Universal token"
msgstr "" msgstr "Universalnøgle"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -1191,7 +1275,7 @@ msgstr "Oppe"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr "Oppe ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
msgid "Updated" msgid "Updated"
@@ -1199,7 +1283,7 @@ msgstr "Opdateret"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Upload" msgstr "Overfør"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Uptime" msgid "Uptime"
@@ -1209,6 +1293,7 @@ msgstr "Oppetid"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Forbrug" msgstr "Forbrug"
@@ -1228,19 +1313,20 @@ msgstr "Brugere"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Value" msgid "Value"
msgstr "" msgstr "Værdi"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "View" msgid "View"
msgstr "Vis" msgstr "Vis"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Se mere" msgstr "Se mere"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "View your 200 most recent alerts." msgid "View your 200 most recent alerts."
msgstr "" msgstr "Se dine 200 nyeste alarmer."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Visible Fields" msgid "Visible Fields"
@@ -1268,7 +1354,7 @@ msgstr "Webhook / Push notifikationer"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart." msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart."
msgstr "" msgstr "Når aktiveret tillader denne nøgle agenter at selvregistrere uden forudgående systemoprettelse. Udløber efter en time eller ved hub-genstart."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: de\n" "Language: de\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-05 16:13\n" "PO-Revision-Date: 2025-10-28 22:59\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: de\n" "X-Crowdin-Language: de\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# Tag} other {# Tage}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# Stunde} other {# Stunden}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# Minute} other {# Minuten}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# Minute} other {# Minuten}}"
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} von {1} Zeile(n) ausgewählt." msgstr "{0} von {1} Zeile(n) ausgewählt."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} Tag} other {{countString} Tage}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} Stunde} other {{countString} Stunden}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} Minute} other {{countString} Minuten}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 Stunde" msgstr "1 Stunde"
@@ -126,7 +123,7 @@ msgstr "Agent"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "Alarm-Verlauf" msgstr "Warnungsverlauf"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
@@ -234,6 +231,10 @@ msgstr "Cache / Puffer"
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapazität"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Vorsicht - potenzieller Datenverlust" msgstr "Vorsicht - potenzieller Datenverlust"
@@ -277,7 +278,11 @@ msgstr "Überprüfe deinen Benachrichtigungsdienst"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Klicken Sie auf einen Container, um weitere Informationen zu sehen." msgstr "Klicke auf einen Container, um weitere Informationen zu sehen."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Klicke auf ein Gerät, um weitere Informationen zu sehen."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
@@ -350,11 +355,11 @@ msgstr "Text kopieren"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>." msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>."
msgstr "Kopieren Sie den Installationsbefehl für den Agent unten oder registrieren Sie Agents automatisch mit einem <0>universellen Token</0>." msgstr "Kopiere den Installationsbefehl für den Agent unten oder registriere Agents automatisch mit einem <0>universellen Token</0>."
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>." msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr "Kopieren Sie den<0>docker-compose.yml</0> Inhalt für den Agent unten oder registrieren Sie Agents automatisch mit einem <1>universellen Token</1>." msgstr "Kopiere den<0>docker-compose.yml</0> Inhalt für den Agent unten oder registriere Agents automatisch mit einem <1>universellen Token</1>."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML" msgid "Copy YAML"
@@ -365,8 +370,17 @@ msgstr "YAML kopieren"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU-Kerne"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU-Zeit-Aufschlüsselung"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU-Auslastung" msgstr "CPU-Auslastung"
@@ -397,6 +411,11 @@ msgstr "Kumulativer Upload"
msgid "Current state" msgid "Current state"
msgstr "Aktueller Zustand" msgstr "Aktueller Zustand"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Zyklen"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Dashboard" msgstr "Dashboard"
@@ -416,7 +435,11 @@ msgstr "Fingerabdruck löschen"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detail" msgstr "Details"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Gerät"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -548,6 +571,10 @@ msgstr "Exportiere die aktuelle Systemkonfiguration."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Fehlgeschlagene Attribute:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Authentifizierung fehlgeschlagen" msgstr "Authentifizierung fehlgeschlagen"
@@ -568,6 +595,7 @@ msgstr "Warnung konnte nicht aktualisiert werden"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -576,6 +604,10 @@ msgstr "Filter..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Fingerabdruck" msgstr "Fingerabdruck"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firm­ware"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Für <0>{min}</0> {min, plural, one {Minute} other {Minuten}}" msgstr "Für <0>{min}</0> {min, plural, one {Minute} other {Minuten}}"
@@ -730,6 +762,10 @@ msgstr "Arbeitsspeichernutzung"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Arbeitsspeichernutzung der Docker-Container" msgstr "Arbeitsspeichernutzung der Docker-Container"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modell"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Netzwerkeinheit"
msgid "No results found." msgid "No results found."
msgstr "Keine Ergebnisse gefunden." msgstr "Keine Ergebnisse gefunden."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Keine Ergebnisse." msgstr "Keine Ergebnisse."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Für dieses Gerät sind keine S.M.A.R.T.-Attribute verfügbar."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Menü öffnen"
msgid "Or continue with" msgid "Or continue with"
msgstr "Oder fortfahren mit" msgstr "Oder fortfahren mit"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Andere"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Bestehende Warnungen überschreiben" msgstr "Bestehende Warnungen überschreiben"
@@ -847,6 +894,15 @@ msgstr "Pausiert"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pausiert ({pausedSystemsLength})" msgstr "Pausiert ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Durchschnittliche Auslastung pro Kern"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Prozentsatz der Zeit in jedem Zustand"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Bitte <0>konfiguriere einen SMTP-Server</0>, um sicherzustellen, dass Warnungen zugestellt werden." msgstr "Bitte <0>konfiguriere einen SMTP-Server</0>, um sicherzustellen, dass Warnungen zugestellt werden."
@@ -884,6 +940,11 @@ msgstr "Bitte melde dich bei deinem Konto an"
msgid "Port" msgid "Port"
msgstr "Port" msgstr "Port"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Eingeschaltet"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -896,7 +957,7 @@ msgstr "Bevorzugte Sprache"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
msgstr "Schlüssel" msgstr "Öffentlicher Schlüssel"
#. Disk read #. Disk read
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -943,6 +1004,14 @@ msgstr "Token rotieren"
msgid "Rows per page" msgid "Rows per page"
msgstr "Zeilen pro Seite" msgstr "Zeilen pro Seite"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T.-Details"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T.-Selbsttest"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Adresse mit der Enter-Taste oder Komma speichern. Leer lassen, um E-Mail-Benachrichtigungen zu deaktivieren." msgstr "Adresse mit der Enter-Taste oder Komma speichern. Leer lassen, um E-Mail-Benachrichtigungen zu deaktivieren."
@@ -954,7 +1023,7 @@ msgstr "Einstellungen speichern"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Save system" msgid "Save system"
msgstr "System sichern" msgstr "System speichern"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Search" msgid "Search"
@@ -972,6 +1041,10 @@ msgstr "Siehe <0>Benachrichtigungseinstellungen</0>, um zu konfigurieren, wie du
msgid "Sent" msgid "Sent"
msgstr "Gesendet" msgstr "Gesendet"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Seriennummer"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Prozentuale Schwellenwerte für Zählerfarben festlegen." msgstr "Prozentuale Schwellenwerte für Zählerfarben festlegen."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Status" msgstr "Status"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tabelle" msgstr "Tabelle"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatur" msgstr "Temperatur"
@@ -1124,13 +1199,18 @@ msgstr "Tokens ermöglichen es Agents, sich zu verbinden und zu registrieren. Fi
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren." msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Gesamt"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Gesamtdatenmenge für jede Schnittstelle empfangen" msgstr "Empfangene Gesamtdatenmenge je Schnittstelle "
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Gesamtdatenmenge für jede Schnittstelle gesendet" msgstr "Gesendete Gesamtdatenmenge je Schnittstelle"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
@@ -1150,7 +1230,7 @@ msgstr "Löst aus, wenn ein Sensor einen Schwellenwert überschreitet"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when combined up/down exceeds a threshold" msgid "Triggers when combined up/down exceeds a threshold"
msgstr "Löst aus, wenn die kombinierte Auf-/Abwärtsbewegung einen Schwellenwert überschreitet" msgstr "Löst aus, wenn die kombinierte Up- und Downloadrate einen Schwellenwert überschreitet"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
@@ -1168,6 +1248,10 @@ msgstr "Löst aus, wenn der Status zwischen online und offline wechselt"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Löst aus, wenn die Nutzung einer Festplatte einen Schwellenwert überschreitet" msgstr "Löst aus, wenn die Nutzung einer Festplatte einen Schwellenwert überschreitet"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Typ"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Betriebszeit"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Nutzung" msgstr "Nutzung"
@@ -1234,6 +1319,7 @@ msgstr "Wert"
msgid "View" msgid "View"
msgstr "Ansicht" msgstr "Ansicht"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Mehr anzeigen" msgstr "Mehr anzeigen"

File diff suppressed because it is too large Load Diff

View File

@@ -13,27 +13,24 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# day} other {# days}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# hour} other {# hours}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} of {1} row(s) selected." msgstr "{0} of {1} row(s) selected."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} day} other {{countString} days}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} hour} other {{countString} hours}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 hour" msgstr "1 hour"
@@ -229,6 +226,10 @@ msgstr "Cache / Buffers"
msgid "Cancel" msgid "Cancel"
msgstr "Cancel" msgstr "Cancel"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Capacity"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Caution - potential data loss" msgstr "Caution - potential data loss"
@@ -274,6 +275,10 @@ msgstr "Check your notification service"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Click on a container to view more information." msgstr "Click on a container to view more information."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Click on a device to view more information."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Click on a system to view more information." msgstr "Click on a system to view more information."
@@ -360,8 +365,17 @@ msgstr "Copy YAML"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU Cores"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU Time Breakdown"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU Usage" msgstr "CPU Usage"
@@ -392,6 +406,11 @@ msgstr "Cumulative Upload"
msgid "Current state" msgid "Current state"
msgstr "Current state" msgstr "Current state"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cycles"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Dashboard" msgstr "Dashboard"
@@ -413,6 +432,10 @@ msgstr "Delete fingerprint"
msgid "Detail" msgid "Detail"
msgstr "Detail" msgstr "Detail"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Device"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -543,6 +566,10 @@ msgstr "Export your current systems configuration."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Failed Attributes:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Failed to authenticate" msgstr "Failed to authenticate"
@@ -563,6 +590,7 @@ msgstr "Failed to update alert"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -571,6 +599,10 @@ msgstr "Filter..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Fingerprint" msgstr "Fingerprint"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgstr "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -725,6 +757,10 @@ msgstr "Memory Usage"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Memory usage of docker containers" msgstr "Memory usage of docker containers"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -756,11 +792,18 @@ msgstr "Network unit"
msgid "No results found." msgid "No results found."
msgstr "No results found." msgstr "No results found."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "No results." msgstr "No results."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "No S.M.A.R.T. attributes available for this device."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -794,6 +837,10 @@ msgstr "Open menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Or continue with" msgstr "Or continue with"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Other"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Overwrite existing alerts" msgstr "Overwrite existing alerts"
@@ -842,6 +889,15 @@ msgstr "Paused"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Paused ({pausedSystemsLength})" msgstr "Paused ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Per-core average utilization"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Percentage of time spent in each state"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgstr "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
@@ -879,6 +935,11 @@ msgstr "Please sign in to your account"
msgid "Port" msgid "Port"
msgstr "Port" msgstr "Port"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Power On"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -938,6 +999,14 @@ msgstr "Rotate token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Rows per page" msgstr "Rows per page"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Details"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Self-Test"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Save address using enter key or comma. Leave blank to disable email notifications." msgstr "Save address using enter key or comma. Leave blank to disable email notifications."
@@ -967,6 +1036,10 @@ msgstr "See <0>notification settings</0> to configure how you receive alerts."
msgid "Sent" msgid "Sent"
msgstr "Sent" msgstr "Sent"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Serial Number"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Set percentage thresholds for meter colors." msgstr "Set percentage thresholds for meter colors."
@@ -1000,6 +1073,7 @@ msgid "State"
msgstr "State" msgstr "State"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1038,6 +1112,7 @@ msgid "Table"
msgstr "Table" msgstr "Table"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temp" msgstr "Temp"
@@ -1119,6 +1194,11 @@ msgstr "Tokens allow agents to connect and register. Fingerprints are stable ide
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Total data received for each interface" msgstr "Total data received for each interface"
@@ -1163,6 +1243,10 @@ msgstr "Triggers when status switches between up and down"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Triggers when usage of any disk exceeds a threshold" msgstr "Triggers when usage of any disk exceeds a threshold"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Type"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1204,6 +1288,7 @@ msgstr "Uptime"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Usage" msgstr "Usage"
@@ -1229,6 +1314,7 @@ msgstr "Value"
msgid "View" msgid "View"
msgstr "View" msgstr "View"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "View more" msgstr "View more"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: es\n" "Language: es\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-11-04 22:13\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: es-ES\n" "X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# día} other {# días}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# hora} other {# horas}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuto} other {# minutos}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# minuto} other {# minutos}}"
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} de {1} fila(s) seleccionada(s)." msgstr "{0} de {1} fila(s) seleccionada(s)."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} día} other {{countString} días}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} hora} other {{countString} horas}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuto} other {{countString} minutos}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 hora" msgstr "1 hora"
@@ -91,15 +88,15 @@ msgstr "Activo"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Alertas Activas" msgstr "Alertas activas"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Agregar <0>Sistema</0>" msgstr "Agregar <0>sistema</0>"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add New System" msgid "Add New System"
msgstr "Agregar Nuevo Sistema" msgstr "Agregar nuevo sistema"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add system" msgid "Add system"
@@ -126,7 +123,7 @@ msgstr "Agente"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "Historial de Alertas" msgstr "Historial de alertas"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
@@ -145,11 +142,11 @@ msgstr "Todos los contenedores"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Todos los Sistemas" msgstr "Todos los sistemas"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
msgstr "¿Está seguro de que desea eliminar {name}?" msgstr "¿Estás seguro de que deseas eliminar {name}?"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?" msgid "Are you sure?"
@@ -192,7 +189,7 @@ msgstr "Utilización promedio de motores GPU"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Backups" msgid "Backups"
msgstr "Copias de Seguridad" msgstr "Copias de seguridad"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
@@ -218,12 +215,12 @@ msgstr "Binario"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)" msgstr "Bits (kbps, Mbps, Gbps)"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)" msgstr "Bytes (kB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
msgid "Cache / Buffers" msgid "Cache / Buffers"
@@ -234,6 +231,10 @@ msgstr "Caché / Buffers"
msgid "Cancel" msgid "Cancel"
msgstr "Cancelar" msgstr "Cancelar"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Capacidad"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Precaución - posible pérdida de datos" msgstr "Precaución - posible pérdida de datos"
@@ -261,31 +262,35 @@ msgstr "Cargando"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Chart options" msgid "Chart options"
msgstr "Opciones de Gráficos" msgstr "Opciones de gráficos"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Check {email} for a reset link." msgid "Check {email} for a reset link."
msgstr "Revise {email} para un enlace de restablecimiento." msgstr "Revisa {email} para un enlace de restablecimiento."
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Check logs for more details." msgid "Check logs for more details."
msgstr "Revise los registros para más detalles." msgstr "Revisa los registros para más detalles."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Check your notification service" msgid "Check your notification service"
msgstr "Verifique su servicio de notificaciones" msgstr "Verifica tu servicio de notificaciones"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Haga clic en un contenedor para ver más información." msgstr "Haga clic en un contenedor para ver más información."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Haz clic en un dispositivo para ver más información."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Haga clic en un sistema para ver más información." msgstr "Haz clic en un sistema para ver más información."
#: src/components/ui/input-copy.tsx #: src/components/ui/input-copy.tsx
msgid "Click to copy" msgid "Click to copy"
msgstr "Haga clic para copiar" msgstr "Haz clic para copiar"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
@@ -294,7 +299,7 @@ msgstr "Instrucciones de línea de comandos"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Configure how you receive alert notifications." msgid "Configure how you receive alert notifications."
msgstr "Configure cómo recibe las notificaciones de alertas." msgstr "Configura cómo recibe las notificaciones de alertas."
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
@@ -365,8 +370,17 @@ msgstr "Copiar YAML"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Núcleos de CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Desglose de tiempo de CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Uso de CPU" msgstr "Uso de CPU"
@@ -378,7 +392,7 @@ msgstr "Crear cuenta"
#. Context: date created #. Context: date created
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Created" msgid "Created"
msgstr "Creado" msgstr "Creada"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Critical (%)" msgid "Critical (%)"
@@ -386,24 +400,29 @@ msgstr "Crítico (%)"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Download" msgid "Cumulative Download"
msgstr "Descarga acumulativa" msgstr "Descarga acumulada"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Cumulative Upload" msgid "Cumulative Upload"
msgstr "Carga acumulativa" msgstr "Carga acumulada"
#. Context: Battery state #. Context: Battery state
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Current state" msgid "Current state"
msgstr "Estado actual" msgstr "Estado actual"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Ciclos"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Tablero" msgstr "Tablero"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Default time period" msgid "Default time period"
msgstr "Período de tiempo predeterminado" msgstr "Periodo de tiempo predeterminado"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -418,6 +437,10 @@ msgstr "Eliminar huella digital"
msgid "Detail" msgid "Detail"
msgstr "Detalle" msgstr "Detalle"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Dispositivo"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -439,7 +462,7 @@ msgstr "Unidad de disco"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Disk Usage" msgid "Disk Usage"
msgstr "Uso de Disco" msgstr "Uso de disco"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Disk usage of {extraFsName}" msgid "Disk usage of {extraFsName}"
@@ -451,11 +474,11 @@ msgstr "Uso de CPU de Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Docker Memory Usage" msgid "Docker Memory Usage"
msgstr "Uso de Memoria de Docker" msgstr "Uso de memoria de Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Docker Network I/O" msgid "Docker Network I/O"
msgstr "E/S de Red de Docker" msgstr "E/S de red de Docker"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Documentation" msgid "Documentation"
@@ -467,11 +490,11 @@ msgstr "Documentación"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "Abajo" msgstr "Caído"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "Abajo ({downSystemsLength})" msgstr "Caído ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Download" msgid "Download"
@@ -503,11 +526,11 @@ msgstr "Vacía"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Enter email address to reset password" msgid "Enter email address to reset password"
msgstr "Ingrese la dirección de correo electrónico para restablecer la contraseña" msgstr "Ingresa la dirección de correo electrónico para restablecer la contraseña"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Enter email address..." msgid "Enter email address..."
msgstr "Ingrese dirección de correo..." msgstr "Ingresa dirección de correo..."
#: src/components/login/otp-forms.tsx #: src/components/login/otp-forms.tsx
msgid "Enter your one-time password." msgid "Enter your one-time password."
@@ -530,7 +553,7 @@ msgstr "Excede {0}{1} en el último {2, plural, one {# minuto} other {# minutos}
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Los sistemas existentes no definidos en <0>config.yml</0> serán eliminados. Por favor, haga copias de seguridad regularmente." msgstr "Los sistemas existentes no definidos en <0>config.yml</0> serán eliminados. Por favor, haz copias de seguridad regularmente."
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
@@ -542,12 +565,16 @@ msgstr "Exportar configuración"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Export your current systems configuration." msgid "Export your current systems configuration."
msgstr "Exporte la configuración actual de sus sistemas." msgstr "Exporta la configuración actual de sus sistemas."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Atributos fallidos:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Error al autenticar" msgstr "Error al autenticar"
@@ -568,6 +595,7 @@ msgstr "Error al actualizar la alerta"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrar..." msgstr "Filtrar..."
@@ -576,13 +604,17 @@ msgstr "Filtrar..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Huella dactilar" msgstr "Huella dactilar"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}" msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Forgot password?" msgid "Forgot password?"
msgstr "¿Olvidó su contraseña?" msgstr "¿Olvidaste tu contraseña?"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -625,7 +657,7 @@ msgstr "Comando Homebrew"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Host / IP" msgid "Host / IP"
msgstr "Host / IP" msgstr "Servidor / IP"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -634,7 +666,7 @@ msgstr "Inactiva"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "If you've lost the password to your admin account, you may reset it using the following command." msgid "If you've lost the password to your admin account, you may reset it using the following command."
msgstr "Si ha perdido la contraseña de su cuenta de administrador, puede restablecerla usando el siguiente comando." msgstr "Si has perdido la contraseña de tu cuenta de administrador, puedes restablecerla usando el siguiente comando."
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image" msgctxt "Docker image"
@@ -660,7 +692,7 @@ msgstr "Diseño"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Carga Media" msgstr "Carga media"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
@@ -681,7 +713,7 @@ msgstr "Carga media"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Cerrar Sesión" msgstr "Cerrar sesión"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Login" msgid "Login"
@@ -700,7 +732,7 @@ msgstr "Registros"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "¿Busca dónde crear alertas? Haga clic en los iconos de campana <0/> en la tabla de sistemas." msgstr "¿Buscas dónde crear alertas? Haz clic en los iconos de campana <0/> en la tabla de sistemas."
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
@@ -714,7 +746,7 @@ msgstr "Instrucciones manuales de configuración"
#. Chart select field. Please try to keep this short. #. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Máx 1 min" msgstr "Máx. 1 min"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -724,11 +756,15 @@ msgstr "Memoria"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Uso de Memoria" msgstr "Uso de memoria"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Uso de memoria de los contenedores de Docker" msgstr "Uso de memoria de los contenedores Docker"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modelo"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
@@ -743,7 +779,7 @@ msgstr "Red"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Network traffic of docker containers" msgid "Network traffic of docker containers"
msgstr "Tráfico de red de los contenedores de Docker" msgstr "Tráfico de red de los contenedores Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
@@ -761,11 +797,18 @@ msgstr "Unidad de red"
msgid "No results found." msgid "No results found."
msgstr "No se encontraron resultados." msgstr "No se encontraron resultados."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Sin resultados." msgstr "Sin resultados."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "No hay atributos S.M.A.R.T. disponibles para este dispositivo."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Abrir menú"
msgid "Or continue with" msgid "Or continue with"
msgstr "O continuar con" msgstr "O continuar con"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Otro"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Sobrescribir alertas existentes" msgstr "Sobrescribir alertas existentes"
@@ -847,43 +894,57 @@ msgstr "Pausado"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pausado ({pausedSystemsLength})" msgstr "Pausado ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Uso promedio por núcleo"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Porcentaje de tiempo dedicado a cada estado"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Por favor, <0>configure un servidor SMTP</0> para asegurar que las alertas sean entregadas." msgstr "Por favor, <0>configura un servidor SMTP</0> para asegurar que las alertas sean entregadas."
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Por favor, revise los registros para más detalles." msgstr "Por favor, revisa los registros para más detalles."
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Por favor, verifique sus credenciales e intente de nuevo" msgstr "Por favor, verifica tus credenciales e inténtalo de nuevo"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Please create an admin account" msgid "Please create an admin account"
msgstr "Por favor, cree una cuenta de administrador" msgstr "Por favor, crea una cuenta de administrador"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Please enable pop-ups for this site" msgid "Please enable pop-ups for this site"
msgstr "Por favor, habilite las ventanas emergentes para este sitio" msgstr "Por favor, habilita las ventanas emergentes para este sitio"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Please log in again" msgid "Please log in again"
msgstr "Por favor, inicie sesión de nuevo" msgstr "Por favor, inicia sesión de nuevo"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Please see <0>the documentation</0> for instructions." msgid "Please see <0>the documentation</0> for instructions."
msgstr "Por favor, consulte <0>la documentación</0> para obtener instrucciones." msgstr "Por favor, consulta <0>la documentación</0> para obtener instrucciones."
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Please sign in to your account" msgid "Please sign in to your account"
msgstr "Por favor, inicie sesión en su cuenta" msgstr "Por favor, inicia sesión en tu cuenta"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Port" msgid "Port"
msgstr "Puerto" msgstr "Puerto"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Encendido"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -891,12 +952,12 @@ msgstr "Utilización precisa en el momento registrado"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Idioma Preferido" msgstr "Idioma preferido"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
msgstr "Clave Pública" msgstr "Clave pública"
#. Disk read #. Disk read
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -923,7 +984,7 @@ msgstr "Solicitar OTP"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Restablecer Contraseña" msgstr "Restablecer contraseña"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
@@ -943,18 +1004,26 @@ msgstr "Rotar token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Filas por página" msgstr "Filas por página"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "Detalles S.M.A.R.T."
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "Autoprueba S.M.A.R.T."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Guarde la dirección usando la tecla enter o coma. Deje en blanco para desactivar las notificaciones por correo." msgstr "Guarda la dirección usando la tecla enter o coma. Deja en blanco para desactivar las notificaciones por correo."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Guardar Configuración" msgstr "Guardar configuración"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Save system" msgid "Save system"
msgstr "Guardar Sistema" msgstr "Guardar sistema"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Search" msgid "Search"
@@ -966,12 +1035,16 @@ msgstr "Buscar sistemas o configuraciones..."
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Consulte <0>configuración de notificaciones</0> para configurar cómo recibe alertas." msgstr "Consulta <0>configuración de notificaciones</0> para configurar cómo recibe alertas."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Sent" msgid "Sent"
msgstr "Enviado" msgstr "Enviado"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Número de serie"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Establecer umbrales de porcentaje para los colores de los medidores." msgstr "Establecer umbrales de porcentaje para los colores de los medidores."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Estado" msgstr "Estado"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1016,7 +1090,7 @@ msgstr "Espacio de swap utilizado por el sistema"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Uso de Swap" msgstr "Uso de swap"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -1036,13 +1110,14 @@ msgstr "Sistemas"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Systems may be managed in a <0>config.yml</0> file inside your data directory." msgid "Systems may be managed in a <0>config.yml</0> file inside your data directory."
msgstr "Los sistemas pueden ser gestionados en un archivo <0>config.yml</0> dentro de su directorio de datos." msgstr "Los sistemas pueden ser gestionados en un archivo <0>config.yml</0> dentro de tu directorio de datos."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Table" msgid "Table"
msgstr "Tabla" msgstr "Tabla"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatura" msgstr "Temperatura"
@@ -1070,7 +1145,7 @@ msgstr "Notificación de prueba enviada"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Luego inicie sesión en el backend y restablezca la contraseña de su cuenta de usuario en la tabla de usuarios." msgstr "Luego inicia sesión en el backend y restablece la contraseña de tu cuenta de usuario en la tabla de usuarios."
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
@@ -1114,7 +1189,7 @@ msgstr "Token"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "Tokens y Huellas Digitales" msgstr "Tokens y huellas digitales"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection." msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection."
@@ -1124,6 +1199,11 @@ msgstr "Los tokens permiten que los agentes se conecten y registren. Las huellas
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub." msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Datos totales recibidos por cada interfaz" msgstr "Datos totales recibidos por cada interfaz"
@@ -1168,6 +1248,10 @@ msgstr "Se activa cuando el estado cambia entre activo e inactivo"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Se activa cuando el uso de cualquier disco supera un umbral" msgstr "Se activa cuando el uso de cualquier disco supera un umbral"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Tipo"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Tiempo de actividad"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Uso" msgstr "Uso"
@@ -1234,13 +1319,14 @@ msgstr "Valor"
msgid "View" msgid "View"
msgstr "Vista" msgstr "Vista"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Ver más" msgstr "Ver más"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "View your 200 most recent alerts." msgid "View your 200 most recent alerts."
msgstr "Ver sus 200 alertas más recientes." msgstr "Ver tus 200 alertas más recientes."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Visible Fields" msgid "Visible Fields"
@@ -1252,7 +1338,7 @@ msgstr "Esperando suficientes registros para mostrar"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "¿Quieres ayudarnos a mejorar nuestras traducciones? Consulta <0>Crowdin</0> para más detalles." msgstr "¿Quieres ayudar a mejorar nuestras traducciones? Consulta <0>Crowdin</0> para más detalles."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
@@ -1292,4 +1378,4 @@ msgstr "Configuración YAML"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Su configuración de usuario ha sido actualizada." msgstr "Tu configuración de usuario ha sido actualizada."

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: fa\n" "Language: fa\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Persian\n" "Language-Team: Persian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: fa\n" "X-Crowdin-Language: fa\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# روز} other {# روز}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# ساعت} other {# ساعت}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# دقیقه} few {# دقیقه} many {# دقیقه} other {# دقیقه}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# دقیقه} few {# دقیقه} many {# دقیقه} ot
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} از {1} ردیف انتخاب شده است." msgstr "{0} از {1} ردیف انتخاب شده است."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} روز} other {{countString} روز}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} ساعت} other {{countString} ساعت}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} دقیقه} few {{countString} دقیقه} many {{countString} دقیقه} other {{countString} دقیقه}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "۱ ساعت" msgstr "۱ ساعت"
@@ -234,6 +231,10 @@ msgstr "حافظه پنهان / بافرها"
msgid "Cancel" msgid "Cancel"
msgstr "لغو" msgstr "لغو"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "ظرفیت"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "احتیاط - احتمال از دست رفتن داده‌ها" msgstr "احتیاط - احتمال از دست رفتن داده‌ها"
@@ -279,6 +280,10 @@ msgstr "سرویس اطلاع‌رسانی خود را بررسی کنید"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "برای مشاهده اطلاعات بیشتر روی کانتینر کلیک کنید." msgstr "برای مشاهده اطلاعات بیشتر روی کانتینر کلیک کنید."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "برای مشاهده اطلاعات بیشتر روی دستگاه کلیک کنید."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "برای مشاهده اطلاعات بیشتر روی یک سیستم کلیک کنید." msgstr "برای مشاهده اطلاعات بیشتر روی یک سیستم کلیک کنید."
@@ -365,8 +370,17 @@ msgstr "کپی YAML"
msgid "CPU" msgid "CPU"
msgstr "پردازنده" msgstr "پردازنده"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "هسته‌های CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "تجزیه زمان CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "میزان استفاده از پردازنده" msgstr "میزان استفاده از پردازنده"
@@ -397,6 +411,11 @@ msgstr "آپلود تجمعی"
msgid "Current state" msgid "Current state"
msgstr "وضعیت فعلی" msgstr "وضعیت فعلی"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "چرخه‌ها"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "داشبورد" msgstr "داشبورد"
@@ -418,6 +437,10 @@ msgstr "حذف اثر انگشت"
msgid "Detail" msgid "Detail"
msgstr "جزئیات" msgstr "جزئیات"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "دستگاه"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "پیکربندی سیستم‌های فعلی خود را خارج کن
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "فارنهایت (°F)" msgstr "فارنهایت (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "ویژگی‌های ناموفق:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "احراز هویت ناموفق بود" msgstr "احراز هویت ناموفق بود"
@@ -568,6 +595,7 @@ msgstr "به‌روزرسانی هشدار ناموفق بود"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "فیلتر..." msgstr "فیلتر..."
@@ -576,6 +604,10 @@ msgstr "فیلتر..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "اثر انگشت" msgstr "اثر انگشت"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "فرم‌ویر"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "برای <0>{min}</0> {min, plural, one {دقیقه} other {دقیقه}}" msgstr "برای <0>{min}</0> {min, plural, one {دقیقه} other {دقیقه}}"
@@ -730,6 +762,10 @@ msgstr "میزان استفاده از حافظه"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "میزان استفاده از حافظه کانتینرهای داکر" msgstr "میزان استفاده از حافظه کانتینرهای داکر"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "مدل"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "واحد شبکه"
msgid "No results found." msgid "No results found."
msgstr "هیچ نتیجه‌ای یافت نشد." msgstr "هیچ نتیجه‌ای یافت نشد."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "نتیجه‌ای یافت نشد." msgstr "نتیجه‌ای یافت نشد."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "هیچ ویژگی S.M.A.R.T برای این دستگاه موجود نیست."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "باز کردن منو"
msgid "Or continue with" msgid "Or continue with"
msgstr "یا ادامه با" msgstr "یا ادامه با"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "سایر"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "بازنویسی هشدارهای موجود" msgstr "بازنویسی هشدارهای موجود"
@@ -847,6 +894,15 @@ msgstr "مکث شده"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "مکث شده ({pausedSystemsLength})" msgstr "مکث شده ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "میانگین استفاده در هر هسته"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "درصد زمان صرف شده در هر حالت"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "لطفاً برای اطمینان از تحویل هشدارها، یک <0>سرور SMTP پیکربندی کنید</0>." msgstr "لطفاً برای اطمینان از تحویل هشدارها، یک <0>سرور SMTP پیکربندی کنید</0>."
@@ -884,6 +940,11 @@ msgstr "لطفاً به حساب کاربری خود وارد شوید"
msgid "Port" msgid "Port"
msgstr "پورت" msgstr "پورت"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "روشن کردن"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "چرخش توکن"
msgid "Rows per page" msgid "Rows per page"
msgstr "ردیف در هر صفحه" msgstr "ردیف در هر صفحه"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "جزئیات S.M.A.R.T"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "تست خود S.M.A.R.T"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "آدرس را با استفاده از کلید Enter یا کاما ذخیره کنید. برای غیرفعال کردن اعلان‌های ایمیلی، خالی بگذارید." msgstr "آدرس را با استفاده از کلید Enter یا کاما ذخیره کنید. برای غیرفعال کردن اعلان‌های ایمیلی، خالی بگذارید."
@@ -972,6 +1041,10 @@ msgstr "برای پیکربندی نحوه دریافت هشدارها، به <0
msgid "Sent" msgid "Sent"
msgstr "ارسال شد" msgstr "ارسال شد"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "شماره سریال"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "آستانه های درصدی را برای رنگ های متر تنظیم کنید." msgstr "آستانه های درصدی را برای رنگ های متر تنظیم کنید."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "وضعیت" msgstr "وضعیت"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "جدول" msgstr "جدول"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "دما" msgstr "دما"
@@ -1124,6 +1199,11 @@ msgstr "توکن‌ها به عامل‌ها اجازه اتصال و ثبت‌
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند." msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "کل"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "داده‌های کل دریافت شده برای هر رابط" msgstr "داده‌های کل دریافت شده برای هر رابط"
@@ -1168,6 +1248,10 @@ msgstr "هنگامی که وضعیت بین بالا و پایین تغییر م
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "هنگامی که استفاده از هر دیسکی از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که استفاده از هر دیسکی از یک آستانه فراتر رود، فعال می‌شود"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "نوع"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "آپتایم"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "میزان استفاده" msgstr "میزان استفاده"
@@ -1234,6 +1319,7 @@ msgstr "مقدار"
msgid "View" msgid "View"
msgstr "مشاهده" msgstr "مشاهده"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "مشاهده بیشتر" msgstr "مشاهده بیشتر"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: fr\n" "Language: fr\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-20 16:38\n" "PO-Revision-Date: 2025-10-28 22:59\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: French\n" "Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: fr\n" "X-Crowdin-Language: fr\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# jour} other {# jours}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# heure} other {# heures}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# min
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} sur {1} ligne(s) sélectionnée(s)." msgstr "{0} sur {1} ligne(s) sélectionnée(s)."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} jour} other {{countString} jours}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} heure} other {{countString} heures}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 heure" msgstr "1 heure"
@@ -234,6 +231,10 @@ msgstr "Cache / Tampons"
msgid "Cancel" msgid "Cancel"
msgstr "Annuler" msgstr "Annuler"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Capacité"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Attention - perte de données potentielle" msgstr "Attention - perte de données potentielle"
@@ -279,6 +280,10 @@ msgstr "Vérifiez votre service de notification"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Cliquez sur un conteneur pour voir plus d'informations." msgstr "Cliquez sur un conteneur pour voir plus d'informations."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Cliquez sur un appareil pour voir plus d'informations."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Cliquez sur un système pour voir plus d'informations." msgstr "Cliquez sur un système pour voir plus d'informations."
@@ -365,8 +370,17 @@ msgstr "Copier YAML"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Cœurs CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Répartition du temps CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Utilisation du CPU" msgstr "Utilisation du CPU"
@@ -397,6 +411,11 @@ msgstr "Téléversement cumulatif"
msgid "Current state" msgid "Current state"
msgstr "État actuel" msgstr "État actuel"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Tableau de bord" msgstr "Tableau de bord"
@@ -418,6 +437,10 @@ msgstr "Supprimer l'empreinte"
msgid "Detail" msgid "Detail"
msgstr "Détail" msgstr "Détail"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Appareil"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "Exportez la configuration actuelle de vos systèmes."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Attributs défaillants :"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Échec de l'authentification" msgstr "Échec de l'authentification"
@@ -568,6 +595,7 @@ msgstr "Échec de la mise à jour de l'alerte"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrer..." msgstr "Filtrer..."
@@ -576,6 +604,10 @@ msgstr "Filtrer..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Empreinte" msgstr "Empreinte"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Micrologiciel"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Pour <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgstr "Pour <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -730,6 +762,10 @@ msgstr "Utilisation de la mémoire"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Utilisation de la mémoire des conteneurs Docker" msgstr "Utilisation de la mémoire des conteneurs Docker"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modèle"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Unité réseau"
msgid "No results found." msgid "No results found."
msgstr "Aucun résultat trouvé." msgstr "Aucun résultat trouvé."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Aucun résultat." msgstr "Aucun résultat."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Aucun attribut S.M.A.R.T. disponible pour cet appareil."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Ouvrir le menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Ou continuer avec" msgstr "Ou continuer avec"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Autre"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Écraser les alertes existantes" msgstr "Écraser les alertes existantes"
@@ -847,6 +894,15 @@ msgstr "En pause"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Mis en pause ({pausedSystemsLength})" msgstr "Mis en pause ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Utilisation moyenne par cœur"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Pourcentage de temps passé dans chaque état"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Veuillez <0>configurer un serveur SMTP</0> pour garantir la livraison des alertes." msgstr "Veuillez <0>configurer un serveur SMTP</0> pour garantir la livraison des alertes."
@@ -884,6 +940,11 @@ msgstr "Veuillez vous connecter à votre compte"
msgid "Port" msgid "Port"
msgstr "Port" msgstr "Port"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Allumage"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -929,7 +990,7 @@ msgstr "Réinitialiser le mot de passe"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Resolved" msgid "Resolved"
msgstr "Résolue" msgstr "Résolution"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
@@ -943,6 +1004,14 @@ msgstr "Faire tourner le token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Lignes par page" msgstr "Lignes par page"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "Détails S.M.A.R.T."
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "Auto-test S.M.A.R.T."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Enregistrez l'adresse en utilisant la touche Entrée ou la virgule. Laissez vide pour désactiver les notifications par email." msgstr "Enregistrez l'adresse en utilisant la touche Entrée ou la virgule. Laissez vide pour désactiver les notifications par email."
@@ -972,6 +1041,10 @@ msgstr "Voir les <0>paramètres de notification</0> pour configurer comment vous
msgid "Sent" msgid "Sent"
msgstr "Envoyé" msgstr "Envoyé"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Numéro de série"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Définir des seuils de pourcentage pour les couleurs des compteurs." msgstr "Définir des seuils de pourcentage pour les couleurs des compteurs."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "État" msgstr "État"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tableau" msgstr "Tableau"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temp." msgstr "Temp."
@@ -1054,7 +1129,7 @@ msgstr "Température"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Temperature unit" msgid "Temperature unit"
msgstr "" msgstr "Unité de température"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Temperatures of system sensors" msgid "Temperatures of system sensors"
@@ -1124,6 +1199,11 @@ msgstr "Les tokens permettent aux agents de se connecter et de s'enregistrer. Le
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub." msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Données totales reçues pour chaque interface" msgstr "Données totales reçues pour chaque interface"
@@ -1134,7 +1214,7 @@ msgstr "Données totales envoyées pour chaque interface"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "" msgstr "Se déclenche lorsque la charge moyenne sur 1 minute dépasse un seuil"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 15 minute load average exceeds a threshold" msgid "Triggers when 15 minute load average exceeds a threshold"
@@ -1168,6 +1248,10 @@ msgstr "Se déclenche lorsque le statut passe de \"Joignable\" à \"Injoignable\
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil" msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr ""
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Temps de fonctionnement"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Utilisation" msgstr "Utilisation"
@@ -1234,6 +1319,7 @@ msgstr "Valeur"
msgid "View" msgid "View"
msgstr "Vue" msgstr "Vue"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Voir plus" msgstr "Voir plus"

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: hr\n" "Language: hr\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-18 23:59\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Croatian\n" "Language-Team: Croatian\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dan} other {# dani}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# sat} other {# sati}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuta} few {# minuta} many {# minuta} other {# minute}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} od {1} redaka izabrano." msgstr "{0} od {1} redaka izabrano."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dan} other {{countString} dani}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} sat} other {{countString} sati}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuta} few {{countString} minuta} many {{countString} minuta} other {{countString} minute}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 sat" msgstr "1 sat"
@@ -234,6 +231,10 @@ msgstr "Predmemorija / Međuspremnici"
msgid "Cancel" msgid "Cancel"
msgstr "Otkaži" msgstr "Otkaži"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapacitet"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Oprez - mogući gubitak podataka" msgstr "Oprez - mogući gubitak podataka"
@@ -279,6 +280,10 @@ msgstr "Provjerite Vaš servis notifikacija"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Kliknite na spremnik za prikaz više informacija." msgstr "Kliknite na spremnik za prikaz više informacija."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Kliknite na uređaj da biste vidjeli više informacija."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Odaberite sustav za prikaz više informacija." msgstr "Odaberite sustav za prikaz više informacija."
@@ -365,8 +370,17 @@ msgstr "Kopiraj YAML"
msgid "CPU" msgid "CPU"
msgstr "Procesor" msgstr "Procesor"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU jezgre"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Raspodjela CPU vremena"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Iskorištenost procesora" msgstr "Iskorištenost procesora"
@@ -397,6 +411,11 @@ msgstr "Kumulativno otpremanje"
msgid "Current state" msgid "Current state"
msgstr "Trenutno stanje" msgstr "Trenutno stanje"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Ciklusi"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Nadzorna ploča" msgstr "Nadzorna ploča"
@@ -418,6 +437,10 @@ msgstr "Izbriši otisak"
msgid "Detail" msgid "Detail"
msgstr "Detalj" msgstr "Detalj"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Uređaj"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "Izvoz trenutne sistemske konfiguracije."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Farenhajt (°F)" msgstr "Farenhajt (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Neuspjeli atributi:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Provjera autentičnosti nije uspjela" msgstr "Provjera autentičnosti nije uspjela"
@@ -568,6 +595,7 @@ msgstr "Ažuriranje upozorenja nije uspjelo"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtriraj..." msgstr "Filtriraj..."
@@ -576,6 +604,10 @@ msgstr "Filtriraj..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Otisak prsta" msgstr "Otisak prsta"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Za <0>{min}</0> {min, plural, one {minutu} other {minute}}" msgstr "Za <0>{min}</0> {min, plural, one {minutu} other {minute}}"
@@ -730,6 +762,10 @@ msgstr "Upotreba memorije"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Upotreba memorije Docker spremnika" msgstr "Upotreba memorije Docker spremnika"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Mjerna jedinica za mrežu"
msgid "No results found." msgid "No results found."
msgstr "Nema rezultata." msgstr "Nema rezultata."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Nema rezultata." msgstr "Nema rezultata."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Nema dostupnih S.M.A.R.T. atributa za ovaj uređaj."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Otvori menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Ili nastavi sa" msgstr "Ili nastavi sa"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Ostalo"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Prebrišite postojeća upozorenja" msgstr "Prebrišite postojeća upozorenja"
@@ -847,6 +894,15 @@ msgstr "Pauzirano"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pauzirano ({pausedSystemsLength})" msgstr "Pauzirano ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Prosječna iskorištenost po jezgri"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Postotak vremena provedenog u svakom stanju"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Molimo <0>konfigurirajte SMTP server</0> kako biste osigurali isporuku upozorenja." msgstr "Molimo <0>konfigurirajte SMTP server</0> kako biste osigurali isporuku upozorenja."
@@ -884,6 +940,11 @@ msgstr "Molimo prijavite se u svoj račun"
msgid "Port" msgid "Port"
msgstr "Port" msgstr "Port"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Uključivanje"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Promijeni token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Redovi po stranici" msgstr "Redovi po stranici"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Detalji"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Samotestiranje"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Spremite adresu pomoću tipke enter ili zareza. Ostavite prazno kako biste onemogućili obavijesti e-poštom." msgstr "Spremite adresu pomoću tipke enter ili zareza. Ostavite prazno kako biste onemogućili obavijesti e-poštom."
@@ -972,6 +1041,10 @@ msgstr "Pogledajte <0>postavke obavijesti</0> da biste konfigurirali način prim
msgid "Sent" msgid "Sent"
msgstr "Poslano" msgstr "Poslano"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Serijski broj"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Postavite pragove postotka za boje mjerača." msgstr "Postavite pragove postotka za boje mjerača."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Stanje" msgstr "Stanje"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tablica" msgstr "Tablica"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temp" msgstr "Temp"
@@ -1124,6 +1199,11 @@ msgstr "Tokeni dopuštaju agentima prijavu i registraciju. Otisci su stabilni id
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeni se uz otiske koriste za autentifikaciju WebSocket veza prema središnjoj kontroli." msgstr "Tokeni se uz otiske koriste za autentifikaciju WebSocket veza prema središnjoj kontroli."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Ukupno"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Ukupni podaci primljeni za svako sučelje" msgstr "Ukupni podaci primljeni za svako sučelje"
@@ -1168,6 +1248,10 @@ msgstr "Pokreće se kada se status sistema promijeni"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Pokreće se kada iskorištenost bilo kojeg diska premaši prag" msgstr "Pokreće se kada iskorištenost bilo kojeg diska premaši prag"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Vrsta"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Vrijeme rada"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Iskorištenost" msgstr "Iskorištenost"
@@ -1234,6 +1319,7 @@ msgstr "Vrijednost"
msgid "View" msgid "View"
msgstr "Prikaz" msgstr "Prikaz"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Prikaži više" msgstr "Prikaži više"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: hu\n" "Language: hu\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-14 23:40\n" "PO-Revision-Date: 2025-10-28 22:59\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Hungarian\n" "Language-Team: Hungarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# nap} other {# nap}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# óra} other {# óra}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# perc} few {# perc} many {# perc} other {# perc}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} a(z) {1} sorból kiválasztva." msgstr "{0} a(z) {1} sorból kiválasztva."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} nap} other {{countString} nap}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} óra} other {{countString} óra}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} perc} few {{countString} perc} many {{countString} perc} other {{countString} perc}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 óra" msgstr "1 óra"
@@ -187,7 +184,7 @@ msgstr "{0} átlagos kihasználtsága"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Average utilization of GPU engines" msgid "Average utilization of GPU engines"
msgstr "GPU motorok átlagos kihasználtsága" msgstr "GPU-k átlagos kihasználtsága"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
@@ -234,6 +231,10 @@ msgstr "Gyorsítótár / Pufferelések"
msgid "Cancel" msgid "Cancel"
msgstr "Mégsem" msgstr "Mégsem"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapacitás"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Figyelem - potenciális adatvesztés" msgstr "Figyelem - potenciális adatvesztés"
@@ -279,6 +280,10 @@ msgstr "Ellenőrizd az értesítési szolgáltatásodat"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Kattintson egy konténerre a további információk megtekintéséhez." msgstr "Kattintson egy konténerre a további információk megtekintéséhez."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Kattintson egy eszközre további információk megtekintéséhez."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "További információkért kattints egy rendszerre." msgstr "További információkért kattints egy rendszerre."
@@ -365,8 +370,17 @@ msgstr "YAML másolása"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU magok"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU idő felbontása"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU használat" msgstr "CPU használat"
@@ -397,6 +411,11 @@ msgstr "Kumulatív feltöltés"
msgid "Current state" msgid "Current state"
msgstr "Jelenlegi állapot" msgstr "Jelenlegi állapot"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Ciklusok"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Áttekintés" msgstr "Áttekintés"
@@ -418,6 +437,10 @@ msgstr "Ujjlenyomat törlése"
msgid "Detail" msgid "Detail"
msgstr "Részlet" msgstr "Részlet"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Eszköz"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "Exportálja a jelenlegi rendszerkonfigurációt."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Sikertelen attribútumok:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Hitelesítés sikertelen" msgstr "Hitelesítés sikertelen"
@@ -568,6 +595,7 @@ msgstr "Nem sikerült frissíteni a riasztást"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Szűrő..." msgstr "Szűrő..."
@@ -576,6 +604,10 @@ msgstr "Szűrő..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Ujjlenyomat" msgstr "Ujjlenyomat"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "A <0>{min}</0> {min, plural, one {perc} other {percek}}" msgstr "A <0>{min}</0> {min, plural, one {perc} other {percek}}"
@@ -603,7 +635,7 @@ msgstr "Általános"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Engines" msgid "GPU Engines"
msgstr "GPU motorok" msgstr "GPU-k"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "GPU Power Draw" msgid "GPU Power Draw"
@@ -730,6 +762,10 @@ msgstr "Memóriahasználat"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Docker konténerek memória használata" msgstr "Docker konténerek memória használata"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modell"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Sávszélesség mértékegysége"
msgid "No results found." msgid "No results found."
msgstr "Nincs találat." msgstr "Nincs találat."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Nincs találat." msgstr "Nincs találat."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Ehhez az eszközhöz nem állnak rendelkezésre S.M.A.R.T. attribútumok."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Menü megnyitása"
msgid "Or continue with" msgid "Or continue with"
msgstr "Vagy folytasd ezzel" msgstr "Vagy folytasd ezzel"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Egyéb"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Felülírja a meglévő riasztásokat" msgstr "Felülírja a meglévő riasztásokat"
@@ -847,6 +894,15 @@ msgstr "Szüneteltetve"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Szüneteltetve ({pausedSystemsLength})" msgstr "Szüneteltetve ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Átlagos kihasználtság magonként"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Az idő százalékos aránya minden állapotban"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Kérjük, <0>konfigurálj egy SMTP szervert</0> az értesítések kézbesítésének biztosítása érdekében." msgstr "Kérjük, <0>konfigurálj egy SMTP szervert</0> az értesítések kézbesítésének biztosítása érdekében."
@@ -884,6 +940,11 @@ msgstr "Kérjük, jelentkezzen be a fiókjába"
msgid "Port" msgid "Port"
msgstr "Port" msgstr "Port"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Bekapcsolás"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Tokenváltás"
msgid "Rows per page" msgid "Rows per page"
msgstr "Sorok száma oldalanként" msgstr "Sorok száma oldalanként"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Részletek"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Önteszt"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Mentse el a címet az Enter billentyű vagy a vessző használatával. Hagyja üresen az e-mail értesítések letiltásához." msgstr "Mentse el a címet az Enter billentyű vagy a vessző használatával. Hagyja üresen az e-mail értesítések letiltásához."
@@ -972,6 +1041,10 @@ msgstr "Lásd <0>az értesítési beállításokat</0>, hogy konfigurálja, hogy
msgid "Sent" msgid "Sent"
msgstr "Elküldve" msgstr "Elküldve"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Sorozatszám"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Százalékos küszöbértékek beállítása a mérőszínekhez." msgstr "Százalékos küszöbértékek beállítása a mérőszínekhez."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Állapot" msgstr "Állapot"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tábla" msgstr "Tábla"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Hőmérséklet" msgstr "Hőmérséklet"
@@ -1124,6 +1199,11 @@ msgstr ""
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr ""
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Összesen"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Összes fogadott adat minden interfészenként" msgstr "Összes fogadott adat minden interfészenként"
@@ -1168,6 +1248,10 @@ msgstr "Bekapcsol, amikor az állapot fel és le között változik"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Bekapcsol, ha a lemez érzékelő túllép egy küszöbértéket" msgstr "Bekapcsol, ha a lemez érzékelő túllép egy küszöbértéket"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Típus"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Üzemidő"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Használat" msgstr "Használat"
@@ -1234,6 +1319,7 @@ msgstr "Érték"
msgid "View" msgid "View"
msgstr "Nézet" msgstr "Nézet"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Továbbiak megjelenítése" msgstr "Továbbiak megjelenítése"

File diff suppressed because it is too large Load Diff

View File

@@ -8,15 +8,15 @@ msgstr ""
"Language: is\n" "Language: is\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-09-22 23:10\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Icelandic\n" "Language-Team: Icelandic\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: is\n" "X-Crowdin-Language: is\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400) #. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -116,7 +116,7 @@ msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr ""
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
@@ -213,7 +213,7 @@ msgstr "Beszel notar <0>Shoutrrr</0> til að tengjast vinsælum tilkynningaþjó
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Binary" msgid "Binary"
msgstr "Binary" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
@@ -234,6 +234,10 @@ msgstr "Skyndiminni / Biðminni"
msgid "Cancel" msgid "Cancel"
msgstr "Hætta við" msgstr "Hætta við"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr ""
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Aðvörun - möguleiki á gagnatapi" msgstr "Aðvörun - möguleiki á gagnatapi"
@@ -279,6 +283,10 @@ msgstr "Athugaðu tilkynningaþjónustuna þína"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Smelltu á gám til að sjá frekari upplýsingar." msgstr "Smelltu á gám til að sjá frekari upplýsingar."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "" msgstr ""
@@ -397,6 +405,11 @@ msgstr ""
msgid "Current state" msgid "Current state"
msgstr "Núverandi ástand" msgstr "Núverandi ástand"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Yfirlitssíða" msgstr "Yfirlitssíða"
@@ -418,6 +431,10 @@ msgstr ""
msgid "Detail" msgid "Detail"
msgstr "Nánar" msgstr "Nánar"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr ""
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +565,10 @@ msgstr ""
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr ""
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Villa í auðkenningu" msgstr "Villa í auðkenningu"
@@ -568,6 +589,7 @@ msgstr "Mistókst að uppfæra tilkynningu"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Sía..." msgstr "Sía..."
@@ -576,6 +598,10 @@ msgstr "Sía..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "" msgstr ""
@@ -593,7 +619,7 @@ msgstr ""
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
msgstr "Full" msgstr ""
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
@@ -625,7 +651,7 @@ msgstr "Homebrew skipun"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Host / IP" msgid "Host / IP"
msgstr "Host / IP" msgstr ""
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -730,6 +756,10 @@ msgstr "Minnisnotkun"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Minnisnotkun docker kerfa" msgstr "Minnisnotkun docker kerfa"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -739,7 +769,7 @@ msgstr "Nafn"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Net" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Network traffic of docker containers" msgid "Network traffic of docker containers"
@@ -748,7 +778,6 @@ msgstr "Net traffík docker kerfa"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "" msgstr ""
@@ -761,11 +790,18 @@ msgstr ""
msgid "No results found." msgid "No results found."
msgstr "Engar niðurstöður fundust." msgstr "Engar niðurstöður fundust."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr ""
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -882,7 +918,7 @@ msgstr "Vinsamlegast skráðu þig inn á aðganginn þinn"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Port" msgid "Port"
msgstr "Port" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -908,11 +944,6 @@ msgstr "Lesa"
msgid "Received" msgid "Received"
msgstr "Móttekið" msgstr "Móttekið"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
msgid "Refresh"
msgstr "Endurhlaða"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "" msgstr ""
@@ -943,6 +974,14 @@ msgstr ""
msgid "Rows per page" msgid "Rows per page"
msgstr "" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr ""
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "" msgstr ""
@@ -970,7 +1009,7 @@ msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Sent" msgid "Sent"
msgstr "Sent" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
@@ -1005,6 +1044,7 @@ msgid "State"
msgstr "" msgstr ""
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1083,7 @@ msgid "Table"
msgstr "Tafla" msgstr "Tafla"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "" msgstr ""
@@ -1168,6 +1209,10 @@ msgstr "Virkjast þegar staða breytist milli virkur og óvirkur"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Virkjast þegar diska notkun fer yfir þröskuld" msgstr "Virkjast þegar diska notkun fer yfir þröskuld"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr ""
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1193,10 +1238,6 @@ msgstr ""
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgid "Updated"
msgstr "Uppfært"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "" msgstr ""
@@ -1293,3 +1334,4 @@ msgstr ""
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Notenda stillingar vistaðar." msgstr "Notenda stillingar vistaðar."

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: it\n" "Language: it\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-30 21:53\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Italian\n" "Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: it\n" "X-Crowdin-Language: it\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# giorno} other {# giorni}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# ora} other {# ore}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuto} other {# minuti}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# minuto} other {# minuti}}"
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} di {1} righe selezionate." msgstr "{0} di {1} righe selezionate."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} giorno} other {{countString} giorni}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} ora} other {{countString} ore}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuto} other {{countString} minuti}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 ora" msgstr "1 ora"
@@ -234,6 +231,10 @@ msgstr "Cache / Buffer"
msgid "Cancel" msgid "Cancel"
msgstr "Annulla" msgstr "Annulla"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Capacità"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Attenzione - possibile perdita di dati" msgstr "Attenzione - possibile perdita di dati"
@@ -279,6 +280,10 @@ msgstr "Controlla il tuo servizio di notifica"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Fare clic su un contenitore per visualizzare ulteriori informazioni." msgstr "Fare clic su un contenitore per visualizzare ulteriori informazioni."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Fare clic su un dispositivo per visualizzare più informazioni."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Clicca su un sistema per visualizzare più informazioni." msgstr "Clicca su un sistema per visualizzare più informazioni."
@@ -365,8 +370,17 @@ msgstr "Copia YAML"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Core CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Suddivisione tempo CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Utilizzo CPU" msgstr "Utilizzo CPU"
@@ -397,6 +411,11 @@ msgstr "Upload cumulativo"
msgid "Current state" msgid "Current state"
msgstr "Stato attuale" msgstr "Stato attuale"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cicli"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Cruscotto" msgstr "Cruscotto"
@@ -418,6 +437,10 @@ msgstr "Elimina impronta digitale"
msgid "Detail" msgid "Detail"
msgstr "Dettagli" msgstr "Dettagli"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Dispositivo"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "Esporta la configurazione attuale dei tuoi sistemi."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Attributi falliti:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Autenticazione fallita" msgstr "Autenticazione fallita"
@@ -568,6 +595,7 @@ msgstr "Aggiornamento dell'avviso fallito"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtra..." msgstr "Filtra..."
@@ -576,6 +604,10 @@ msgstr "Filtra..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Impronta digitale" msgstr "Impronta digitale"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Per <0>{min}</0> {min, plural, one {minuto} other {minuti}}" msgstr "Per <0>{min}</0> {min, plural, one {minuto} other {minuti}}"
@@ -730,6 +762,10 @@ msgstr "Utilizzo Memoria"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Utilizzo della memoria dei container Docker" msgstr "Utilizzo della memoria dei container Docker"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modello"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Unità rete"
msgid "No results found." msgid "No results found."
msgstr "Nessun risultato trovato." msgstr "Nessun risultato trovato."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Nessun risultato." msgstr "Nessun risultato."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Nessun attributo S.M.A.R.T. disponibile per questo dispositivo."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Apri menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Oppure continua con" msgstr "Oppure continua con"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Altro"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Sovrascrivi avvisi esistenti" msgstr "Sovrascrivi avvisi esistenti"
@@ -847,6 +894,15 @@ msgstr "In pausa"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "In pausa ({pausedSystemsLength})" msgstr "In pausa ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Utilizzo medio per core"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Percentuale di tempo trascorso in ogni stato"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Si prega di <0>configurare un server SMTP</0> per garantire la consegna degli avvisi." msgstr "Si prega di <0>configurare un server SMTP</0> per garantire la consegna degli avvisi."
@@ -884,6 +940,11 @@ msgstr "Si prega di accedere al proprio account"
msgid "Port" msgid "Port"
msgstr "Porta" msgstr "Porta"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Accensione"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Ruota token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Righe per pagina" msgstr "Righe per pagina"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "Dettagli S.M.A.R.T."
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "Autotest S.M.A.R.T."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Salva l'indirizzo usando il tasto invio o la virgola. Lascia vuoto per disabilitare le notifiche email." msgstr "Salva l'indirizzo usando il tasto invio o la virgola. Lascia vuoto per disabilitare le notifiche email."
@@ -972,6 +1041,10 @@ msgstr "Vedi <0>impostazioni di notifica</0> per configurare come ricevere gli a
msgid "Sent" msgid "Sent"
msgstr "Inviato" msgstr "Inviato"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Numero di serie"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Imposta le soglie percentuali per i colori dei contatori." msgstr "Imposta le soglie percentuali per i colori dei contatori."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Stato" msgstr "Stato"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tabella" msgstr "Tabella"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatura" msgstr "Temperatura"
@@ -1124,6 +1199,11 @@ msgstr "I token consentono agli agenti di connettersi e registrarsi. Le impronte
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub." msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Totale"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Dati totali ricevuti per ogni interfaccia" msgstr "Dati totali ricevuti per ogni interfaccia"
@@ -1168,6 +1248,10 @@ msgstr "Attiva quando lo stato passa tra up e down"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Attiva quando l'utilizzo di un disco supera una soglia" msgstr "Attiva quando l'utilizzo di un disco supera una soglia"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Tipo"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Tempo di attività"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Utilizzo" msgstr "Utilizzo"
@@ -1234,6 +1319,7 @@ msgstr "Valore"
msgid "View" msgid "View"
msgstr "Vista" msgstr "Vista"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Visualizza altro" msgstr "Visualizza altro"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: ja\n" "Language: ja\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Japanese\n" "Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: ja\n" "X-Crowdin-Language: ja\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# 日} other {# 日}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# 時間} other {# 時間}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# 分} few {# 分} many {# 分} other {# 分}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# 分} few {# 分} many {# 分} other {# 分}}"
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{1}行のうち{0}行が選択されました。" msgstr "{1}行のうち{0}行が選択されました。"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} 日} other {{countString} 日}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} 時間} other {{countString} 時間}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} 分} few {{countString} 分} many {{countString} 分} other {{countString} 分}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1時間" msgstr "1時間"
@@ -234,6 +231,10 @@ msgstr "キャッシュ / バッファ"
msgid "Cancel" msgid "Cancel"
msgstr "キャンセル" msgstr "キャンセル"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "容量"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "注意 - データ損失の可能性" msgstr "注意 - データ損失の可能性"
@@ -279,6 +280,10 @@ msgstr "通知サービスを確認してください"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "詳細情報を表示するにはコンテナをクリックしてください。" msgstr "詳細情報を表示するにはコンテナをクリックしてください。"
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "詳細情報を表示するにはデバイスをクリックしてください。"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "システムをクリックして詳細を表示します。" msgstr "システムをクリックして詳細を表示します。"
@@ -363,10 +368,19 @@ msgstr "YAMLをコピー"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU コア"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU 時間の内訳"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU使用率" msgstr "CPU使用率"
@@ -397,6 +411,11 @@ msgstr "累積アップロード"
msgid "Current state" msgid "Current state"
msgstr "現在の状態" msgstr "現在の状態"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "サイクル"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "ダッシュボード" msgstr "ダッシュボード"
@@ -418,6 +437,10 @@ msgstr "フィンガープリントを削除"
msgid "Detail" msgid "Detail"
msgstr "詳細" msgstr "詳細"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "デバイス"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "現在のシステム設定をエクスポートします。"
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "華氏 (°F)" msgstr "華氏 (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "失敗した属性:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "認証に失敗しました" msgstr "認証に失敗しました"
@@ -568,6 +595,7 @@ msgstr "アラートの更新に失敗しました"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "フィルター..." msgstr "フィルター..."
@@ -576,6 +604,10 @@ msgstr "フィルター..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "フィンガープリント" msgstr "フィンガープリント"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "ファームウェア"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "<0>{min}</0> {min, plural, one {分} other {分}}の間" msgstr "<0>{min}</0> {min, plural, one {分} other {分}}の間"
@@ -730,6 +762,10 @@ msgstr "メモリ使用率"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Dockerコンテナのメモリ使用率" msgstr "Dockerコンテナのメモリ使用率"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "モデル"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "ネットワーク単位"
msgid "No results found." msgid "No results found."
msgstr "結果が見つかりませんでした。" msgstr "結果が見つかりませんでした。"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "結果がありません。" msgstr "結果がありません。"
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "このデバイスのS.M.A.R.T.属性は利用できません。"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "メニューを開く"
msgid "Or continue with" msgid "Or continue with"
msgstr "または、以下の方法でログイン" msgstr "または、以下の方法でログイン"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "その他"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "既存のアラートを上書き" msgstr "既存のアラートを上書き"
@@ -847,6 +894,15 @@ msgstr "一時停止中"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "一時停止 ({pausedSystemsLength})" msgstr "一時停止 ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "コアごとの平均使用率"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "各状態で費やした時間の割合"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "アラートが配信されるように<0>SMTPサーバーを設定</0>してください。" msgstr "アラートが配信されるように<0>SMTPサーバーを設定</0>してください。"
@@ -884,6 +940,11 @@ msgstr "アカウントにサインインしてください"
msgid "Port" msgid "Port"
msgstr "ポート" msgstr "ポート"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "電源オン"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "トークンをローテート"
msgid "Rows per page" msgid "Rows per page"
msgstr "ページあたりの行数" msgstr "ページあたりの行数"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T.詳細"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T.セルフテスト"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Enterキーまたはカンマを使用してアドレスを保存します。空白のままにするとメール通知が無効になります。" msgstr "Enterキーまたはカンマを使用してアドレスを保存します。空白のままにするとメール通知が無効になります。"
@@ -972,6 +1041,10 @@ msgstr "アラートの受信方法を設定するには<0>通知設定</0>を
msgid "Sent" msgid "Sent"
msgstr "送信" msgstr "送信"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "シリアル番号"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "メーターの色を変更するしきい値(パーセンテージ)を設定します。" msgstr "メーターの色を変更するしきい値(パーセンテージ)を設定します。"
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "状態" msgstr "状態"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "テーブル" msgstr "テーブル"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "温度" msgstr "温度"
@@ -1124,6 +1199,11 @@ msgstr "トークンはエージェントの接続と登録を可能にします
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。" msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。"
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "総数"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "各インターフェースの総受信データ量" msgstr "各インターフェースの総受信データ量"
@@ -1168,6 +1248,10 @@ msgstr "ステータスが上から下に切り替わるときにトリガーさ
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "ディスクの使用量がしきい値を超えたときにトリガーされます" msgstr "ディスクの使用量がしきい値を超えたときにトリガーされます"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "タイプ"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "稼働時間"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "使用量" msgstr "使用量"
@@ -1234,6 +1319,7 @@ msgstr "値"
msgid "View" msgid "View"
msgstr "表示" msgstr "表示"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "もっと見る" msgstr "もっと見る"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ko\n" "Language: ko\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-09-23 02:45\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Korean\n" "Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# 일} other {# 일}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# 시간} other {# 시간}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# 분} few {# 분} many {# 분} other {# 분}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{1}개의 행 중 {0}개가 선택되었습니다." msgstr "{1}개의 행 중 {0}개가 선택되었습니다."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} 일} other {{countString} 일}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} 시간} other {{countString} 시간}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} 분} few {{countString} 분} many {{countString} 분} other {{countString} 분}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1시간" msgstr "1시간"
@@ -234,6 +231,10 @@ msgstr "캐시 / 버퍼"
msgid "Cancel" msgid "Cancel"
msgstr "취소" msgstr "취소"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "용량"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "주의 - 데이터 손실 가능성" msgstr "주의 - 데이터 손실 가능성"
@@ -279,6 +280,10 @@ msgstr "알림 서비스를 확인하세요."
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "더 많은 정보를 보려면 컨테이너를 클릭하세요." msgstr "더 많은 정보를 보려면 컨테이너를 클릭하세요."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "더 많은 정보를 보려면 장치를 클릭하세요."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "더 많은 정보를 보려면 시스템을 클릭하세요." msgstr "더 많은 정보를 보려면 시스템을 클릭하세요."
@@ -365,8 +370,17 @@ msgstr "YAML 복사"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU 코어"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU 시간 분배"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU 사용량" msgstr "CPU 사용량"
@@ -397,6 +411,11 @@ msgstr "누적 업로드"
msgid "Current state" msgid "Current state"
msgstr "현재 상태" msgstr "현재 상태"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "사이클"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "대시보드" msgstr "대시보드"
@@ -418,6 +437,10 @@ msgstr "지문 삭제"
msgid "Detail" msgid "Detail"
msgstr "세부사항" msgstr "세부사항"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "장치"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "현재 시스템 구성 내보내기"
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "화씨 (°F)" msgstr "화씨 (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "실패한 속성:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "인증 실패" msgstr "인증 실패"
@@ -568,6 +595,7 @@ msgstr "알림 수정 실패"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "필터..." msgstr "필터..."
@@ -576,6 +604,10 @@ msgstr "필터..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "지문" msgstr "지문"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "펌웨어"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "<0>{min}</0> {min, plural, one {분} other {분}} 동안" msgstr "<0>{min}</0> {min, plural, one {분} other {분}} 동안"
@@ -730,6 +762,10 @@ msgstr "메모리 사용량"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Docker 컨테이너의 메모리 사용량" msgstr "Docker 컨테이너의 메모리 사용량"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "모델"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "네트워크 단위"
msgid "No results found." msgid "No results found."
msgstr "결과가 없습니다." msgstr "결과가 없습니다."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "결과 없음." msgstr "결과 없음."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "이 장치에 사용할 수 있는 S.M.A.R.T. 속성이 없습니다."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "메뉴 열기"
msgid "Or continue with" msgid "Or continue with"
msgstr "또는 아래 항목으로 진행하기" msgstr "또는 아래 항목으로 진행하기"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "기타"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "기존 알림 덮어쓰기" msgstr "기존 알림 덮어쓰기"
@@ -847,6 +894,15 @@ msgstr "일시 정지됨"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "일시 정지됨 ({pausedSystemsLength})" msgstr "일시 정지됨 ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "코어별 평균 사용률"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "각 상태에서 보낸 시간의 백분율"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "알림이 전달되도록 <0>SMTP 서버를 구성</0>하세요." msgstr "알림이 전달되도록 <0>SMTP 서버를 구성</0>하세요."
@@ -884,6 +940,11 @@ msgstr "계정에 로그인하세요."
msgid "Port" msgid "Port"
msgstr "포트" msgstr "포트"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "전원 켜기"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "토큰 회전"
msgid "Rows per page" msgid "Rows per page"
msgstr "페이지당 행 수" msgstr "페이지당 행 수"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. 세부 정보"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. 자체 테스트"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Enter 키 또는 쉼표를 사용하여 주소를 저장하세요. 이메일 알림을 비활성화하려면 비워 두세요." msgstr "Enter 키 또는 쉼표를 사용하여 주소를 저장하세요. 이메일 알림을 비활성화하려면 비워 두세요."
@@ -972,6 +1041,10 @@ msgstr "알림을 받는 방법을 구성하려면 <0>알림 설정</0>을 참
msgid "Sent" msgid "Sent"
msgstr "보냄" msgstr "보냄"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "시리얼 번호"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "그래프 미터 색상의 백분율 임계값을 설정합니다." msgstr "그래프 미터 색상의 백분율 임계값을 설정합니다."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "상태" msgstr "상태"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "표" msgstr "표"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "온도" msgstr "온도"
@@ -1124,6 +1199,11 @@ msgstr "토큰은 에이전트가 연결하고 등록할 수 있도록 합니다
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "토큰과 지문은 허브에 대한 WebSocket 연결을 인증하는 데 사용됩니다." msgstr "토큰과 지문은 허브에 대한 WebSocket 연결을 인증하는 데 사용됩니다."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "총"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "각 인터페이스별 총합 다운로드 데이터량" msgstr "각 인터페이스별 총합 다운로드 데이터량"
@@ -1168,6 +1248,10 @@ msgstr "시스템의 전원이 켜지거나 꺼질때 트리거됩니다."
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "디스크 사용량이 임계값을 초과할 때 트리거됩니다." msgstr "디스크 사용량이 임계값을 초과할 때 트리거됩니다."
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "유형"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "가동 시간"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "사용량" msgstr "사용량"
@@ -1234,6 +1319,7 @@ msgstr "값"
msgid "View" msgid "View"
msgstr "보기" msgstr "보기"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "더 보기" msgstr "더 보기"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: nl\n" "Language: nl\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-28 22:59\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Dutch\n" "Language-Team: Dutch\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: nl\n" "X-Crowdin-Language: nl\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dag} other {# dagen}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# uur} other {# uren}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuut} other {# minuten}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# minuut} other {# minuten}}"
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} van de {1} rij(en) geselecteerd." msgstr "{0} van de {1} rij(en) geselecteerd."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dag} other {{countString} dagen}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} uur} other {{countString} uren}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuut} other {{countString} minuten}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 uur" msgstr "1 uur"
@@ -54,7 +51,7 @@ msgstr "1 minuut"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 week" msgid "1 week"
msgstr "1 week" msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "12 hours" msgid "12 hours"
@@ -116,11 +113,11 @@ msgstr "Weergaveopties voor grafieken aanpassen."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr ""
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
@@ -218,29 +215,33 @@ msgstr "Binair"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)" msgstr ""
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Buffers" msgstr ""
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Annuleren" msgstr "Annuleren"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Capaciteit"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Opgelet - potentieel gegevensverlies" msgstr "Opgelet - potentieel gegevensverlies"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Celsius (°C)" msgid "Celsius (°C)"
msgstr "Celsius (°C)" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Change display units for metrics." msgid "Change display units for metrics."
@@ -279,6 +280,10 @@ msgstr "Controleer je meldingsservice"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Klik op een container om meer informatie te zien." msgstr "Klik op een container om meer informatie te zien."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Klik op een apparaat om meer informatie te bekijken."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Klik op een systeem om meer informatie te bekijken." msgstr "Klik op een systeem om meer informatie te bekijken."
@@ -363,10 +368,19 @@ msgstr "YAML kopiëren"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU-kernen"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU-tijdverdeling"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Processorgebruik" msgstr "Processorgebruik"
@@ -397,9 +411,14 @@ msgstr "Cumulatieve upload"
msgid "Current state" msgid "Current state"
msgstr "Huidige status" msgstr "Huidige status"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cycli"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Dashboard" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Default time period" msgid "Default time period"
@@ -416,7 +435,11 @@ msgstr "Vingerafdruk verwijderen"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detail" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Apparaat"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -546,7 +569,11 @@ msgstr "Exporteer je huidige systeemconfiguratie."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Mislukte kenmerken:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
@@ -568,14 +595,19 @@ msgstr "Bijwerken waarschuwing mislukt"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Vingerafdruk" msgstr "Vingerafdruk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Voor <0>{min}</0> {min, plural, one {minuut} other {minuten}}" msgstr "Voor <0>{min}</0> {min, plural, one {minuut} other {minuten}}"
@@ -639,7 +671,7 @@ msgstr "Als je het wachtwoord voor je beheerdersaccount bent kwijtgeraakt, kan j
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image" msgctxt "Docker image"
msgid "Image" msgid "Image"
msgstr "Image" msgstr ""
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Invalid email address." msgid "Invalid email address."
@@ -648,7 +680,7 @@ msgstr "Ongeldig e-mailadres."
#. Linux kernel #. Linux kernel
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
@@ -696,7 +728,7 @@ msgstr "Aanmelding mislukt"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Logs" msgstr ""
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
@@ -714,7 +746,7 @@ msgstr "Handmatige installatie-instructies"
#. Chart select field. Please try to keep this short. #. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Max 1 min" msgstr ""
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -730,6 +762,10 @@ msgstr "Geheugengebruik"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Geheugengebruik van docker containers" msgstr "Geheugengebruik van docker containers"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -739,7 +775,7 @@ msgstr "Naam"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Net" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Network traffic of docker containers" msgid "Network traffic of docker containers"
@@ -761,11 +797,18 @@ msgstr "Netwerk eenheid"
msgid "No results found." msgid "No results found."
msgstr "Geen resultaten gevonden." msgstr "Geen resultaten gevonden."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Geen resultaten." msgstr "Geen resultaten."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Geen S.M.A.R.T. kenmerken beschikbaar voor dit apparaat."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -793,12 +836,16 @@ 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 #: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu" msgid "Open menu"
msgstr "Open menu" msgstr ""
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Or continue with" msgid "Or continue with"
msgstr "Of ga verder met" msgstr "Of ga verder met"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Overig"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Overschrijf bestaande waarschuwingen" msgstr "Overschrijf bestaande waarschuwingen"
@@ -847,6 +894,15 @@ msgstr "Gepauzeerd"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Gepauzeerd ({pausedSystemsLength})" msgstr "Gepauzeerd ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Gemiddeld gebruik per kern"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Percentage tijd besteed in elke status"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "<0>Configureer een SMTP-server </0> om ervoor te zorgen dat waarschuwingen worden afgeleverd." msgstr "<0>Configureer een SMTP-server </0> om ervoor te zorgen dat waarschuwingen worden afgeleverd."
@@ -884,6 +940,11 @@ msgstr "Meld je aan bij je account"
msgid "Port" msgid "Port"
msgstr "Poort" msgstr "Poort"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Inschakelen"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Roteer Token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Rijen per pagina" msgstr "Rijen per pagina"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Zelf-test"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Bewaar het adres met de enter-toets of komma. Laat leeg om e-mailmeldingen uit te schakelen." msgstr "Bewaar het adres met de enter-toets of komma. Laat leeg om e-mailmeldingen uit te schakelen."
@@ -972,6 +1041,10 @@ msgstr "Zie <0>notificatie-instellingen</0> om te configureren hoe je meldingen
msgid "Sent" msgid "Sent"
msgstr "Verzonden" msgstr "Verzonden"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Serienummer"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Stel percentagedrempels in voor meterkleuren." msgstr "Stel percentagedrempels in voor meterkleuren."
@@ -1005,10 +1078,11 @@ msgid "State"
msgstr "Status" msgstr "Status"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tabel" msgstr "Tabel"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatuur" msgstr "Temperatuur"
@@ -1062,7 +1137,7 @@ msgstr "Temperatuur van systeem sensoren"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>" msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>" msgstr ""
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Test notification sent" msgid "Test notification sent"
@@ -1124,6 +1199,11 @@ msgstr "Tokens staan agenten toe om verbinding te maken met en te registreren. V
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens en vingerafdrukken worden gebruikt om WebSocket verbindingen te verifiëren naar de hub." msgstr "Tokens en vingerafdrukken worden gebruikt om WebSocket verbindingen te verifiëren naar de hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Totaal"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Totaal ontvangen gegevens per interface" msgstr "Totaal ontvangen gegevens per interface"
@@ -1168,6 +1248,10 @@ msgstr "Triggert wanneer de status schakelt tussen up en down"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Triggert wanneer het gebruik van een schijf een drempelwaarde overschrijdt" msgstr "Triggert wanneer het gebruik van een schijf een drempelwaarde overschrijdt"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr ""
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Actief"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Gebruik" msgstr "Gebruik"
@@ -1234,6 +1319,7 @@ msgstr "Waarde"
msgid "View" msgid "View"
msgstr "Weergave" msgstr "Weergave"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Meer weergeven" msgstr "Meer weergeven"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: no\n" "Language: no\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-13 17:31\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Norwegian\n" "Language-Team: Norwegian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dag} other {# dager}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# time} other {# timer}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minutt} other {# minutter}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} av {1} rad(er) valgt." msgstr "{0} av {1} rad(er) valgt."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dag} other {{countString} dager}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} time} other {{countString} timer}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minutt} other {{countString} minutter}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 time" msgstr "1 time"
@@ -234,6 +231,10 @@ msgstr "Cache / Buffere"
msgid "Cancel" msgid "Cancel"
msgstr "Avbryt" msgstr "Avbryt"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapasitet"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Advarsel - potensielt tap av data" msgstr "Advarsel - potensielt tap av data"
@@ -279,6 +280,10 @@ msgstr "Sjekk din meldingstjeneste"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Klikk på en container for å se mer informasjon." msgstr "Klikk på en container for å se mer informasjon."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Klikk på en enhet for å se mer informasjon."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Klikk på et system for å se mer informasjon." msgstr "Klikk på et system for å se mer informasjon."
@@ -365,8 +370,17 @@ msgstr "Kopier YAML"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU-kjerner"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU-tidsoppdeling"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU-bruk" msgstr "CPU-bruk"
@@ -397,6 +411,11 @@ msgstr "Kumulativ opplasting"
msgid "Current state" msgid "Current state"
msgstr "Nåværende tilstand" msgstr "Nåværende tilstand"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Sykluser"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Dashbord" msgstr "Dashbord"
@@ -416,7 +435,11 @@ msgstr "Slett fingeravtrykk"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detalj" msgstr "Detaljer"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Enhet"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -548,6 +571,10 @@ msgstr "Eksporter din nåværende systemkonfigurasjon"
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Mislykkede attributter:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Autentisering mislyktes" msgstr "Autentisering mislyktes"
@@ -568,6 +595,7 @@ msgstr "Kunne ikke oppdatere alarm"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -576,6 +604,10 @@ msgstr "Filter..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Fingeravtrykk" msgstr "Fingeravtrykk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Fastvare"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "I <0>{min}</0> {min, plural, one {minutt} other {minutter}}" msgstr "I <0>{min}</0> {min, plural, one {minutt} other {minutter}}"
@@ -730,6 +762,10 @@ msgstr "Minnebruk"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Minnebruk av docker-konteinere" msgstr "Minnebruk av docker-konteinere"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modell"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Nettverksenhet"
msgid "No results found." msgid "No results found."
msgstr "Ingen resultater funnet." msgstr "Ingen resultater funnet."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Ingen resultater." msgstr "Ingen resultater."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Ingen S.M.A.R.T.-attributter tilgjengelig for denne enheten."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Åpne meny"
msgid "Or continue with" msgid "Or continue with"
msgstr "Eller fortsett med" msgstr "Eller fortsett med"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Andre"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Overskriv eksisterende alarmer" msgstr "Overskriv eksisterende alarmer"
@@ -847,6 +894,15 @@ msgstr "Satt på Pause"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pauset ({pausedSystemsLength})" msgstr "Pauset ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Gjennomsnittlig utnyttelse per kjerne"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Prosentandel av tid brukt i hver tilstand"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Vennligst <0>konfigurer en SMTP-server</0> for å forsikre deg om at varsler blir levert." msgstr "Vennligst <0>konfigurer en SMTP-server</0> for å forsikre deg om at varsler blir levert."
@@ -884,6 +940,11 @@ msgstr "Vennligst logg inn på kontoen din"
msgid "Port" msgid "Port"
msgstr "Port" msgstr "Port"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Påslag"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Forny token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Rader per side" msgstr "Rader per side"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T.-detaljer"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. selvtest"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Lagre adressen med Enter-tasten eller komma. La feltet være tomt for å deaktivere e-postvarsler." msgstr "Lagre adressen med Enter-tasten eller komma. La feltet være tomt for å deaktivere e-postvarsler."
@@ -972,6 +1041,10 @@ msgstr "Se <0>varslingsinnstillingene</0> for å konfigurere hvordan du vil mott
msgid "Sent" msgid "Sent"
msgstr "Sendt" msgstr "Sendt"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Serienummer"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Angi prosentvise terskler for målerfarger." msgstr "Angi prosentvise terskler for målerfarger."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Tilstand" msgstr "Tilstand"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tabell" msgstr "Tabell"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temp" msgstr "Temp"
@@ -1124,6 +1199,11 @@ msgstr "Tokens lar agenter koble til og registrere seg selv. Fingeravtrykk er st
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens og fingeravtrykk blir brukt for å autentisere WebSocket-tilkoblinger til huben." msgstr "Tokens og fingeravtrykk blir brukt for å autentisere WebSocket-tilkoblinger til huben."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Totalt mottatt data for hvert grensesnitt" msgstr "Totalt mottatt data for hvert grensesnitt"
@@ -1168,6 +1248,10 @@ msgstr "Slår inn når statusen veksler mellom oppe og nede"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Slår inn når forbruk av hvilken som helst disk overstiger en grenseverdi" msgstr "Slår inn når forbruk av hvilken som helst disk overstiger en grenseverdi"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr ""
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Oppetid"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Forbruk" msgstr "Forbruk"
@@ -1234,6 +1319,7 @@ msgstr "Verdi"
msgid "View" msgid "View"
msgstr "Visning" msgstr "Visning"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Se mer" msgstr "Se mer"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: pl\n" "Language: pl\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-09-18 15:36\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Polish\n" "Language-Team: Polish\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dzień} few {# dni} many {# dni} other {# dni}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {godzinę} few {# godziny} many {# godzin} other {# godziny}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} z {1} wybranych wierszy." msgstr "{0} z {1} wybranych wierszy."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dzień} few {{countString} dni} many {{countString} dni} other {{countString} dni}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {godzinę} few {{countString} godziny} many {{countString} godzin} other {{countString} godziny}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuta} few {{countString} minuty} many {{countString} minut} other {{countString} minut}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 godzina" msgstr "1 godzina"
@@ -234,6 +231,10 @@ msgstr "Pamięć podręczna / Bufory"
msgid "Cancel" msgid "Cancel"
msgstr "Anuluj" msgstr "Anuluj"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Pojemność"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Uwaga- potencjalna utrata danych." msgstr "Uwaga- potencjalna utrata danych."
@@ -279,6 +280,10 @@ msgstr "Sprawdź swój serwis powiadomień"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Kliknij na kontener, aby wyświetlić więcej informacji." msgstr "Kliknij na kontener, aby wyświetlić więcej informacji."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Kliknij na urządzenie, aby wyświetlić więcej informacji."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Kliknij na system, aby zobaczyć więcej informacji." msgstr "Kliknij na system, aby zobaczyć więcej informacji."
@@ -365,8 +370,17 @@ msgstr "Kopiuj YAML"
msgid "CPU" msgid "CPU"
msgstr "Procesor" msgstr "Procesor"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Rdzenie CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Podział czasu CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Użycie procesora" msgstr "Użycie procesora"
@@ -397,6 +411,11 @@ msgstr "Wysyłanie skumulowane"
msgid "Current state" msgid "Current state"
msgstr "Aktualny stan" msgstr "Aktualny stan"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cykle"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Panel kontrolny" msgstr "Panel kontrolny"
@@ -418,6 +437,10 @@ msgstr "Usuń odcisk palca"
msgid "Detail" msgid "Detail"
msgstr "Szczegół" msgstr "Szczegół"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Urządzenie"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "Eksportuj aktualną konfigurację systemów."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Nieudane atrybuty:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Błąd autoryzacji" msgstr "Błąd autoryzacji"
@@ -568,6 +595,7 @@ msgstr "Nie udało się zaktualizować powiadomienia"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtruj..." msgstr "Filtruj..."
@@ -576,6 +604,10 @@ msgstr "Filtruj..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Odcisk palca" msgstr "Odcisk palca"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Oprogramowanie sprzętowe"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Na <0>{min}</0> {min, plural, one {minutę} other {minut}}" msgstr "Na <0>{min}</0> {min, plural, one {minutę} other {minut}}"
@@ -730,6 +762,10 @@ msgstr "Wykorzystanie pamięci"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Użycie pamięci przez kontenery Docker." msgstr "Użycie pamięci przez kontenery Docker."
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Jednostka sieciowa"
msgid "No results found." msgid "No results found."
msgstr "Brak wyników." msgstr "Brak wyników."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Brak wyników." msgstr "Brak wyników."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Brak dostępnych atrybutów S.M.A.R.T. dla tego urządzenia."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Otwórz menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Lub kontynuuj z" msgstr "Lub kontynuuj z"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Inne"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Nadpisz istniejące alerty" msgstr "Nadpisz istniejące alerty"
@@ -847,6 +894,15 @@ msgstr "Wstrzymane"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Wstrzymane ({pausedSystemsLength})" msgstr "Wstrzymane ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Średnie wykorzystanie na rdzeń"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Procent czasu spędzonego w każdym stanie"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Proszę <0>skonfigurować serwer SMTP</0>, aby zapewnić dostarczanie powiadomień." msgstr "Proszę <0>skonfigurować serwer SMTP</0>, aby zapewnić dostarczanie powiadomień."
@@ -884,6 +940,11 @@ msgstr "Zaloguj się na swoje konto"
msgid "Port" msgid "Port"
msgstr "Port" msgstr "Port"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Włączony"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Zmień token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Wiersze na stronę" msgstr "Wiersze na stronę"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "Szczegóły S.M.A.R.T."
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "Samodiagnostyka S.M.A.R.T."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Zapisz adres, używając klawisza enter lub przecinka. Pozostaw puste, aby wyłączyć powiadomienia e-mail." msgstr "Zapisz adres, używając klawisza enter lub przecinka. Pozostaw puste, aby wyłączyć powiadomienia e-mail."
@@ -972,6 +1041,10 @@ msgstr "Zobacz <0>ustawienia powiadomień</0>, aby skonfigurować sposób, w jak
msgid "Sent" msgid "Sent"
msgstr "Wysłane" msgstr "Wysłane"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Numer seryjny"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Ustaw progi procentowe dla kolorów mierników." msgstr "Ustaw progi procentowe dla kolorów mierników."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Stan" msgstr "Stan"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tabela" msgstr "Tabela"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temperatura" msgstr "Temperatura"
@@ -1124,6 +1199,11 @@ msgstr "Tokeny umożliwiają agentom łączenie się i rejestrację. Odciski pal
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeny i odciski palców (fingerprinty) służą do uwierzytelniania połączeń WebSocket z hubem." msgstr "Tokeny i odciski palców (fingerprinty) służą do uwierzytelniania połączeń WebSocket z hubem."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Łącznie"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Całkowita ilość danych odebranych dla każdego interfejsu" msgstr "Całkowita ilość danych odebranych dla każdego interfejsu"
@@ -1168,6 +1248,10 @@ msgstr "Wyzwalane, gdy status przełącza się między stanem aktywnym a nieakty
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Wyzwalane, gdy wykorzystanie któregokolwiek dysku przekroczy ustalony próg" msgstr "Wyzwalane, gdy wykorzystanie któregokolwiek dysku przekroczy ustalony próg"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Typ"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Czas pracy"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Wykorzystanie" msgstr "Wykorzystanie"
@@ -1234,6 +1319,7 @@ msgstr "Wartość"
msgid "View" msgid "View"
msgstr "Widok" msgstr "Widok"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Zobacz więcej" msgstr "Zobacz więcej"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: pt\n" "Language: pt\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-09 12:03\n" "PO-Revision-Date: 2025-11-04 22:13\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Portuguese\n" "Language-Team: Portuguese\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: pt-PT\n" "X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dia} other {# dias}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# hora} other {# horas}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuto} other {# minutos}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# minuto} other {# minutos}}"
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} de {1} linha(s) selecionada(s)." msgstr "{0} de {1} linha(s) selecionada(s)."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dia} other {{countString} dias}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} hora} other {{countString} horas}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuto} other {{countString} minutos}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 hora" msgstr "1 hora"
@@ -137,7 +134,7 @@ msgstr "Alertas"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/containers.tsx #: src/components/routes/containers.tsx
msgid "All Containers" msgid "All Containers"
msgstr "Todos os contentores" msgstr "Todos os Contêineres"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
@@ -227,13 +224,17 @@ msgstr "Bytes (KB/s, MB/s, GB/s)"
#: src/components/charts/mem-chart.tsx #: src/components/charts/mem-chart.tsx
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Buffers" msgstr ""
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Cancelar" msgstr "Cancelar"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Capacidade"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Cuidado - possível perda de dados" msgstr "Cuidado - possível perda de dados"
@@ -279,6 +280,10 @@ msgstr "Verifique seu serviço de notificação"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Clique num contentor para ver mais informações." msgstr "Clique num contentor para ver mais informações."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Clique em um dispositivo para ver mais informações."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Clique em um sistema para ver mais informações." msgstr "Clique em um sistema para ver mais informações."
@@ -365,8 +370,17 @@ msgstr "Copiar YAML"
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Núcleos de CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Distribuição do Tempo de CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Uso de CPU" msgstr "Uso de CPU"
@@ -397,6 +411,11 @@ msgstr "Upload cumulativo"
msgid "Current state" msgid "Current state"
msgstr "Estado atual" msgstr "Estado atual"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Ciclos"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Painel" msgstr "Painel"
@@ -418,6 +437,10 @@ msgstr "Excluir impressão digital"
msgid "Detail" msgid "Detail"
msgstr "Detalhe" msgstr "Detalhe"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Dispositivo"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -467,7 +490,7 @@ msgstr "Documentação"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "Desligado" msgstr "Desligado"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
@@ -548,6 +571,10 @@ msgstr "Exporte a configuração atual dos seus sistemas."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Atributos com Falha:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Falha na autenticação" msgstr "Falha na autenticação"
@@ -568,6 +595,7 @@ msgstr "Falha ao atualizar alerta"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrar..." msgstr "Filtrar..."
@@ -576,6 +604,10 @@ msgstr "Filtrar..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Impressão digital" msgstr "Impressão digital"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}" msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
@@ -625,7 +657,7 @@ msgstr "Comando Homebrew"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Host / IP" msgid "Host / IP"
msgstr "Host / IP" msgstr ""
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -648,7 +680,7 @@ msgstr "Endereço de email inválido."
#. Linux kernel #. Linux kernel
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Kernel" msgid "Kernel"
msgstr "Kernel" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Language" msgid "Language"
@@ -696,7 +728,7 @@ msgstr "Tentativa de login falhou"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Logs" msgid "Logs"
msgstr "Logs" msgstr ""
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
@@ -730,6 +762,10 @@ msgstr "Uso de Memória"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Uso de memória dos contêineres Docker" msgstr "Uso de memória dos contêineres Docker"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modelo"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Unidade de rede"
msgid "No results found." msgid "No results found."
msgstr "Nenhum resultado encontrado." msgstr "Nenhum resultado encontrado."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Sem resultados." msgstr "Sem resultados."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Nenhum atributo S.M.A.R.T. disponível para este dispositivo."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Abrir menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Ou continue com" msgstr "Ou continue com"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Outro"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Sobrescrever alertas existentes" msgstr "Sobrescrever alertas existentes"
@@ -847,6 +894,15 @@ msgstr "Pausado"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pausado ({pausedSystemsLength})" msgstr "Pausado ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Utilização média por núcleo"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Percentagem de tempo gasto em cada estado"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Por favor, <0>configure um servidor SMTP</0> para garantir que os alertas sejam entregues." msgstr "Por favor, <0>configure um servidor SMTP</0> para garantir que os alertas sejam entregues."
@@ -884,6 +940,11 @@ msgstr "Por favor, entre na sua conta"
msgid "Port" msgid "Port"
msgstr "Porta" msgstr "Porta"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Ligado"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Rotacionar token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Linhas por página" msgstr "Linhas por página"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "Detalhes S.M.A.R.T."
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "Auto-teste S.M.A.R.T."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Salve o endereço usando a tecla enter ou vírgula. Deixe em branco para desativar notificações por email." msgstr "Salve o endereço usando a tecla enter ou vírgula. Deixe em branco para desativar notificações por email."
@@ -972,6 +1041,10 @@ msgstr "Veja <0>configurações de notificação</0> para configurar como você
msgid "Sent" msgid "Sent"
msgstr "Enviado" msgstr "Enviado"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Número de Série"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Defina os limiares de porcentagem para as cores do medidor." msgstr "Defina os limiares de porcentagem para as cores do medidor."
@@ -1005,10 +1078,11 @@ msgid "State"
msgstr "Estado" msgstr "Estado"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Estado"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
@@ -1043,9 +1117,10 @@ msgid "Table"
msgstr "Tabela" msgstr "Tabela"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Temp" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
@@ -1108,7 +1183,7 @@ msgstr "Alternar tema"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token" msgid "Token"
msgstr "Token" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1124,6 +1199,11 @@ msgstr "Os tokens permitem que os agentes se conectem e registrem. As impressõe
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens e impressões digitais são usados para autenticar conexões WebSocket ao hub." msgstr "Tokens e impressões digitais são usados para autenticar conexões WebSocket ao hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Dados totais recebidos para cada interface" msgstr "Dados totais recebidos para cada interface"
@@ -1168,6 +1248,10 @@ msgstr "Dispara quando o status alterna entre ativo e inativo"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Dispara quando o uso de qualquer disco excede um limite" msgstr "Dispara quando o uso de qualquer disco excede um limite"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Tipo"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1187,7 +1271,7 @@ msgstr "Desconhecida"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "Ligado" msgstr "Ligado"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
@@ -1209,6 +1293,7 @@ msgstr "Tempo de Atividade"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Uso" msgstr "Uso"
@@ -1234,6 +1319,7 @@ msgstr "Valor"
msgid "View" msgid "View"
msgstr "Visual" msgstr "Visual"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Ver mais" msgstr "Ver mais"

File diff suppressed because it is too large Load Diff

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: ru\n" "Language: ru\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-09-28 07:31\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: ru\n" "X-Crowdin-Language: ru\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# день} other {# дней}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# час} other {# часов}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# минута} few {# минут} many {# минут} other {# минуты}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# минута} few {# минут} many {# минут}
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "Выбрано {0} из {1} строк." msgstr "Выбрано {0} из {1} строк."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} день} other {{countString} дней}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} час} other {{countString} часов}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} минута} few {{countString} минут} many {{countString} минут} other {{countString} минуты}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 час" msgstr "1 час"
@@ -234,6 +231,10 @@ msgstr "Кэш / Буферы"
msgid "Cancel" msgid "Cancel"
msgstr "Отмена" msgstr "Отмена"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Емкость"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Внимание - возможная потеря данных" msgstr "Внимание - возможная потеря данных"
@@ -279,6 +280,10 @@ msgstr "Проверьте ваш сервис уведомлений"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Нажмите на контейнер, чтобы просмотреть дополнительную информацию." msgstr "Нажмите на контейнер, чтобы просмотреть дополнительную информацию."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Нажмите на устройство для просмотра дополнительной информации."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Нажмите на систему для просмотра дополнительной информации." msgstr "Нажмите на систему для просмотра дополнительной информации."
@@ -365,8 +370,17 @@ msgstr "Скопировать YAML"
msgid "CPU" msgid "CPU"
msgstr "ЦП" msgstr "ЦП"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Ядра ЦП"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Распределение времени ЦП"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Использование CPU" msgstr "Использование CPU"
@@ -397,6 +411,11 @@ msgstr "Совокупная выгрузка"
msgid "Current state" msgid "Current state"
msgstr "Текущее состояние" msgstr "Текущее состояние"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Циклы"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Панель управления" msgstr "Панель управления"
@@ -418,6 +437,10 @@ msgstr "Удалить отпечаток"
msgid "Detail" msgid "Detail"
msgstr "Подробности" msgstr "Подробности"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Устройство"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "Экспортируйте текущую конфигурацию си
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Фаренгейт (°F)" msgstr "Фаренгейт (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Неудачные атрибуты:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Не удалось аутентифицировать" msgstr "Не удалось аутентифицировать"
@@ -568,6 +595,7 @@ msgstr "Не удалось обновить оповещение"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Фильтр..." msgstr "Фильтр..."
@@ -576,6 +604,10 @@ msgstr "Фильтр..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Отпечаток" msgstr "Отпечаток"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Прошивка"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "На <0>{min}</0> {min, plural, one {минуту} other {минут}}" msgstr "На <0>{min}</0> {min, plural, one {минуту} other {минут}}"
@@ -730,6 +762,10 @@ msgstr "Использование памяти"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Использование памяти контейнерами Docker" msgstr "Использование памяти контейнерами Docker"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Модель"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Единицы измерения скорости сети"
msgid "No results found." msgid "No results found."
msgstr "Результаты не найдены." msgstr "Результаты не найдены."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Нет результатов." msgstr "Нет результатов."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Для этого устройства нет доступных атрибутов S.M.A.R.T."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Открыть меню"
msgid "Or continue with" msgid "Or continue with"
msgstr "Или продолжить с" msgstr "Или продолжить с"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Другое"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Перезаписать существующие оповещения" msgstr "Перезаписать существующие оповещения"
@@ -847,6 +894,15 @@ msgstr "Пауза"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Пауза ({pausedSystemsLength})" msgstr "Пауза ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Среднее использование на ядро"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Процент времени, проведенного в каждом состоянии"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Пожалуйста, <0>настройте SMTP-сервер</0>, чтобы гарантировать доставку оповещений." msgstr "Пожалуйста, <0>настройте SMTP-сервер</0>, чтобы гарантировать доставку оповещений."
@@ -884,6 +940,11 @@ msgstr "Пожалуйста, войдите в свою учетную запи
msgid "Port" msgid "Port"
msgstr "Порт" msgstr "Порт"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Включение питания"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Обновить токен"
msgid "Rows per page" msgid "Rows per page"
msgstr "Строк на странице" msgstr "Строк на странице"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "Детали S.M.A.R.T."
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "Самотестирование S.M.A.R.T."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Сохраните адрес, используя клавишу ввода или запятую. Оставьте пустым, чтобы отключить уведомления по электронной почте." msgstr "Сохраните адрес, используя клавишу ввода или запятую. Оставьте пустым, чтобы отключить уведомления по электронной почте."
@@ -972,6 +1041,10 @@ msgstr "Смотрите <0>настройки уведомлений</0>, чт
msgid "Sent" msgid "Sent"
msgstr "Отправлено" msgstr "Отправлено"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Серийный номер"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Установите процентные пороги для цветов счетчиков." msgstr "Установите процентные пороги для цветов счетчиков."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Состояние" msgstr "Состояние"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Таблица" msgstr "Таблица"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Темп" msgstr "Темп"
@@ -1124,6 +1199,11 @@ msgstr "Токены позволяют агентам подключаться
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токены и отпечатки используются для аутентификации соединений WebSocket с хабом." msgstr "Токены и отпечатки используются для аутентификации соединений WebSocket с хабом."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Итого"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Общий объем полученных данных для каждого интерфейса" msgstr "Общий объем полученных данных для каждого интерфейса"
@@ -1168,6 +1248,10 @@ msgstr "Срабатывает, когда статус переключаетс
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Срабатывает, когда использование любого диска превышает порог" msgstr "Срабатывает, когда использование любого диска превышает порог"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Тип"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Время работы"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Использование" msgstr "Использование"
@@ -1234,6 +1319,7 @@ msgstr "Значение"
msgid "View" msgid "View"
msgstr "Вид" msgstr "Вид"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Показать больше" msgstr "Показать больше"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: sl\n" "Language: sl\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-09-25 17:11\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Slovenian\n" "Language-Team: Slovenian\n"
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: sl\n" "X-Crowdin-Language: sl\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dan} two {# dneva} few {# dni} other {# dni}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# ura} two {# uri} few {# ur} other {# ur}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuta} few {# minuti} many {# minut} other {# minut}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# minuta} few {# minuti} many {# minut} other {# minut}
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} od {1} vrstic izbranih." msgstr "{0} od {1} vrstic izbranih."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dan} two {{countString} dneva} few {{countString} dni} other {{countString} dni}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} ura} two {{countString} uri} few {{countString} ur} other {{countString} ur}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuta} few {{countString} minuti} many {{countString} minut} other {{countString} minut}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 ura" msgstr "1 ura"
@@ -63,7 +60,7 @@ msgstr "12 ur"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "15 min" msgid "15 min"
msgstr "" msgstr "15 min"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "24 hours" msgid "24 hours"
@@ -76,7 +73,7 @@ msgstr "30 dni"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "5 min" msgid "5 min"
msgstr "" msgstr "5 min"
#. Table column #. Table column
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -87,7 +84,7 @@ msgstr "Dejanja"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Active" msgid "Active"
msgstr "" msgstr "Aktivno"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Active Alerts" msgid "Active Alerts"
@@ -120,13 +117,13 @@ msgstr "Administrator"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "" msgstr "Zgodovina opozoril"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
@@ -153,7 +150,7 @@ msgstr "Ali ste prepričani, da želite izbrisati {name}?"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Are you sure?" msgid "Are you sure?"
msgstr "" msgstr "Ali ste prepričani?"
#: src/components/copy-to-clipboard.tsx #: src/components/copy-to-clipboard.tsx
msgid "Automatic copy requires a secure context." msgid "Automatic copy requires a secure context."
@@ -234,6 +231,10 @@ msgstr "Predpomnilnik / medpomnilniki"
msgid "Cancel" msgid "Cancel"
msgstr "Prekliči" msgstr "Prekliči"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapaciteta"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Pozor - možna izguba podatkov" msgstr "Pozor - možna izguba podatkov"
@@ -244,7 +245,7 @@ msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Change display units for metrics." msgid "Change display units for metrics."
msgstr "" msgstr "Spremenite enote prikaza za metrike."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Change general application options." msgid "Change general application options."
@@ -279,9 +280,13 @@ msgstr "Preverite storitev obveščanja"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Kliknite na kontejner za več informacij." msgstr "Kliknite na kontejner za več informacij."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Kliknite na napravo za več informacij."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "" msgstr "Kliknite na sistem za več informacij."
#: src/components/ui/input-copy.tsx #: src/components/ui/input-copy.tsx
msgid "Click to copy" msgid "Click to copy"
@@ -303,7 +308,7 @@ msgstr "Potrdite geslo"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "" msgstr "Povezava je prekinjena"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -329,7 +334,7 @@ msgstr "Kopiraj docker run"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Environment variables" msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "" msgstr "Kopiraj okoljske spremenljivke"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
@@ -350,11 +355,11 @@ msgstr "Kopiraj besedilo"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>." msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>."
msgstr "" msgstr "Kopirajte namestitveni ukaz za agenta spodaj ali registrirajte agente samodejno z <0>univerzalnim žetonom</0>."
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>." msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr "" msgstr "Kopirajte<0>docker-compose.yml</0> vsebino za agenta spodaj ali registrirajte agente samodejno z <1>univerzalnim žetonom</1>."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML" msgid "Copy YAML"
@@ -363,10 +368,19 @@ msgstr ""
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU jedra"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Razčlenitev časa CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU poraba" msgstr "CPU poraba"
@@ -378,7 +392,7 @@ msgstr "Ustvari račun"
#. Context: date created #. Context: date created
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Created" msgid "Created"
msgstr "" msgstr "Ustvarjeno"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Critical (%)" msgid "Critical (%)"
@@ -397,6 +411,11 @@ msgstr "Kumulativno nalaganje"
msgid "Current state" msgid "Current state"
msgstr "Trenutno stanje" msgstr "Trenutno stanje"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cikli"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Nadzorna plošča" msgstr "Nadzorna plošča"
@@ -412,12 +431,16 @@ msgstr "Izbriši"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "" msgstr "Izbriši prstni odtis"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Podrobnost" msgstr "Podrobnost"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Naprava"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -425,11 +448,11 @@ msgstr "Prazni se"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Disk I/O" msgid "Disk I/O"
msgstr "Disk I/O" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Disk unit" msgid "Disk unit"
@@ -467,11 +490,11 @@ msgstr "Dokumentacija"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "" msgstr "Nedelujoč"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr "Nedelujoči ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Download" msgid "Download"
@@ -479,12 +502,12 @@ msgstr "Prenesi"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr "Trajanje"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "" msgstr "Uredi"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
@@ -534,7 +557,7 @@ msgstr "Obstoječi sistemi, ki niso definirani v <0>config.yml</0>, bodo izbrisa
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "" msgstr "Izvozi"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Export configuration" msgid "Export configuration"
@@ -548,6 +571,10 @@ msgstr "Izvozi trenutne nastavitve sistema."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Neuspeli atributi:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Preverjanje pristnosti ni uspelo" msgstr "Preverjanje pristnosti ni uspelo"
@@ -568,14 +595,19 @@ msgstr "Opozorila ni bilo mogoče posodobiti"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filtriraj..."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Za <0>{min}</0> {min, plural, one {minuto} other {minut}}" msgstr "Za <0>{min}</0> {min, plural, one {minuto} other {minut}}"
@@ -660,24 +692,24 @@ msgstr "Postavitev"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "" msgstr "Povprečna obremenitev"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "" msgstr "Povprečna obremenitev 15m"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "" msgstr "Povprečna obremenitev 1m"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "" msgstr "Povprečna obremenitev 5m"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr "Povpr. obrem."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
@@ -709,7 +741,7 @@ msgstr "Upravljajte nastavitve prikaza in obvestil."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr "Navodila za ročno nastavitev"
#. Chart select field. Please try to keep this short. #. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -730,6 +762,10 @@ msgstr "Poraba pomnilnika"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Poraba pomnilnika docker kontejnerjev" msgstr "Poraba pomnilnika docker kontejnerjev"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,10 +797,17 @@ msgstr ""
msgid "No results found." msgid "No results found."
msgstr "Ni rezultatov." msgstr "Ni rezultatov."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "" msgstr "Ni rezultatov."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Za to napravo ni na voljo atributov S.M.A.R.T."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
@@ -799,6 +842,10 @@ msgstr "Odpri menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Ali nadaljuj z" msgstr "Ali nadaljuj z"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Drugo"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Prepiši obstoječe alarme" msgstr "Prepiši obstoječe alarme"
@@ -812,7 +859,7 @@ msgstr "Stran"
#. placeholder {1}: table.getPageCount() #. placeholder {1}: table.getPageCount()
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Page {0} of {1}" msgid "Page {0} of {1}"
msgstr "" msgstr "Stran {0} od {1}"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Pages / Settings" msgid "Pages / Settings"
@@ -829,7 +876,7 @@ msgstr "Geslo mora imeti vsaj 8 znakov."
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Password must be less than 72 bytes." msgid "Password must be less than 72 bytes."
msgstr "" msgstr "Geslo mora biti krajše od 72 bajtov."
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Password reset request received" msgid "Password reset request received"
@@ -847,6 +894,15 @@ msgstr "Zaustavljeno"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pavzirano za {pausedSystemsLength}" msgstr "Pavzirano za {pausedSystemsLength}"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Povprečna izkoriščenost na jedro"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Odstotek časa, preživetega v vsakem stanju"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "<0>Nastavite strežnik SMTP</0>, da zagotovite dostavo opozoril." msgstr "<0>Nastavite strežnik SMTP</0>, da zagotovite dostavo opozoril."
@@ -884,6 +940,11 @@ msgstr "Prijavite se v svoj račun"
msgid "Port" msgid "Port"
msgstr "Vrata" msgstr "Vrata"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Vklopljeno"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -929,7 +990,7 @@ msgstr "Ponastavi geslo"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Resolved" msgid "Resolved"
msgstr "" msgstr "Rešeno"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
@@ -937,11 +998,19 @@ msgstr "Nadaljuj"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "" msgstr "Zavrti žeton"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Rows per page" msgid "Rows per page"
msgstr "" msgstr "Vrstic na stran"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. podrobnosti"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. samotestiranje"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
@@ -954,7 +1023,7 @@ msgstr "Shrani nastavitve"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Save system" msgid "Save system"
msgstr "" msgstr "Shrani sistem"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Search" msgid "Search"
@@ -972,6 +1041,10 @@ msgstr "Glejte <0>nastavitve obvestil</0>, da nastavite način prejemanja opozor
msgid "Sent" msgid "Sent"
msgstr "Poslano" msgstr "Poslano"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Serijska številka"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Nastavite odstotne pragove za barve merilnikov." msgstr "Nastavite odstotne pragove za barve merilnikov."
@@ -1002,13 +1075,14 @@ msgstr "Razvrsti po"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "State" msgid "State"
msgstr "" msgstr "Stanje"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
@@ -1028,7 +1102,7 @@ msgstr "Sistemsko"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "System load averages over time" msgid "System load averages over time"
msgstr "" msgstr "Sistemske povprečne obremenitve skozi čas"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
@@ -1043,9 +1117,10 @@ msgid "Table"
msgstr "Tabela" msgstr "Tabela"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "" msgstr "Temp"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
@@ -1078,7 +1153,7 @@ msgstr "Tega dejanja ni mogoče razveljaviti. To bo trajno izbrisalo vse trenutn
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "This will permanently delete all selected records from the database." msgid "This will permanently delete all selected records from the database."
msgstr "" msgstr "To bo trajno izbrisalo vse izbrane zapise iz zbirke podatkov."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Throughput of {extraFsName}" msgid "Throughput of {extraFsName}"
@@ -1108,21 +1183,26 @@ msgstr "Obrni temo"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token" msgid "Token"
msgstr "" msgstr "Žeton"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "" msgstr "Žetoni in prstni odtisi"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection." msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection."
msgstr "" msgstr "Žetoni omogočajo agentom povezavo in registracijo. Prstni odtisi so stabilni identifikatorji, edinstveni za vsak sistem, nastavljeni ob prvi povezavi."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr "Žetoni in prstni odtisi se uporabljajo za preverjanje pristnosti WebSocket povezav do vozlišča."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Skupaj"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
@@ -1134,15 +1214,15 @@ msgstr "Skupni poslani podatki za vsak vmesnik"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "" msgstr "Sproži se, ko 1-minutna povprečna obremenitev preseže prag"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 15 minute load average exceeds a threshold" msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr "" msgstr "Sproži se, ko 15-minutna povprečna obremenitev preseže prag"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 5 minute load average exceeds a threshold" msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr "" msgstr "Sproži se, ko 5-minutna povprečna obremenitev preseže prag"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
@@ -1168,15 +1248,19 @@ msgstr "Sproži se, ko se stanje preklaplja med gor in dol"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Sproži se, ko uporaba katerega koli diska preseže prag" msgstr "Sproži se, ko uporaba katerega koli diska preseže prag"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Vrsta"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
msgstr "" msgstr "Nastavitve enot"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Universal token" msgid "Universal token"
msgstr "" msgstr "Univerzalni žeton"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -1187,11 +1271,11 @@ msgstr "Neznana"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "" msgstr "Delujoč"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr "Delujoči ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
msgid "Updated" msgid "Updated"
@@ -1209,6 +1293,7 @@ msgstr "Čas delovanja"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Uporaba" msgstr "Uporaba"
@@ -1228,19 +1313,20 @@ msgstr "Uporabniki"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Value" msgid "Value"
msgstr "" msgstr "Vrednost"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "View" msgid "View"
msgstr "Pogled" msgstr "Pogled"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Prikaži več" msgstr "Prikaži več"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "View your 200 most recent alerts." msgid "View your 200 most recent alerts."
msgstr "" msgstr "Oglejte si svojih 200 najnovejših opozoril."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Visible Fields" msgid "Visible Fields"
@@ -1268,7 +1354,7 @@ msgstr "Webhook / potisna obvestila"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart." msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart."
msgstr "" msgstr "Ko je omogočeno, ta žeton omogoča agentom samoregistracijo brez predhodnega ustvarjanja sistema. Poteče po eni uri ali ob ponovnem zagonu vozlišča."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: sv\n" "Language: sv\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Swedish\n" "Language-Team: Swedish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: sv-SE\n" "X-Crowdin-Language: sv-SE\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dag} other {# dagar}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# timme} other {# timmar}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr ""
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr ""
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} av {1} rad(er) valda." msgstr "{0} av {1} rad(er) valda."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dag} other {{countString} dagar}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} timme} other {{countString} timmar}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minut} few {{countString} minuter} many {{countString} minuter} other {{countString} minuter}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 timme" msgstr "1 timme"
@@ -46,7 +43,7 @@ msgstr "1 timme"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "1 min" msgid "1 min"
msgstr "1 min" msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 minute" msgid "1 minute"
@@ -63,7 +60,7 @@ msgstr "12 timmar"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "15 min" msgid "15 min"
msgstr "15 min" msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "24 hours" msgid "24 hours"
@@ -76,7 +73,7 @@ msgstr "30 dagar"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "5 min" msgid "5 min"
msgstr "5 min" msgstr ""
#. Table column #. Table column
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -116,17 +113,17 @@ msgstr "Justera visningsalternativ för diagram."
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr ""
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "" msgstr "Larmhistorik"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
@@ -218,7 +215,7 @@ msgstr "Binär"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)" msgstr "Bit (Kbps, Mbps, Gbps)"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
@@ -234,13 +231,17 @@ msgstr "Cache / Buffertar"
msgid "Cancel" msgid "Cancel"
msgstr "Avbryt" msgstr "Avbryt"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapacitet"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Varning - potentiell dataförlust" msgstr "Varning - potentiell dataförlust"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Celsius (°C)" msgid "Celsius (°C)"
msgstr "Celsius (°C)" msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Change display units for metrics." msgid "Change display units for metrics."
@@ -279,9 +280,13 @@ msgstr "Kontrollera din aviseringstjänst"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Klicka på en behållare för att visa mer information." msgstr "Klicka på en behållare för att visa mer information."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Klicka på en enhet för att visa mer information."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "" msgstr "Klicka på ett system för att visa mer information."
#: src/components/ui/input-copy.tsx #: src/components/ui/input-copy.tsx
msgid "Click to copy" msgid "Click to copy"
@@ -329,7 +334,7 @@ msgstr "Kopiera docker run"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgctxt "Environment variables" msgctxt "Environment variables"
msgid "Copy env" msgid "Copy env"
msgstr "" msgstr "Kopiera env"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Copy host" msgid "Copy host"
@@ -350,11 +355,11 @@ msgstr "Kopiera text"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>." msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>."
msgstr "" msgstr "Kopiera installationskommandot för agenten nedan, eller registrera agenter automatiskt med en <0>universal token</0>."
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>." msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr "" msgstr "Kopiera <0>docker-compose.yml</0>-innehållet för agenten nedan, eller registrera agenter automatiskt med en <1>universal token</1>."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML" msgid "Copy YAML"
@@ -363,10 +368,19 @@ msgstr "Kopiera YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU-kärnor"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU-tidsuppdelning"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU-användning" msgstr "CPU-användning"
@@ -397,9 +411,14 @@ msgstr "Kumulativ uppladdning"
msgid "Current state" msgid "Current state"
msgstr "Aktuellt tillstånd" msgstr "Aktuellt tillstånd"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cykler"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Dashboard" msgstr "Instrumentpanel"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Default time period" msgid "Default time period"
@@ -412,12 +431,16 @@ msgstr "Ta bort"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "" msgstr "Ta bort fingeravtryck"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detalj" msgstr "Detalj"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Enhet"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -425,15 +448,15 @@ msgstr "Urladdar"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Disk I/O" msgid "Disk I/O"
msgstr "Disk I/O" msgstr "Disk-I/O"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Disk unit" msgid "Disk unit"
msgstr "" msgstr "Diskenhet"
#: src/components/charts/disk-chart.tsx #: src/components/charts/disk-chart.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -467,11 +490,11 @@ msgstr "Dokumentation"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Down" msgid "Down"
msgstr "" msgstr "Nere"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Down ({downSystemsLength})" msgid "Down ({downSystemsLength})"
msgstr "" msgstr "Nere ({downSystemsLength})"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Download" msgid "Download"
@@ -479,12 +502,12 @@ msgstr "Ladda ner"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Duration" msgid "Duration"
msgstr "" msgstr "Varaktighet"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Edit" msgid "Edit"
msgstr "" msgstr "Redigera"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
@@ -534,7 +557,7 @@ msgstr "Befintliga system som inte definieras i <0>config.yml</0> kommer att tas
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "" msgstr "Exportera"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Export configuration" msgid "Export configuration"
@@ -546,7 +569,11 @@ msgstr "Exportera din nuvarande systemkonfiguration."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "" msgstr "Fahrenheit (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Misslyckade attribut:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
@@ -568,13 +595,18 @@ msgstr "Kunde inte uppdatera larm"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrera..." msgstr "Filtrera..."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint" msgid "Fingerprint"
msgstr "" msgstr "Fingeravtryck"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Fast programvara"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -593,7 +625,7 @@ msgstr "FreeBSD kommando"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Full" msgid "Full"
msgstr "Full" msgstr ""
#. Context: General settings #. Context: General settings
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
@@ -639,7 +671,7 @@ msgstr "Om du har glömt lösenordet till ditt administratörskonto kan du åter
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image" msgctxt "Docker image"
msgid "Image" msgid "Image"
msgstr "Image" msgstr "Avbild"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Invalid email address." msgid "Invalid email address."
@@ -656,28 +688,28 @@ msgstr "Språk"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Layout" msgid "Layout"
msgstr "Layout" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "" msgstr "Genomsnittlig belastning"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 15m" msgid "Load Average 15m"
msgstr "" msgstr "Genomsnittlig belastning 15m"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 1m" msgid "Load Average 1m"
msgstr "" msgstr "Genomsnittlig belastning 1m"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Load Average 5m" msgid "Load Average 5m"
msgstr "" msgstr "Genomsnittlig belastning 5m"
#. Short label for load average #. Short label for load average
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Load Avg" msgid "Load Avg"
msgstr "" msgstr "Belastning"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
@@ -709,12 +741,12 @@ msgstr "Hantera visnings- och aviseringsinställningar."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr "Manuella installationsinstruktioner"
#. Chart select field. Please try to keep this short. #. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Max 1 min" msgstr ""
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -730,6 +762,10 @@ msgstr "Minnesanvändning"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Minnesanvändning för dockercontainrar" msgstr "Minnesanvändning för dockercontainrar"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Modell"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -755,16 +791,23 @@ msgstr "Nätverkstrafik för publika gränssnitt"
#. Context: Bytes or bits #. Context: Bytes or bits
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Network unit" msgid "Network unit"
msgstr "" msgstr "Nätverksenhet"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "No results found." msgid "No results found."
msgstr "Inga resultat hittades." msgstr "Inga resultat hittades."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "" msgstr "Inga resultat."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Inga S.M.A.R.T.-attribut tillgängliga för den här enheten."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
@@ -799,6 +842,10 @@ msgstr "Öppna menyn"
msgid "Or continue with" msgid "Or continue with"
msgstr "Eller fortsätt med" msgstr "Eller fortsätt med"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Annat"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Skriv över befintliga larm" msgstr "Skriv över befintliga larm"
@@ -812,7 +859,7 @@ msgstr "Sida"
#. placeholder {1}: table.getPageCount() #. placeholder {1}: table.getPageCount()
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Page {0} of {1}" msgid "Page {0} of {1}"
msgstr "" msgstr "Sida {0} av {1}"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Pages / Settings" msgid "Pages / Settings"
@@ -829,7 +876,7 @@ msgstr "Lösenordet måste vara minst 8 tecken."
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Password must be less than 72 bytes." msgid "Password must be less than 72 bytes."
msgstr "" msgstr "Lösenordet måste vara mindre än 72 byte."
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Password reset request received" msgid "Password reset request received"
@@ -845,7 +892,16 @@ msgstr "Pausad"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "" msgstr "Pausad ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Genomsnittlig användning per kärna"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Procentandel av tid spenderad i varje tillstånd"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
@@ -882,7 +938,12 @@ msgstr "Vänligen logga in på ditt konto"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Port" msgid "Port"
msgstr "Port" msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Påslagen"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -929,7 +990,7 @@ msgstr "Återställ lösenord"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Resolved" msgid "Resolved"
msgstr "" msgstr "Löst"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
@@ -937,11 +998,19 @@ msgstr "Återuppta"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "" msgstr "Rotera token"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Rows per page" msgid "Rows per page"
msgstr "" msgstr "Rader per sida"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T.-detaljer"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. själftest"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
@@ -954,7 +1023,7 @@ msgstr "Spara inställningar"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Save system" msgid "Save system"
msgstr "" msgstr "Spara system"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Search" msgid "Search"
@@ -972,6 +1041,10 @@ msgstr "Se <0>aviseringsinställningar</0> för att konfigurera hur du tar emot
msgid "Sent" msgid "Sent"
msgstr "Skickat" msgstr "Skickat"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Serienummer"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Ställ in procentuella tröskelvärden för mätarfärger." msgstr "Ställ in procentuella tröskelvärden för mätarfärger."
@@ -1002,13 +1075,14 @@ msgstr "Sortera efter"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "State" msgid "State"
msgstr "" msgstr "Tillstånd"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
@@ -1024,11 +1098,11 @@ msgstr "Swap-användning"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "System" msgid "System"
msgstr "System" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "System load averages over time" msgid "System load averages over time"
msgstr "" msgstr "Systembelastning över tid"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
@@ -1043,9 +1117,10 @@ msgid "Table"
msgstr "Tabell" msgstr "Tabell"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "" msgstr "Temp"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
@@ -1054,7 +1129,7 @@ msgstr "Temperatur"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Temperature unit" msgid "Temperature unit"
msgstr "" msgstr "Temperaturenhet"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Temperatures of system sensors" msgid "Temperatures of system sensors"
@@ -1078,7 +1153,7 @@ msgstr "Den här åtgärden kan inte ångras. Detta kommer permanent att ta bort
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "This will permanently delete all selected records from the database." msgid "This will permanently delete all selected records from the database."
msgstr "" msgstr "Detta kommer permanent att ta bort alla valda poster från databasen."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Throughput of {extraFsName}" msgid "Throughput of {extraFsName}"
@@ -1108,21 +1183,26 @@ msgstr "Växla tema"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token" msgid "Token"
msgstr "" msgstr "Token"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "" msgstr "Tokens & fingeravtryck"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection." msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection."
msgstr "" msgstr "Tokens tillåter agenter att ansluta och registrera. Fingeravtryck är stabila identifierare unika för varje system, inställda vid första anslutning."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr "Tokens och fingeravtryck används för att autentisera WebSocket-anslutningar till hubben."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
@@ -1134,15 +1214,15 @@ msgstr "Totalt skickad data för varje gränssnitt"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "" msgstr "Utlöses när 1-minuters genomsnittlig belastning överskrider ett tröskelvärde"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 15 minute load average exceeds a threshold" msgid "Triggers when 15 minute load average exceeds a threshold"
msgstr "" msgstr "Utlöses när 15-minuters genomsnittlig belastning överskrider ett tröskelvärde"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 5 minute load average exceeds a threshold" msgid "Triggers when 5 minute load average exceeds a threshold"
msgstr "" msgstr "Utlöses när 5-minuters genomsnittlig belastning överskrider ett tröskelvärde"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when any sensor exceeds a threshold" msgid "Triggers when any sensor exceeds a threshold"
@@ -1168,15 +1248,19 @@ msgstr "Utlöses när status växlar mellan upp och ner"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Utlöses när användningen av någon disk överskrider ett tröskelvärde" msgstr "Utlöses när användningen av någon disk överskrider ett tröskelvärde"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Typ"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
msgstr "" msgstr "Enhetsinställningar"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Universal token" msgid "Universal token"
msgstr "" msgstr "Universal token"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -1187,11 +1271,11 @@ msgstr "Okänd"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Up" msgid "Up"
msgstr "" msgstr "Upp"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr "Upp ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
msgid "Updated" msgid "Updated"
@@ -1209,6 +1293,7 @@ msgstr "Drifttid"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Användning" msgstr "Användning"
@@ -1228,19 +1313,20 @@ msgstr "Användare"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
msgid "Value" msgid "Value"
msgstr "" msgstr "Värde"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "View" msgid "View"
msgstr "Visa" msgstr "Visa"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Visa mer" msgstr "Visa mer"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "View your 200 most recent alerts." msgid "View your 200 most recent alerts."
msgstr "" msgstr "Visa dina 200 senaste larm."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Visible Fields" msgid "Visible Fields"
@@ -1268,7 +1354,7 @@ msgstr "Webhook / Push-aviseringar"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart." msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart."
msgstr "" msgstr "När aktiverad tillåter denna token agenter att självregistrera utan tidigare systemskapande. Upphör efter en timme eller vid hub-omstart."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: tr\n" "Language: tr\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-28 23:21\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Turkish\n" "Language-Team: Turkish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: tr\n" "X-Crowdin-Language: tr\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# gün} other {# gün}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# saat} other {# saat}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# dakika} few {# dakika} many {# dakika} other {# dakika}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# dakika} few {# dakika} many {# dakika} other {# dakik
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{1} satırdan {0} tanesi seçildi." msgstr "{1} satırdan {0} tanesi seçildi."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} gün} other {{countString} gün}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} saat} other {{countString} saat}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} dakika} few {{countString} dakika} many {{countString} dakika} other {{countString} dakika}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 saat" msgstr "1 saat"
@@ -234,6 +231,10 @@ msgstr "Önbellek / Tamponlar"
msgid "Cancel" msgid "Cancel"
msgstr "İptal" msgstr "İptal"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Kapasite"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Dikkat - potansiyel veri kaybı" msgstr "Dikkat - potansiyel veri kaybı"
@@ -279,6 +280,10 @@ msgstr "Bildirim hizmetinizi kontrol edin"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Daha fazla bilgi görüntülemek için bir konteynere tıklayın." msgstr "Daha fazla bilgi görüntülemek için bir konteynere tıklayın."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Daha fazla bilgi görüntülemek için bir cihaza tıklayın."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Daha fazla bilgi görmek için bir sisteme tıklayın." msgstr "Daha fazla bilgi görmek için bir sisteme tıklayın."
@@ -363,10 +368,19 @@ msgstr "YAML'ı kopyala"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU Çekirdekleri"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU Zaman Dağılımı"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU Kullanımı" msgstr "CPU Kullanımı"
@@ -397,6 +411,11 @@ msgstr "Kümülatif Yükleme"
msgid "Current state" msgid "Current state"
msgstr "Mevcut durum" msgstr "Mevcut durum"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Döngüler"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Gösterge Paneli" msgstr "Gösterge Paneli"
@@ -418,6 +437,10 @@ msgstr "Parmak izini sil"
msgid "Detail" msgid "Detail"
msgstr "Ayrıntı" msgstr "Ayrıntı"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Cihaz"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -425,7 +448,7 @@ msgstr "Boşalıyor"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Disk I/O" msgid "Disk I/O"
@@ -548,6 +571,10 @@ msgstr "Mevcut sistem yapılandırmanızı dışa aktarın."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenhayt (°F)" msgstr "Fahrenhayt (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Başarısız Özellikler:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Kimlik doğrulama başarısız" msgstr "Kimlik doğrulama başarısız"
@@ -568,6 +595,7 @@ msgstr "Uyarı güncellenemedi"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrele..." msgstr "Filtrele..."
@@ -576,6 +604,10 @@ msgstr "Filtrele..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Parmak izi" msgstr "Parmak izi"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Donanım Yazılımı"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "<0>{min}</0> {min, plural, one {dakika} other {dakika}} için" msgstr "<0>{min}</0> {min, plural, one {dakika} other {dakika}} için"
@@ -625,7 +657,7 @@ msgstr "Homebrew komutu"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Host / IP" msgid "Host / IP"
msgstr "Host / IP" msgstr "Ana Bilgisayar / IP"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
@@ -730,6 +762,10 @@ msgstr "Bellek Kullanımı"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Docker konteynerlerinin bellek kullanımı" msgstr "Docker konteynerlerinin bellek kullanımı"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Ağ birimi"
msgid "No results found." msgid "No results found."
msgstr "Sonuç bulunamadı." msgstr "Sonuç bulunamadı."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Sonuç yok." msgstr "Sonuç yok."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Bu cihaz için kullanılabilir S.M.A.R.T. özelliği yok."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Menüyü aç"
msgid "Or continue with" msgid "Or continue with"
msgstr "Veya devam et" msgstr "Veya devam et"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Diğer"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Mevcut uyarıların üzerine yaz" msgstr "Mevcut uyarıların üzerine yaz"
@@ -847,6 +894,15 @@ msgstr "Duraklatıldı"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Duraklatıldı ({pausedSystemsLength})" msgstr "Duraklatıldı ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Çekirdek başına ortalama kullanım"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Her durumda harcanan zamanın yüzdesi"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Uyarıların teslim edilmesini sağlamak için lütfen bir SMTP sunucusu <0>yapılandırın</0>." msgstr "Uyarıların teslim edilmesini sağlamak için lütfen bir SMTP sunucusu <0>yapılandırın</0>."
@@ -882,7 +938,12 @@ msgstr "Lütfen hesabınıza giriş yapın"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Port" msgid "Port"
msgstr "Port" msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Güç Açık"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -943,6 +1004,14 @@ msgstr "Token'ı döndür"
msgid "Rows per page" msgid "Rows per page"
msgstr "Sayfa başına satır" msgstr "Sayfa başına satır"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Ayrıntıları"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "S.M.A.R.T. Kendiliğinden Test"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Adresleri enter tuşu veya virgül ile kaydedin. E-posta bildirimlerini devre dışı bırakmak için boş bırakın." msgstr "Adresleri enter tuşu veya virgül ile kaydedin. E-posta bildirimlerini devre dışı bırakmak için boş bırakın."
@@ -972,6 +1041,10 @@ msgstr "Uyarıları nasıl alacağınızı yapılandırmak için <0>bildirim aya
msgid "Sent" msgid "Sent"
msgstr "Gönderildi" msgstr "Gönderildi"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Seri Numarası"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Sayaç renkleri için yüzde eşiklerini ayarlayın." msgstr "Sayaç renkleri için yüzde eşiklerini ayarlayın."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Durum" msgstr "Durum"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Tablo" msgstr "Tablo"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Sıc" msgstr "Sıc"
@@ -1062,7 +1137,7 @@ msgstr "Sistem sensörlerinin sıcaklıkları"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>" msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>" msgstr "<0>URL</0>'yi Test Et"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Test notification sent" msgid "Test notification sent"
@@ -1108,7 +1183,7 @@ msgstr "Temayı değiştir"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token" msgid "Token"
msgstr "Token" msgstr ""
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
@@ -1124,6 +1199,11 @@ msgstr "Token'lar agentların bağlanıp kaydolmasına izin verir. Parmak izleri
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Token'lar ve parmak izleri hub'a WebSocket bağlantılarını doğrulamak için kullanılır." msgstr "Token'lar ve parmak izleri hub'a WebSocket bağlantılarını doğrulamak için kullanılır."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Toplam"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Her arayüz için alınan toplam veri" msgstr "Her arayüz için alınan toplam veri"
@@ -1168,6 +1248,10 @@ msgstr "Durum yukarı ve aşağı arasında değiştiğinde tetiklenir"
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Herhangi bir diskin kullanımı bir eşiği aştığında tetiklenir" msgstr "Herhangi bir diskin kullanımı bir eşiği aştığında tetiklenir"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Tür"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Çalışma Süresi"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Kullanım" msgstr "Kullanım"
@@ -1234,6 +1319,7 @@ msgstr "Değer"
msgid "View" msgid "View"
msgstr "Görüntüle" msgstr "Görüntüle"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Daha fazla göster" msgstr "Daha fazla göster"

View File

@@ -8,30 +8,15 @@ msgstr ""
"Language: uk\n" "Language: uk\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-08-30 16:20\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Ukrainian\n" "Language-Team: Ukrainian\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: beszel\n" "X-Crowdin-Project: beszel\n"
"X-Crowdin-Project-ID: 733311\n" "X-Crowdin-Project-ID: 733311\n"
"X-Crowdin-Language: uk\n" "X-Crowdin-Language: uk\n"
"X-Crowdin-File: /main/beszel/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 16\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# день} few {# дні} many {# днів} other {# дня}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# година} few {# години} many {# годин} other {# години}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# хвилина} few {# хвилини} many {# хвилин} other {# хвилини}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
@@ -39,6 +24,18 @@ msgstr "{0, plural, one {# хвилина} few {# хвилини} many {# хви
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "Вибрано {0} з {1} рядків." msgstr "Вибрано {0} з {1} рядків."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} день} few {{countString} дні} many {{countString} днів} other {{countString} дня}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} година} few {{countString} години} many {{countString} годин} other {{countString} години}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} хвилина} few {{countString} хвилини} many {{countString} хвилин} other {{countString} хвилини}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 година" msgstr "1 година"
@@ -234,6 +231,10 @@ msgstr "Кеш / Буфери"
msgid "Cancel" msgid "Cancel"
msgstr "Скасувати" msgstr "Скасувати"
#: src/components/routes/system/smart-table.tsx
msgid "Capacity"
msgstr "Ємність"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Caution - potential data loss" msgid "Caution - potential data loss"
msgstr "Увага - можливе втрата даних" msgstr "Увага - можливе втрата даних"
@@ -279,6 +280,10 @@ msgstr "Перевірте свій сервіс сповіщень"
msgid "Click on a container to view more information." msgid "Click on a container to view more information."
msgstr "Натисніть на контейнер, щоб переглянути більше інформації." msgstr "Натисніть на контейнер, щоб переглянути більше інформації."
#: src/components/routes/system/smart-table.tsx
msgid "Click on a device to view more information."
msgstr "Натисніть на пристрій, щоб переглянути більше інформації."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Click on a system to view more information." msgid "Click on a system to view more information."
msgstr "Натисніть на систему, щоб переглянути більше інформації." msgstr "Натисніть на систему, щоб переглянути більше інформації."
@@ -365,8 +370,17 @@ msgstr "Копіювати YAML"
msgid "CPU" msgid "CPU"
msgstr "ЦП" msgstr "ЦП"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Ядра ЦП"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Розподіл часу ЦП"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Використання ЦП" msgstr "Використання ЦП"
@@ -397,6 +411,11 @@ msgstr "Кумулятивне відвантаження"
msgid "Current state" msgid "Current state"
msgstr "Поточний стан" msgstr "Поточний стан"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Цикли"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Dashboard" msgid "Dashboard"
msgstr "Панель управління" msgstr "Панель управління"
@@ -418,6 +437,10 @@ msgstr "Видалити відбиток"
msgid "Detail" msgid "Detail"
msgstr "Деталі" msgstr "Деталі"
#: src/components/routes/system/smart-table.tsx
msgid "Device"
msgstr "Пристрій"
#. Context: Battery state #. Context: Battery state
#: src/lib/i18n.ts #: src/lib/i18n.ts
msgid "Discharging" msgid "Discharging"
@@ -548,6 +571,10 @@ msgstr "Експортуйте поточну конфігурацію сист
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Фаренгейт (°F)" msgstr "Фаренгейт (°F)"
#: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:"
msgstr "Невдалі атрибути:"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Failed to authenticate" msgid "Failed to authenticate"
msgstr "Не вдалося автентифікувати" msgstr "Не вдалося автентифікувати"
@@ -568,6 +595,7 @@ msgstr "Не вдалося оновити сповіщення"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Фільтр..." msgstr "Фільтр..."
@@ -576,6 +604,10 @@ msgstr "Фільтр..."
msgid "Fingerprint" msgid "Fingerprint"
msgstr "Відбиток" msgstr "Відбиток"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Прошивка"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgstr "Протягом <0>{min}</0> {min, plural, one {хвилини} other {хвилин}}" msgstr "Протягом <0>{min}</0> {min, plural, one {хвилини} other {хвилин}}"
@@ -730,6 +762,10 @@ msgstr "Використання пам'яті"
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Використання пам'яті контейнерами Docker" msgstr "Використання пам'яті контейнерами Docker"
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Модель"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -761,11 +797,18 @@ msgstr "Одиниця виміру мережі"
msgid "No results found." msgid "No results found."
msgstr "Результатів не знайдено." msgstr "Результатів не знайдено."
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx
msgid "No results." msgid "No results."
msgstr "Немає результатів." msgstr "Немає результатів."
#: src/components/routes/system/smart-table.tsx
msgid "No S.M.A.R.T. attributes available for this device."
msgstr "Для цього пристрою немає доступних атрибутів S.M.A.R.T."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "No systems found." msgid "No systems found."
@@ -799,6 +842,10 @@ msgstr "Відкрити меню"
msgid "Or continue with" msgid "Or continue with"
msgstr "Або продовжити з" msgstr "Або продовжити з"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Інше"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Перезаписати існуючі сповіщення" msgstr "Перезаписати існуючі сповіщення"
@@ -847,6 +894,15 @@ msgstr "Призупинено"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Призупинено ({pausedSystemsLength})" msgstr "Призупинено ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Середнє використання на ядро"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Відсоток часу, проведеного в кожному стані"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Будь ласка, <0>налаштуйте SMTP сервер</0>, щоб забезпечити доставку сповіщень." msgstr "Будь ласка, <0>налаштуйте SMTP сервер</0>, щоб забезпечити доставку сповіщень."
@@ -884,6 +940,11 @@ msgstr "Будь ласка, увійдіть у свій обліковий з
msgid "Port" msgid "Port"
msgstr "Порт" msgstr "Порт"
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr "Увімкнення живлення"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -943,6 +1004,14 @@ msgstr "Оновити токен"
msgid "Rows per page" msgid "Rows per page"
msgstr "Рядків на сторінку" msgstr "Рядків на сторінку"
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details"
msgstr "Деталі S.M.A.R.T."
#: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Self-Test"
msgstr "Самодіагностика S.M.A.R.T."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Збережіть адресу, використовуючи клавішу Enter або кому. Залиште порожнім, щоб вимкнути сповіщення електронною поштою." msgstr "Збережіть адресу, використовуючи клавішу Enter або кому. Залиште порожнім, щоб вимкнути сповіщення електронною поштою."
@@ -972,6 +1041,10 @@ msgstr "Перегляньте <0>налаштування сповіщень</0
msgid "Sent" msgid "Sent"
msgstr "Відправлено" msgstr "Відправлено"
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr "Серійний номер"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Встановіть відсоткові пороги для кольорів лічильників." msgstr "Встановіть відсоткові пороги для кольорів лічильників."
@@ -1005,6 +1078,7 @@ msgid "State"
msgstr "Стан" msgstr "Стан"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
@@ -1043,6 +1117,7 @@ msgid "Table"
msgstr "Таблиця" msgstr "Таблиця"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Temp" msgid "Temp"
msgstr "Температура" msgstr "Температура"
@@ -1124,6 +1199,11 @@ msgstr "Токени дозволяють агентам підключатис
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токени та відбитки використовуються для автентифікації WebSocket з'єднань до хабу." msgstr "Токени та відбитки використовуються для автентифікації WebSocket з'єднань до хабу."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Разом"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Загальний обсяг отриманих даних для кожного інтерфейсу" msgstr "Загальний обсяг отриманих даних для кожного інтерфейсу"
@@ -1168,6 +1248,10 @@ msgstr "Спрацьовує, коли статус перемикається
msgid "Triggers when usage of any disk exceeds a threshold" msgid "Triggers when usage of any disk exceeds a threshold"
msgstr "Спрацьовує, коли використання будь-якого диска перевищує поріг" msgstr "Спрацьовує, коли використання будь-якого диска перевищує поріг"
#: src/components/routes/system/smart-table.tsx
msgid "Type"
msgstr "Тип"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1209,6 +1293,7 @@ msgstr "Час роботи"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Використання" msgstr "Використання"
@@ -1234,6 +1319,7 @@ msgstr "Значення"
msgid "View" msgid "View"
msgstr "Вигляд" msgstr "Вигляд"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Переглянути більше" msgstr "Переглянути більше"

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