mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-25 23:16:17 +01:00
Compare commits
45 Commits
b18528d24a
...
top-proces
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbbdd49fc2 | ||
|
|
fc0947aa04 | ||
|
|
1d546a4091 | ||
|
|
f60b3bbbfb | ||
|
|
8e99b9f1ad | ||
|
|
fa5ed2bc11 | ||
|
|
21d961ab97 | ||
|
|
aaa93b84d2 | ||
|
|
6a562ce03b | ||
|
|
3dbc48727e | ||
|
|
85ac2e5e9a | ||
|
|
af6bd4e505 | ||
|
|
e54c4b3499 | ||
|
|
078c88f825 | ||
|
|
85169b6c5e | ||
|
|
d0ff8ee2c0 | ||
|
|
e898768997 | ||
|
|
0f5b504f23 | ||
|
|
365d291393 | ||
|
|
3dbab24c0f | ||
|
|
1f67fb7c8d | ||
|
|
219e09fc78 | ||
|
|
cd9c2bd9ab | ||
|
|
9f969d843c | ||
|
|
b22a6472fc | ||
|
|
d231ace28e | ||
|
|
473cb7f437 | ||
|
|
783ed9f456 | ||
|
|
9a9a89ee50 | ||
|
|
5122d0341d | ||
|
|
81731689da | ||
|
|
b3e9857448 | ||
|
|
2eda9eb0e3 | ||
|
|
82a5df5048 | ||
|
|
f11564a7ac | ||
|
|
9df4d29236 | ||
|
|
1452817423 | ||
|
|
c57e496f5e | ||
|
|
6287f7003c | ||
|
|
37037b1f4e | ||
|
|
7cf123a99e | ||
|
|
97394e775f | ||
|
|
d5c381188b | ||
|
|
b107d12a62 | ||
|
|
e646f2c1fc |
@@ -10,8 +10,10 @@ import (
|
|||||||
"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,7 +23,7 @@ func HasReadableBattery() bool {
|
|||||||
haveCheckedBattery = true
|
haveCheckedBattery = true
|
||||||
batteries, err := battery.GetAll()
|
batteries, err := battery.GetAll()
|
||||||
for _, bat := range batteries {
|
for _, bat := range batteries {
|
||||||
if bat.Full > 0 {
|
if bat != nil && (bat.Full > 0 || bat.Design > 0) {
|
||||||
systemHasBattery = true
|
systemHasBattery = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -54,7 +56,7 @@ func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
|||||||
// 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
|
||||||
|
|||||||
211
agent/cpu.go
211
agent/cpu.go
@@ -2,12 +2,19 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/henrygd/beszel/internal/entities/system"
|
||||||
"github.com/shirou/gopsutil/v4/cpu"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
|
"github.com/shirou/gopsutil/v4/process"
|
||||||
)
|
)
|
||||||
|
|
||||||
var lastCpuTimes = make(map[uint16]cpu.TimesStat)
|
var lastCpuTimes = make(map[uint16]cpu.TimesStat)
|
||||||
|
var lastPerCoreCpuTimes = make(map[uint16][]cpu.TimesStat)
|
||||||
|
var lastProcessCpuTimes = make(map[uint16]map[int32]float64)
|
||||||
|
var lastProcessCpuSampleTime = make(map[uint16]time.Time)
|
||||||
|
|
||||||
// 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 +22,206 @@ 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
|
||||||
|
}
|
||||||
|
if processes, err := process.Processes(); err == nil {
|
||||||
|
snapshot := make(map[int32]float64, len(processes))
|
||||||
|
for _, proc := range processes {
|
||||||
|
if times, err := proc.Times(); err == nil {
|
||||||
|
snapshot[proc.Pid] = times.Total()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastProcessCpuTimes[60000] = snapshot
|
||||||
|
lastProcessCpuSampleTime[60000] = time.Now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTopCpuProcess returns the process with the highest CPU usage since the last run
|
||||||
|
// for the given cache interval. It returns nil if insufficient data is available.
|
||||||
|
func getTopCpuProcess(cacheTimeMs uint16) (*system.TopCpuProcess, error) {
|
||||||
|
processes, err := process.Processes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
lastTimes, ok := lastProcessCpuTimes[cacheTimeMs]
|
||||||
|
if !ok {
|
||||||
|
if fallback := lastProcessCpuTimes[60000]; fallback != nil {
|
||||||
|
copied := make(map[int32]float64, len(fallback))
|
||||||
|
for pid, total := range fallback {
|
||||||
|
copied[pid] = total
|
||||||
|
}
|
||||||
|
lastTimes = copied
|
||||||
|
lastProcessCpuTimes[cacheTimeMs] = copied
|
||||||
|
} else {
|
||||||
|
lastTimes = make(map[int32]float64)
|
||||||
|
lastProcessCpuTimes[cacheTimeMs] = lastTimes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSample := lastProcessCpuSampleTime[cacheTimeMs]
|
||||||
|
if lastSample.IsZero() {
|
||||||
|
if fallback := lastProcessCpuSampleTime[60000]; !fallback.IsZero() {
|
||||||
|
lastSample = fallback
|
||||||
|
lastProcessCpuSampleTime[cacheTimeMs] = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := now.Sub(lastSample).Seconds()
|
||||||
|
if lastSample.IsZero() || elapsed <= 0 {
|
||||||
|
snapshot := make(map[int32]float64, len(processes))
|
||||||
|
for _, proc := range processes {
|
||||||
|
if times, err := proc.Times(); err == nil {
|
||||||
|
snapshot[proc.Pid] = times.Total()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastProcessCpuTimes[cacheTimeMs] = snapshot
|
||||||
|
lastProcessCpuSampleTime[cacheTimeMs] = now
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cpuCount := float64(runtime.NumCPU())
|
||||||
|
if cpuCount <= 0 {
|
||||||
|
cpuCount = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot := make(map[int32]float64, len(processes))
|
||||||
|
var topName string
|
||||||
|
var topPercent float64
|
||||||
|
|
||||||
|
for _, proc := range processes {
|
||||||
|
times, err := proc.Times()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
total := times.Total()
|
||||||
|
pid := proc.Pid
|
||||||
|
snapshot[pid] = total
|
||||||
|
|
||||||
|
lastTotal, ok := lastTimes[pid]
|
||||||
|
if !ok || total <= lastTotal {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
percent := clampPercent((total - lastTotal) / (elapsed * cpuCount) * 100)
|
||||||
|
if percent <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := proc.Name()
|
||||||
|
if err != nil || name == "" {
|
||||||
|
if exe, exeErr := proc.Exe(); exeErr == nil && exe != "" {
|
||||||
|
name = filepath.Base(exe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if percent > topPercent {
|
||||||
|
topPercent = percent
|
||||||
|
topName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastProcessCpuTimes[cacheTimeMs] = snapshot
|
||||||
|
lastProcessCpuSampleTime[cacheTimeMs] = now
|
||||||
|
|
||||||
|
if topName == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &system.TopCpuProcess{
|
||||||
|
Name: topName,
|
||||||
|
Percent: topPercent,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateBusy calculates the CPU busy percentage between two time points.
|
// calculateBusy calculates the CPU busy percentage between two time points.
|
||||||
@@ -41,13 +231,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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -1624,3 +1626,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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ 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()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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/blang/semver"
|
"github.com/blang/semver"
|
||||||
@@ -170,6 +171,8 @@ 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
|
||||||
default:
|
default:
|
||||||
response.Error = fmt.Sprintf("unsupported response type: %T", data)
|
response.Error = fmt.Sprintf("unsupported response type: %T", data)
|
||||||
}
|
}
|
||||||
|
|||||||
601
agent/smart.go
601
agent/smart.go
@@ -3,8 +3,11 @@ package agent
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,6 +22,7 @@ type SmartManager struct {
|
|||||||
SmartDataMap map[string]*smart.SmartData
|
SmartDataMap map[string]*smart.SmartData
|
||||||
SmartDevices []*DeviceInfo
|
SmartDevices []*DeviceInfo
|
||||||
refreshMutex sync.Mutex
|
refreshMutex sync.Mutex
|
||||||
|
lastScanTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type scanOutput struct {
|
type scanOutput struct {
|
||||||
@@ -35,16 +39,21 @@ 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.Debug("smartctl scan failed", "err", scanErr)
|
slog.Debug("smartctl scan failed", "err", scanErr)
|
||||||
}
|
}
|
||||||
@@ -56,7 +65,7 @@ func (sm *SmartManager) Refresh() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := sm.CollectSmart(deviceInfo); err != nil {
|
if err := sm.CollectSmart(deviceInfo); err != nil {
|
||||||
slog.Debug("smartctl collect failed, skipping", "device", deviceInfo.Name, "err", err)
|
slog.Debug("smartctl collect failed", "device", deviceInfo.Name, "err", err)
|
||||||
collectErr = err
|
collectErr = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,77 +135,310 @@ 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, "smartctl", "--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 {
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// 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, "smartctl", 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, "smartctl", 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.Debug("smartctl failed", "device", deviceInfo.Name, "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
slog.Debug("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 +453,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.Debug("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(©Dev, prev)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalDevices = append(finalDevices, ©Dev)
|
||||||
|
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(©Dev, prev)
|
||||||
|
} else if copyDev.Type != "" {
|
||||||
|
copyDev.parserType = normalizeParserType(copyDev.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalDevices = append(finalDevices, ©Dev)
|
||||||
|
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 +653,19 @@ func (sm *SmartManager) parseSmartForSata(output []byte) (bool, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if data.SerialNumber == "" {
|
if data.SerialNumber == "" {
|
||||||
slog.Debug("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 +688,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 +719,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 +815,19 @@ func (sm *SmartManager) parseSmartForNvme(output []byte) (bool, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if data.SerialNumber == "" {
|
if data.SerialNumber == "" {
|
||||||
slog.Debug("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
|
||||||
@@ -384,9 +877,11 @@ 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() error {
|
||||||
if _, err := exec.LookPath("smartctl"); err == nil {
|
if _, err := exec.LookPath("smartctl"); err == nil {
|
||||||
|
slog.Debug("smartctl found")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("smartctl not found")
|
slog.Debug("smartctl not found")
|
||||||
|
return errors.New("smartctl not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSmartManager creates and initializes a new SmartManager
|
// NewSmartManager creates and initializes a new SmartManager
|
||||||
|
|||||||
590
agent/smart_test.go
Normal file
590
agent/smart_test.go
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
//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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,12 +83,33 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if topProcess, err := getTopCpuProcess(cacheTimeMs); err == nil {
|
||||||
|
if topProcess != nil {
|
||||||
|
topProcess.Percent = twoDecimals(topProcess.Percent)
|
||||||
|
systemStats.TopCpuProcess = topProcess
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slog.Error("Error getting top cpu process", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// per-core cpu usage
|
||||||
|
if perCoreUsage, err := getPerCoreCpuUsage(cacheTimeMs); err == nil {
|
||||||
|
systemStats.CpuCoresUsage = perCoreUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
// load average
|
// load average
|
||||||
|
|||||||
272
agent/test-data/smart/nvme0.json
Normal file
272
agent/test-data/smart/nvme0.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
agent/test-data/smart/scan.json
Normal file
36
agent/test-data/smart/scan.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
125
agent/test-data/smart/scsi.json
Normal file
125
agent/test-data/smart/scsi.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1013
agent/test-data/smart/sda.json
Normal file
1013
agent/test-data/smart/sda.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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.15.3"
|
||||||
// AppName is the name of the application.
|
// AppName is the name of the application.
|
||||||
AppName = "beszel"
|
AppName = "beszel"
|
||||||
)
|
)
|
||||||
|
|||||||
39
go.mod
39
go.mod
@@ -1,9 +1,6 @@
|
|||||||
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
|
||||||
@@ -12,16 +9,16 @@ require (
|
|||||||
github.com/gliderlabs/ssh v0.3.8
|
github.com/gliderlabs/ssh v0.3.8
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/lxzan/gws v1.8.9
|
github.com/lxzan/gws v1.8.9
|
||||||
github.com/nicholas-fedor/shoutrrr v0.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 +32,18 @@ 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/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 +51,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
|
||||||
)
|
)
|
||||||
|
|||||||
106
go.sum
106
go.sum
@@ -31,8 +31,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=
|
||||||
@@ -54,8 +54,8 @@ github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs
|
|||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/pprof v0.0.0-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 +63,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 +90,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 +99,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 +120,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 +144,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 +187,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=
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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"`
|
||||||
// }
|
// }
|
||||||
|
|||||||
62
internal/entities/smart/smart_test.go
Normal file
62
internal/entities/smart/smart_test.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ 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"
|
||||||
@@ -41,9 +42,29 @@ 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..]
|
||||||
|
TopCpuProcess *TopCpuProcess `json:"tcp,omitempty" cbor:"35,keyasint,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
@@ -133,3 +154,8 @@ type CombinedData struct {
|
|||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TopCpuProcess struct {
|
||||||
|
Name string `json:"n" cbor:"0,keyasint"`
|
||||||
|
Percent float64 `json:"p" cbor:"1,keyasint"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -307,7 +323,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})
|
||||||
@@ -338,7 +354,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)
|
||||||
|
|||||||
@@ -718,7 +718,9 @@ func init() {
|
|||||||
"type": "autodate"
|
"type": "autodate"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"indexes": [],
|
"indexes": [
|
||||||
|
"CREATE INDEX ` + "`" + `idx_systems_status` + "`" + ` ON ` + "`" + `systems` + "`" + ` (` + "`" + `status` + "`" + `)"
|
||||||
|
],
|
||||||
"system": false
|
"system": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default defineConfig({
|
|||||||
"es",
|
"es",
|
||||||
"fa",
|
"fa",
|
||||||
"fr",
|
"fr",
|
||||||
|
"he",
|
||||||
"hr",
|
"hr",
|
||||||
"hu",
|
"hu",
|
||||||
"it",
|
"it",
|
||||||
|
|||||||
4
internal/site/package-lock.json
generated
4
internal/site/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.14.1",
|
"version": "0.15.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"version": "0.14.1",
|
"version": "0.15.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@henrygd/queue": "^1.0.7",
|
"@henrygd/queue": "^1.0.7",
|
||||||
"@henrygd/semaphore": "^0.0.2",
|
"@henrygd/semaphore": "^0.0.2",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "beszel",
|
"name": "beszel",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.14.1",
|
"version": "0.15.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
|
|||||||
@@ -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,23 @@ export default function AreaChartDefault({
|
|||||||
domain,
|
domain,
|
||||||
legend,
|
legend,
|
||||||
itemSorter,
|
itemSorter,
|
||||||
|
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
|
itemSorter?: (a: any, b: any) => number
|
||||||
// logRender?: boolean
|
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,12 +61,13 @@ 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 && <YAxis
|
||||||
direction="ltr"
|
direction="ltr"
|
||||||
orientation={chartData.orientation}
|
orientation={chartData.orientation}
|
||||||
className="tracking-tighter"
|
className="tracking-tighter"
|
||||||
@@ -70,7 +76,7 @@ export default function AreaChartDefault({
|
|||||||
tickFormatter={(value, index) => updateYAxisWidth(tickFormatter(value, index))}
|
tickFormatter={(value, index) => updateYAxisWidth(tickFormatter(value, index))}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
/>
|
/>}
|
||||||
{xAxis(chartData)}
|
{xAxis(chartData)}
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
animationEasing="ease-out"
|
animationEasing="ease-out"
|
||||||
@@ -99,10 +105,11 @@ 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>
|
||||||
|
|||||||
@@ -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))`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
|
|||||||
|
|
||||||
// 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)
|
const changeTime = Date.now()
|
||||||
|
setTimeout(() => fetchData(Date.now() - changeTime + 1000), 100)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -361,7 +362,7 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
|
|||||||
<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,7 +376,7 @@ 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>
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
debounce,
|
debounce,
|
||||||
decimalString,
|
decimalString,
|
||||||
formatBytes,
|
formatBytes,
|
||||||
|
secondsToString,
|
||||||
getHostDisplayValue,
|
getHostDisplayValue,
|
||||||
listen,
|
listen,
|
||||||
parseSemVer,
|
parseSemVer,
|
||||||
@@ -72,6 +73,7 @@ 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"
|
||||||
|
|
||||||
|
|
||||||
@@ -96,8 +98,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 +360,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 +586,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}
|
||||||
@@ -965,9 +964,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,
|
||||||
@@ -1126,7 +1125,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}
|
||||||
|
|||||||
195
internal/site/src/components/routes/system/cpu-sheet.tsx
Normal file
195
internal/site/src/components/routes/system/cpu-sheet.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,153 +115,138 @@ 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>([])
|
||||||
const [rowSelection, setRowSelection] = React.useState({})
|
const [rowSelection, setRowSelection] = React.useState({})
|
||||||
const [smartData, setSmartData] = React.useState<Record<string, SmartData> | undefined>(undefined)
|
const [smartData, setSmartData] = React.useState<Record<string, SmartData> | undefined>(undefined)
|
||||||
@@ -284,14 +278,14 @@ export default function DisksTable({ systemId }: { systemId: string }) {
|
|||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: diskData,
|
data: diskData,
|
||||||
columns: columns,
|
columns: columns,
|
||||||
// onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
onColumnFiltersChange: setColumnFilters,
|
onColumnFiltersChange: setColumnFilters,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
onRowSelectionChange: setRowSelection,
|
onRowSelectionChange: setRowSelection,
|
||||||
state: {
|
state: {
|
||||||
// sorting,
|
sorting,
|
||||||
columnFilters,
|
columnFilters,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
},
|
},
|
||||||
@@ -331,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(
|
||||||
@@ -354,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()
|
||||||
@@ -378,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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,14 +257,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 +277,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -429,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` })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 "تم تحديد {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 ساعة"
|
||||||
@@ -373,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 "استخدام وحدة المعالجة المركزية"
|
||||||
@@ -836,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 "الكتابة فوق التنبيهات الحالية"
|
||||||
@@ -884,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> لضمان تسليم التنبيهات."
|
||||||
@@ -1269,6 +1288,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 "الاستخدام"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "عرض المزيد"
|
||||||
|
|||||||
@@ -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 "{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 час"
|
||||||
@@ -373,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 "Употреба на процесор"
|
||||||
@@ -836,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 "Презапиши съществуващи тревоги"
|
||||||
@@ -884,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> за да се подсигуриш, че тревогите са доставени."
|
||||||
@@ -1269,6 +1288,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 "Употреба"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "Виж повече"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 {# 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
|
||||||
#: 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} 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
|
||||||
@@ -120,7 +117,7 @@ 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
|
||||||
@@ -373,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"
|
||||||
@@ -429,7 +435,7 @@ 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
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Device"
|
msgid "Device"
|
||||||
@@ -442,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"
|
||||||
@@ -507,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"
|
||||||
@@ -600,7 +606,7 @@ msgstr "Otisk"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "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}}"
|
||||||
@@ -758,7 +764,7 @@ msgstr "Využití paměti docker kontejnerů"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -836,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í"
|
||||||
@@ -884,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."
|
||||||
@@ -919,7 +938,7 @@ 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
|
#. Power On Time
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
@@ -1164,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
|
||||||
@@ -1269,6 +1288,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í"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-25 10:58\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"
|
||||||
@@ -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 {# dage}}"
|
|
||||||
|
|
||||||
#. 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 {# minut} 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} af {1} række(r) valgt."
|
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"
|
||||||
msgstr "1 time"
|
msgstr "1 time"
|
||||||
@@ -373,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-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"
|
||||||
@@ -600,7 +606,7 @@ msgstr "Fingeraftryk"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "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}}"
|
||||||
@@ -769,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"
|
||||||
@@ -836,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"
|
||||||
@@ -884,6 +894,15 @@ msgstr "Sat på pause"
|
|||||||
msgid "Paused ({pausedSystemsLength})"
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
msgstr "Sat på pause ({pausedSystemsLength})"
|
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."
|
||||||
msgstr "Konfigurer <0>en SMTP server</0> for at sikre at alarmer bliver leveret."
|
msgstr "Konfigurer <0>en SMTP server</0> for at sikre at alarmer bliver leveret."
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,7 @@ msgstr "Værdi"
|
|||||||
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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-25 21:09\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"
|
||||||
@@ -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 {# 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
|
||||||
#: 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} 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"
|
||||||
@@ -373,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"
|
||||||
@@ -600,7 +606,7 @@ msgstr "Fingerabdruck"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "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}}"
|
||||||
@@ -836,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"
|
||||||
@@ -884,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."
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -368,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"
|
||||||
@@ -831,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"
|
||||||
@@ -879,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."
|
||||||
@@ -1264,6 +1283,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"
|
||||||
|
|
||||||
@@ -1289,6 +1309,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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-25 21:09\n"
|
"PO-Revision-Date: 2025-11-01 17:41\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"
|
||||||
@@ -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 {# 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
|
||||||
#: 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} 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"
|
||||||
@@ -373,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"
|
||||||
@@ -836,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"
|
||||||
@@ -884,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 "Utilización 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>configure un servidor SMTP</0> para asegurar que las alertas sean entregadas."
|
||||||
@@ -1000,11 +1019,11 @@ msgstr "Guarde la dirección usando la tecla enter o coma. Deje en blanco para d
|
|||||||
#: 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"
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,7 @@ 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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 "{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 "۱ ساعت"
|
||||||
@@ -373,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 "میزان استفاده از پردازنده"
|
||||||
@@ -836,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 "بازنویسی هشدارهای موجود"
|
||||||
@@ -884,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>."
|
||||||
@@ -1269,6 +1288,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 "میزان استفاده"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "مشاهده بیشتر"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-25 20:53\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"
|
||||||
@@ -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 {# 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
|
||||||
#: 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} 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"
|
||||||
@@ -373,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"
|
||||||
@@ -408,7 +414,7 @@ msgstr "État actuel"
|
|||||||
#. Power Cycles
|
#. Power Cycles
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Cycles"
|
msgid "Cycles"
|
||||||
msgstr "Cycles"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/command-palette.tsx
|
#: src/components/command-palette.tsx
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
@@ -836,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"
|
||||||
@@ -884,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."
|
||||||
@@ -1226,7 +1245,7 @@ msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Type"
|
msgstr ""
|
||||||
|
|
||||||
#. Temperature / network units
|
#. Temperature / network units
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
1376
internal/site/src/locales/he/he.po
Normal file
1376
internal/site/src/locales/he/he.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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-20 21:37\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"
|
||||||
@@ -373,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"
|
||||||
@@ -600,7 +606,7 @@ msgstr "Otisak prsta"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "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}}"
|
||||||
@@ -758,7 +764,7 @@ msgstr "Upotreba memorije Docker spremnika"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -836,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"
|
||||||
@@ -884,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."
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -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-20 21:37\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"
|
||||||
@@ -373,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"
|
||||||
@@ -600,7 +606,7 @@ msgstr "Ujjlenyomat"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "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}}"
|
||||||
@@ -836,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"
|
||||||
@@ -884,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."
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
1353
internal/site/src/locales/id/id.po
Normal file
1353
internal/site/src/locales/id/id.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -778,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 ""
|
||||||
|
|
||||||
@@ -921,11 +920,6 @@ msgstr "Vinsamlegast skráðu þig inn á aðganginn þinn"
|
|||||||
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"
|
||||||
@@ -950,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 ""
|
|
||||||
|
|
||||||
#: src/components/login/login.tsx
|
#: src/components/login/login.tsx
|
||||||
msgid "Request a one-time password"
|
msgid "Request a one-time password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1022,10 +1011,6 @@ msgstr ""
|
|||||||
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 "Stilltu prósentuþröskuld fyrir mælaliti."
|
msgstr "Stilltu prósentuþröskuld fyrir mælaliti."
|
||||||
@@ -1253,10 +1238,6 @@ msgstr ""
|
|||||||
msgid "Up ({upSystemsLength})"
|
msgid "Up ({upSystemsLength})"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/containers-table/containers-table-columns.tsx
|
|
||||||
msgid "Updated"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/components/routes/system/network-sheet.tsx
|
#: src/components/routes/system/network-sheet.tsx
|
||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1353,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."
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 {# 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
|
||||||
#: 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} 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"
|
||||||
@@ -373,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"
|
||||||
@@ -836,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"
|
||||||
@@ -884,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."
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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時間"
|
||||||
@@ -373,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 "CPU使用率"
|
msgstr "CPU使用率"
|
||||||
@@ -836,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 "既存のアラートを上書き"
|
||||||
@@ -884,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>してください。"
|
||||||
@@ -1269,6 +1288,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 "使用量"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "もっと見る"
|
||||||
|
|||||||
@@ -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-10-20 21:37\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시간"
|
||||||
@@ -373,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 사용량"
|
||||||
@@ -836,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 "기존 알림 덮어쓰기"
|
||||||
@@ -884,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>하세요."
|
||||||
@@ -1269,6 +1288,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 "사용량"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "더 보기"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 {# 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
|
||||||
#: 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} 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"
|
||||||
@@ -373,8 +370,17 @@ msgstr "YAML kopiëren"
|
|||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr ""
|
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"
|
||||||
@@ -600,7 +606,7 @@ msgstr "Vingerafdruk"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "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}}"
|
||||||
@@ -758,7 +764,7 @@ msgstr "Geheugengebruik van docker containers"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -836,6 +842,10 @@ msgstr ""
|
|||||||
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"
|
||||||
@@ -884,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."
|
||||||
@@ -987,7 +1006,7 @@ msgstr "Rijen per pagina"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "S.M.A.R.T. Details"
|
msgid "S.M.A.R.T. Details"
|
||||||
msgstr "S.M.A.R.T. Details"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "S.M.A.R.T. Self-Test"
|
msgid "S.M.A.R.T. Self-Test"
|
||||||
@@ -1226,7 +1245,7 @@ msgstr "Triggert wanneer het gebruik van een schijf een drempelwaarde overschrij
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Type"
|
msgstr ""
|
||||||
|
|
||||||
#. Temperature / network units
|
#. Temperature / network units
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -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-22 10:37\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"
|
||||||
@@ -373,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"
|
||||||
@@ -836,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"
|
||||||
@@ -884,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."
|
||||||
@@ -1226,7 +1245,7 @@ msgstr "Slår inn når forbruk av hvilken som helst disk overstiger en grensever
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Type"
|
msgstr ""
|
||||||
|
|
||||||
#. Temperature / network units
|
#. Temperature / network units
|
||||||
#: src/components/routes/settings/general.tsx
|
#: src/components/routes/settings/general.tsx
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -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-10-20 21:37\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"
|
||||||
@@ -373,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"
|
||||||
@@ -836,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"
|
||||||
@@ -884,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ń."
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-20 21:37\n"
|
"PO-Revision-Date: 2025-10-30 21:52\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"
|
||||||
@@ -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 {# 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
|
||||||
#: 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} 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"
|
||||||
@@ -227,7 +224,7 @@ 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
|
||||||
@@ -373,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 da CPU"
|
||||||
|
|
||||||
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
|
msgid "CPU Time Breakdown"
|
||||||
|
msgstr "Detalhamento do tempo da 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"
|
||||||
@@ -600,7 +606,7 @@ msgstr "Impressão digital"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Firmware"
|
msgid "Firmware"
|
||||||
msgstr "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}}"
|
||||||
@@ -674,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"
|
||||||
@@ -722,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."
|
||||||
@@ -836,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"
|
||||||
@@ -884,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."
|
||||||
@@ -1101,7 +1120,7 @@ msgstr "Tabela"
|
|||||||
#: src/components/routes/system/smart-table.tsx
|
#: 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
|
||||||
@@ -1164,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
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: ro\n"
|
"Language: ro\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 21:37\n"
|
"PO-Revision-Date: 2025-10-28 23:00\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Romanian\n"
|
"Language-Team: Romanian\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
||||||
@@ -18,21 +18,6 @@ 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 ""
|
|
||||||
|
|
||||||
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{0, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. 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
|
||||||
@@ -234,6 +219,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 +268,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 ""
|
||||||
@@ -397,6 +390,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 +416,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 +550,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 +574,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 +583,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 ""
|
msgstr ""
|
||||||
@@ -730,6 +741,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 +776,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 ""
|
||||||
|
|
||||||
#: 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."
|
||||||
@@ -884,6 +906,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 +970,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 ""
|
||||||
@@ -972,6 +1007,10 @@ msgstr ""
|
|||||||
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 +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 ""
|
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 ""
|
||||||
@@ -1168,6 +1209,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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 "Выбрано {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 час"
|
||||||
@@ -373,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"
|
||||||
@@ -836,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 "Перезаписать существующие оповещения"
|
||||||
@@ -884,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>, чтобы гарантировать доставку оповещений."
|
||||||
@@ -1269,6 +1288,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 "Использование"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "Показать больше"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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} 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
|
||||||
#: 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} 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"
|
||||||
@@ -120,7 +117,7 @@ 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
|
||||||
@@ -373,8 +370,17 @@ msgstr ""
|
|||||||
msgid "CPU"
|
msgid "CPU"
|
||||||
msgstr ""
|
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"
|
||||||
@@ -758,7 +764,7 @@ msgstr "Poraba pomnilnika docker kontejnerjev"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -836,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"
|
||||||
@@ -884,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."
|
||||||
@@ -1063,7 +1082,7 @@ msgstr "Stanje"
|
|||||||
#: 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"
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,7 @@ msgstr "Vrednost"
|
|||||||
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č"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 {# 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 "{0, plural, one {# minut} few {# minuter} many {# minuter} other {# minuter}}"
|
|
||||||
|
|
||||||
#. 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) 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,11 +113,11 @@ 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
|
||||||
@@ -244,7 +241,7 @@ 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."
|
||||||
@@ -371,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"
|
||||||
@@ -442,7 +448,7 @@ 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"
|
||||||
@@ -619,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
|
||||||
@@ -682,7 +688,7 @@ 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"
|
||||||
@@ -740,7 +746,7 @@ 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
|
||||||
@@ -836,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"
|
||||||
@@ -884,6 +894,15 @@ msgstr "Pausad"
|
|||||||
msgid "Paused ({pausedSystemsLength})"
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
msgstr "Pausad ({pausedSystemsLength})"
|
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."
|
||||||
msgstr "Vänligen <0>konfigurera en SMTP-server</0> för att säkerställa att larm levereras."
|
msgstr "Vänligen <0>konfigurera en SMTP-server</0> för att säkerställa att larm levereras."
|
||||||
@@ -919,7 +938,7 @@ 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
|
#. Power On Time
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
@@ -1063,7 +1082,7 @@ msgstr "Tillstånd"
|
|||||||
#: 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"
|
||||||
@@ -1079,7 +1098,7 @@ 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"
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,7 @@ msgstr "Värde"
|
|||||||
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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 {# 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
|
||||||
#: 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} 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"
|
||||||
@@ -371,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ı"
|
||||||
@@ -442,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"
|
||||||
@@ -758,7 +764,7 @@ msgstr "Docker konteynerlerinin bellek kullanımı"
|
|||||||
|
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
msgid "Model"
|
msgid "Model"
|
||||||
msgstr "Model"
|
msgstr ""
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
#: src/components/alerts-history-columns.tsx
|
#: src/components/alerts-history-columns.tsx
|
||||||
@@ -836,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"
|
||||||
@@ -884,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>."
|
||||||
@@ -919,7 +938,7 @@ 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
|
#. Power On Time
|
||||||
#: src/components/routes/system/smart-table.tsx
|
#: src/components/routes/system/smart-table.tsx
|
||||||
@@ -1164,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
|
||||||
@@ -1269,6 +1288,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"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ 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-10-20 21:37\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"
|
||||||
@@ -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 {# день} 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
|
||||||
#: 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} з {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 година"
|
||||||
@@ -373,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 "Використання ЦП"
|
||||||
@@ -836,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 "Перезаписати існуючі сповіщення"
|
||||||
@@ -884,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>, щоб забезпечити доставку сповіщень."
|
||||||
@@ -1269,6 +1288,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 "Використання"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "Переглянути більше"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: vi\n"
|
"Language: vi\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 21:37\n"
|
"PO-Revision-Date: 2025-10-28 23:00\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Vietnamese\n"
|
"Language-Team: Vietnamese\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 {# ngày} other {# ngày}}"
|
|
||||||
|
|
||||||
#. placeholder {0}: Math.trunc(system.info.u / 3600)
|
|
||||||
#: src/components/routes/system.tsx
|
|
||||||
msgid "{0, plural, one {# hour} other {# hours}}"
|
|
||||||
msgstr "{0, plural, one {# giờ} other {# giờ}}"
|
|
||||||
|
|
||||||
#. 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 {# phút} few {# phút} many {# phút} other {# phút}}"
|
|
||||||
|
|
||||||
#. 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 "Đã chọn {0} trên {1} hàng."
|
msgstr "Đã chọn {0} trên {1} hàng."
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
|
||||||
|
msgstr "{count, plural, one {{countString} ngày} other {{countString} ngày}}"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
|
||||||
|
msgstr "{count, plural, one {{countString} giờ} other {{countString} giờ}}"
|
||||||
|
|
||||||
|
#: src/lib/utils.ts
|
||||||
|
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
|
||||||
|
msgstr "{count, plural, one {{countString} phút} few {{countString} phút} many {{countString} phút} other {{countString} phút}}"
|
||||||
|
|
||||||
#: src/lib/utils.ts
|
#: src/lib/utils.ts
|
||||||
msgid "1 hour"
|
msgid "1 hour"
|
||||||
msgstr "1 giờ"
|
msgstr "1 giờ"
|
||||||
@@ -371,10 +368,19 @@ msgstr "Sao chép 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 "Nhân CPU"
|
||||||
|
|
||||||
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
|
msgid "CPU Time Breakdown"
|
||||||
|
msgstr "Phân tích thời gian 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 "Sử dụng CPU"
|
msgstr "Sử dụng CPU"
|
||||||
@@ -507,7 +513,7 @@ msgstr "Chỉnh sửa"
|
|||||||
#: 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"
|
||||||
@@ -836,6 +842,10 @@ msgstr "Mở menu"
|
|||||||
msgid "Or continue with"
|
msgid "Or continue with"
|
||||||
msgstr "Hoặc tiếp tục với"
|
msgstr "Hoặc tiếp tục với"
|
||||||
|
|
||||||
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Khác"
|
||||||
|
|
||||||
#: src/components/alerts/alerts-sheet.tsx
|
#: src/components/alerts/alerts-sheet.tsx
|
||||||
msgid "Overwrite existing alerts"
|
msgid "Overwrite existing alerts"
|
||||||
msgstr "Ghi đè các cảnh báo hiện có"
|
msgstr "Ghi đè các cảnh báo hiện có"
|
||||||
@@ -884,6 +894,15 @@ msgstr "Đã tạm dừng"
|
|||||||
msgid "Paused ({pausedSystemsLength})"
|
msgid "Paused ({pausedSystemsLength})"
|
||||||
msgstr "Đã tạm dừng ({pausedSystemsLength})"
|
msgstr "Đã tạm dừng ({pausedSystemsLength})"
|
||||||
|
|
||||||
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
|
msgid "Per-core average utilization"
|
||||||
|
msgstr "Tỷ lệ sử dụng trung bình mỗi nhân"
|
||||||
|
|
||||||
|
#: src/components/routes/system/cpu-sheet.tsx
|
||||||
|
msgid "Percentage of time spent in each state"
|
||||||
|
msgstr "Phần trăm thời gian dành cho mỗi trạng thái"
|
||||||
|
|
||||||
#: 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 "Vui lòng <0>cấu hình máy chủ SMTP</0> để đảm bảo cảnh báo được gửi đi."
|
msgstr "Vui lòng <0>cấu hình máy chủ SMTP</0> để đảm bảo cảnh báo được gửi đi."
|
||||||
@@ -1164,7 +1183,7 @@ msgstr "Chuyển đổi chủ đề"
|
|||||||
#: 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
|
||||||
@@ -1269,6 +1288,7 @@ msgstr "Thời gian hoạt động"
|
|||||||
#: 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 "Sử dụng"
|
msgstr "Sử dụng"
|
||||||
|
|
||||||
@@ -1294,6 +1314,7 @@ msgstr "Giá trị"
|
|||||||
msgid "View"
|
msgid "View"
|
||||||
msgstr "Xem"
|
msgstr "Xem"
|
||||||
|
|
||||||
|
#: 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 "Xem thêm"
|
msgstr "Xem thêm"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\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-25 21:09\n"
|
"PO-Revision-Date: 2025-10-30 21:53\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Simplified\n"
|
"Language-Team: Chinese Simplified\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 "已选择 {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 小时"
|
||||||
@@ -95,7 +92,7 @@ msgstr "启用的警报"
|
|||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Add <0>System</0>"
|
msgid "Add <0>System</0>"
|
||||||
msgstr "添加客户端</0>"
|
msgstr "<0>添加客户端</0>"
|
||||||
|
|
||||||
#: src/components/add-system.tsx
|
#: src/components/add-system.tsx
|
||||||
msgid "Add New System"
|
msgid "Add New System"
|
||||||
@@ -373,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 使用率"
|
||||||
@@ -836,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 "覆盖现有警报"
|
||||||
@@ -884,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>以确保警报被传递。"
|
||||||
@@ -1269,6 +1288,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 "使用"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "查看更多"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\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 21:37\n"
|
"PO-Revision-Date: 2025-10-28 23:00\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional, Hong Kong\n"
|
"Language-Team: Chinese Traditional, Hong Kong\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小時"
|
||||||
@@ -373,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 "CPU 使用率"
|
msgstr "CPU 使用率"
|
||||||
@@ -836,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 "覆蓋現有警報"
|
||||||
@@ -884,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>以確保警報被傳送。"
|
||||||
@@ -1269,6 +1288,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 "使用"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "查看更多"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: zh\n"
|
"Language: zh\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 21:37\n"
|
"PO-Revision-Date: 2025-11-01 07:52\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: Chinese Traditional\n"
|
"Language-Team: Chinese Traditional\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小時"
|
||||||
@@ -373,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 使用率"
|
||||||
@@ -836,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 "覆蓋現有警報"
|
||||||
@@ -884,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>以確保能傳送警報。"
|
||||||
@@ -1269,6 +1288,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 "使用量"
|
||||||
|
|
||||||
@@ -1294,6 +1314,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 "查看更多"
|
||||||
|
|||||||
4
internal/site/src/types.d.ts
vendored
4
internal/site/src/types.d.ts
vendored
@@ -84,6 +84,10 @@ export interface SystemStats {
|
|||||||
cpu: number
|
cpu: number
|
||||||
/** peak cpu */
|
/** peak cpu */
|
||||||
cpum?: number
|
cpum?: number
|
||||||
|
/** cpu breakdown [user, system, iowait, steal, idle] (0-100 integers) */
|
||||||
|
cpub?: number[]
|
||||||
|
/** per-core cpu usage [CPU0..] (0-100 integers) */
|
||||||
|
cpus?: number[]
|
||||||
// TODO: remove these in future release in favor of la
|
// TODO: remove these in future release in favor of la
|
||||||
/** load average 1 minute */
|
/** load average 1 minute */
|
||||||
l1?: number
|
l1?: number
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ The [quick start guide](https://beszel.dev/guide/getting-started) and other docu
|
|||||||
- **Temperature** - Host system sensors.
|
- **Temperature** - Host system sensors.
|
||||||
- **GPU usage / power draw** - Nvidia, AMD, and Intel.
|
- **GPU usage / power draw** - Nvidia, AMD, and Intel.
|
||||||
- **Battery** - Host system battery charge.
|
- **Battery** - Host system battery charge.
|
||||||
|
- **Containers** - Status and metrics of all running Docker / Podman containers.
|
||||||
|
- **S.M.A.R.T.** - Host system disk health.
|
||||||
|
|
||||||
## Help and discussion
|
## Help and discussion
|
||||||
|
|
||||||
@@ -57,7 +59,7 @@ Please search existing issues and discussions before opening a new one. I try my
|
|||||||
|
|
||||||
#### Bug reports and feature requests
|
#### Bug reports and feature requests
|
||||||
|
|
||||||
Bug reports and detailed feature requests should be posted on [GitHub issues](https://github.com/henrygd/beszel/issues).
|
Bug reports and feature requests can be posted on [GitHub issues](https://github.com/henrygd/beszel/issues).
|
||||||
|
|
||||||
#### Support and general discussion
|
#### Support and general discussion
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,61 @@
|
|||||||
|
## 0.15.3
|
||||||
|
|
||||||
|
- Add CPU state details and per-core usage. (#1356)
|
||||||
|
|
||||||
|
- Add `EXCLUDE_CONTAINERS` environment variable to exclude containers from being monitored. (#1352)
|
||||||
|
|
||||||
|
- Add `INTEL_GPU_DEVICE` environment variable to specify Intel GPU device. (#1285)
|
||||||
|
|
||||||
|
- Improve parsing of edge case S.M.A.R.T. power on times. (#1347)
|
||||||
|
|
||||||
|
- Fix empty disk I/O values for extra disks. (#1355)
|
||||||
|
|
||||||
|
- Fix battery nil pointer error. (#1353)
|
||||||
|
|
||||||
|
- Add Hebrew with translations by @gabay.
|
||||||
|
|
||||||
|
- Update `shoutrrr` and `gopsutil` dependencies.
|
||||||
|
|
||||||
|
## 0.15.2
|
||||||
|
|
||||||
|
- Improve S.M.A.R.T. device detection logic (fix regression in 0.15.1) (#1345)
|
||||||
|
|
||||||
|
## 0.15.1
|
||||||
|
|
||||||
|
- Add `SMART_DEVICES` environment variable to specify devices and types. (#373, #1335)
|
||||||
|
|
||||||
|
- Add support for `scsi`, `sntasmedia`, and `sntrealtek` S.M.A.R.T. types. (#373, #1335)
|
||||||
|
|
||||||
|
- Handle power-on time attributes that are formatted as strings (e.g., "0h+0m+0.000s").
|
||||||
|
|
||||||
|
- Skip virtual disks in S.M.A.R.T. monitoring. (#1332)
|
||||||
|
|
||||||
|
- Add sorting to the S.M.A.R.T. table. (#1333)
|
||||||
|
|
||||||
|
- Fix incorrect disk rendering in S.M.A.R.T. device details. (#1336)
|
||||||
|
|
||||||
|
- Fix `SHARE_ALL_SYSTEMS` setting not working for containers. (#1334)
|
||||||
|
|
||||||
|
- Fix text contrast issue when container details are disabled. (#1324)
|
||||||
|
|
||||||
## 0.15.0
|
## 0.15.0
|
||||||
|
|
||||||
- Add initial S.M.A.R.T. support for disk health monitoring. (#962)
|
- Add initial S.M.A.R.T. support for disk health monitoring. (#962)
|
||||||
|
|
||||||
|
- Add `henrygd/beszel-agent:alpine` Docker image and include `smartmontools` in all non-base agent images.
|
||||||
|
|
||||||
|
- Remove environment variables from container details (#1305)
|
||||||
|
|
||||||
- Add `CONTAINER_DETAILS` environment variable to control access to container logs and info APIs. (#1305)
|
- Add `CONTAINER_DETAILS` environment variable to control access to container logs and info APIs. (#1305)
|
||||||
|
|
||||||
- Improve temperature chart by allowing y-axis to start above 0 for better readability. (#1307)
|
- Improve temperature chart by allowing y-axis to start above 0 for better readability. (#1307)
|
||||||
|
|
||||||
- Add `henrygd/beszel-agent:alpine` Docker image and include `smartmontools` in all non-base agent images.
|
|
||||||
|
|
||||||
- Improve battery detection logic. (#1287)
|
- Improve battery detection logic. (#1287)
|
||||||
|
|
||||||
|
- Limit docker log size to prevent possible memory leak. (#1322)
|
||||||
|
|
||||||
|
- Update Go dependencies.
|
||||||
|
|
||||||
## 0.14.1
|
## 0.14.1
|
||||||
|
|
||||||
- Add `MFA_OTP` environment variable to enable email-based one-time password for users and/or superusers.
|
- Add `MFA_OTP` environment variable to enable email-based one-time password for users and/or superusers.
|
||||||
|
|||||||
Reference in New Issue
Block a user