Compare commits

...
Author SHA1 Message Date
Petr RajtslegrandGitHub 6a7b2772d9 fix(site): switch theme live when system preference changes (#2328)
Listen for prefers-color-scheme changes in ThemeProvider and expose resolvedTheme so the login border color follows the active theme without a hard refresh.
2026-09-13 12:40:21 -04:00
David JangandGitHub 086091a0fe fix(site): discard pending history when switching to live charts (#2333) 2026-09-13 12:32:04 -04:00
f204dc17e6 feat(agent): Add docker image update available flag (#2211)
Co-authored-by: henrygd <hank@henrygd.me>
2026-09-10 19:39:40 -04:00
Alec RubinandGitHub 5fe1583655 fix(agent): don't warn about unset HUB_URL in SSH-only mode (#2316) 2026-09-10 16:58:12 -04:00
henrygd 6d82ee70b1 chore: replace google/uuid with new go uuid package 2026-09-10 10:39:11 -04:00
henrygd bb270e02a8 i18n: add translations for new strings and remove source matching translations 2026-09-09 20:53:52 -04:00
hankandGitHub 312c109138 i18n: New Crowdin updates (#2322) 2026-09-09 20:34:47 -04:00
henrygd c938368089 i18n: update translation strings 2026-09-09 20:27:22 -04:00
8d6a5d5f6e feat(agent): report btrfs filesystems as storage pools (#2315)
Co-authored-by: henrygd <hank@henrygd.me>
2026-09-09 19:53:39 -04:00
henrygd 98687be2f2 fix(hub): reject null systemd service entries to prevent hub panic 2026-09-08 10:08:33 -04:00
henrygd e39e153ca0 fix(hub): prevent readonly users listing tokens of their shared systems via api 2026-09-08 09:57:58 -04:00
5b87f7d7cb fix(hub): always clear auth store when encountring 4xx after token expires (#2310)
Co-authored-by: henrygd <hank@henrygd.me>
2026-09-08 08:21:00 -04:00
hankandGitHub 9a0aa5a89e fix: prevent possible dns rebinding for alert notifications directing to internal sevices (#2314)
- Validate resolved IPs immediately before connecting.
- Guard Shoutrrr requests during initialization and delivery.
- Require admins for services without HTTP client support.
- Test DNS rebinding, redirects, internal hosts, and authorization.
- Upgrade Shoutrrr to 0.20.0 to support custom dialcontext
2026-09-08 08:05:47 -04:00
henrygd 997adc19bb fix: show idle GPU utilization in systems table (#2312) 2026-09-07 13:13:05 -04:00
Sven van GinkelandGitHub 08d813620c Add ZFS pool/dataset utilities (zpool, zfs) to the full and slim nvidia agent images (#2311) 2026-09-07 11:05:02 -04:00
henrygd 6cb302fcf6 fix(hub): bound realtime metric fetching and enforce access control by system
- synchronize realtime subscription state
- prevent overlapping fetches per system
- add realtime worker lifecycle tests
- reject subscriptions without system access
- test access revocation and shared-system permissions
2026-09-06 13:24:53 -04:00
59eed073c3 fix(install): add beszel to operator group on FreeBSD for SMART device access (#2286)
On FreeBSD the beszel agent needs to be a member of the operator group
to access SMART-capable devices via smartctl (e.g. /dev/nvme*,
/dev/xpt0*). The wheel group alone does not grant that access, so the
install script now adds the beszel user to operator when that group
exists, mirroring the Linux disk-group handling.

Fixes #1431

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-06 11:34:03 -04:00
111 changed files with 6285 additions and 2684 deletions
+4 -4
View File
@@ -48,7 +48,7 @@ type Agent struct {
keys []gossh.PublicKey // SSH public keys
smartManager *SmartManager // Manages SMART data
systemdManager *systemdManager // Manages systemd services
zfsManager *ZfsManager // Manages ZFS pool and dataset data
storagePoolManager *StoragePoolManager // Manages storage pool and dataset data
}
// NewAgent creates a new agent with the given data directory for persisting data.
@@ -122,12 +122,12 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
// initialize handler registry
agent.handlerRegistry = NewHandlerRegistry()
agent.zfsManager = newZfsManager()
agent.storagePoolManager = newStoragePoolManager()
// ZFS_INTERVAL env var to update ZFS detail data at this interval
// Retain ZFS_INTERVAL for the shared storage pool detail refresh interval.
if zfsIntervalEnv, exists := utils.GetEnv("ZFS_INTERVAL"); exists {
if duration, err := time.ParseDuration(zfsIntervalEnv); err == nil && duration > 0 {
agent.zfsManager.detailInterval = duration
agent.storagePoolManager.detailInterval = duration
agent.systemDetails.ZfsInterval = duration
slog.Info("ZFS_INTERVAL", "duration", duration)
} else {
+26
View File
@@ -0,0 +1,26 @@
// Package btrfs reads btrfs filesystem state from sysfs.
package btrfs
// Filesystem is a mounted btrfs filesystem read from /sys/fs/btrfs/<uuid>.
type Filesystem struct {
UUID string // stable filesystem UUID from sysfs
MountID string // kernel filesystem identity for matching monitored mounts
IODevice string // sole member block-device name, empty for multi-device/unknown pools
Name string // label, else first mountpoint, else UUID
Size uint64 // effective usable capacity, or raw member capacity when Raw
Raw bool // capacity and usage are physical bytes, unsuitable for disk alerts
Alloc uint64 // raw bytes allocated to data, metadata and system chunks
Health string // ONLINE, or DEGRADED when a device is missing
NRead uint64 // cumulative bytes read across member devices
NWrite uint64 // cumulative bytes written across member devices
Devices []Device
}
// Device is one member device (devinfo/<devid>) with its error counters.
type Device struct {
Name string // "devid N"; sysfs does not expose the block device path
State string // ONLINE or MISSING
ReadErrs uint64
WriteErrs uint64
CorruptionErrs uint64
}
+285
View File
@@ -0,0 +1,285 @@
//go:build linux
package btrfs
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"unsafe"
"github.com/henrygd/beszel/agent/utils"
"golang.org/x/sys/unix"
)
var (
sysfsPath = "/sys/fs/btrfs"
mountsPath = "/proc/self/mounts"
mountinfoPath = "/proc/self/mountinfo"
mountUUID = MountID
deviceSize = ioctlDeviceSize
filesystemUsage = statfsUsage
)
// Filesystems returns all mounted btrfs filesystems, or nil when there are none.
func Filesystems() ([]Filesystem, error) {
entries, err := os.ReadDir(sysfsPath)
if errors.Is(err, os.ErrNotExist) {
return nil, nil
}
if err != nil {
return nil, err
}
mounts := mountpointsByDevice()
var filesystems []Filesystem
for _, entry := range entries {
if !entry.IsDir() || entry.Name() == "features" {
continue
}
fs, err := readFilesystem(filepath.Join(sysfsPath, entry.Name()), mounts)
if err != nil {
return nil, fmt.Errorf("btrfs %s: %w", entry.Name(), err)
}
filesystems = append(filesystems, fs)
}
return filesystems, nil
}
func readFilesystem(dir string, mounts map[string]string) (Filesystem, error) {
fs := Filesystem{UUID: filepath.Base(dir), Name: utils.ReadStringFile(filepath.Join(dir, "label")), Health: "UNKNOWN"}
for _, kind := range []string{"data", "metadata", "system"} {
if value, ok := utils.ReadUintFile(filepath.Join(dir, "allocation", kind, "disk_used")); ok {
fs.Alloc += value
}
}
// devices/<name> links to the block device's sysfs directory.
devices, err := os.ReadDir(filepath.Join(dir, "devices"))
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fs, err
}
mountpoint := mounts["uuid:"+fs.UUID]
if fs.Name == "" {
fs.Name = mountpoint
}
var backingSize uint64
for _, dev := range devices {
if mountpoint == "" {
mountpoint = mounts[dev.Name()]
}
if fs.Name == "" {
fs.Name = mountpoint
}
devDir := filepath.Join(dir, "devices", dev.Name())
if size, ok := utils.ReadUintFile(filepath.Join(devDir, "size")); ok {
backingSize += size * 512
}
if stat := strings.Fields(utils.ReadStringFile(filepath.Join(devDir, "stat"))); len(stat) >= 7 {
fs.NRead += parseUint(stat[2]) * 512
fs.NWrite += parseUint(stat[6]) * 512
}
}
devids, err := os.ReadDir(filepath.Join(dir, "devinfo"))
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fs, err
}
capacityAvailable := len(devids) > 0
healthKnown := len(devids) > 0
for _, devid := range devids {
devDir := filepath.Join(dir, "devinfo", devid.Name())
// Replacement targets do not add filesystem capacity.
replaceTarget, _ := utils.ReadUintFile(filepath.Join(devDir, "replace_target"))
if replaceTarget != 1 {
devid, err := strconv.ParseUint(devid.Name(), 10, 64)
if err != nil {
return fs, err
}
size, err := deviceSize(mountpoint, devid)
if err != nil {
capacityAvailable = false
}
fs.Size += size
}
dev := Device{Name: "devid " + devid.Name(), State: "ONLINE"}
missing := utils.ReadStringFile(filepath.Join(devDir, "missing"))
if missing != "0" && missing != "1" {
healthKnown = false
dev.State = "UNKNOWN"
}
if missing == "1" {
dev.State = "MISSING"
fs.Health = "DEGRADED"
}
for line := range strings.Lines(utils.ReadStringFile(filepath.Join(devDir, "error_stats"))) {
if fields := strings.Fields(line); len(fields) == 2 {
switch fields[0] {
case "read_errs":
dev.ReadErrs = parseUint(fields[1])
case "write_errs":
dev.WriteErrs = parseUint(fields[1])
case "corruption_errs":
dev.CorruptionErrs = parseUint(fields[1])
}
}
}
fs.Devices = append(fs.Devices, dev)
}
// Use one capacity source for the whole filesystem: device IDs cannot be
// reliably matched to block-device names in sysfs. A partial ioctl result
// must not be added to the complete backing-device total.
if !capacityAvailable {
fs.Size = backingSize
}
if fs.Health != "DEGRADED" && healthKnown {
fs.Health = "ONLINE"
}
fs.MountID = mountUUID(mountpoint)
if len(devices) == 1 && len(devids) == 1 && fs.Health == "ONLINE" {
fs.IODevice = devices[0].Name()
}
fs.Raw = true
if used, available, err := filesystemUsage(mountpoint); err == nil {
// Effective capacity excludes reserved/unavailable space, so Size-Alloc
// is available to applications and the usage ratio matches df.
fs.Size, fs.Alloc, fs.Raw = used+available, used, false
}
if fs.Name == "" {
fs.Name = filepath.Base(dir)
}
return fs, nil
}
// mountpointsByDevice prefers UUID matches from mountinfo and retains source
// device names as a fallback for environments where FS_INFO is unavailable.
func mountpointsByDevice() map[string]string {
mounts := mountpointsByUUID(utils.ReadStringFile(mountinfoPath), mountUUID)
for line := range strings.Lines(utils.ReadStringFile(mountsPath)) {
fields := strings.Fields(line)
if len(fields) < 3 || fields[2] != "btrfs" {
continue
}
device := fields[0]
if resolved, err := filepath.EvalSymlinks(device); err == nil {
device = resolved
}
if _, seen := mounts[filepath.Base(device)]; !seen {
mounts[filepath.Base(device)] = unescapeMountPath(fields[1])
}
}
return mounts
}
func parseUint(s string) uint64 {
n, _ := strconv.ParseUint(s, 10, 64)
return n
}
// ioctlDeviceSize reads Btrfs's recorded device size, which can be smaller
// than the block device after a filesystem resize. BTRFS_IOC_DEV_INFO is
// _IOWR(0x94, 30, struct btrfs_ioctl_dev_info_args), a 4096-byte ABI structure.
func ioctlDeviceSize(mountpoint string, devid uint64) (uint64, error) {
if mountpoint == "" {
return 0, errors.New("no accessible mountpoint")
}
f, err := os.Open(mountpoint)
if err != nil {
return 0, err
}
defer f.Close()
args := struct {
Devid uint64
UUID [16]byte
BytesUsed uint64
TotalBytes uint64
Reserved [4096 - 40]byte
}{Devid: devid}
_, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), 0xd000941e, uintptr(unsafe.Pointer(&args)))
if errno != 0 {
return 0, errno
}
return args.TotalBytes, nil
}
// The filesystem magic is unsigned even when Statfs_t.Type is int32.
func isBtrfs(stat *unix.Statfs_t) bool {
return uint32(stat.Type) == unix.BTRFS_SUPER_MAGIC
}
func statfsUsage(path string) (used, available uint64, err error) {
if path == "" {
return 0, 0, errors.New("no accessible mountpoint")
}
var stat unix.Statfs_t
if err = unix.Statfs(path, &stat); err != nil {
return
}
if !isBtrfs(&stat) {
return 0, 0, errors.New("mountpoint is not Btrfs")
}
blockSize := uint64(stat.Bsize)
return (stat.Blocks - min(stat.Blocks, stat.Bfree)) * blockSize, min(stat.Blocks, stat.Bavail) * blockSize, nil
}
// MountID returns the filesystem UUID via BTRFS_IOC_FS_INFO. Unlike statfs
// f_fsid, this identity is shared by all subvolumes and bind mounts.
func MountID(path string) string {
if path == "" {
return ""
}
var stat unix.Statfs_t
if unix.Statfs(path, &stat) != nil || !isBtrfs(&stat) {
return ""
}
f, err := os.Open(path)
if err != nil {
return ""
}
defer f.Close()
args := struct {
MaxID uint64
NumDevices uint64
FSID [16]byte
Reserved [992]byte
}{}
// _IOR(0x94, 31, 1024). Reuse the platform's read-direction bits;
// MIPS/PowerPC use a different encoding than asm-generic.
request := uintptr(unix.FS_IOC_GETFLAGS&0xe0000000) | 0x0400941f
_, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), request, uintptr(unsafe.Pointer(&args)))
if errno != 0 {
return ""
}
id := args.FSID
return fmt.Sprintf("%x-%x-%x-%x-%x", id[:4], id[4:6], id[6:8], id[8:10], id[10:])
}
// Btrfs mountinfo device numbers can be virtual (0:N), so query the UUID
// through the mount instead of comparing those numbers with sysfs block devs.
// Retry another path when a bind mount is inaccessible. Once resolved, reuse
// the result for that mount device to avoid opening every Docker bind mount.
func mountpointsByUUID(mountinfo string, identify func(string) string) map[string]string {
mounts := make(map[string]string)
resolved := make(map[string]bool)
for line := range strings.Lines(mountinfo) {
before, after, ok := strings.Cut(line, " - ")
fields, fs := strings.Fields(before), strings.Fields(after)
if !ok || len(fields) < 6 || len(fs) < 3 || fs[0] != "btrfs" || resolved[fields[2]] {
continue
}
path := unescapeMountPath(fields[4])
uuid := identify(path)
if uuid == "" {
continue
}
resolved[fields[2]] = true
if mounts["uuid:"+uuid] == "" {
mounts["uuid:"+uuid] = path
}
}
return mounts
}
func unescapeMountPath(path string) string {
return strings.NewReplacer(`\040`, " ", `\011`, "\t", `\012`, "\n", `\134`, `\`).Replace(path)
}
+274
View File
@@ -0,0 +1,274 @@
//go:build testing && linux
package btrfs
import (
"os"
"path/filepath"
"strconv"
"testing"
"github.com/henrygd/beszel/agent/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func TestFilesystems(t *testing.T) {
root := t.TempDir()
oldSysfs, oldMounts := sysfsPath, mountsPath
sysfsPath, mountsPath = root, filepath.Join(root, "mounts")
t.Cleanup(func() { sysfsPath, mountsPath = oldSysfs, oldMounts })
fsDir := filepath.Join(root, "1b2c3d4e-0000-0000-0000-000000000000")
write := func(rel, content string) {
path := filepath.Join(fsDir, rel)
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
require.NoError(t, os.WriteFile(path, []byte(content), 0o644))
}
require.NoError(t, os.MkdirAll(filepath.Join(root, "features"), 0o755))
oldUsage := filesystemUsage
filesystemUsage = func(string) (uint64, uint64, error) { return 0, 0, os.ErrNotExist }
t.Cleanup(func() { filesystemUsage = oldUsage })
oldDeviceSize := deviceSize
t.Cleanup(func() { deviceSize = oldDeviceSize })
deviceSize = func(_ string, devid uint64) (uint64, error) {
value, _ := utils.ReadUintFile(filepath.Join(fsDir, "recorded-size", strconv.FormatUint(devid, 10)))
return value, nil
}
// Recorded member capacities differ from the unchanged backing devices.
write("recorded-size/1", "256000\n")
write("recorded-size/2", "128000\n")
write("label", "tank\n")
write("allocation/data/disk_used", "4096\n")
write("allocation/metadata/disk_used", "2048\n")
write("allocation/system/disk_used", "1024\n")
write("devices/sda/size", "1000\n")
write("devices/sda/stat", "10 0 200 0 20 0 400 0 0 0 0\n")
write("devices/sdb/size", "1000\n")
write("devices/sdb/stat", "10 0 100 0 20 0 100 0 0 0 0\n")
write("devinfo/1/missing", "0\n")
write("devinfo/1/error_stats", "write_errs 1\nread_errs 2\nflush_errs 0\ncorruption_errs 3\ngeneration_errs 0\n")
write("devinfo/2/missing", "1\n")
filesystems, err := Filesystems()
require.NoError(t, err)
require.Len(t, filesystems, 1)
assert.Equal(t, Filesystem{
UUID: "1b2c3d4e-0000-0000-0000-000000000000", Raw: true, Name: "tank", Size: 384000, Alloc: 7168, Health: "DEGRADED", NRead: 153600, NWrite: 256000,
Devices: []Device{
{Name: "devid 1", State: "ONLINE", ReadErrs: 2, WriteErrs: 1, CorruptionErrs: 3},
{Name: "devid 2", State: "MISSING"},
},
}, filesystems[0])
// Unlabeled filesystems fall back to the first mountpoint, then the UUID.
write("label", "\n")
require.NoError(t, os.WriteFile(mountsPath, []byte(
"/dev/sdz1 /other btrfs rw 0 0\n/dev/sdb /mnt/storage btrfs rw 0 0\n/dev/sdb /mnt/storage/sub btrfs rw,subvol=/sub 0 0\n",
), 0o644))
filesystems, err = Filesystems()
require.NoError(t, err)
assert.Equal(t, "/mnt/storage", filesystems[0].Name)
require.NoError(t, os.Remove(mountsPath))
filesystems, err = Filesystems()
require.NoError(t, err)
assert.Equal(t, "1b2c3d4e-0000-0000-0000-000000000000", filesystems[0].Name)
write("devinfo/3/replace_target", "1\n")
write("recorded-size/3", "512000\n")
filesystems, err = Filesystems()
require.NoError(t, err)
assert.Equal(t, uint64(384000), filesystems[0].Size, "replacement target must not inflate capacity")
deviceSize = func(string, uint64) (uint64, error) { return 0, os.ErrPermission }
filesystems, err = Filesystems()
require.NoError(t, err)
require.Len(t, filesystems, 1)
assert.Equal(t, uint64(1024000), filesystems[0].Size)
assert.Equal(t, "DEGRADED", filesystems[0].Health)
assert.Equal(t, uint64(153600), filesystems[0].NRead)
// A partial ioctl result must not be mixed with the backing-device total.
deviceSize = func(_ string, devid uint64) (uint64, error) {
if devid == 2 {
return 0, os.ErrPermission
}
return 256000, nil
}
filesystems, err = Filesystems()
require.NoError(t, err)
assert.Equal(t, uint64(1024000), filesystems[0].Size)
// With no mount visible (e.g. Docker), the real lookup falls back too.
deviceSize = ioctlDeviceSize
filesystems, err = Filesystems()
require.NoError(t, err)
require.Len(t, filesystems, 1)
assert.Equal(t, uint64(1024000), filesystems[0].Size)
filesystemUsage = func(string) (uint64, uint64, error) { return 100, 900, nil }
filesystems, err = Filesystems()
require.NoError(t, err)
assert.Equal(t, uint64(1000), filesystems[0].Size)
assert.Equal(t, uint64(100), filesystems[0].Alloc)
assert.False(t, filesystems[0].Raw)
}
func TestFilesystemsNoBtrfs(t *testing.T) {
oldPath := sysfsPath
sysfsPath = filepath.Join(t.TempDir(), "missing")
t.Cleanup(func() { sysfsPath = oldPath })
filesystems, err := Filesystems()
require.NoError(t, err)
assert.Nil(t, filesystems)
}
func TestIoctlDeviceSizeFailure(t *testing.T) {
_, err := ioctlDeviceSize("", 1)
require.Error(t, err)
_, err = ioctlDeviceSize(t.TempDir(), 1)
require.Error(t, err)
assert.ErrorIs(t, err, unix.ENOTTY)
}
func TestMountpointsDecodeEscapes(t *testing.T) {
oldMounts := mountsPath
mountsPath = filepath.Join(t.TempDir(), "mounts")
t.Cleanup(func() { mountsPath = oldMounts })
require.NoError(t, os.WriteFile(mountsPath, []byte("/dev/test-btrfs /mnt/my\\040data btrfs rw 0 0\n"), 0o644))
assert.Equal(t, "/mnt/my data", mountpointsByDevice()["test-btrfs"])
}
func TestFilesystemWithoutDevinfo(t *testing.T) {
root := t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(root, "devices", "sda"), 0755))
require.NoError(t, os.WriteFile(filepath.Join(root, "devices", "sda", "size"), []byte("1000"), 0644))
fs, err := readFilesystem(root, nil)
require.NoError(t, err)
assert.Equal(t, uint64(512000), fs.Size)
assert.True(t, fs.Raw)
assert.Equal(t, "UNKNOWN", fs.Health)
assert.Empty(t, fs.Devices)
require.NoError(t, os.MkdirAll(filepath.Join(root, "devinfo", "1"), 0755))
fs, err = readFilesystem(root, nil)
require.NoError(t, err)
assert.Equal(t, "UNKNOWN", fs.Health)
require.Len(t, fs.Devices, 1)
assert.Equal(t, "UNKNOWN", fs.Devices[0].State)
// Some older interfaces lack the devices directory too.
fs, err = readFilesystem(t.TempDir(), nil)
require.NoError(t, err)
assert.Equal(t, "UNKNOWN", fs.Health)
}
func TestLocalBtrfsUsage(t *testing.T) {
path := os.Getenv("BESZEL_TEST_BTRFS_MOUNT")
if path == "" {
t.Skip("set BESZEL_TEST_BTRFS_MOUNT for read-only live validation")
}
used, available, err := statfsUsage(path)
require.NoError(t, err)
filesystems, err := Filesystems()
require.NoError(t, err)
for _, fs := range filesystems {
if !fs.Raw && fs.Alloc == used && fs.Size == used+available {
t.Logf("pool=%s used=%d available=%d effective_capacity=%d", fs.Name, used, available, fs.Size)
return
}
}
t.Fatal("collector did not report the mounted filesystem's usable capacity")
}
func TestMountID(t *testing.T) {
assert.Empty(t, MountID(""))
assert.Empty(t, MountID(filepath.Join(t.TempDir(), "missing")))
path := os.Getenv("BESZEL_TEST_BTRFS_MOUNT")
if path == "" {
t.Skip("set BESZEL_TEST_BTRFS_MOUNT for live identity validation")
}
id := MountID(path)
require.NotEmpty(t, id)
assert.Equal(t, id, MountID(filepath.Join(path, ".")))
}
func TestMountinfoUUIDLookup(t *testing.T) {
info := `1 0 0:40 /@ /inaccessible ro shared:1 - btrfs /dev/mapper/unavailable rw
2 0 0:40 /@/docker/hosts /etc/hosts ro - btrfs /dev/mapper/unavailable rw
3 0 0:40 /@/docker/hostname /etc/hostname ro - btrfs /dev/mapper/unavailable rw
4 0 0:41 /subvol /extra-filesystems/my\040disk ro master:2 - btrfs /dev/missing rw
5 0 0:42 / /ext4 ro - ext4 /dev/mapper/unavailable rw
malformed
6 0 0:43 / /bad ro - btrfs
`
var calls []string
mounts := mountpointsByUUID(info, func(path string) string {
calls = append(calls, path)
switch path {
case "/etc/hosts":
return "root-uuid"
case "/extra-filesystems/my disk":
return "extra-uuid"
}
return ""
})
assert.Equal(t, map[string]string{"uuid:root-uuid": "/etc/hosts", "uuid:extra-uuid": "/extra-filesystems/my disk"}, mounts)
assert.Equal(t, []string{"/inaccessible", "/etc/hosts", "/extra-filesystems/my disk"}, calls)
}
func TestDockerFilesystemWithoutDeviceNodes(t *testing.T) {
root := t.TempDir()
oldSysfs, oldMounts, oldInfo, oldUUID, oldUsage := sysfsPath, mountsPath, mountinfoPath, mountUUID, filesystemUsage
t.Cleanup(func() {
sysfsPath, mountsPath, mountinfoPath, mountUUID, filesystemUsage = oldSysfs, oldMounts, oldInfo, oldUUID, oldUsage
})
sysfsPath = filepath.Join(root, "sysfs")
mountsPath = filepath.Join(root, "missing-mounts")
mountinfoPath = filepath.Join(root, "mountinfo")
uuid := "11111111-1111-4111-8111-111111111111"
dir := filepath.Join(sysfsPath, uuid)
for path, content := range map[string]string{"devices/dm-0/size": "1000", "devinfo/1/missing": "0"} {
target := filepath.Join(dir, path)
require.NoError(t, os.MkdirAll(filepath.Dir(target), 0755))
require.NoError(t, os.WriteFile(target, []byte(content), 0644))
}
require.NoError(t, os.WriteFile(mountinfoPath, []byte("2 1 0:40 /@/docker/hosts /etc/hosts ro - btrfs /dev/mapper/not-in-container rw\n"), 0644))
mountUUID = func(path string) string {
if path == "/etc/hosts" {
return uuid
}
return ""
}
filesystemUsage = func(path string) (uint64, uint64, error) { require.Equal(t, "/etc/hosts", path); return 100, 900, nil }
fs, err := Filesystems()
require.NoError(t, err)
require.Len(t, fs, 1)
assert.Equal(t, uuid, fs[0].MountID)
assert.Equal(t, "dm-0", fs[0].IODevice)
assert.False(t, fs[0].Raw)
assert.Equal(t, uint64(1000), fs[0].Size)
}
func TestLivePoolMountIdentity(t *testing.T) {
path := os.Getenv("BESZEL_TEST_BTRFS_MOUNT")
if path == "" {
t.Skip("set BESZEL_TEST_BTRFS_MOUNT for live validation")
}
id := MountID(path)
require.NotEmpty(t, id)
pools, err := Filesystems()
require.NoError(t, err)
for _, pool := range pools {
if pool.UUID != id {
continue
}
assert.Equal(t, id, pool.MountID)
assert.False(t, pool.Raw)
t.Logf("uuid=%s mount_identity=%s io_device=%s raw=%v", pool.UUID, pool.MountID, pool.IODevice, pool.Raw)
return
}
t.Fatal("mounted Btrfs filesystem was not discovered")
}
+11
View File
@@ -0,0 +1,11 @@
//go:build !linux
package btrfs
import "errors"
func Filesystems() ([]Filesystem, error) {
return nil, errors.ErrUnsupported
}
func MountID(string) string { return "" }
+6 -1
View File
@@ -30,6 +30,11 @@ const (
wsDeadline = 120 * time.Second
)
// errNoHubURL is returned when HUB_URL is unset. This is not a failure
// condition: an agent configured with only a public key runs in SSH-only mode,
// where the hub dials the agent and no outbound WebSocket client is expected.
var errNoHubURL = errors.New("HUB_URL environment variable not set")
type caCertFileError struct {
err error
}
@@ -63,7 +68,7 @@ type WebSocketClient struct {
func newWebSocketClient(agent *Agent) (client *WebSocketClient, err error) {
hubURLStr, exists := utils.GetEnv("HUB_URL")
if !exists {
return nil, errors.New("HUB_URL environment variable not set")
return nil, errNoHubURL
}
client = &WebSocketClient{}
+22
View File
@@ -32,6 +32,28 @@ import (
"golang.org/x/crypto/ssh"
)
// TestNewWebSocketClientNoHubURL verifies that an unset HUB_URL returns the
// errNoHubURL sentinel rather than an opaque error. Callers rely on this to
// distinguish SSH-only mode -- a supported configuration in which the hub dials
// the agent -- from an actual misconfiguration.
func TestNewWebSocketClientNoHubURL(t *testing.T) {
agent := createTestAgent(t)
// t.Setenv registers restoration of the original value; unset afterwards so
// GetEnv's LookupEnv reports the variable as absent rather than empty.
t.Setenv("BESZEL_AGENT_HUB_URL", "")
os.Unsetenv("BESZEL_AGENT_HUB_URL")
t.Setenv("HUB_URL", "")
os.Unsetenv("HUB_URL")
t.Setenv("BESZEL_AGENT_TOKEN", "test-token")
client, err := newWebSocketClient(agent)
require.Error(t, err)
assert.Nil(t, client)
assert.ErrorIs(t, err, errNoHubURL)
}
// TestNewWebSocketClient tests WebSocket client creation
func TestNewWebSocketClient(t *testing.T) {
agent := createTestAgent(t)
+9 -1
View File
@@ -91,7 +91,15 @@ func (c *ConnectionManager) Start(serverOptions ServerOptions) error {
if errors.As(err, &caCertErr) {
return err
}
slog.Warn("Error creating WebSocket client", "err", err)
disableSSH, _ := utils.GetEnv("DISABLE_SSH")
if errors.Is(err, errNoHubURL) && disableSSH != "true" {
// SSH-only mode: the hub dials the agent, so there is nothing to warn
// about. With SSH also disabled there is no connection method at all,
// so that case still warns.
slog.Debug("WebSocket client not configured", "err", err)
} else {
slog.Warn("Error creating WebSocket client", "err", err)
}
}
c.wsClient = wsClient
+14 -14
View File
@@ -18,11 +18,11 @@ import (
// fsRegistrationContext holds the shared lookup state needed to resolve a
// filesystem into the tracked fsStats key and metadata.
type fsRegistrationContext struct {
filesystem string // device part of optional FILESYSTEM env var
filesystemName string // optional custom name from FILESYSTEM=device__name
isWindows bool
efPath string // path to extra filesystems (default "/extra-filesystems")
diskIoCounters map[string]disk.IOCountersStat
filesystem string // device part of optional FILESYSTEM env var
filesystemName string // optional custom name from FILESYSTEM=device__name
isWindows bool
efPath string // path to extra filesystems (default "/extra-filesystems")
diskIoCounters map[string]disk.IOCountersStat
}
// diskDiscovery groups the transient state for a single initializeDiskInfo run so
@@ -325,11 +325,11 @@ func (a *Agent) initializeDiskInfo() {
}
slog.Debug("Disk I/O", "diskstats", diskIoCounters)
ctx := fsRegistrationContext{
filesystem: filesystem,
filesystemName: filesystemName,
isWindows: isWindows,
diskIoCounters: diskIoCounters,
efPath: "/extra-filesystems",
filesystem: filesystem,
filesystemName: filesystemName,
isWindows: isWindows,
diskIoCounters: diskIoCounters,
efPath: "/extra-filesystems",
}
// Get the appropriate root mount point for this system
@@ -540,8 +540,8 @@ func (a *Agent) initializeDiskIoStats(diskIoCounters map[string]disk.IOCountersS
// ZFS datasets have no /proc/diskstats entry, so they are excluded from
// I/O tracking instead of warning about a missing device (#1541).
var zfsMountpoints map[string]bool
if a.zfsManager != nil {
zfsMountpoints = a.zfsManager.ZfsMountpoints()
if a.storagePoolManager != nil {
zfsMountpoints = a.storagePoolManager.ZfsMountpoints()
}
for device, stats := range a.fsStats {
if zfsMountpoints[stats.Mountpoint] {
@@ -574,8 +574,8 @@ func (a *Agent) updateDiskUsage(systemStats *system.Stats) {
// ZFS dataset mountpoints use `zfs list` values because statfs(2) reports
// dataset-level usage that excludes child datasets (#1541).
var zfsUsage map[string]zfsDatasetUsage
if a.zfsManager != nil {
zfsUsage = a.zfsManager.DatasetUsage()
if a.storagePoolManager != nil {
zfsUsage = a.storagePoolManager.DatasetUsage()
}
// disk usage
+11 -10
View File
@@ -4,6 +4,7 @@ package agent
import (
"testing"
"time"
"github.com/henrygd/beszel/agent/zfs"
"github.com/henrygd/beszel/internal/entities/system"
@@ -16,8 +17,8 @@ import (
// is a ZFS dataset reports `zfs list` usage (which includes child datasets)
// instead of the dataset-scoped statfs values (#1541).
func TestUpdateDiskUsageZfsMountpoint(t *testing.T) {
zm := &ZfsManager{}
zm.datasetsFn = func() ([]zfs.Dataset, error) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{
{Name: "tank", Used: 12000000000000, Avail: 11999000000000, Mountpoint: "/tank"},
}, nil
@@ -26,7 +27,7 @@ func TestUpdateDiskUsageZfsMountpoint(t *testing.T) {
fsStats: map[string]*system.FsStats{
"tank": {Root: false, Mountpoint: "/tank"},
},
zfsManager: zm,
storagePoolManager: zm,
}
var stats system.Stats
@@ -43,8 +44,8 @@ func TestUpdateDiskUsageZfsMountpoint(t *testing.T) {
// TestUpdateDiskUsageZfsRootPopulatesSystemStats verifies the root disk values
// are derived from ZFS usage when the root mountpoint is a ZFS dataset.
func TestUpdateDiskUsageZfsRootPopulatesSystemStats(t *testing.T) {
zm := &ZfsManager{}
zm.datasetsFn = func() ([]zfs.Dataset, error) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{
{Name: "rpool/ROOT/pve-1", Used: 900000000000, Avail: 300000000000, Mountpoint: "/"},
}, nil
@@ -53,7 +54,7 @@ func TestUpdateDiskUsageZfsRootPopulatesSystemStats(t *testing.T) {
fsStats: map[string]*system.FsStats{
"rpool/ROOT/pve-1": {Root: true, Mountpoint: "/"},
},
zfsManager: zm,
storagePoolManager: zm,
}
var stats system.Stats
@@ -85,8 +86,8 @@ func TestUpdateDiskUsageWithoutZfsManager(t *testing.T) {
// TestInitializeDiskIoStatsSkipsZfsMountpoints verifies ZFS filesystems are
// excluded from diskstats I/O tracking instead of warning about a missing device.
func TestInitializeDiskIoStatsSkipsZfsMountpoints(t *testing.T) {
zm := &ZfsManager{}
zm.datasetsFn = func() ([]zfs.Dataset, error) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{{Name: "tank", Mountpoint: "/tank"}}, nil
}
agent := &Agent{
@@ -94,8 +95,8 @@ func TestInitializeDiskIoStatsSkipsZfsMountpoints(t *testing.T) {
"tank": {Root: false, Mountpoint: "/tank"},
"sda1": {Root: false, Mountpoint: "/mnt/data"},
},
zfsManager: zm,
diskPrev: make(map[uint16]map[string]prevDisk),
storagePoolManager: zm,
diskPrev: make(map[uint16]map[string]prevDisk),
}
agent.initializeDiskIoStats(map[string]disk.IOCountersStat{
+22 -8
View File
@@ -65,10 +65,14 @@ type dockerManager struct {
dockerVersionChecked bool // Whether a version probe has completed successfully
isWindows bool // Whether the Docker Engine API is running on Windows
buf *bytes.Buffer // Buffer to store and read response bodies
apiStats *container.ApiStats // Reusable API stats object
excludeContainers []string // Patterns to exclude containers by name
usingPodman bool // Whether the Docker Engine API is running on Podman
registryClient *http.Client // Client for registry requests; nil uses a client with a 10-second timeout
imageUpdatesMutex sync.RWMutex // Protects imageUpdates, its entries, and imageUpdatesRunning
imageUpdates map[string]*imageUpdateStatus // Shared update status keyed by normalized image reference
imageUpdatesRunning bool // Whether a background image-update batch is in progress
// Cache-time-aware tracking for CPU stats (similar to cpu.go)
// Maps cache time intervals to container-specific CPU usage tracking
lastCpuContainer map[uint16]map[string]uint64 // cacheTimeMs -> containerId -> last cpu container usage
@@ -161,6 +165,9 @@ func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats,
clear(dm.validIds)
}
// Only schedule auxiliary work here; metrics never wait for image discovery.
dm.refreshImageUpdates(dm.apiContainerList, time.Now())
var failedContainers []*container.ApiInfo
for _, ctr := range dm.apiContainerList {
@@ -506,6 +513,17 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
}
}
// Read and decode the response before locking shared stats to avoid blocking
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("container stats request failed: %s", resp.Status)
}
res := &container.ApiStats{}
if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
return err
}
updateAvailable := dm.cachedImageUpdate(ctr.Image)
dm.containerStatsMutex.Lock()
defer dm.containerStatsMutex.Unlock()
@@ -520,6 +538,9 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
stats.Status = statusText
stats.Health = health
stats.Image = ctr.Image
stats.UpdateAvailable = updateAvailable
if len(ctr.Ports) > 0 {
stats.Ports = convertContainerPortsToString(ctr)
}
@@ -532,12 +553,6 @@ func (dm *dockerManager) updateContainerStats(ctr *container.ApiInfo, cacheTimeM
stats.NetworkSent = 0
stats.NetworkRecv = 0
res := dm.apiStats
res.Networks = nil
if err := dm.decode(resp, res); err != nil {
return err
}
// Initialize CPU tracking for this cache time interval
dm.initializeCpuTracking(cacheTimeMs)
@@ -695,7 +710,6 @@ func newDockerManager(agent *Agent) *dockerManager {
containerStatsMap: make(map[string]*container.Stats),
sem: make(chan struct{}, 5),
apiContainerList: []*container.ApiInfo{},
apiStats: &container.ApiStats{},
excludeContainers: excludeContainers,
// Initialize cache-time-aware tracking structures
+105
View File
@@ -0,0 +1,105 @@
package agent
import (
"log/slog"
"sync"
"time"
"github.com/distribution/reference"
"github.com/henrygd/beszel/internal/entities/container"
)
const imageUpdateInterval = time.Hour
type imageUpdateStatus struct {
available bool
checkedAt time.Time
}
func normalizedImageReference(image string) string {
named, err := reference.ParseNormalizedNamed(image)
if err != nil {
return ""
}
// Digest-pinned references cannot move to a new version.
if _, pinned := named.(reference.Digested); pinned {
return ""
}
return reference.TagNameOnly(named).String()
}
// refreshImageUpdates starts at most one background batch. Neither its network
// work nor its completion is part of the container metrics wait group.
func (dm *dockerManager) refreshImageUpdates(containers []*container.ApiInfo, now time.Time) {
dm.imageUpdatesMutex.Lock()
defer dm.imageUpdatesMutex.Unlock()
if dm.imageUpdatesRunning {
return
}
if dm.imageUpdates == nil {
dm.imageUpdates = make(map[string]*imageUpdateStatus)
}
active := make(map[string]struct{}, len(containers))
pending := make(map[string]*imageUpdateStatus)
for _, ctr := range containers {
if len(ctr.Names) > 0 && dm.shouldExcludeContainer(ctr.Names[0][1:]) {
continue
}
key := normalizedImageReference(ctr.Image)
if key == "" {
continue
}
active[key] = struct{}{}
entry := dm.imageUpdates[key]
if entry == nil {
entry = &imageUpdateStatus{}
dm.imageUpdates[key] = entry
}
if entry.checkedAt.IsZero() || now.Sub(entry.checkedAt) >= imageUpdateInterval {
pending[key] = entry
}
}
for key := range dm.imageUpdates {
if _, ok := active[key]; !ok {
delete(dm.imageUpdates, key)
}
}
if len(pending) == 0 {
return
}
dm.imageUpdatesRunning = true
go func() {
// Limit auxiliary requests even on hosts running many different images.
sem := make(chan struct{}, 2)
var wg sync.WaitGroup
for key, entry := range pending {
sem <- struct{}{}
wg.Add(1)
go func() {
defer wg.Done()
defer func() { <-sem }()
available, err := dm.checkImageUpdate(key)
if err != nil {
available = false
slog.Debug("Image update check failed", "image", key, "err", err)
}
dm.imageUpdatesMutex.Lock()
entry.available = available
entry.checkedAt = time.Now()
dm.imageUpdatesMutex.Unlock()
}()
}
wg.Wait()
dm.imageUpdatesMutex.Lock()
dm.imageUpdatesRunning = false
dm.imageUpdatesMutex.Unlock()
}()
}
func (dm *dockerManager) cachedImageUpdate(image string) bool {
key := normalizedImageReference(image)
dm.imageUpdatesMutex.RLock()
defer dm.imageUpdatesMutex.RUnlock()
entry := dm.imageUpdates[key]
return entry != nil && entry.available
}
+225
View File
@@ -0,0 +1,225 @@
//go:build testing
package agent
import (
"encoding/json"
"fmt"
"github.com/fxamacker/cbor/v2"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/henrygd/beszel/internal/entities/container"
"github.com/stretchr/testify/require"
)
func waitForImageUpdates(t *testing.T, dm *dockerManager) {
t.Helper()
require.Eventually(t, func() bool {
dm.imageUpdatesMutex.RLock()
defer dm.imageUpdatesMutex.RUnlock()
return !dm.imageUpdatesRunning
}, time.Second*3, time.Millisecond)
}
func TestImageUpdateCacheAndStats(t *testing.T) {
local := "sha256:" + strings.Repeat("a", 64)
remote := "sha256:" + strings.Repeat("b", 64)
var inspections, lookups atomic.Int32
var fail atomic.Bool
var upToDate atomic.Bool
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.HasPrefix(r.URL.Path, "/images/"):
inspections.Add(1)
fmt.Fprintf(w, `{"RepoDigests":["docker.io/library/nginx@%s"]}`, local)
case r.URL.Path == "/containers/json":
fmt.Fprint(w, `[{"Id":"aaaaaaaaaaaa","Names":["/one"],"Image":"nginx","Status":"Up 2 hours"},{"Id":"bbbbbbbbbbbb","Names":["/two"],"Image":"docker.io/library/nginx:latest","Status":"Up 2 hours"}]`)
case strings.Contains(r.URL.Path, "/stats"):
fmt.Fprint(w, `{"memory_stats":{"usage":1048576},"cpu_stats":{},"networks":{}}`)
default:
http.NotFound(w, r)
}
}))
defer server.Close()
dm := newDockerManagerForVersionTest(server)
dm.dockerVersionChecked = true
dm.registryClient = &http.Client{Timeout: time.Second, Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
if fail.Load() {
return nil, fmt.Errorf("registry unavailable")
}
response := &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(`{"token":"test"}`))}
if r.Method == http.MethodHead {
lookups.Add(1)
digest := remote
if upToDate.Load() {
digest = local
}
response.Header.Set("Docker-Content-Digest", digest)
}
return response, nil
})}
stats, err := dm.getDockerStats(defaultCacheTimeMs)
require.NoError(t, err)
require.Len(t, stats, 2)
waitForImageUpdates(t, dm)
require.EqualValues(t, 1, lookups.Load())
require.EqualValues(t, 1, inspections.Load())
stats, err = dm.getDockerStats(defaultCacheTimeMs)
require.NoError(t, err)
for _, stat := range stats {
require.True(t, stat.UpdateAvailable)
if stat.Id == "aaaaaaaaaaaa" {
require.Equal(t, "nginx", stat.Image)
} else {
require.Equal(t, "docker.io/library/nginx:latest", stat.Image)
}
}
require.EqualValues(t, 1, lookups.Load())
expire := func() {
dm.imageUpdatesMutex.Lock()
dm.imageUpdates["docker.io/library/nginx:latest"].checkedAt = time.Now().Add(-imageUpdateInterval)
dm.imageUpdatesMutex.Unlock()
}
upToDate.Store(true)
expire()
_, err = dm.getDockerStats(defaultCacheTimeMs)
require.NoError(t, err)
waitForImageUpdates(t, dm)
require.EqualValues(t, 2, lookups.Load())
require.False(t, dm.cachedImageUpdate("nginx:latest"))
// An expired positive result is cleared on failure, and the failure itself
// is cached so realtime stats do not retry a broken registry every second.
dm.imageUpdatesMutex.Lock()
dm.imageUpdates["docker.io/library/nginx:latest"].available = true
dm.imageUpdatesMutex.Unlock()
fail.Store(true)
expire()
_, err = dm.getDockerStats(defaultCacheTimeMs)
require.NoError(t, err)
waitForImageUpdates(t, dm)
failedInspections := inspections.Load()
stats, err = dm.getDockerStats(defaultCacheTimeMs)
require.NoError(t, err)
require.Len(t, stats, 2)
require.Equal(t, failedInspections, inspections.Load())
for _, stat := range stats {
require.False(t, stat.UpdateAvailable)
require.Equal(t, 1.0, stat.Mem)
}
}
func TestImageDiscoveryDoesNotBlockStats(t *testing.T) {
started := make(chan struct{}, 1)
release := make(chan struct{})
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/images/") {
fmt.Fprintf(w, `{"RepoDigests":["example.com/app@sha256:%s"]}`, strings.Repeat("a", 64))
} else {
fmt.Fprint(w, `{"memory_stats":{"usage":1048576}}`)
}
}))
defer server.Close()
dm := newDockerManagerForVersionTest(server)
defer func() { close(release); waitForImageUpdates(t, dm) }()
dm.registryClient = &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
started <- struct{}{}
<-release
return nil, fmt.Errorf("timeout")
})}
ctr := &container.ApiInfo{IdShort: "aaaaaaaaaaaa", Image: "example.com/app", Names: []string{"/one"}}
dm.refreshImageUpdates([]*container.ApiInfo{ctr}, time.Now())
select {
case <-started:
case <-time.After(3 * time.Second):
t.Fatal("check did not start")
}
done := make(chan error, 1)
go func() { done <- dm.updateContainerStats(ctr, defaultCacheTimeMs) }()
select {
case err := <-done:
require.NoError(t, err)
case <-time.After(time.Second):
t.Fatal("registry blocked stats")
}
dm.imageUpdatesMutex.RLock()
require.True(t, dm.imageUpdatesRunning)
dm.imageUpdatesMutex.RUnlock()
}
func TestNormalizeImageUpdateReferences(t *testing.T) {
require.Equal(t, normalizedImageReference("nginx"), normalizedImageReference("docker.io/library/nginx:latest"))
require.Empty(t, normalizedImageReference("bad reference"))
require.Empty(t, normalizedImageReference("nginx@sha256:"+strings.Repeat("a", 64)))
}
// A stats request can return headers promptly and then stall while reading its
// body. The stats-map mutex must remain available during that read.
func TestStatsResponseBodyDoesNotHoldStatsLock(t *testing.T) {
started := make(chan struct{})
release := make(chan struct{})
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.(http.Flusher).Flush()
close(started)
<-release
fmt.Fprint(w, `{"memory_stats":{"usage":1048576}}`)
}))
defer server.Close()
dm := newDockerManagerForVersionTest(server)
done := make(chan error, 1)
go func() {
done <- dm.updateContainerStats(&container.ApiInfo{IdShort: "aaaaaaaaaaaa", Names: []string{"/one"}, Image: "nginx"}, defaultCacheTimeMs)
}()
<-started
locked := make(chan struct{})
go func() { dm.containerStatsMutex.Lock(); dm.containerStatsMutex.Unlock(); close(locked) }()
select {
case <-locked:
case <-time.After(time.Second):
close(release)
<-done
t.Fatal("Docker response body held the stats mutex")
}
close(release)
require.NoError(t, <-done)
}
func TestImageUpdateStatsEncoding(t *testing.T) {
original := container.Stats{Image: "nginx:latest", UpdateAvailable: true}
encoded, err := cbor.Marshal(original)
require.NoError(t, err)
var fields map[int]any
require.NoError(t, cbor.Unmarshal(encoded, &fields))
require.Equal(t, true, fields[11])
require.Equal(t, "nginx:latest", fields[8])
var decoded container.Stats
require.NoError(t, cbor.Unmarshal(encoded, &decoded))
require.True(t, decoded.UpdateAvailable)
require.Equal(t, original.Image, decoded.Image)
encoded, err = json.Marshal(original)
require.NoError(t, err)
require.Contains(t, string(encoded), `"u":true`)
}
func TestImageUpdateCacheExpiryBoundaryAndPruning(t *testing.T) {
now := time.Now()
key := normalizedImageReference("nginx")
dm := &dockerManager{imageUpdates: map[string]*imageUpdateStatus{
key: {available: true, checkedAt: now},
"unused.example/image:latest": {checkedAt: now},
}}
dm.refreshImageUpdates([]*container.ApiInfo{{Image: "nginx"}}, now.Add(imageUpdateInterval-time.Nanosecond))
require.False(t, dm.imageUpdatesRunning)
require.Len(t, dm.imageUpdates, 1)
require.True(t, dm.cachedImageUpdate("nginx:latest"))
dm.refreshImageUpdates(nil, now)
require.Empty(t, dm.imageUpdates)
}
+222
View File
@@ -0,0 +1,222 @@
package agent
import (
_ "crypto/sha256"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/distribution/reference"
"github.com/opencontainers/go-digest"
)
const imageRegistryTimeout = 10 * time.Second
const imageManifestAccept = "application/vnd.docker.distribution.manifest.list.v2+json, " +
"application/vnd.docker.distribution.manifest.v2+json, " +
"application/vnd.oci.image.manifest.v1+json, " +
"application/vnd.oci.image.index.v1+json"
// checkImageUpdate compares the digest recorded by Docker for image with the
// digest currently advertised by its registry. A digest-pinned reference is
// immutable and therefore never has an update available.
func (dm *dockerManager) checkImageUpdate(image string) (bool, error) {
named, err := reference.ParseNormalizedNamed(image)
if err != nil {
return false, fmt.Errorf("parse image reference %q: %w", image, err)
}
if _, pinned := named.(reference.Digested); pinned {
return false, nil
}
named = reference.TagNameOnly(named)
registry := reference.Domain(named)
repository := reference.Path(named)
tag := named.(reference.Tagged).Tag()
localDigest, err := dm.inspectImageDigest(image, registry, repository)
if err != nil {
return false, err
}
remoteDigest, err := dm.registryImageDigest(registry, repository, tag)
if err != nil {
return false, err
}
return remoteDigest != localDigest, nil
}
// inspectImageDigest reads Docker's image metadata without using dm.decode.
// The checker runs in the image-discovery goroutine, so it must not hold any
// of the container statistics locks while waiting on the Docker API.
func (dm *dockerManager) inspectImageDigest(image, registry, repository string) (string, error) {
if dm.client == nil {
return "", fmt.Errorf("inspect image %q: Docker client is unavailable", image)
}
endpoint := "http://localhost/images/" + url.PathEscape(image) + "/json"
resp, err := dm.client.Get(endpoint)
if err != nil {
return "", fmt.Errorf("inspect image %q: %w", image, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("inspect image %q failed: %s", image, responseStatus(resp))
}
var inspect struct {
RepoDigests []string `json:"RepoDigests"`
}
if err := json.NewDecoder(resp.Body).Decode(&inspect); err != nil {
return "", fmt.Errorf("decode image inspect %q: %w", image, err)
}
if len(inspect.RepoDigests) == 0 {
return "", fmt.Errorf("inspect image %q returned no repository digests", image)
}
localDigest, ok := matchingRepositoryDigest(inspect.RepoDigests, registry, repository)
if !ok {
return "", fmt.Errorf("inspect image %q returned no valid digest for %s/%s", image, registry, repository)
}
return localDigest, nil
}
// matchingRepositoryDigest returns a valid digest belonging to the requested
// repository. Docker can return multiple RepoDigests for one local image; an
// unrelated first entry must never be used for the comparison.
func matchingRepositoryDigest(repoDigests []string, registry, repository string) (string, bool) {
for _, repoDigest := range repoDigests {
repoDigest = strings.TrimSpace(repoDigest)
at := strings.LastIndexByte(repoDigest, '@')
if at <= 0 || at == len(repoDigest)-1 || strings.Contains(repoDigest[:at], "@") {
continue
}
repoRef, err := reference.ParseNormalizedNamed(repoDigest[:at])
if err != nil || reference.Path(repoRef) != repository || !sameRegistry(reference.Domain(repoRef), registry) {
continue
}
if _, hasTag := repoRef.(reference.Tagged); hasTag {
continue
}
d, err := digest.Parse(repoDigest[at+1:])
if err != nil {
continue
}
return d.String(), true
}
return "", false
}
func sameRegistry(left, right string) bool {
left = canonicalRegistry(left)
right = canonicalRegistry(right)
return left == right ||
(left == "ghcr.io" && right == "lscr.io") ||
(left == "lscr.io" && right == "ghcr.io")
}
func canonicalRegistry(registry string) string {
if registry == "index.docker.io" {
return "docker.io"
}
return registry
}
func (dm *dockerManager) registryImageDigest(registry, repository, tag string) (string, error) {
client := dm.registryClient
if client == nil {
client = &http.Client{Timeout: imageRegistryTimeout}
}
token, err := dm.registryToken(client, registry, repository)
if err != nil {
return "", err
}
host := registry
if registry == "docker.io" {
host = "registry-1.docker.io"
}
manifestURL := "https://" + host + "/v2/" + repository + "/manifests/" + url.PathEscape(tag)
req, err := http.NewRequest(http.MethodHead, manifestURL, nil)
if err != nil {
return "", fmt.Errorf("create manifest request: %w", err)
}
req.Header.Set("Accept", imageManifestAccept)
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("fetch manifest %s:%s: %w", registry, repository, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("manifest request for %s:%s failed: %s", repository, tag, responseStatus(resp))
}
remote := strings.TrimSpace(resp.Header.Get("Docker-Content-Digest"))
d, err := digest.Parse(remote)
if err != nil {
return "", fmt.Errorf("manifest request for %s:%s returned invalid digest: %w", repository, tag, err)
}
return d.String(), nil
}
func (dm *dockerManager) registryToken(client *http.Client, registry, repository string) (string, error) {
var authURL string
switch registry {
case "docker.io":
authURL = "https://auth.docker.io/token?service=registry.docker.io&scope=" + url.QueryEscape("repository:"+repository+":pull")
case "ghcr.io", "lscr.io":
// lscr.io is the LinuxServer alias for its GHCR-backed images.
authURL = "https://ghcr.io/token?service=ghcr.io&scope=" + url.QueryEscape("repository:"+repository+":pull")
default:
// Anonymous registries remain supported, as they were before the
// authenticated Docker Hub and GHCR paths were added.
return "", nil
}
req, err := http.NewRequest(http.MethodGet, authURL, nil)
if err != nil {
return "", fmt.Errorf("create registry auth request: %w", err)
}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("fetch registry auth token for %s: %w", repository, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("registry auth request for %s failed: %s", repository, responseStatus(resp))
}
var tokenResponse struct {
Token string `json:"token"`
AccessToken string `json:"access_token"`
}
if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil {
return "", fmt.Errorf("decode registry auth response for %s: %w", repository, err)
}
token := strings.TrimSpace(tokenResponse.Token)
if token == "" {
token = strings.TrimSpace(tokenResponse.AccessToken)
}
if token == "" {
return "", fmt.Errorf("registry auth response for %s contained no token", repository)
}
return token, nil
}
func responseStatus(resp *http.Response) string {
if resp.Status != "" {
return resp.Status
}
return http.StatusText(resp.StatusCode)
}
+204
View File
@@ -0,0 +1,204 @@
//go:build testing
package agent
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"github.com/stretchr/testify/require"
)
type registryTransportFunc func(*http.Request) (*http.Response, error)
func (fn registryTransportFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return fn(req)
}
func registryResponse(status int, body string) *http.Response {
return &http.Response{
StatusCode: status,
Status: fmt.Sprintf("%d %s", status, http.StatusText(status)),
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(body)),
}
}
func registryDigest(fill byte) string {
return "sha256:" + strings.Repeat(string(fill), 64)
}
func newRegistryChecker(t *testing.T, inspectBody string, transport http.RoundTripper) *dockerManager {
t.Helper()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/images/") {
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, inspectBody)
return
}
http.NotFound(w, r)
}))
t.Cleanup(server.Close)
return &dockerManager{
client: newDockerManagerForVersionTest(server).client,
registryClient: &http.Client{Transport: transport},
}
}
func TestCheckImageUpdateUsesInspectAndManifestDigests(t *testing.T) {
local := registryDigest('a')
remote := registryDigest('b')
var authCalls, manifestCalls atomic.Int32
dm := newRegistryChecker(t, fmt.Sprintf(`{"RepoDigests":["docker.io/library/alpine@%s"]}`, local), registryTransportFunc(func(req *http.Request) (*http.Response, error) {
switch {
case req.Method == http.MethodGet && req.URL.Host == "auth.docker.io":
authCalls.Add(1)
require.Equal(t, "/token", req.URL.Path)
return registryResponse(http.StatusOK, `{"token":"test-token"}`), nil
case req.Method == http.MethodHead && req.URL.Host == "registry-1.docker.io":
manifestCalls.Add(1)
require.Equal(t, "/v2/library/alpine/manifests/latest", req.URL.Path)
require.Equal(t, "Bearer test-token", req.Header.Get("Authorization"))
resp := registryResponse(http.StatusOK, "")
resp.Header.Set("Docker-Content-Digest", remote)
return resp, nil
default:
return registryResponse(http.StatusNotFound, ""), nil
}
}))
available, err := dm.checkImageUpdate("alpine")
require.NoError(t, err)
require.True(t, available)
require.EqualValues(t, 1, authCalls.Load())
require.EqualValues(t, 1, manifestCalls.Load())
}
func TestCheckImageUpdateReportsUnknownInspectState(t *testing.T) {
for _, test := range []struct {
name string
body string
}{
{name: "missing field", body: `{}`},
{name: "empty field", body: `{"RepoDigests":[]}`},
{name: "malformed reference", body: `{"RepoDigests":["not-a-repo-digest"]}`},
{name: "wrong repository", body: `{"RepoDigests":["docker.io/library/busybox@` + registryDigest('a') + `"]}`},
{name: "malformed digest", body: `{"RepoDigests":["docker.io/library/alpine@sha256:not-a-digest"]}`},
} {
t.Run(test.name, func(t *testing.T) {
var registryCalls atomic.Int32
dm := newRegistryChecker(t, test.body, registryTransportFunc(func(req *http.Request) (*http.Response, error) {
registryCalls.Add(1)
return registryResponse(http.StatusOK, `{"token":"unexpected"}`), nil
}))
available, err := dm.checkImageUpdate("alpine")
require.Error(t, err)
require.False(t, available)
require.EqualValues(t, 0, registryCalls.Load(), "invalid local state must not query a registry")
})
}
}
func TestCheckImageUpdateChecksInspectAuthAndManifestStatuses(t *testing.T) {
local := registryDigest('a')
validInspect := fmt.Sprintf(`{"RepoDigests":["docker.io/library/alpine@%s"]}`, local)
tests := []struct {
name string
inspectCode int
authCode int
manifestCode int
remote string
want string
}{
{name: "inspect status", inspectCode: http.StatusNotFound, want: "inspect image"},
{name: "auth status", inspectCode: http.StatusOK, authCode: http.StatusUnauthorized, want: "registry auth"},
{name: "manifest status", inspectCode: http.StatusOK, authCode: http.StatusOK, manifestCode: http.StatusNotFound, remote: local, want: "manifest request"},
{name: "missing digest", inspectCode: http.StatusOK, authCode: http.StatusOK, manifestCode: http.StatusOK, want: "invalid digest"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if test.inspectCode != http.StatusOK && strings.HasPrefix(r.URL.Path, "/images/") {
w.WriteHeader(test.inspectCode)
return
}
_, _ = io.WriteString(w, validInspect)
}))
t.Cleanup(server.Close)
calls := 0
dm := &dockerManager{client: newDockerManagerForVersionTest(server).client, registryClient: &http.Client{Transport: registryTransportFunc(func(req *http.Request) (*http.Response, error) {
calls++
if req.Method == http.MethodGet {
return registryResponse(test.authCode, `{"token":"test"}`), nil
}
response := registryResponse(test.manifestCode, "")
response.Header.Set("Docker-Content-Digest", test.remote)
return response, nil
})}}
_, err := dm.checkImageUpdate("alpine")
require.Error(t, err)
require.Contains(t, err.Error(), test.want)
if test.inspectCode != http.StatusOK {
require.Zero(t, calls)
}
})
}
}
func TestCheckImageUpdateSupportsAnonymousAndLSCRRegistries(t *testing.T) {
t.Run("anonymous registry", func(t *testing.T) {
local := registryDigest('a')
var calls atomic.Int32
dm := newRegistryChecker(t, fmt.Sprintf(`{"RepoDigests":["example.com/app@%s"]}`, local), registryTransportFunc(func(req *http.Request) (*http.Response, error) {
calls.Add(1)
require.Equal(t, http.MethodHead, req.Method)
require.Equal(t, "example.com", req.URL.Host)
resp := registryResponse(http.StatusOK, "")
resp.Header.Set("Docker-Content-Digest", local)
return resp, nil
}))
available, err := dm.checkImageUpdate("example.com/app")
require.NoError(t, err)
require.False(t, available)
require.EqualValues(t, 1, calls.Load())
})
t.Run("lscr ghcr alias", func(t *testing.T) {
local := registryDigest('a')
var authCalls, manifestCalls atomic.Int32
dm := newRegistryChecker(t, fmt.Sprintf(`{"RepoDigests":["ghcr.io/linuxserver/app@%s"]}`, local), registryTransportFunc(func(req *http.Request) (*http.Response, error) {
if req.Method == http.MethodGet {
authCalls.Add(1)
return registryResponse(http.StatusOK, `{"token":"test"}`), nil
}
manifestCalls.Add(1)
require.Equal(t, "lscr.io", req.URL.Host)
resp := registryResponse(http.StatusOK, "")
resp.Header.Set("Docker-Content-Digest", local)
return resp, nil
}))
available, err := dm.checkImageUpdate("lscr.io/linuxserver/app")
require.NoError(t, err)
require.False(t, available)
require.EqualValues(t, 1, authCalls.Load())
require.EqualValues(t, 1, manifestCalls.Load())
})
}
func TestCheckImageUpdateSkipsPinnedDigest(t *testing.T) {
image := "docker.io/library/alpine@" + registryDigest('a')
dm := &dockerManager{}
available, err := dm.checkImageUpdate(image)
require.NoError(t, err)
require.False(t, available)
}
-2
View File
@@ -1184,7 +1184,6 @@ func TestUpdateContainerStatsPodmanCpuCalculation(t *testing.T) {
}
})},
containerStatsMap: make(map[string]*container.Stats),
apiStats: &container.ApiStats{},
usingPodman: true,
lastCpuContainer: map[uint16]map[string]uint64{
defaultCacheTimeMs: {"0123456789ab": prevCpuUsage},
@@ -1676,7 +1675,6 @@ func TestUpdateContainerStatsUsesPodmanInspectHealthFallback(t *testing.T) {
}
})},
containerStatsMap: make(map[string]*container.Stats),
apiStats: &container.ApiStats{},
usingPodman: true,
lastCpuContainer: make(map[uint16]map[string]uint64),
lastCpuSystem: make(map[uint16]map[string]uint64),
+2 -2
View File
@@ -186,14 +186,14 @@ func (h *GetSmartDataHandler) Handle(hctx *HandlerContext) error {
type GetZfsDataHandler struct{}
func (h *GetZfsDataHandler) Handle(hctx *HandlerContext) error {
if hctx.Agent.zfsManager == nil {
if hctx.Agent.storagePoolManager == nil {
return hctx.SendResponse(nil, hctx.RequestID)
}
var req common.ZfsDataRequest
if err := cbor.Unmarshal(hctx.Request.Data, &req); err != nil {
return err
}
return hctx.SendResponse(hctx.Agent.zfsManager.GetDetail(req.Force), hctx.RequestID)
return hctx.SendResponse(hctx.Agent.storagePoolManager.GetDetail(req.Force), hctx.RequestID)
}
////////////////////////////////////////////////////////////////////////////
+5 -5
View File
@@ -34,19 +34,19 @@ func TestNewAgentResponseSmartData(t *testing.T) {
func TestGetZfsDataHandlerForceRefresh(t *testing.T) {
poolCalls := 0
zm := &ZfsManager{detailInterval: time.Hour}
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
poolCalls++
return []zfs.PoolStat{{Name: "tank", Alloc: uint64(poolCalls)}}, nil
}
zm.poolStatusesFn = func() ([]zfs.PoolStatus, error) { return nil, nil }
zm.datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
zm.backends[0].poolStatusesFn = func() ([]zfs.PoolStatus, error) { return nil, nil }
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
zm.GetDetail(false)
requestData, err := cbor.Marshal(common.ZfsDataRequest{Force: true})
assert.NoError(t, err)
ctx := &HandlerContext{
Agent: &Agent{zfsManager: zm},
Agent: &Agent{storagePoolManager: zm},
Request: &common.HubRequest[cbor.RawMessage]{
Action: common.GetZfsData,
Data: requestData,
+462
View File
@@ -0,0 +1,462 @@
package agent
import (
"errors"
"log/slog"
"os/exec"
"strings"
"sync"
"time"
"github.com/henrygd/beszel/agent/btrfs"
"github.com/henrygd/beszel/agent/zfs"
"github.com/henrygd/beszel/internal/entities/system"
zfsentity "github.com/henrygd/beszel/internal/entities/zfs"
)
// zfsDatasetUsage holds usage values for a ZFS dataset mountpoint.
type zfsDatasetUsage struct {
used uint64
avail uint64
}
// datasetUsageRefreshInterval controls how often `zfs list` is re-run for the
// mountpoint usage map. Dataset inventory changes rarely.
const datasetUsageRefreshInterval = 5 * time.Minute
// poolStatsRefreshInterval controls how often `zpool list` is re-run for pool
// capacity. Health and I/O are read from procfs on Linux, so the utility only
// needs to refresh slow-moving space accounting.
const poolStatsRefreshInterval = time.Minute
// btrfsFilesystems is the btrfs source; overridable in tests.
var btrfsFilesystems = btrfs.Filesystems
type poolKernelSample struct {
nread uint64
nwrite uint64
at time.Time
}
// StoragePoolManager combines independent backend inventories. Metrics and
// dataset usage require the agent lock; GetDetail is safe for concurrent calls.
type StoragePoolManager struct {
backends []*poolBackend
detailInterval time.Duration
}
// poolBackend owns one backend's collectors and caches. Collector functions
// are immutable after construction and may run concurrently for metrics/details.
type poolBackend struct {
name string
poolStatsFn func() ([]zfs.PoolStat, error) // capacity/health source
datasetsFn func() ([]zfs.Dataset, error) // dataset inventory source
kernelStatsFn func() ([]zfs.PoolKernelStat, error) // procfs pool state/I/O source
poolStatusesFn func() ([]zfs.PoolStatus, error) // scrub/vdev detail source
poolData []zfs.PoolStat // cached pool inventory (TTL below)
lastPoolStats time.Time
kernelSamples map[string]poolKernelSample
datasetUsage map[string]zfsDatasetUsage // mountpoint -> usage
lastUsageRefresh time.Time
// Detail data (pools, vdevs, scrub, datasets) is cached and refreshed on
// an interval. Accessed from handler goroutines, so it is mutex-protected.
detailMu sync.Mutex
detail *zfsentity.ZfsData
lastDetailRefresh time.Time
detailFailed bool
}
func newStoragePoolManager() *StoragePoolManager {
return &StoragePoolManager{
backends: []*poolBackend{newZfsBackend(), newBtrfsBackend()},
detailInterval: time.Hour,
}
}
func newZfsBackend() *poolBackend {
return &poolBackend{
name: "zfs",
poolStatsFn: optionalPoolSource(zfs.PoolStats),
datasetsFn: zfs.Datasets,
kernelStatsFn: optionalPoolSource(zfs.PoolKernelStats),
poolStatusesFn: optionalPoolSource(zfs.PoolStatuses),
}
}
func newBtrfsBackend() *poolBackend {
return &poolBackend{
name: "btrfs",
poolStatsFn: btrfsSource(btrfsPoolStats),
kernelStatsFn: btrfsSource(btrfsKernelStats),
poolStatusesFn: btrfsSource(btrfsPoolStatuses),
}
}
// datasets is optional: only backends that expose datasets provide a collector.
func (b *poolBackend) datasets() ([]zfs.Dataset, error) {
if b.datasetsFn == nil {
return nil, nil
}
return b.datasetsFn()
}
// A missing utility/interface is a successfully observed absent backend.
func optionalPoolSource[T any](source func() ([]T, error)) func() ([]T, error) {
return func() ([]T, error) {
items, err := source()
if errors.Is(err, zfs.ErrNoZfs) || errors.Is(err, exec.ErrNotFound) || errors.Is(err, errors.ErrUnsupported) {
return nil, nil
}
return items, err
}
}
func btrfsSource[T any](convert func(btrfs.Filesystem) T) func() ([]T, error) {
return func() ([]T, error) {
filesystems, err := optionalPoolSource(btrfsFilesystems)()
if err != nil {
return nil, err
}
items := make([]T, 0, len(filesystems))
for _, fs := range filesystems {
items = append(items, convert(fs))
}
return items, nil
}
}
// Update refreshes systemStats.ZfsPools with the latest pool data. I/O
// throughput and health come from inexpensive kernel kstats on Linux. Pool
// capacity and dataset usage come from separately cached utility calls. The
// pool map is empty when both backends are absent.
func (m *StoragePoolManager) Update(systemStats *system.Stats) {
// Rebuild the combined map so successful pool removals clear old samples.
systemStats.ZfsPools = nil
for _, backend := range m.backends {
backend.updateBackendStats(systemStats)
}
}
func (b *poolBackend) updateBackendStats(systemStats *system.Stats) {
pools := b.poolStats()
if len(pools) == 0 {
b.kernelSamples = nil
return
}
kernelStats, ioRates := b.kernelStats()
if systemStats.ZfsPools == nil {
systemStats.ZfsPools = make(map[string]*system.ZfsPool, len(pools))
}
for i := range pools {
pool := &pools[i]
// Full precision, matching the dataset values below; the frontend
// formats any magnitude.
stats := &system.ZfsPool{
DisplayName: pool.DisplayName,
Raw: pool.Raw,
Total: float64(pool.Size) / (1024 * 1024 * 1024),
Used: float64(pool.Alloc) / (1024 * 1024 * 1024),
Health: pool.Health,
}
if kernel, exists := kernelStats[pool.Name]; exists && kernel.Health != "" {
stats.Health = kernel.Health
}
if io, exists := ioRates[pool.Name]; exists {
stats.ReadBytes = io.NRead
stats.WriteBytes = io.NWrite
}
slog.Debug("Storage pool sample", "backend", b.name, "pool", pool.Name, "health", stats.Health, "used_gb", stats.Used, "read_bps", stats.ReadBytes, "write_bps", stats.WriteBytes)
systemStats.ZfsPools[pool.Name] = stats
}
}
// poolStats returns the cached pool inventory, calling its collector at most
// every poolStatsRefreshInterval. On failure the previous inventory is
// retained and the refresh is retried on the next cadence.
func (b *poolBackend) poolStats() []zfs.PoolStat {
if b.lastPoolStats.IsZero() || time.Since(b.lastPoolStats) >= poolStatsRefreshInterval {
pools, err := b.poolStatsFn()
if err != nil {
slog.Debug("Storage pool stats unavailable", "backend", b.name, "err", err)
} else {
b.poolData = pools
}
b.lastPoolStats = time.Now()
}
return b.poolData
}
// kernelStats reads cumulative pool counters and converts them to per-second
// rates. Counter decreases indicate a pool export/import and reset the
// baseline instead of producing an underflow spike.
func (b *poolBackend) kernelStats() (map[string]zfs.PoolKernelStat, map[string]zfs.PoolIoStats) {
if b.kernelStatsFn == nil {
return nil, nil
}
stats, err := b.kernelStatsFn()
if err != nil {
slog.Debug("Storage pool kernel stats unavailable", "backend", b.name, "err", err)
return nil, nil
}
now := time.Now()
byName := make(map[string]zfs.PoolKernelStat, len(stats))
rates := make(map[string]zfs.PoolIoStats, len(stats))
nextSamples := make(map[string]poolKernelSample, len(stats))
for _, stat := range stats {
byName[stat.Name] = stat
if previous, ok := b.kernelSamples[stat.Name]; ok && now.After(previous.at) &&
stat.NRead >= previous.nread && stat.NWrite >= previous.nwrite {
seconds := now.Sub(previous.at).Seconds()
rates[stat.Name] = zfs.PoolIoStats{
NRead: uint64(float64(stat.NRead-previous.nread) / seconds),
NWrite: uint64(float64(stat.NWrite-previous.nwrite) / seconds),
}
}
nextSamples[stat.Name] = poolKernelSample{nread: stat.NRead, nwrite: stat.NWrite, at: now}
}
b.kernelSamples = nextSamples
return byName, rates
}
// refreshDatasetUsage re-runs `zfs list` when the refresh window has elapsed
// and rebuilds the mountpoint-keyed usage map.
func (b *poolBackend) refreshDatasetUsage() {
if !b.lastUsageRefresh.IsZero() && time.Since(b.lastUsageRefresh) < datasetUsageRefreshInterval {
return
}
datasets, err := b.datasets()
if err != nil {
slog.Debug("Storage pool dataset usage unavailable", "backend", b.name, "err", err)
} else {
usage := make(map[string]zfsDatasetUsage, len(datasets))
for _, ds := range datasets {
if ds.Mountpoint != "" && ds.Mountpoint != "-" {
usage[ds.Mountpoint] = zfsDatasetUsage{used: ds.Used, avail: ds.Avail}
}
}
b.datasetUsage = usage
}
b.lastUsageRefresh = time.Now()
}
// DatasetUsage returns ZFS dataset usage keyed by mountpoint, refreshed at
// most every datasetUsageRefreshInterval. On failure the previous map is
// retained and a debug log is emitted.
func (m *StoragePoolManager) DatasetUsage() map[string]zfsDatasetUsage {
for _, backend := range m.backends {
if backend.name == "zfs" {
backend.refreshDatasetUsage()
return backend.datasetUsage
}
}
return nil
}
// GetDetail combines backend snapshots, identifying successful inventories so
// the hub can accept partial updates without deleting failed backend records.
func (m *StoragePoolManager) GetDetail(force bool) *zfsentity.ZfsData {
data := &zfsentity.ZfsData{Complete: true}
for _, backend := range m.backends {
snapshot := backend.getBackendDetail(force, m.detailInterval)
data.Pools = append(data.Pools, snapshot.Pools...)
if snapshot.Complete {
data.CompleteBackends = append(data.CompleteBackends, backend.name)
} else {
data.Complete = false
}
}
return data
}
func (b *poolBackend) getBackendDetail(force bool, interval time.Duration) *zfsentity.ZfsData {
b.detailMu.Lock()
defer b.detailMu.Unlock()
if force || b.detailFailed || b.detail == nil || time.Since(b.lastDetailRefresh) >= interval {
if data, err := b.collectDetail(b.detail); err != nil {
b.detailFailed = true
slog.Debug("Storage pool detail collection failed", "backend", b.name, "err", err)
if b.detail == nil {
return &zfsentity.ZfsData{}
}
return &zfsentity.ZfsData{Pools: b.detail.Pools}
} else {
b.detailFailed = false
b.detail = data
b.lastDetailRefresh = time.Now()
}
}
if b.detail == nil {
return &zfsentity.ZfsData{}
}
return b.detail
}
// collectDetail builds a ZfsData payload from the current system state.
func (b *poolBackend) collectDetail(previous *zfsentity.ZfsData) (*zfsentity.ZfsData, error) {
pools, err := b.poolStatsFn()
if err != nil {
return nil, err
}
if len(pools) == 0 {
return &zfsentity.ZfsData{Pools: []*zfsentity.PoolDetail{}, Complete: true}, nil
}
statuses, statusErr := b.poolStatusesFn()
if statusErr != nil {
slog.Debug("Storage pool status unavailable", "backend", b.name, "err", statusErr)
}
datasets, datasetsErr := b.datasets()
if datasetsErr != nil {
slog.Debug("Storage pool datasets unavailable", "backend", b.name, "err", datasetsErr)
}
statusByPool := make(map[string]zfs.PoolStatus, len(statuses))
for _, st := range statuses {
statusByPool[st.Name] = st
}
previousByPool := make(map[string]*zfsentity.PoolDetail)
if previous != nil {
for _, pool := range previous.Pools {
if pool != nil {
previousByPool[pool.Name] = pool
}
}
}
data := &zfsentity.ZfsData{Pools: make([]*zfsentity.PoolDetail, 0, len(pools)), Complete: true}
for i := range pools {
p := &pools[i]
detail := &zfsentity.PoolDetail{
DisplayName: p.DisplayName,
Raw: p.Raw,
Name: p.Name,
Health: p.Health,
Size: p.Size,
Alloc: p.Alloc,
Free: p.Free,
}
if st, ok := statusByPool[p.Name]; statusErr == nil && ok {
if st.Scrub.State != "" && st.Scrub.State != "NONE" {
detail.Scrub = &zfsentity.Scrub{
State: st.Scrub.State,
Progress: st.Scrub.Progress,
Errors: st.Scrub.Errors,
}
}
for _, v := range st.Vdevs {
detail.Vdevs = append(detail.Vdevs, &zfsentity.Vdev{
Name: v.Name,
State: v.State,
ReadErrs: v.ReadErrs,
WriteErrs: v.WriteErrs,
ChecksumErrs: v.ChecksumErrs,
})
}
} else {
if cached := previousByPool[p.Name]; cached != nil {
detail.Scrub = cached.Scrub
detail.Vdevs = cached.Vdevs
}
}
if datasetsErr == nil {
foundDataset := false
for _, ds := range datasets {
if poolOfDataset(ds.Name) == p.Name {
foundDataset = true
detail.Datasets = append(detail.Datasets, &zfsentity.Dataset{
Name: ds.Name,
Used: ds.Used,
Avail: ds.Avail,
Mountpoint: ds.Mountpoint,
})
}
}
if !foundDataset {
if cached := previousByPool[p.Name]; cached != nil {
detail.Datasets = cached.Datasets
}
}
} else if cached := previousByPool[p.Name]; cached != nil {
detail.Datasets = cached.Datasets
}
data.Pools = append(data.Pools, detail)
}
return data, nil
}
// poolOfDataset returns the pool name for a dataset name (everything before
// the first '/'). Datasets without a separator belong to a pool of the same
// name.
func poolOfDataset(name string) string {
if idx := strings.IndexByte(name, '/'); idx >= 0 {
return name[:idx]
}
return name
}
// ZfsMountpoints returns the set of mountpoints backed by ZFS datasets.
func (m *StoragePoolManager) ZfsMountpoints() map[string]bool {
usage := m.DatasetUsage()
mountpoints := make(map[string]bool, len(usage))
for mountpoint := range usage {
mountpoints[mountpoint] = true
}
return mountpoints
}
func btrfsPoolStats(fs btrfs.Filesystem) zfs.PoolStat {
return zfs.PoolStat{MountID: fs.MountID, IODevice: fs.IODevice, Raw: fs.Raw, DisplayName: fs.Name, Name: "b:" + fs.UUID, Size: fs.Size, Alloc: fs.Alloc, Free: fs.Size - min(fs.Alloc, fs.Size), Health: fs.Health}
}
func btrfsKernelStats(fs btrfs.Filesystem) zfs.PoolKernelStat {
return zfs.PoolKernelStat{Name: "b:" + fs.UUID, Health: fs.Health, NRead: fs.NRead, NWrite: fs.NWrite}
}
func btrfsPoolStatuses(fs btrfs.Filesystem) zfs.PoolStatus {
status := zfs.PoolStatus{Name: "b:" + fs.UUID, State: fs.Health, Scrub: zfs.ScrubStatus{State: "NONE"}}
for _, dev := range fs.Devices {
status.Vdevs = append(status.Vdevs, zfs.VdevStatus{
Name: dev.Name, State: dev.State,
ReadErrs: dev.ReadErrs, WriteErrs: dev.WriteErrs, ChecksumErrs: dev.CorruptionErrs,
})
}
return status
}
// markDuplicateCharts leaves pool telemetry and detail intact, but tells the
// hub which charts already have a filesystem equivalent. Only exact kernel
// filesystem and I/O-device matches qualify; labels are never used.
func (m *StoragePoolManager) markDuplicateCharts(stats *system.Stats, filesystems map[string]*system.FsStats, mountID func(string) string) {
identities := make(map[string]string, len(filesystems))
for device, fs := range filesystems {
if fs.DiskTotal > 0 {
identities[device] = mountID(fs.Mountpoint)
}
}
for _, backend := range m.backends {
for _, pool := range backend.poolData {
sample := stats.ZfsPools[pool.Name]
if sample == nil || pool.MountID == "" {
continue
}
for device, identity := range identities {
if identity != pool.MountID {
continue
}
// Raw physical usage is not equivalent to a filesystem usage chart.
sample.HideUsage = !pool.Raw
if pool.IODevice != "" && pool.IODevice == device {
sample.HideIO = true
}
}
}
}
}
+505
View File
@@ -0,0 +1,505 @@
//go:build testing
package agent
import (
"errors"
"fmt"
"os/exec"
"strings"
"sync"
"testing"
"time"
"github.com/henrygd/beszel/agent/btrfs"
"github.com/henrygd/beszel/agent/zfs"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestOptionalPoolSource(t *testing.T) {
failure := errors.New("timeout")
for _, err := range []error{nil, zfs.ErrNoZfs, fmt.Errorf("zpool: %w", exec.ErrNotFound), errors.ErrUnsupported, failure} {
_, got := optionalPoolSource(func() ([]zfs.PoolStat, error) { return nil, err })()
if err == failure {
assert.ErrorIs(t, got, failure)
} else {
assert.NoError(t, got)
}
}
}
type poolTestBackend struct {
name string
err error
alloc uint64
read uint64
empty bool
}
func (state *poolTestBackend) backend() *poolBackend {
name := "zfs"
if strings.HasPrefix(state.name, "b:") {
name = "btrfs"
}
return &poolBackend{
name: name,
poolStatsFn: func() ([]zfs.PoolStat, error) {
if state.empty {
return nil, state.err
}
return []zfs.PoolStat{{Name: state.name, Size: 100, Alloc: state.alloc}}, state.err
},
kernelStatsFn: func() ([]zfs.PoolKernelStat, error) {
return []zfs.PoolKernelStat{{Name: state.name, NRead: state.read}}, state.err
},
poolStatusesFn: func() ([]zfs.PoolStatus, error) { return nil, nil },
datasetsFn: func() ([]zfs.Dataset, error) { return nil, nil },
}
}
func TestIndependentPoolBackendCaches(t *testing.T) {
for _, failed := range []int{0, 1} {
t.Run([]string{"zfs", "btrfs"}[failed], func(t *testing.T) {
states := []*poolTestBackend{{name: "tank", alloc: 10}, {name: "b:uuid", alloc: 10}}
managers := []*poolBackend{states[0].backend(), states[1].backend()}
zm := &StoragePoolManager{backends: managers, detailInterval: time.Hour}
var stats system.Stats
zm.Update(&stats)
require.Len(t, stats.ZfsPools, 2)
require.True(t, zm.GetDetail(true).Complete)
baseline := poolKernelSample{at: time.Now().Add(-time.Second)}
for i, m := range managers {
m.lastPoolStats = time.Time{}
m.kernelSamples[states[i].name] = baseline
states[i].alloc = 20
states[i].read = 100
}
states[failed].err = errors.New("collection failed")
zm.Update(&stats)
healthy := 1 - failed
assert.Equal(t, uint64(10), managers[failed].poolData[0].Alloc)
assert.Equal(t, uint64(20), managers[healthy].poolData[0].Alloc)
assert.Equal(t, baseline, managers[failed].kernelSamples[states[failed].name])
assert.Zero(t, stats.ZfsPools[states[failed].name].ReadBytes)
assert.Positive(t, stats.ZfsPools[states[healthy].name].ReadBytes)
partial := zm.GetDetail(true)
assert.False(t, partial.Complete)
assert.False(t, partial.CanRefreshPool(states[failed].name))
assert.True(t, partial.CanRefreshPool(states[healthy].name))
assert.Equal(t, uint64(10), partial.Pools[failed].Alloc)
assert.Equal(t, uint64(20), partial.Pools[healthy].Alloc)
assert.False(t, zm.GetDetail(false).Complete, "a failed forced refresh must not become complete from cache")
// Successful empty inventory removes only the healthy backend's pool.
states[healthy].empty = true
managers[healthy].lastPoolStats = time.Time{}
zm.Update(&stats)
require.Len(t, stats.ZfsPools, 1)
assert.Contains(t, stats.ZfsPools, states[failed].name)
partial = zm.GetDetail(true)
require.Len(t, partial.Pools, 1)
assert.True(t, partial.CanRefreshPool(states[healthy].name))
// Recovery uses the retained I/O baseline, then normal removal works.
states[failed].err = nil
managers[failed].lastPoolStats = time.Time{}
zm.Update(&stats)
assert.Positive(t, stats.ZfsPools[states[failed].name].ReadBytes)
assert.True(t, zm.GetDetail(true).Complete)
states[failed].empty = true
managers[failed].lastPoolStats = time.Time{}
zm.Update(&stats)
assert.Empty(t, stats.ZfsPools)
assert.Empty(t, zm.GetDetail(true).Pools)
})
}
}
func TestIndependentBackendsWithoutCache(t *testing.T) {
z := &poolTestBackend{name: "tank", err: errors.New("ZFS failure")}
b := &poolTestBackend{name: "b:uuid", alloc: 20}
zm := &StoragePoolManager{backends: []*poolBackend{z.backend(), b.backend()}, detailInterval: time.Hour}
var stats system.Stats
zm.Update(&stats)
require.Len(t, stats.ZfsPools, 1)
assert.Contains(t, stats.ZfsPools, "b:uuid")
detail := zm.GetDetail(true)
require.Len(t, detail.Pools, 1)
assert.False(t, detail.Complete)
assert.Equal(t, []string{"btrfs"}, detail.CompleteBackends)
}
func TestConcurrentBackendDetailsAndMetrics(t *testing.T) {
zm := &StoragePoolManager{backends: []*poolBackend{(&poolTestBackend{name: "tank"}).backend(), (&poolTestBackend{name: "b:uuid"}).backend()}, detailInterval: time.Hour}
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(metrics bool) {
defer wg.Done()
for j := 0; j < 10; j++ {
if metrics {
zm.Update(&system.Stats{})
} else {
zm.GetDetail(true)
}
}
}(i == 0)
}
wg.Wait()
}
func TestStoragePoolBackendOrder(t *testing.T) {
z := (&poolTestBackend{name: "tank"}).backend()
b := (&poolTestBackend{name: "b:uuid"}).backend()
b.datasetsFn = nil // Btrfs does not expose datasets.
z.datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{{Name: "tank/data", Mountpoint: "/tank", Used: 10}}, nil
}
m := &StoragePoolManager{backends: []*poolBackend{b, z}, detailInterval: time.Hour}
var stats system.Stats
m.Update(&stats)
require.Len(t, stats.ZfsPools, 2)
detail := m.GetDetail(true)
require.True(t, detail.Complete)
assert.Equal(t, []string{"btrfs", "zfs"}, detail.CompleteBackends)
assert.Empty(t, detail.Pools[0].Datasets)
assert.Len(t, detail.Pools[1].Datasets, 1)
assert.Equal(t, uint64(10), m.DatasetUsage()["/tank"].used)
b.poolData[0].MountID = "uuid"
b.poolData[0].IODevice = "sda"
calls := 0
m.markDuplicateCharts(&stats, map[string]*system.FsStats{
"sda": {Mountpoint: "/", DiskTotal: 100},
}, func(string) string { calls++; return "uuid" })
assert.Equal(t, 1, calls, "resolve each filesystem once across all backends")
assert.True(t, stats.ZfsPools["b:uuid"].HideUsage)
assert.True(t, stats.ZfsPools["b:uuid"].HideIO)
}
func TestUpdatePopulatesZfsPools(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank", Size: 23999000000000, Alloc: 12000000000000, Free: 11999000000000, Health: "DEGRADED"}}, nil
}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{
{Name: "tank/apps", Used: 5000000000000, Avail: 11999000000000, Mountpoint: "/tank/apps"},
{Name: "tank/backup", Used: 6000000000000, Avail: 11999000000000, Mountpoint: "/tank/backup"},
// Small zvol (Proxmox VM EFI disk): must not round to zero.
{Name: "rpool/vm-100-disk-2", Used: 4194304, Avail: 0, Mountpoint: "-"},
}, nil
}
var kernelCalls int
zm.backends[0].kernelStatsFn = func() ([]zfs.PoolKernelStat, error) {
kernelCalls++
return []zfs.PoolKernelStat{{
Name: "tank", Health: "ONLINE",
NRead: uint64(kernelCalls-1) * 1250, NWrite: uint64(kernelCalls-1) * 5120,
}}, nil
}
var stats system.Stats
// The first kernel sample establishes the cumulative-counter baseline.
zm.Update(&stats)
zm.backends[0].kernelSamples["tank"] = poolKernelSample{at: time.Now().Add(-time.Second)}
zm.Update(&stats)
require.NotNil(t, stats.ZfsPools)
require.Contains(t, stats.ZfsPools, "tank")
assert.InDelta(t, 22350.8105, stats.ZfsPools["tank"].Total, 0.0001) // Size in GiB
assert.InDelta(t, 11175.8709, stats.ZfsPools["tank"].Used, 0.0001) // Alloc in GiB
assert.Equal(t, "ONLINE", stats.ZfsPools["tank"].Health)
assert.InDelta(t, 1250, stats.ZfsPools["tank"].ReadBytes, 5)
assert.InDelta(t, 5120, stats.ZfsPools["tank"].WriteBytes, 5)
}
// TestUpdateKernelStatsMissing verifies pools without a kernel sample report zero
// I/O instead of erroring.
func TestUpdateKernelStatsMissing(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank", Size: 1, Alloc: 1, Health: "ONLINE"}}, nil
}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
zm.backends[0].kernelStatsFn = func() ([]zfs.PoolKernelStat, error) {
return nil, zfs.ErrNoZfs
}
var stats system.Stats
zm.Update(&stats)
require.NotNil(t, stats.ZfsPools)
assert.Equal(t, uint64(0), stats.ZfsPools["tank"].ReadBytes)
assert.Equal(t, uint64(0), stats.ZfsPools["tank"].WriteBytes)
}
func TestUpdateKernelCounterReset(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank", Health: "ONLINE"}}, nil
}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
zm.backends[0].kernelSamples = map[string]poolKernelSample{
"tank": {nread: 100, nwrite: 200, at: time.Now().Add(-time.Second)},
}
zm.backends[0].kernelStatsFn = func() ([]zfs.PoolKernelStat, error) {
return []zfs.PoolKernelStat{{Name: "tank", Health: "ONLINE", NRead: 10, NWrite: 20}}, nil
}
var stats system.Stats
zm.Update(&stats)
assert.Equal(t, uint64(0), stats.ZfsPools["tank"].ReadBytes)
assert.Equal(t, uint64(0), stats.ZfsPools["tank"].WriteBytes)
}
func TestUpdateNoZfs(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
calls := 0
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
calls++
return nil, zfs.ErrNoZfs
}
var stats system.Stats
zm.Update(&stats)
zm.Update(&stats)
assert.Nil(t, stats.ZfsPools)
assert.Equal(t, 1, calls, "failed pool discovery should be cached until the next refresh interval")
}
func TestUpdateEmptyPools(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
calls := 0
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
calls++
return nil, nil
}
var stats system.Stats
zm.Update(&stats)
zm.Update(&stats)
assert.Nil(t, stats.ZfsPools)
assert.Equal(t, 1, calls, "an empty pool inventory should be cached until the next refresh interval")
}
func TestDatasetUsage(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
calls := 0
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
calls++
return []zfs.Dataset{
{Name: "tank", Used: 12000000000000, Avail: 11999000000000, Mountpoint: "/tank"},
{Name: "tank/apps", Used: 1000000000000, Avail: 11999000000000, Mountpoint: "/tank/apps"},
{Name: "rpool", Used: 900000000000, Avail: 300000000000, Mountpoint: "-"}, // zvol/unmounted: excluded
}, nil
}
usage := zm.DatasetUsage()
require.Len(t, usage, 2)
assert.Equal(t, zfsDatasetUsage{used: 12000000000000, avail: 11999000000000}, usage["/tank"])
assert.Equal(t, zfsDatasetUsage{used: 1000000000000, avail: 11999000000000}, usage["/tank/apps"])
assert.Equal(t, 1, calls)
// Second call within the refresh window must not re-run the collector.
zm.DatasetUsage()
assert.Equal(t, 1, calls)
}
func TestDatasetUsageRefreshOnErrorKeepsPrevious(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{{Name: "tank", Used: 1, Avail: 1, Mountpoint: "/tank"}}, nil
}
assert.Len(t, zm.DatasetUsage(), 1)
// Force refresh window expiry, then a failing collector.
zm.backends[0].lastUsageRefresh = time.Now().Add(-10 * time.Minute)
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
return nil, zfs.ErrNoZfs
}
usage := zm.DatasetUsage()
assert.Len(t, usage, 1, "previous usage should be retained on error")
}
func TestGetDetailForceRefresh(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
poolCalls := 0
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
poolCalls++
return []zfs.PoolStat{{Name: "tank", Alloc: uint64(poolCalls)}}, nil
}
zm.backends[0].poolStatusesFn = func() ([]zfs.PoolStatus, error) { return nil, nil }
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
first := zm.GetDetail(false)
assert.True(t, first.Complete)
require.Len(t, first.Pools, 1)
assert.Equal(t, uint64(1), first.Pools[0].Alloc)
cached := zm.GetDetail(false)
require.Len(t, cached.Pools, 1)
assert.Equal(t, uint64(1), cached.Pools[0].Alloc)
assert.Equal(t, 1, poolCalls)
refreshed := zm.GetDetail(true)
assert.True(t, refreshed.Complete)
require.Len(t, refreshed.Pools, 1)
assert.Equal(t, uint64(2), refreshed.Pools[0].Alloc)
assert.Equal(t, 2, poolCalls)
}
func TestGetDetailSuccessfulEmptyInventoryClearsCache(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank"}}, nil
}
zm.backends[0].poolStatusesFn = func() ([]zfs.PoolStatus, error) { return nil, nil }
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
require.Len(t, zm.GetDetail(false).Pools, 1)
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) { return nil, nil }
empty := zm.GetDetail(true)
assert.True(t, empty.Complete)
assert.Empty(t, empty.Pools)
}
func TestGetDetailFailureReturnsIncompleteCachedInventory(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank"}}, nil
}
zm.backends[0].poolStatusesFn = func() ([]zfs.PoolStatus, error) {
return []zfs.PoolStatus{{Name: "tank", Vdevs: []zfs.VdevStatus{{Name: "mirror-0"}}}}, nil
}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{{Name: "tank/data"}}, nil
}
first := zm.GetDetail(false)
require.True(t, first.Complete)
require.Len(t, first.Pools[0].Vdevs, 1)
require.Len(t, first.Pools[0].Datasets, 1)
zm.backends[0].poolStatusesFn = func() ([]zfs.PoolStatus, error) { return nil, zfs.ErrNoZfs }
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) { return nil, zfs.ErrNoZfs }
partial := zm.GetDetail(true)
require.True(t, partial.Complete)
require.Len(t, partial.Pools[0].Vdevs, 1)
require.Len(t, partial.Pools[0].Datasets, 1)
zm.backends[0].poolStatsFn = func() ([]zfs.PoolStat, error) { return nil, zfs.ErrNoZfs }
lastSuccessfulRefresh := zm.backends[0].lastDetailRefresh
failed := zm.GetDetail(true)
assert.False(t, failed.Complete)
require.Len(t, failed.Pools, 1)
assert.Equal(t, "tank", failed.Pools[0].Name)
assert.Equal(t, lastSuccessfulRefresh, zm.backends[0].lastDetailRefresh)
}
func TestZfsMountpoints(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs"}}}
zm.backends[0].datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{
{Name: "tank", Mountpoint: "/tank"},
{Name: "rpool/ROOT/pve-1", Mountpoint: "/"},
}, nil
}
mountpoints := zm.ZfsMountpoints()
assert.Len(t, mountpoints, 2)
assert.True(t, mountpoints["/tank"])
assert.True(t, mountpoints["/"])
}
func TestBtrfsRawCapacityPropagates(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs", poolStatsFn: func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{btrfsPoolStats(btrfs.Filesystem{UUID: "raw", Name: "raw", Size: 200, Alloc: 100, Raw: true})}, nil
},
poolStatusesFn: func() ([]zfs.PoolStatus, error) { return nil, nil },
datasetsFn: func() ([]zfs.Dataset, error) { return nil, nil }}}}
var stats system.Stats
zm.Update(&stats)
require.True(t, stats.ZfsPools["b:raw"].Raw)
detail := zm.GetDetail(true)
require.True(t, detail.Complete)
require.Len(t, detail.Pools, 1)
assert.True(t, detail.Pools[0].Raw)
}
func TestMarkDuplicatePoolCharts(t *testing.T) {
for _, tc := range []struct {
name, poolID, device string
raw bool
diskTotal float64
wantUsage, wantIO bool
}{
{"single device root", "fs1", "dm-0", false, 100, true, true},
{"multi device", "fs1", "", false, 100, true, false},
{"different IO device", "fs1", "nvme0n1", false, 100, true, false},
{"different filesystem", "fs2", "dm-0", false, 100, false, false},
{"unknown identity", "", "dm-0", false, 100, false, false},
{"raw usage", "fs1", "dm-0", true, 100, false, true},
{"failed disk collection", "fs1", "dm-0", false, 0, false, false},
} {
t.Run(tc.name, func(t *testing.T) {
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs", poolData: []zfs.PoolStat{{Name: "arbitrary label", MountID: tc.poolID, IODevice: tc.device, Raw: tc.raw}}}}}
stats := &system.Stats{ZfsPools: map[string]*system.ZfsPool{"arbitrary label": {}}}
fs := map[string]*system.FsStats{"dm-0": {Root: true, Mountpoint: "/", DiskTotal: tc.diskTotal}}
zm.markDuplicateCharts(stats, fs, func(string) string { return "fs1" })
assert.Equal(t, tc.wantUsage, stats.ZfsPools["arbitrary label"].HideUsage)
assert.Equal(t, tc.wantIO, stats.ZfsPools["arbitrary label"].HideIO)
// Bind mounts and custom extra-filesystem names have the same identity.
fs["dm-0"].Root = false
fs["dm-0"].Mountpoint = "/extra-filesystems/storage"
fs["dm-0"].Name = "custom name"
stats.ZfsPools["arbitrary label"] = &system.ZfsPool{}
zm.markDuplicateCharts(stats, fs, func(string) string { return "fs1" })
assert.Equal(t, tc.wantUsage, stats.ZfsPools["arbitrary label"].HideUsage)
assert.Equal(t, tc.wantIO, stats.ZfsPools["arbitrary label"].HideIO)
})
}
}
func TestBtrfsPoolIdentities(t *testing.T) {
old := btrfsFilesystems
t.Cleanup(func() { btrfsFilesystems = old })
label := "tank"
btrfsFilesystems = func() ([]btrfs.Filesystem, error) {
return []btrfs.Filesystem{
{UUID: "11111111-1111-4111-8111-111111111111", Name: label, Size: 100, Health: "ONLINE", NRead: 100, Devices: []btrfs.Device{{Name: "first"}}},
{UUID: "22222222-2222-4222-8222-222222222222", Name: "tank", Size: 200, Health: "DEGRADED", NRead: 200, Devices: []btrfs.Device{{Name: "second"}}},
}, nil
}
zm := &StoragePoolManager{detailInterval: time.Hour, backends: []*poolBackend{{name: "zfs", poolStatsFn: func() ([]zfs.PoolStat, error) { return []zfs.PoolStat{{Name: "tank", Size: 300}}, nil },
kernelStatsFn: func() ([]zfs.PoolKernelStat, error) { return []zfs.PoolKernelStat{{Name: "tank", NRead: 300}}, nil },
poolStatusesFn: func() ([]zfs.PoolStatus, error) {
return []zfs.PoolStatus{{Name: "tank", Vdevs: []zfs.VdevStatus{{Name: "zfs-device"}}}}, nil
},
datasetsFn: func() ([]zfs.Dataset, error) { return []zfs.Dataset{{Name: "tank/data"}}, nil }}, newBtrfsBackend()}}
first := "b:11111111-1111-4111-8111-111111111111"
second := "b:22222222-2222-4222-8222-222222222222"
var stats system.Stats
zm.Update(&stats)
require.Len(t, stats.ZfsPools, 3)
assert.Contains(t, stats.ZfsPools, "tank")
assert.Equal(t, "ONLINE", stats.ZfsPools[first].Health)
assert.Equal(t, "DEGRADED", stats.ZfsPools[second].Health)
assert.Equal(t, uint64(100), zm.backends[1].kernelSamples[first].nread)
assert.Equal(t, uint64(200), zm.backends[1].kernelSamples[second].nread)
detail := zm.GetDetail(true)
require.Len(t, detail.Pools, 3)
assert.Equal(t, "zfs-device", detail.Pools[0].Vdevs[0].Name)
assert.Len(t, detail.Pools[0].Datasets, 1)
assert.Equal(t, "first", detail.Pools[1].Vdevs[0].Name)
assert.Empty(t, detail.Pools[1].Datasets)
assert.Equal(t, "second", detail.Pools[2].Vdevs[0].Name)
label = "renamed"
zm.backends[0].lastPoolStats = time.Time{}
zm.backends[1].lastPoolStats = time.Time{}
zm.Update(&stats)
require.Len(t, stats.ZfsPools, 3)
assert.Equal(t, "renamed", stats.ZfsPools[first].DisplayName)
assert.Equal(t, first, zm.GetDetail(true).Pools[1].Name)
assert.Equal(t, "renamed", zm.GetDetail(true).Pools[1].DisplayName)
}
+4 -2
View File
@@ -12,6 +12,7 @@ import (
"github.com/henrygd/beszel"
"github.com/henrygd/beszel/agent/battery"
"github.com/henrygd/beszel/agent/btrfs"
"github.com/henrygd/beszel/agent/utils"
"github.com/henrygd/beszel/agent/zfs"
"github.com/henrygd/beszel/internal/entities/container"
@@ -219,8 +220,9 @@ func (a *Agent) getSystemStats(cacheTimeMs uint16) system.Stats {
// disk i/o (cache-aware per interval)
a.updateDiskIo(cacheTimeMs, &systemStats)
// zfs pool stats
a.zfsManager.Update(&systemStats)
// storage pool stats
a.storagePoolManager.Update(&systemStats)
a.storagePoolManager.markDuplicateCharts(&systemStats, a.fsStats, btrfs.MountID)
// network stats (per cache interval)
a.updateNetworkStats(cacheTimeMs, &systemStats)
+9 -5
View File
@@ -33,11 +33,15 @@ var ErrNoZfs = errors.New("zfs utilities unavailable")
// PoolStat is a snapshot of a ZFS pool's capacity and health.
type PoolStat struct {
Name string
Size uint64 // total capacity in bytes
Alloc uint64 // allocated bytes
Free uint64 // free bytes
Health string // ONLINE, DEGRADED, FAULTED, ...
DisplayName string // optional friendly name; Name remains the stable key
MountID string // Btrfs filesystem identity, empty for other backends
IODevice string // sole Btrfs member device, if known
Raw bool // physical accounting rather than usable filesystem space
Name string
Size uint64 // total capacity in bytes
Alloc uint64 // allocated bytes
Free uint64 // free bytes
Health string // ONLINE, DEGRADED, FAULTED, ...
}
// PoolKernelStat is the inexpensive pool telemetry exposed by the ZFS kernel.
-320
View File
@@ -1,320 +0,0 @@
package agent
import (
"log/slog"
"strings"
"sync"
"time"
"github.com/henrygd/beszel/agent/zfs"
"github.com/henrygd/beszel/internal/entities/system"
zfsentity "github.com/henrygd/beszel/internal/entities/zfs"
)
// zfsDatasetUsage holds usage values for a ZFS dataset mountpoint.
type zfsDatasetUsage struct {
used uint64
avail uint64
}
// datasetUsageRefreshInterval controls how often `zfs list` is re-run for the
// mountpoint usage map. Dataset inventory changes rarely.
const datasetUsageRefreshInterval = 5 * time.Minute
// poolStatsRefreshInterval controls how often `zpool list` is re-run for pool
// capacity. Health and I/O are read from procfs on Linux, so the utility only
// needs to refresh slow-moving space accounting.
const poolStatsRefreshInterval = time.Minute
type poolKernelSample struct {
nread uint64
nwrite uint64
at time.Time
}
// ZfsManager collects ZFS pool and dataset statistics. Collection functions
// are fields so unit tests can substitute them (same pattern as
// diskDiscovery.usageFn). It is safe for concurrent use by a single goroutine
// only; callers must hold the agent lock like updateDiskUsage does.
type ZfsManager struct {
poolStatsFn func() ([]zfs.PoolStat, error) // capacity/health source
datasetsFn func() ([]zfs.Dataset, error) // dataset inventory source
kernelStatsFn func() ([]zfs.PoolKernelStat, error) // procfs pool state/I/O source
poolStatusesFn func() ([]zfs.PoolStatus, error) // scrub/vdev detail source
poolData []zfs.PoolStat // cached pool inventory (TTL below)
lastPoolStats time.Time
kernelSamples map[string]poolKernelSample
datasetUsage map[string]zfsDatasetUsage // mountpoint -> usage
lastUsageRefresh time.Time
// Detail data (pools, vdevs, scrub, datasets) is cached and refreshed on
// an interval. Accessed from handler goroutines, so it is mutex-protected.
detailMu sync.Mutex
detail *zfsentity.ZfsData
lastDetailRefresh time.Time
detailInterval time.Duration
}
// newZfsManager creates a ZfsManager wired to the system's ZFS utilities.
func newZfsManager() *ZfsManager {
return &ZfsManager{
poolStatsFn: zfs.PoolStats,
datasetsFn: zfs.Datasets,
kernelStatsFn: zfs.PoolKernelStats,
poolStatusesFn: zfs.PoolStatuses,
detailInterval: time.Hour,
}
}
// Update refreshes systemStats.ZfsPools with the latest pool data. I/O
// throughput and health come from inexpensive kernel kstats on Linux. Pool
// capacity and dataset usage come from separately cached utility calls. It is
// a no-op when ZFS is absent.
func (zm *ZfsManager) Update(systemStats *system.Stats) {
pools := zm.poolStats()
if len(pools) == 0 {
return
}
kernelStats, ioRates := zm.kernelStats()
if systemStats.ZfsPools == nil {
systemStats.ZfsPools = make(map[string]*system.ZfsPool, len(pools))
}
for i := range pools {
pool := &pools[i]
// Full precision, matching the dataset values below; the frontend
// formats any magnitude.
stats := &system.ZfsPool{
Total: float64(pool.Size) / (1024 * 1024 * 1024),
Used: float64(pool.Alloc) / (1024 * 1024 * 1024),
Health: pool.Health,
}
if kernel, exists := kernelStats[pool.Name]; exists && kernel.Health != "" {
stats.Health = kernel.Health
}
if io, exists := ioRates[pool.Name]; exists {
stats.ReadBytes = io.NRead
stats.WriteBytes = io.NWrite
}
slog.Debug("ZFS pool sample", "pool", pool.Name, "health", stats.Health, "used_gb", stats.Used, "read_bps", stats.ReadBytes, "write_bps", stats.WriteBytes)
systemStats.ZfsPools[pool.Name] = stats
}
}
// poolStats returns the cached pool inventory, re-running `zpool list` at most
// every poolStatsRefreshInterval. On failure the previous inventory is
// retained and the refresh is retried on the next cadence.
func (zm *ZfsManager) poolStats() []zfs.PoolStat {
if zm.lastPoolStats.IsZero() || time.Since(zm.lastPoolStats) >= poolStatsRefreshInterval {
pools, err := zm.poolStatsFn()
if err != nil {
slog.Debug("ZFS pool stats unavailable", "err", err)
} else {
zm.poolData = pools
}
zm.lastPoolStats = time.Now()
}
return zm.poolData
}
// kernelStats reads cumulative pool counters and converts them to per-second
// rates. Counter decreases indicate a pool export/import and reset the
// baseline instead of producing an underflow spike.
func (zm *ZfsManager) kernelStats() (map[string]zfs.PoolKernelStat, map[string]zfs.PoolIoStats) {
if zm.kernelStatsFn == nil {
return nil, nil
}
stats, err := zm.kernelStatsFn()
if err != nil {
slog.Debug("ZFS kernel stats unavailable", "err", err)
return nil, nil
}
now := time.Now()
byName := make(map[string]zfs.PoolKernelStat, len(stats))
rates := make(map[string]zfs.PoolIoStats, len(stats))
nextSamples := make(map[string]poolKernelSample, len(stats))
for _, stat := range stats {
byName[stat.Name] = stat
if previous, ok := zm.kernelSamples[stat.Name]; ok && now.After(previous.at) &&
stat.NRead >= previous.nread && stat.NWrite >= previous.nwrite {
seconds := now.Sub(previous.at).Seconds()
rates[stat.Name] = zfs.PoolIoStats{
NRead: uint64(float64(stat.NRead-previous.nread) / seconds),
NWrite: uint64(float64(stat.NWrite-previous.nwrite) / seconds),
}
}
nextSamples[stat.Name] = poolKernelSample{nread: stat.NRead, nwrite: stat.NWrite, at: now}
}
zm.kernelSamples = nextSamples
return byName, rates
}
// refreshDatasetUsage re-runs `zfs list` when the refresh window has elapsed
// and rebuilds the mountpoint-keyed usage map.
func (zm *ZfsManager) refreshDatasetUsage() {
if !zm.lastUsageRefresh.IsZero() && time.Since(zm.lastUsageRefresh) < datasetUsageRefreshInterval {
return
}
datasets, err := zm.datasetsFn()
if err != nil {
slog.Debug("ZFS dataset usage unavailable", "err", err)
} else {
usage := make(map[string]zfsDatasetUsage, len(datasets))
for _, ds := range datasets {
if ds.Mountpoint != "" && ds.Mountpoint != "-" {
usage[ds.Mountpoint] = zfsDatasetUsage{used: ds.Used, avail: ds.Avail}
}
}
zm.datasetUsage = usage
}
zm.lastUsageRefresh = time.Now()
}
// DatasetUsage returns ZFS dataset usage keyed by mountpoint, refreshed at
// most every datasetUsageRefreshInterval. On failure the previous map is
// retained and a debug log is emitted.
func (zm *ZfsManager) DatasetUsage() map[string]zfsDatasetUsage {
zm.refreshDatasetUsage()
return zm.datasetUsage
}
// GetDetail returns ZFS detail data (pool health, scrub, vdevs, datasets).
// Scheduled requests use the cached snapshot until stale; manual requests can
// force collection. On failure the previous snapshot is retained.
func (zm *ZfsManager) GetDetail(force bool) *zfsentity.ZfsData {
zm.detailMu.Lock()
defer zm.detailMu.Unlock()
if force || zm.detail == nil || time.Since(zm.lastDetailRefresh) >= zm.detailInterval {
if data, err := zm.collectDetail(zm.detail); err != nil {
slog.Debug("ZFS detail collection failed", "err", err)
if zm.detail == nil {
return &zfsentity.ZfsData{}
}
return &zfsentity.ZfsData{Pools: zm.detail.Pools}
} else {
zm.detail = data
zm.lastDetailRefresh = time.Now()
}
}
if zm.detail == nil {
return &zfsentity.ZfsData{}
}
return zm.detail
}
// collectDetail builds a ZfsData payload from the current system state.
func (zm *ZfsManager) collectDetail(previous *zfsentity.ZfsData) (*zfsentity.ZfsData, error) {
pools, err := zm.poolStatsFn()
if err != nil {
return nil, err
}
if len(pools) == 0 {
return &zfsentity.ZfsData{Pools: []*zfsentity.PoolDetail{}, Complete: true}, nil
}
statuses, statusErr := zm.poolStatusesFn()
if statusErr != nil {
slog.Debug("ZFS pool status unavailable", "err", statusErr)
}
datasets, datasetsErr := zm.datasetsFn()
if datasetsErr != nil {
slog.Debug("ZFS datasets unavailable", "err", datasetsErr)
}
statusByPool := make(map[string]zfs.PoolStatus, len(statuses))
for _, st := range statuses {
statusByPool[st.Name] = st
}
previousByPool := make(map[string]*zfsentity.PoolDetail)
if previous != nil {
for _, pool := range previous.Pools {
if pool != nil {
previousByPool[pool.Name] = pool
}
}
}
data := &zfsentity.ZfsData{Pools: make([]*zfsentity.PoolDetail, 0, len(pools)), Complete: true}
for i := range pools {
p := &pools[i]
detail := &zfsentity.PoolDetail{
Name: p.Name,
Health: p.Health,
Size: p.Size,
Alloc: p.Alloc,
Free: p.Free,
}
if st, ok := statusByPool[p.Name]; statusErr == nil && ok {
if st.Scrub.State != "" && st.Scrub.State != "NONE" {
detail.Scrub = &zfsentity.Scrub{
State: st.Scrub.State,
Progress: st.Scrub.Progress,
Errors: st.Scrub.Errors,
}
}
for _, v := range st.Vdevs {
detail.Vdevs = append(detail.Vdevs, &zfsentity.Vdev{
Name: v.Name,
State: v.State,
ReadErrs: v.ReadErrs,
WriteErrs: v.WriteErrs,
ChecksumErrs: v.ChecksumErrs,
})
}
} else {
if cached := previousByPool[p.Name]; cached != nil {
detail.Scrub = cached.Scrub
detail.Vdevs = cached.Vdevs
}
}
if datasetsErr == nil {
foundDataset := false
for _, ds := range datasets {
if poolOfDataset(ds.Name) == p.Name {
foundDataset = true
detail.Datasets = append(detail.Datasets, &zfsentity.Dataset{
Name: ds.Name,
Used: ds.Used,
Avail: ds.Avail,
Mountpoint: ds.Mountpoint,
})
}
}
if !foundDataset {
if cached := previousByPool[p.Name]; cached != nil {
detail.Datasets = cached.Datasets
}
}
} else if cached := previousByPool[p.Name]; cached != nil {
detail.Datasets = cached.Datasets
}
data.Pools = append(data.Pools, detail)
}
return data, nil
}
// poolOfDataset returns the pool name for a dataset name (everything before
// the first '/'). Datasets without a separator belong to a pool of the same
// name.
func poolOfDataset(name string) string {
if idx := strings.IndexByte(name, '/'); idx >= 0 {
return name[:idx]
}
return name
}
// ZfsMountpoints returns the set of mountpoints backed by ZFS datasets.
func (zm *ZfsManager) ZfsMountpoints() map[string]bool {
usage := zm.DatasetUsage()
mountpoints := make(map[string]bool, len(usage))
for mountpoint := range usage {
mountpoints[mountpoint] = true
}
return mountpoints
}
-245
View File
@@ -1,245 +0,0 @@
//go:build testing
package agent
import (
"testing"
"time"
"github.com/henrygd/beszel/agent/zfs"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUpdatePopulatesZfsPools(t *testing.T) {
zm := &ZfsManager{}
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank", Size: 23999000000000, Alloc: 12000000000000, Free: 11999000000000, Health: "DEGRADED"}}, nil
}
zm.datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{
{Name: "tank/apps", Used: 5000000000000, Avail: 11999000000000, Mountpoint: "/tank/apps"},
{Name: "tank/backup", Used: 6000000000000, Avail: 11999000000000, Mountpoint: "/tank/backup"},
// Small zvol (Proxmox VM EFI disk): must not round to zero.
{Name: "rpool/vm-100-disk-2", Used: 4194304, Avail: 0, Mountpoint: "-"},
}, nil
}
var kernelCalls int
zm.kernelStatsFn = func() ([]zfs.PoolKernelStat, error) {
kernelCalls++
return []zfs.PoolKernelStat{{
Name: "tank", Health: "ONLINE",
NRead: uint64(kernelCalls-1) * 1250, NWrite: uint64(kernelCalls-1) * 5120,
}}, nil
}
var stats system.Stats
// The first kernel sample establishes the cumulative-counter baseline.
zm.Update(&stats)
zm.kernelSamples["tank"] = poolKernelSample{at: time.Now().Add(-time.Second)}
zm.Update(&stats)
require.NotNil(t, stats.ZfsPools)
require.Contains(t, stats.ZfsPools, "tank")
assert.InDelta(t, 22350.8105, stats.ZfsPools["tank"].Total, 0.0001) // Size in GiB
assert.InDelta(t, 11175.8709, stats.ZfsPools["tank"].Used, 0.0001) // Alloc in GiB
assert.Equal(t, "ONLINE", stats.ZfsPools["tank"].Health)
assert.InDelta(t, 1250, stats.ZfsPools["tank"].ReadBytes, 5)
assert.InDelta(t, 5120, stats.ZfsPools["tank"].WriteBytes, 5)
}
// TestUpdateKernelStatsMissing verifies pools without a kernel sample report zero
// I/O instead of erroring.
func TestUpdateKernelStatsMissing(t *testing.T) {
zm := &ZfsManager{}
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank", Size: 1, Alloc: 1, Health: "ONLINE"}}, nil
}
zm.datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
zm.kernelStatsFn = func() ([]zfs.PoolKernelStat, error) {
return nil, zfs.ErrNoZfs
}
var stats system.Stats
zm.Update(&stats)
require.NotNil(t, stats.ZfsPools)
assert.Equal(t, uint64(0), stats.ZfsPools["tank"].ReadBytes)
assert.Equal(t, uint64(0), stats.ZfsPools["tank"].WriteBytes)
}
func TestUpdateKernelCounterReset(t *testing.T) {
zm := &ZfsManager{}
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank", Health: "ONLINE"}}, nil
}
zm.datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
zm.kernelSamples = map[string]poolKernelSample{
"tank": {nread: 100, nwrite: 200, at: time.Now().Add(-time.Second)},
}
zm.kernelStatsFn = func() ([]zfs.PoolKernelStat, error) {
return []zfs.PoolKernelStat{{Name: "tank", Health: "ONLINE", NRead: 10, NWrite: 20}}, nil
}
var stats system.Stats
zm.Update(&stats)
assert.Equal(t, uint64(0), stats.ZfsPools["tank"].ReadBytes)
assert.Equal(t, uint64(0), stats.ZfsPools["tank"].WriteBytes)
}
func TestUpdateNoZfs(t *testing.T) {
zm := &ZfsManager{}
calls := 0
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
calls++
return nil, zfs.ErrNoZfs
}
var stats system.Stats
zm.Update(&stats)
zm.Update(&stats)
assert.Nil(t, stats.ZfsPools)
assert.Equal(t, 1, calls, "failed pool discovery should be cached until the next refresh interval")
}
func TestUpdateEmptyPools(t *testing.T) {
zm := &ZfsManager{}
calls := 0
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
calls++
return nil, nil
}
var stats system.Stats
zm.Update(&stats)
zm.Update(&stats)
assert.Nil(t, stats.ZfsPools)
assert.Equal(t, 1, calls, "an empty pool inventory should be cached until the next refresh interval")
}
func TestDatasetUsage(t *testing.T) {
zm := &ZfsManager{}
calls := 0
zm.datasetsFn = func() ([]zfs.Dataset, error) {
calls++
return []zfs.Dataset{
{Name: "tank", Used: 12000000000000, Avail: 11999000000000, Mountpoint: "/tank"},
{Name: "tank/apps", Used: 1000000000000, Avail: 11999000000000, Mountpoint: "/tank/apps"},
{Name: "rpool", Used: 900000000000, Avail: 300000000000, Mountpoint: "-"}, // zvol/unmounted: excluded
}, nil
}
usage := zm.DatasetUsage()
require.Len(t, usage, 2)
assert.Equal(t, zfsDatasetUsage{used: 12000000000000, avail: 11999000000000}, usage["/tank"])
assert.Equal(t, zfsDatasetUsage{used: 1000000000000, avail: 11999000000000}, usage["/tank/apps"])
assert.Equal(t, 1, calls)
// Second call within the refresh window must not re-run the collector.
zm.DatasetUsage()
assert.Equal(t, 1, calls)
}
func TestDatasetUsageRefreshOnErrorKeepsPrevious(t *testing.T) {
zm := &ZfsManager{}
zm.datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{{Name: "tank", Used: 1, Avail: 1, Mountpoint: "/tank"}}, nil
}
assert.Len(t, zm.DatasetUsage(), 1)
// Force refresh window expiry, then a failing collector.
zm.lastUsageRefresh = time.Now().Add(-10 * time.Minute)
zm.datasetsFn = func() ([]zfs.Dataset, error) {
return nil, zfs.ErrNoZfs
}
usage := zm.DatasetUsage()
assert.Len(t, usage, 1, "previous usage should be retained on error")
}
func TestGetDetailForceRefresh(t *testing.T) {
zm := &ZfsManager{detailInterval: time.Hour}
poolCalls := 0
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
poolCalls++
return []zfs.PoolStat{{Name: "tank", Alloc: uint64(poolCalls)}}, nil
}
zm.poolStatusesFn = func() ([]zfs.PoolStatus, error) { return nil, nil }
zm.datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
first := zm.GetDetail(false)
assert.True(t, first.Complete)
require.Len(t, first.Pools, 1)
assert.Equal(t, uint64(1), first.Pools[0].Alloc)
cached := zm.GetDetail(false)
require.Len(t, cached.Pools, 1)
assert.Equal(t, uint64(1), cached.Pools[0].Alloc)
assert.Equal(t, 1, poolCalls)
refreshed := zm.GetDetail(true)
assert.True(t, refreshed.Complete)
require.Len(t, refreshed.Pools, 1)
assert.Equal(t, uint64(2), refreshed.Pools[0].Alloc)
assert.Equal(t, 2, poolCalls)
}
func TestGetDetailSuccessfulEmptyInventoryClearsCache(t *testing.T) {
zm := &ZfsManager{detailInterval: time.Hour}
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank"}}, nil
}
zm.poolStatusesFn = func() ([]zfs.PoolStatus, error) { return nil, nil }
zm.datasetsFn = func() ([]zfs.Dataset, error) { return nil, nil }
require.Len(t, zm.GetDetail(false).Pools, 1)
zm.poolStatsFn = func() ([]zfs.PoolStat, error) { return nil, nil }
empty := zm.GetDetail(true)
assert.True(t, empty.Complete)
assert.Empty(t, empty.Pools)
}
func TestGetDetailFailureReturnsIncompleteCachedInventory(t *testing.T) {
zm := &ZfsManager{detailInterval: time.Hour}
zm.poolStatsFn = func() ([]zfs.PoolStat, error) {
return []zfs.PoolStat{{Name: "tank"}}, nil
}
zm.poolStatusesFn = func() ([]zfs.PoolStatus, error) {
return []zfs.PoolStatus{{Name: "tank", Vdevs: []zfs.VdevStatus{{Name: "mirror-0"}}}}, nil
}
zm.datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{{Name: "tank/data"}}, nil
}
first := zm.GetDetail(false)
require.True(t, first.Complete)
require.Len(t, first.Pools[0].Vdevs, 1)
require.Len(t, first.Pools[0].Datasets, 1)
zm.poolStatusesFn = func() ([]zfs.PoolStatus, error) { return nil, zfs.ErrNoZfs }
zm.datasetsFn = func() ([]zfs.Dataset, error) { return nil, zfs.ErrNoZfs }
partial := zm.GetDetail(true)
require.True(t, partial.Complete)
require.Len(t, partial.Pools[0].Vdevs, 1)
require.Len(t, partial.Pools[0].Datasets, 1)
zm.poolStatsFn = func() ([]zfs.PoolStat, error) { return nil, zfs.ErrNoZfs }
lastSuccessfulRefresh := zm.lastDetailRefresh
failed := zm.GetDetail(true)
assert.False(t, failed.Complete)
require.Len(t, failed.Pools, 1)
assert.Equal(t, "tank", failed.Pools[0].Name)
assert.Equal(t, lastSuccessfulRefresh, zm.lastDetailRefresh)
}
func TestZfsMountpoints(t *testing.T) {
zm := &ZfsManager{}
zm.datasetsFn = func() ([]zfs.Dataset, error) {
return []zfs.Dataset{
{Name: "tank", Mountpoint: "/tank"},
{Name: "rpool/ROOT/pve-1", Mountpoint: "/"},
}, nil
}
mountpoints := zm.ZfsMountpoints()
assert.Len(t, mountpoints, 2)
assert.True(t, mountpoints["/tank"])
assert.True(t, mountpoints["/"])
}
+4 -2
View File
@@ -5,12 +5,13 @@ go 1.27.1
require (
github.com/blang/semver v3.5.1+incompatible
github.com/coreos/go-systemd/v22 v22.7.0
github.com/distribution/reference v0.6.0
github.com/ebitengine/purego v0.11.0
github.com/fxamacker/cbor/v2 v2.9.3
github.com/gliderlabs/ssh v0.3.8
github.com/google/uuid v1.6.0
github.com/lxzan/gws v1.10.1
github.com/nicholas-fedor/shoutrrr v0.19.0
github.com/nicholas-fedor/shoutrrr v0.20.0
github.com/opencontainers/go-digest v1.0.0
github.com/pocketbase/dbx v1.12.0
github.com/pocketbase/pocketbase v0.40.2
github.com/shirou/gopsutil/v4 v4.26.8
@@ -41,6 +42,7 @@ require (
github.com/go-sql-driver/mysql v1.9.1 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.20.0 // indirect
+8 -4
View File
@@ -15,6 +15,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCOBXMn8=
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -54,8 +56,8 @@ github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArs
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20260902005441-ca85771921e4 h1:/6mPXfWmhv8eKck12I0YNIcIjwHtxP3YRIMKiEgTjWg=
github.com/google/pprof v0.0.0-20260902005441-ca85771921e4/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe h1:QAinXoAFJdGQYztXn3VpFey7KCwpedbZ/EkzbplQ0cY=
github.com/google/pprof v0.0.0-20260906184651-6331bc6350fe/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -83,12 +85,14 @@ github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsRe
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nicholas-fedor/shoutrrr v0.19.0 h1:Rl6bpK3DXuR2Trtx2JV8t+wjUwkHdRHrc8nBKoEpHr0=
github.com/nicholas-fedor/shoutrrr v0.19.0/go.mod h1:Glfdi8AGTbnEn2k2+hW62n8oL0i9vqRVFtXaUIthNks=
github.com/nicholas-fedor/shoutrrr v0.20.0 h1:hMAxIYlfAeZ1FcTDgU0kUOvVXUsOirWo8IWlnzGLkac=
github.com/nicholas-fedor/shoutrrr v0.20.0/go.mod h1:hgde37yNWCXh8+N6WemyDRMNYLOFTf326GsBx8Z7CFA=
github.com/onsi/ginkgo/v2 v2.32.1 h1:6tlvcDm/3sE8lGJbZ4+d4mO3RLy24/tQWOFzVSQNIfw=
github.com/onsi/ginkgo/v2 v2.32.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
github.com/onsi/gomega v1.43.0 h1:VlG/1FxqNxhSO+lq/OHBNaaqwiBK/mO8JbVkX9Y+FeU=
github.com/onsi/gomega v1.43.0/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.12.0 h1:/oLErM+A0b4xI0PWTGPqSDVjzix48PqI/bng2l0PzoA=
github.com/pocketbase/dbx v1.12.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
+23 -6
View File
@@ -20,10 +20,10 @@ type hubLike interface {
}
type AlertManager struct {
hub hubLike
stopOnce sync.Once
pendingAlerts sync.Map
alertsCache *AlertsCache
hub hubLike
stopOnce sync.Once
pendingAlerts sync.Map
alertsCache *AlertsCache
}
type AlertMessageData struct {
@@ -66,6 +66,7 @@ type SystemAlertGPUData struct {
}
type SystemAlertZfsPool struct {
Raw bool `json:"raw,omitempty"`
Total float64 `json:"d"`
Used float64 `json:"du"`
}
@@ -231,8 +232,20 @@ func (am *AlertManager) SendAlert(data AlertMessageData) error {
am.hub.Logger().Error("Failed to unmarshal user settings", "err", err)
}
// send alerts via webhooks
send := sendPublicNotification
if len(userAlertSettings.Webhooks) > 0 {
// Read the owner's current role at delivery time, including for URLs
// saved before an admin was demoted. Never fall back on lookup failure.
owner, err := am.hub.FindRecordById("users", data.UserID)
if err != nil {
return fmt.Errorf("load notification owner: %w", err)
}
if owner.GetString("role") == "admin" {
send = shoutrrr.Send
}
}
for _, webhook := range userAlertSettings.Webhooks {
if err := am.SendShoutrrrAlert(webhook, data.Title, data.Message, data.Link, data.LinkText); err != nil {
if err := am.sendShoutrrrAlert(webhook, data.Title, data.Message, data.Link, data.LinkText, send); err != nil {
am.hub.Logger().Error("Failed to send shoutrrr alert", "err", err)
}
}
@@ -263,6 +276,10 @@ func (am *AlertManager) SendAlert(data AlertMessageData) error {
// SendShoutrrrAlert sends an alert via a Shoutrrr URL
func (am *AlertManager) SendShoutrrrAlert(notificationUrl, title, message, link, linkText string) error {
return am.sendShoutrrrAlert(notificationUrl, title, message, link, linkText, shoutrrr.Send)
}
func (am *AlertManager) sendShoutrrrAlert(notificationUrl, title, message, link, linkText string, send func(string, string) error) error {
// Parse the URL
parsedURL, err := url.Parse(notificationUrl)
if err != nil {
@@ -305,7 +322,7 @@ func (am *AlertManager) SendShoutrrrAlert(notificationUrl, title, message, link,
parsedURL.RawQuery = queryParams.Encode()
// log.Println("URL after modification:", parsedURL.String())
err = shoutrrr.Send(parsedURL.String(), message)
err = send(parsedURL.String(), message)
if err == nil {
am.hub.Logger().Info("Sent shoutrrr alert", "title", title)
+7 -65
View File
@@ -3,13 +3,11 @@ package alerts
import (
"database/sql"
"errors"
"net"
"net/http"
"net/url"
"slices"
"strings"
"github.com/henrygd/beszel/internal/hub/utils"
"github.com/nicholas-fedor/shoutrrr"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/core"
)
@@ -147,72 +145,16 @@ func (am *AlertManager) SendTestNotification(e *core.RequestEvent) error {
if err != nil || data.URL == "" {
return e.BadRequestError("URL is required", err)
}
// Only allow admins to send test notifications to internal URLs
send := shoutrrr.Send
if !e.Auth.IsSuperuser() && e.Auth.GetString("role") != "admin" {
internalURL, err := isInternalURL(data.URL)
if err != nil {
return e.BadRequestError(err.Error(), nil)
}
if internalURL {
return e.ForbiddenError("Only admins can send to internal destinations", nil)
}
send = sendPublicNotification
}
err = am.sendShoutrrrAlert(data.URL, "Test Alert", "This is a notification from Beszel.", am.hub.Settings().Meta.AppURL, "View Beszel", send)
if errors.Is(err, errInternalDestination) || errors.Is(err, errUnrestrictedService) {
return e.ForbiddenError(err.Error(), nil)
}
err = am.SendShoutrrrAlert(data.URL, "Test Alert", "This is a notification from Beszel.", am.hub.Settings().Meta.AppURL, "View Beszel")
if err != nil {
return e.JSON(200, map[string]string{"err": err.Error()})
}
return e.JSON(200, map[string]bool{"err": false})
}
// isInternalURL checks if the given shoutrrr URL points to an internal destination (localhost or private IP)
func isInternalURL(rawURL string) (bool, error) {
parsedURL, err := url.Parse(rawURL)
if err != nil {
return false, err
}
host := parsedURL.Hostname()
if host == "" {
return false, nil
}
if strings.EqualFold(host, "localhost") {
return true, nil
}
if ip := net.ParseIP(host); ip != nil {
return isInternalIP(ip), nil
}
// Some Shoutrrr URLs use the host position for service identifiers rather than a
// network hostname (for example, discord://token@webhookid). Restrict DNS lookups
// to names that look like actual hostnames so valid service URLs keep working.
if !strings.Contains(host, ".") {
return false, nil
}
ips, err := net.LookupIP(host)
if err != nil {
return false, nil
}
if slices.ContainsFunc(ips, isInternalIP) {
return true, nil
}
return false, nil
}
var cgnatNetwork = &net.IPNet{
IP: net.IPv4(100, 64, 0, 0),
Mask: net.CIDRMask(10, 32),
}
func isInternalIP(ip net.IP) bool {
return ip.IsPrivate() ||
ip.IsLoopback() ||
ip.IsUnspecified() ||
ip.IsLinkLocalUnicast() ||
ip.IsMulticast() ||
cgnatNetwork.Contains(ip)
}
+33 -44
View File
@@ -7,10 +7,11 @@ import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"github.com/henrygd/beszel/internal/alerts"
beszelTests "github.com/henrygd/beszel/internal/tests"
pbTests "github.com/pocketbase/pocketbase/tests"
@@ -29,43 +30,6 @@ func jsonReader(v any) io.Reader {
return bytes.NewReader(data)
}
func TestIsInternalURL(t *testing.T) {
testCases := []struct {
name string
url string
internal bool
}{
{name: "loopback ipv4", url: "generic://127.0.0.1", internal: true},
{name: "private ipv4", url: "generic://10.0.0.1", internal: true},
{name: "localhost hostname", url: "generic://localhost", internal: true},
{name: "localhost with path", url: "generic+http://localhost/api/v1/postStuff", internal: true},
{name: "loopback with port and path", url: "generic+http://127.0.0.1:8080/api/v1/postStuff", internal: true},
{name: "public hostname", url: "generic+https://beszel.dev/api/v1/postStuff", internal: false},
{name: "cloud metadata ipv4", url: "generic://169.254.169.254", internal: true},
{name: "link-local ipv4", url: "generic://169.254.1.1", internal: true},
{name: "link-local ipv6", url: "generic://[fe80::1]", internal: true},
{name: "mapped link-local ipv4", url: "generic://[::ffff:169.254.169.254]", internal: true},
{name: "cgnat lower boundary", url: "generic://100.64.0.0", internal: true},
{name: "cgnat upper boundary", url: "generic://100.127.255.255", internal: true},
{name: "below cgnat", url: "generic://100.63.255.255", internal: false},
{name: "above cgnat", url: "generic://100.128.0.0", internal: false},
{name: "multicast ipv4", url: "generic://224.0.0.1", internal: true},
{name: "multicast ipv6", url: "generic://[ff02::1]", internal: true},
{name: "public ipv4", url: "generic://8.8.8.8", internal: false},
{name: "public ipv6", url: "generic://[2001:4860:4860::8888]", internal: false},
{name: "token style service url", url: "discord://abc123@123456789", internal: false},
{name: "single label service url", url: "slack://token@team/channel", internal: false},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
internal, err := alerts.IsInternalURL(testCase.url)
assert.NoError(t, err)
assert.Equal(t, testCase.internal, internal)
})
}
}
func TestUserAlertsApi(t *testing.T) {
hub, _ := beszelTests.NewTestHub(t.TempDir())
defer hub.Cleanup()
@@ -457,6 +421,17 @@ func TestSendTestNotification(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
var delivered atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
delivered.Add(1)
}))
defer server.Close()
localURL := "generic+" + server.URL
readonlyUser, err := beszelTests.CreateUserWithRole(hub, "readonly@example.com", "password123", "readonly")
assert.NoError(t, err)
readonlyToken, err := readonlyUser.NewAuthToken()
assert.NoError(t, err)
userToken, err := user.NewAuthToken()
adminUser, err := beszelTests.CreateUserWithRole(hub, "admin@example.com", "password123", "admin")
@@ -481,11 +456,11 @@ func TestSendTestNotification(t *testing.T) {
ExpectedContent: []string{"requires valid"},
TestAppFactory: testAppFactory,
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
"url": localURL,
}),
},
{
Name: "POST /test-notification - with external auth should succeed",
Name: "POST /test-notification - invalid service reports error",
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
TestAppFactory: testAppFactory,
@@ -493,7 +468,7 @@ func TestSendTestNotification(t *testing.T) {
"Authorization": userToken,
},
Body: jsonReader(map[string]any{
"url": "generic://8.8.8.8",
"url": "unknown://example.com",
}),
ExpectedStatus: 200,
ExpectedContent: []string{"\"err\":"},
@@ -535,10 +510,10 @@ func TestSendTestNotification(t *testing.T) {
"Authorization": adminUserToken,
},
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
"url": localURL,
}),
ExpectedStatus: 200,
ExpectedContent: []string{"\"err\":"},
ExpectedContent: []string{"\"err\":false"},
},
{
Name: "POST /test-notification - internal url with superuser auth should succeed",
@@ -549,14 +524,28 @@ func TestSendTestNotification(t *testing.T) {
"Authorization": superuserToken,
},
Body: jsonReader(map[string]any{
"url": "generic://127.0.0.1",
"url": localURL,
}),
ExpectedStatus: 200,
ExpectedContent: []string{"\"err\":"},
},
}
for _, url := range []string{localURL, "smtp://user:pass@127.0.0.1/?fromAddress=sender@example.com&toAddresses=recipient@example.com", "mqtt://127.0.0.1/topic"} {
scenarios = append(scenarios, beszelTests.ApiScenario{
Name: "readonly cannot send to " + url,
Method: http.MethodPost,
URL: "/api/beszel/test-notification",
TestAppFactory: testAppFactory,
Headers: map[string]string{"Authorization": readonlyToken},
Body: jsonReader(map[string]any{"url": url}),
ExpectedStatus: 403,
ExpectedContent: []string{"Only admins"},
})
}
for _, scenario := range scenarios {
scenario.Test(t)
}
assert.EqualValues(t, 2, delivered.Load(), "only admin and superuser requests should reach the server")
}
+8 -3
View File
@@ -78,7 +78,7 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
}
}
for _, pool := range data.Stats.ZfsPools {
if pool != nil && pool.Total > 0 {
if pool != nil && !pool.Raw && pool.Total > 0 {
usedPct := pool.Used / pool.Total * 100
if usedPct > maxUsedPct {
maxUsedPct = usedPct
@@ -256,7 +256,7 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
}
// add zfs pool usage from historical record
for key, pool := range stats.ZfsPools {
if pool.Total > 0 {
if !pool.Raw && pool.Total > 0 {
zfsKey := zfsDiskAlertKey(key)
if _, ok := alert.mapSums[zfsKey]; !ok {
alert.mapSums[zfsKey] = 0.0
@@ -319,6 +319,11 @@ func (am *AlertManager) HandleSystemAlerts(systemRecord *core.Record, data *syst
if sumPct > maxPct {
maxPct = sumPct
alert.descriptor = diskAlertDescriptor(key)
if poolKey, ok := strings.CutPrefix(key, "zfs:"); ok {
if pool := data.Stats.ZfsPools[poolKey]; pool != nil && pool.DisplayName != "" {
alert.descriptor = diskAlertDescriptor(zfsDiskAlertKey(pool.DisplayName))
}
}
}
}
alert.val = float64(maxPct / float32(alert.count))
@@ -370,7 +375,7 @@ func zfsDiskAlertKey(poolName string) string {
func diskAlertDescriptor(key string) string {
if poolName, ok := strings.CutPrefix(key, "zfs:"); ok {
return fmt.Sprintf("Usage of ZFS pool %s", poolName)
return fmt.Sprintf("Usage of storage pool %s", poolName)
}
return fmt.Sprintf("Usage of %s", key)
}
-4
View File
@@ -100,10 +100,6 @@ func (am *AlertManager) SetAlertTriggered(alert CachedAlertData, triggered bool)
return am.setAlertTriggered(alert, triggered)
}
func IsInternalURL(rawURL string) (bool, error) {
return isInternalURL(rawURL)
}
// BuildContainerLogExcerpt exposes buildContainerLogExcerpt for testing.
func BuildContainerLogExcerpt(raw string) string {
return buildContainerLogExcerpt(raw)
+66
View File
@@ -0,0 +1,66 @@
//go:build testing
package alerts_test
import (
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"github.com/henrygd/beszel/internal/alerts"
beszelTests "github.com/henrygd/beszel/internal/tests"
"github.com/pocketbase/dbx"
"github.com/stretchr/testify/require"
)
func TestPersistedWebhooksUseCurrentOwnerRole(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
am := alerts.NewTestAlertManagerWithoutWorker(hub)
var delivered atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
delivered.Add(1)
}))
defer server.Close()
settings, err := hub.FindFirstRecordByFilter("user_settings", "user={:user}", dbx.Params{"user": user.Id})
require.NoError(t, err)
settings.Set("settings", alerts.UserNotificationSettings{Webhooks: []string{"generic+" + server.URL}})
require.NoError(t, hub.Save(settings))
message := alerts.AlertMessageData{UserID: user.Id, Title: "Test", Message: "Persisted webhook"}
// Keep the same URL and manager while changing roles, so cached privileges
// or treating previously saved URLs as trusted would fail this test.
for _, tc := range []struct {
name string
role string
want int32
}{
{"regular user", "user", 0},
{"readonly user", "readonly", 0},
{"promoted admin", "admin", 1},
{"demoted admin", "user", 1},
} {
t.Run(tc.name, func(t *testing.T) {
user.Set("role", tc.role)
require.NoError(t, hub.Save(user))
// Webhook errors are logged; SendAlert continues to email delivery.
require.NoError(t, am.SendAlert(message))
require.Equal(t, tc.want, delivered.Load())
})
}
t.Run("missing owner fails closed", func(t *testing.T) {
// Model an orphaned settings record without deleting it through the
// normal user deletion cascade.
const missingOwner = "missingowner123"
settings.Set("user", missingOwner)
require.NoError(t, hub.SaveNoValidate(settings))
message.UserID = missingOwner
err := am.SendAlert(message)
require.ErrorContains(t, err, "load notification owner")
require.EqualValues(t, 1, delivered.Load())
})
}
+8 -5
View File
@@ -46,12 +46,15 @@ func (am *AlertManager) handleZfsPoolHealthAlert(e *core.RecordEvent, oldHealth
}
systemName := systemRecord.GetString("name")
poolName := e.Record.GetString("name")
poolName := e.Record.GetString("display_name")
if poolName == "" {
poolName = e.Record.GetString("name")
}
title := fmt.Sprintf("ZFS pool %s on %s: %s", newHealth, systemName, poolName)
message := fmt.Sprintf("ZFS pool %s (%s) was first observed as %s", poolName, systemName, newHealth)
title := fmt.Sprintf("Storage pool %s on %s: %s", newHealth, systemName, poolName)
message := fmt.Sprintf("Storage pool %s (%s) was first observed as %s", poolName, systemName, newHealth)
if oldSeverity > 0 {
message = fmt.Sprintf("ZFS pool %s (%s) health changed from %s to %s", poolName, systemName, oldHealth, newHealth)
message = fmt.Sprintf("Storage pool %s (%s) health changed from %s to %s", poolName, systemName, oldHealth, newHealth)
}
userIDs := systemRecord.GetStringSlice("users")
@@ -116,7 +119,7 @@ func createZfsPoolHistoryRecord(app core.App, userID, systemID, alertID, poolNam
record.Set("user", userID)
record.Set("system", systemID)
record.Set("alert_id", alertID)
record.Set("name", "ZFS Pool: "+poolName)
record.Set("name", "Storage Pool: "+poolName)
return app.Save(record)
}
+34
View File
@@ -143,3 +143,37 @@ func TestDiskAlertZfsPoolMultiMinute(t *testing.T) {
assert.False(t, diskAlert.GetBool("triggered"),
"Alert should be resolved when ZFS pool average (50%%) drops below threshold (80%%)")
}
func TestDiskAlertIgnoresRawPool(t *testing.T) {
for _, minutes := range []int{0, 2} {
hub, user := beszelTests.GetHubWithUser(t)
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
require.NoError(t, err)
alert, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{"name": "Disk", "system": systems[0].Id, "user": user.Id, "value": 80, "min": minutes})
require.NoError(t, err)
pools := map[string]*system.ZfsPool{"btrfs": {Total: 100, Used: 99, Raw: true}}
for _, offset := range []time.Duration{-180, -90, -60, -30} {
data, err := json.Marshal(system.Stats{ZfsPools: pools})
require.NoError(t, err)
record, err := beszelTests.CreateRecord(hub, "system_stats", map[string]any{"system": systems[0].Id, "type": "1m", "stats": string(data)})
require.NoError(t, err)
record.SetRaw("created", time.Now().UTC().Add(offset*time.Second).Format(types.DefaultDateLayout))
require.NoError(t, hub.SaveNoValidate(record))
}
require.NoError(t, hub.GetAlertManager().HandleSystemAlerts(systems[0], &system.CombinedData{Stats: system.Stats{ZfsPools: pools}}))
time.Sleep(20 * time.Millisecond)
record, err := hub.FindRecordById("alerts", alert.Id)
require.NoError(t, err)
assert.False(t, record.GetBool("triggered"))
if minutes > 0 {
// A current usable sample must not make raw historical values eligible.
pools["btrfs"].Raw = false
require.NoError(t, hub.GetAlertManager().HandleSystemAlerts(systems[0], &system.CombinedData{Stats: system.Stats{ZfsPools: pools}}))
time.Sleep(20 * time.Millisecond)
record, err = hub.FindRecordById("alerts", alert.Id)
require.NoError(t, err)
assert.False(t, record.GetBool("triggered"))
}
hub.Cleanup()
}
}
+1 -1
View File
@@ -10,6 +10,6 @@ import (
func TestZfsDiskAlertKeyIsNamespaced(t *testing.T) {
assert.Equal(t, "zfs:tank", zfsDiskAlertKey("tank"))
assert.Equal(t, "Usage of ZFS pool tank", diskAlertDescriptor(zfsDiskAlertKey("tank")))
assert.Equal(t, "Usage of storage pool tank", diskAlertDescriptor(zfsDiskAlertKey("tank")))
assert.Equal(t, "Usage of tank", diskAlertDescriptor("tank"))
}
+3 -3
View File
@@ -42,7 +42,7 @@ func TestZfsPoolAlertOnlineToDegraded(t *testing.T) {
assert.EqualValues(t, 1, hub.TestMailer.TotalSend(), "should have 1 email sent after pool became DEGRADED")
lastMessage := hub.TestMailer.LastMessage()
assert.Contains(t, lastMessage.Subject, "ZFS pool DEGRADED on test-system")
assert.Contains(t, lastMessage.Subject, "Storage pool DEGRADED on test-system")
assert.Contains(t, lastMessage.Subject, "tank")
assert.Contains(t, lastMessage.Text, "ONLINE to DEGRADED")
}
@@ -76,7 +76,7 @@ func TestZfsPoolAlertDegradedToFaulted(t *testing.T) {
assert.EqualValues(t, 2, hub.TestMailer.TotalSend(), "should alert on initial DEGRADED state and later FAULTED transition")
lastMessage := hub.TestMailer.LastMessage()
assert.Contains(t, lastMessage.Subject, "ZFS pool FAULTED on test-system")
assert.Contains(t, lastMessage.Subject, "Storage pool FAULTED on test-system")
}
func TestZfsPoolAlertNoAlertOnRecovery(t *testing.T) {
@@ -239,7 +239,7 @@ func TestZfsPoolAlertWritesHistory(t *testing.T) {
history, err := hub.FindRecordsByFilter("alerts_history", "alert_id={:alert_id}", "", 0, 0, map[string]any{"alert_id": pool.Id})
assert.NoError(t, err)
require.Len(t, history, 1, "expected one history entry per user")
assert.Equal(t, "ZFS Pool: tank", history[0].GetString("name"))
assert.Equal(t, "Storage Pool: tank", history[0].GetString("name"))
assert.Equal(t, system.Id, history[0].GetString("system"))
}
+150
View File
@@ -0,0 +1,150 @@
package alerts
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/netip"
"sync/atomic"
"syscall"
"time"
"github.com/nicholas-fedor/shoutrrr/pkg/router"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
)
var (
errInternalDestination = errors.New("Only admins can send to internal destinations")
errUnrestrictedService = errors.New("Only admins can use this notification service") // Restrict services w/o custom connection support
publicNotificationDialer = &net.Dialer{
Timeout: 10 * time.Second,
// Control checks each resolved address immediately before connecting.
Control: func(_, address string, _ syscall.RawConn) error { return checkNotificationAddress(address) },
}
publicNotificationClient = newPublicNotificationClient()
)
func newPublicNotificationClient() *http.Client {
return &http.Client{
Timeout: 15 * time.Second,
Transport: &http.Transport{
// Do not use proxies: they can resolve the target themselves and
// bypass the destination check on our socket.
DialContext: publicNotificationDialer.DialContext,
TLSHandshakeTimeout: 10 * time.Second,
IdleConnTimeout: 90 * time.Second,
},
}
}
func checkNotificationAddress(address string) error {
addr, err := netip.ParseAddrPort(address)
if err != nil || addr.Addr().Zone() != "" {
return errInternalDestination
}
ip := net.IP(addr.Addr().AsSlice())
if !ip.IsGlobalUnicast() || isInternalIP(ip) {
return errInternalDestination
}
return nil
}
func sendPublicNotification(rawURL, message string) error {
client := &notificationClient{Client: publicNotificationClient}
service, err := newPublicNotificationService(rawURL, types.SenderOptions{HTTPClient: client, DialContext: client.dialContext})
if err == nil {
if closer, ok := service.(io.Closer); ok {
defer closer.Close()
}
err = service.Send(message, &types.Params{})
}
// Some services format errors without preserving their error chain.
if client.blocked.Load() {
return errInternalDestination
}
return err
}
type notificationClient struct {
*http.Client
blocked atomic.Bool
}
func (c *notificationClient) Do(req *http.Request) (*http.Response, error) {
response, err := c.Client.Do(req)
if errors.Is(err, errInternalDestination) {
c.blocked.Store(true)
}
return response, err
}
func (c *notificationClient) dialContext(ctx context.Context, network, address string) (net.Conn, error) {
conn, err := publicNotificationDialer.DialContext(ctx, network, address)
if errors.Is(err, errInternalDestination) {
c.blocked.Store(true)
}
return conn, err
}
func newPublicNotificationService(rawURL string, opts types.SenderOptions) (types.Service, error) {
r := &router.ServiceRouter{}
scheme, serviceURL, err := r.ExtractServiceName(rawURL)
if err != nil {
return nil, err
}
service, err := r.NewService(scheme)
if err != nil {
return nil, err
}
httpSetter, httpOK := service.(types.HTTPClientSetter)
dialSetter, dialOK := service.(types.DialContextSetter)
if (!httpOK || opts.HTTPClient == nil) && (!dialOK || opts.DialContext == nil) {
return nil, errUnrestrictedService
}
if serviceURL.Scheme != scheme {
custom, ok := service.(types.CustomURLService)
if !ok {
return nil, fmt.Errorf("%w: %s", router.ErrCustomURLsNotSupported, scheme)
}
serviceURL, err = custom.GetServiceURLFromCustom(serviceURL)
if err != nil {
return nil, err
}
}
// Shoutrrr v0.20.0 CreateSenderWithOptions injects only AFTER Initialize.
// Matrix can log in during Initialize, so inject before it as well.
if httpOK {
httpSetter.SetHTTPClient(opts.HTTPClient)
}
if dialOK {
dialSetter.SetDialContext(opts.DialContext)
}
if err := service.Initialize(serviceURL, nil); err != nil {
return nil, err
}
// Some initializers replace their HTTP client with a default client.
if httpOK {
httpSetter.SetHTTPClient(opts.HTTPClient)
}
if dialOK {
dialSetter.SetDialContext(opts.DialContext)
}
return service, nil
}
var cgnatNetwork = &net.IPNet{
IP: net.IPv4(100, 64, 0, 0),
Mask: net.CIDRMask(10, 32),
}
func isInternalIP(ip net.IP) bool {
return ip.IsPrivate() ||
ip.IsLoopback() ||
ip.IsUnspecified() ||
ip.IsLinkLocalUnicast() ||
ip.IsMulticast() ||
cgnatNetwork.Contains(ip)
}
+217
View File
@@ -0,0 +1,217 @@
package alerts
import (
"context"
"encoding/binary"
"errors"
"io"
"net"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
"golang.org/x/net/dns/dnsmessage"
)
func TestCheckNotificationAddress(t *testing.T) {
for _, host := range []string{"127.0.0.1", "10.0.0.1", "172.16.0.1", "192.168.0.1", "169.254.169.254", "100.64.0.0", "100.127.255.255", "0.0.0.0", "224.0.0.1", "255.255.255.255", "::1", "::", "fc00::1", "fe80::1", "ff02::1", "::ffff:127.0.0.1", "::ffff:169.254.169.254", "fe80::1%lo", "localhost", "consul"} {
t.Run(host, func(t *testing.T) {
if err := checkNotificationAddress(net.JoinHostPort(host, "80")); !errors.Is(err, errInternalDestination) {
t.Fatalf("expected blocked address, got %v", err)
}
})
}
for _, host := range []string{"8.8.8.8", "100.63.255.255", "100.128.0.0", "2001:4860:4860::8888"} {
if err := checkNotificationAddress(net.JoinHostPort(host, "443")); err != nil {
t.Errorf("public address %s: %v", host, err)
}
}
}
func TestPublicNotificationBlocksInternalRequests(t *testing.T) {
var hits atomic.Int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hits.Add(1)
}))
defer server.Close()
host := strings.TrimPrefix(server.URL, "http://")
for _, rawURL := range []string{
"generic+http://" + host,
"generic+https://" + host,
"generic+http://localhost:" + strings.Split(host, ":")[1],
"matrix://user:password@" + host + "/room?disabletls=yes",
"mattermost://" + host + "/token?disabletls=yes",
} {
t.Run(rawURL, func(t *testing.T) {
if err := sendPublicNotification(rawURL, "test"); !errors.Is(err, errInternalDestination) {
t.Fatalf("expected internal destination error, got %v", err)
}
})
}
if hits.Load() != 0 {
t.Fatal("internal server received a request")
}
}
type notificationRoundTripper func(*http.Request) (*http.Response, error)
func (f notificationRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func TestPublicNotificationRedirect(t *testing.T) {
client := newPublicNotificationClient()
defer client.CloseIdleConnections()
transport := client.Transport
client.Transport = notificationRoundTripper(func(r *http.Request) (*http.Response, error) {
if r.URL.Host == "public.example" {
return &http.Response{StatusCode: 307, Header: http.Header{"Location": {"http://127.0.0.1/"}}, Body: io.NopCloser(strings.NewReader("")), Request: r}, nil
}
return transport.RoundTrip(r)
})
_, err := client.Get("http://public.example/")
if !errors.Is(err, errInternalDestination) {
t.Fatalf("expected redirect to be blocked, got %v", err)
}
}
func TestPublicNotificationServiceClient(t *testing.T) {
for _, rawURL := range []string{"generic+http://public.example/path", "discord://token@123456789", "slack://hook:AAAAAAAAA-BBBBBBBBB-123456789123456789123456@webhook"} {
t.Run(rawURL, func(t *testing.T) {
var hits int
client := &http.Client{Transport: notificationRoundTripper(func(r *http.Request) (*http.Response, error) {
hits++
body := `{"ok":true}`
if strings.HasPrefix(rawURL, "slack:") {
body = "ok"
}
return &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body)), Request: r}, nil
})}
service, err := newPublicNotificationService(rawURL, types.SenderOptions{HTTPClient: client})
if err != nil {
t.Fatal(err)
}
if err := service.Send("test", &types.Params{}); err != nil {
t.Fatal(err)
}
if hits == 0 {
t.Fatal("injected client was not used")
}
})
}
}
func TestPublicNotificationDNS(t *testing.T) {
// Supply deterministic DNS responses over an in-memory TCP connection.
// The first lookup sees a public IP; subsequent lookups see loopback.
var rebound atomic.Bool
resolver := &net.Resolver{PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
client, server := net.Pipe()
go func() {
defer server.Close()
var size [2]byte
if _, err := io.ReadFull(server, size[:]); err != nil {
return
}
buf := make([]byte, binary.BigEndian.Uint16(size[:]))
if _, err := io.ReadFull(server, buf); err != nil {
return
}
var msg dnsmessage.Message
if err := msg.Unpack(buf); err != nil {
return
}
msg.Header.Response = true
msg.Header.RecursionAvailable = true
q := msg.Questions[0]
if q.Type == dnsmessage.TypeA {
ip := [4]byte{8, 8, 8, 8}
if rebound.Load() {
ip = [4]byte{127, 0, 0, 1}
}
msg.Answers = []dnsmessage.Resource{{Header: dnsmessage.ResourceHeader{Name: q.Name, Type: q.Type, Class: dnsmessage.ClassINET}, Body: &dnsmessage.AResource{A: ip}}}
}
buf, err := msg.Pack()
if err != nil {
return
}
binary.BigEndian.PutUint16(size[:], uint16(len(buf)))
server.Write(append(size[:], buf...))
}()
return client, nil
}}
// These tests do not run in parallel; restore the process resolver afterward.
previous := net.DefaultResolver
net.DefaultResolver = resolver
t.Cleanup(func() { net.DefaultResolver = previous })
ips, err := resolver.LookupIP(context.Background(), "ip4", "rebind.example")
if err != nil || len(ips) != 1 || !ips[0].Equal(net.IPv4(8, 8, 8, 8)) {
t.Fatalf("initial DNS lookup: %v, %v", ips, err)
}
rebound.Store(true)
client := newPublicNotificationClient()
defer client.CloseIdleConnections()
for _, host := range []string{"rebind.example", "consul"} {
guarded := &notificationClient{Client: client}
conn, dialErr := guarded.dialContext(context.Background(), "tcp", net.JoinHostPort(host, "25"))
if conn != nil {
conn.Close()
}
if !errors.Is(dialErr, errInternalDestination) || !guarded.blocked.Load() {
t.Errorf("expected TCP dial-time rejection for %s, got %v", host, dialErr)
}
_, err := client.Get("http://" + host + "/")
if !errors.Is(err, errInternalDestination) {
t.Errorf("expected dial-time rejection for %s, got %v", host, err)
}
}
}
func TestPublicNotificationTCP(t *testing.T) {
for _, rawURL := range []string{
"smtp://user:pass@HOST:25/?fromAddress=sender@example.com&toAddresses=recipient@example.com",
"smtp://user:pass@HOST:465/?fromAddress=sender@example.com&toAddresses=recipient@example.com",
"mqtt://HOST:1883/topic",
"mqtts://HOST:8883/topic",
} {
t.Run(rawURL, func(t *testing.T) {
t.Parallel()
t.Run("internal destination", func(t *testing.T) {
err := sendPublicNotification(strings.ReplaceAll(rawURL, "HOST", "127.0.0.1"), "test")
if !errors.Is(err, errInternalDestination) {
t.Fatalf("expected blocked destination, got %v", err)
}
})
t.Run("public destination uses injected dialer", func(t *testing.T) {
var calls atomic.Int32
stopped := errors.New("test dial stopped")
service, err := newPublicNotificationService(strings.ReplaceAll(rawURL, "HOST", "8.8.8.8"), types.SenderOptions{
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
calls.Add(1)
if network != "tcp" || !strings.HasPrefix(address, "8.8.8.8:") {
t.Errorf("unexpected dial: %s %s", network, address)
}
if err := checkNotificationAddress(address); err != nil {
t.Error(err)
}
return nil, stopped
},
})
if err != nil {
t.Fatal(err)
}
if closer, ok := service.(io.Closer); ok {
defer closer.Close()
}
if err := service.Send("test", &types.Params{}); err == nil {
t.Fatal("expected dial failure")
}
if calls.Load() == 0 {
t.Fatal("custom dialer was not used")
}
})
})
}
}
+5
View File
@@ -43,6 +43,11 @@ COPY --from=builder /app/agent/test-data/amdgpu.ids /usr/share/libdrm/amdgpu.ids
# Copy smartmontools binaries and config files
COPY --from=smartmontools-builder /usr/sbin/smartctl /usr/sbin/smartctl
# Install ZFS userspace utilities (zpool, zfs) for pool/dataset monitoring
RUN apt-get update && apt-get install -y --no-install-recommends \
zfsutils-linux \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Ensure data persistence across container recreations
VOLUME ["/var/lib/beszel-agent"]
+29
View File
@@ -65,6 +65,32 @@ RUN set -eux; \
cp -v "$interp" "/out/rootfs$interp"; \
fi
# --------------------------
# ZFS utilities builder stage
# --------------------------
FROM --platform=$TARGETPLATFORM debian:bookworm-slim AS zfsutils-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
zfsutils-linux \
&& rm -rf /var/lib/apt/lists/*
# Copy the zpool/zfs binaries and their required runtime libraries
RUN set -eux; \
mkdir -p /out/rootfs/lib /out/rootfs/lib64 /out/rootfs/usr/lib; \
for bin in /usr/sbin/zpool /usr/sbin/zfs; do \
mkdir -p "/out/rootfs$(dirname "$bin")"; \
cp -v "$bin" "/out/rootfs$bin"; \
ldd "$bin" \
| awk '{print $3}' \
| grep '^/' \
| xargs -r -I '{}' sh -c 'mkdir -p "/out/rootfs$(dirname "{}")"; cp -v "{}" "/out/rootfs{}"'; \
interp="$(ldd "$bin" | awk "/ld-linux/ {print \$1}")"; \
if [ -n "$interp" ] && [ -e "$interp" ]; then \
mkdir -p "/out/rootfs$(dirname "$interp")"; \
cp -v "$interp" "/out/rootfs$interp"; \
fi; \
done
# --------------------------
# Final image: lightweight multi-arch NVIDIA agent (slim)
# --------------------------
@@ -78,6 +104,9 @@ COPY --from=builder /app/agent/test-data/amdgpu.ids /usr/share/libdrm/amdgpu.ids
COPY --from=smartmontools-builder /usr/sbin/smartctl /usr/sbin/smartctl
COPY --from=smartmontools-builder /out/rootfs/ /
# Copy ZFS utilities (zpool, zfs) binaries and required runtime libraries
COPY --from=zfsutils-builder /out/rootfs/ /
# nvidia-smi is intentionally not bundled.
# Mount the host binary instead, for example:
# - /usr/bin/nvidia-smi:/usr/bin/nvidia-smi:ro
+6 -5
View File
@@ -186,11 +186,12 @@ type Stats struct {
NetworkRecv float64 `json:"nr,omitzero" cbor:"4,keyasint,omitzero"` // deprecated 0.18.3 (MB) - keep field for old agents/records
Bandwidth [2]uint64 `json:"b,omitzero" cbor:"9,keyasint,omitzero"` // [sent bytes, recv bytes]
Health DockerHealth `json:"-" cbor:"5,keyasint"`
Status string `json:"-" cbor:"6,keyasint"`
Id string `json:"-" cbor:"7,keyasint"`
Image string `json:"-" cbor:"8,keyasint"`
Ports string `json:"-" cbor:"10,keyasint"`
Health DockerHealth `json:"-" cbor:"5,keyasint"`
Status string `json:"-" cbor:"6,keyasint"`
Id string `json:"-" cbor:"7,keyasint"`
Image string `json:"-" cbor:"8,keyasint"`
Ports string `json:"-" cbor:"10,keyasint"`
UpdateAvailable bool `json:"u,omitzero" cbor:"11,keyasint,omitzero"`
// PrevCpu [2]uint64 `json:"-"`
CpuSystem uint64 `json:"-"`
CpuContainer uint64 `json:"-"`
+9 -5
View File
@@ -59,11 +59,15 @@ type Stats struct {
// ZfsPool holds per-pool ZFS metrics for a single collection interval.
type ZfsPool struct {
Total float64 `json:"d" cbor:"0,keyasint"` // total capacity in GiB
Used float64 `json:"du" cbor:"1,keyasint"` // allocated in GiB
ReadBytes uint64 `json:"rb,omitzero" cbor:"2,keyasint,omitzero"` // read throughput in bytes/s
WriteBytes uint64 `json:"wb,omitzero" cbor:"3,keyasint,omitzero"` // write throughput in bytes/s
Health string `json:"h,omitempty" cbor:"4,keyasint,omitempty"` // ONLINE, DEGRADED, FAULTED, ...
DisplayName string `json:"n,omitempty" cbor:"8,keyasint,omitempty"`
HideUsage bool `json:"hu,omitempty" cbor:"6,keyasint,omitempty"` // equivalent filesystem usage chart exists
HideIO bool `json:"hi,omitempty" cbor:"7,keyasint,omitempty"` // equivalent filesystem I/O chart exists
Raw bool `json:"raw,omitempty" cbor:"5,keyasint,omitempty"`
Total float64 `json:"d" cbor:"0,keyasint"` // total capacity in GiB
Used float64 `json:"du" cbor:"1,keyasint"` // allocated in GiB
ReadBytes uint64 `json:"rb,omitzero" cbor:"2,keyasint,omitzero"` // read throughput in bytes/s
WriteBytes uint64 `json:"wb,omitzero" cbor:"3,keyasint,omitzero"` // write throughput in bytes/s
Health string `json:"h,omitempty" cbor:"4,keyasint,omitempty"` // ONLINE, DEGRADED, FAULTED, ...
}
// Uint8Slice wraps []uint8 to customize JSON encoding while keeping CBOR efficient.
+32 -8
View File
@@ -1,23 +1,47 @@
// Package zfs defines the ZFS detail data exchanged between agent and hub.
package zfs
import "strings"
// ZfsData is the detail payload returned by the agent for the GetZfsData action.
type ZfsData struct {
Pools []*PoolDetail `json:"pools,omitempty"`
Complete bool `json:"complete,omitempty"`
// Backends whose inventories are complete, even when another backend failed.
CompleteBackends []string `json:"completeBackends,omitempty"`
}
// CanRefreshPool also governs deletion: missing pools may only be removed
// after a successful inventory of their backend. Complete supports old agents.
func (data *ZfsData) CanRefreshPool(name string) bool {
if data.Complete {
return true
}
backend := "zfs"
if strings.HasPrefix(name, "b:") {
backend = "btrfs"
}
for _, complete := range data.CompleteBackends {
if complete == backend {
return true
}
}
return false
}
// PoolDetail holds the verbose state of a single pool: capacity, health,
// scrub, vdev, and dataset information.
type PoolDetail struct {
Name string `json:"name"`
Health string `json:"health,omitempty"`
Size uint64 `json:"size,omitempty"` // bytes
Alloc uint64 `json:"alloc,omitempty"` // bytes
Free uint64 `json:"free,omitempty"` // bytes
Scrub *Scrub `json:"scrub,omitempty"`
Vdevs []*Vdev `json:"vdevs,omitempty"`
Datasets []*Dataset `json:"datasets,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Raw bool `json:"raw,omitempty"`
Name string `json:"name"`
Health string `json:"health,omitempty"`
Size uint64 `json:"size,omitempty"` // bytes
Alloc uint64 `json:"alloc,omitempty"` // bytes
Free uint64 `json:"free,omitempty"` // bytes
Scrub *Scrub `json:"scrub,omitempty"`
Vdevs []*Vdev `json:"vdevs,omitempty"`
Datasets []*Dataset `json:"datasets,omitempty"`
}
// Scrub holds the scrub (or resilver) status of a pool.
+1 -1
View File
@@ -6,9 +6,9 @@ import (
"regexp"
"strings"
"time"
"uuid"
"github.com/blang/semver"
"github.com/google/uuid"
"github.com/henrygd/beszel"
"github.com/henrygd/beszel/internal/alerts"
"github.com/henrygd/beszel/internal/ghupdate"
+2 -2
View File
@@ -99,8 +99,8 @@ func setCollectionAuthSettings(app core.App) error {
}
if err := applyCollectionRules(app, []string{"fingerprints"}, collectionRules{
list: &systemScopedReadRule,
view: &systemScopedReadRule,
list: &systemScopedWriteRule,
view: &systemScopedWriteRule,
create: &systemScopedWriteRule,
update: &systemScopedWriteRule,
delete: &systemScopedWriteRule,
+4 -4
View File
@@ -88,8 +88,8 @@ func TestCollectionRulesDefault(t *testing.T) {
// fingerprints collection
fingerprintsCollection, err := hub.FindCollectionByNameOrId("fingerprints")
require.NoError(t, err, "Failed to find fingerprints collection")
assert.Equal(t, isUserInSystemUsers, *fingerprintsCollection.ListRule)
assert.Equal(t, isUserInSystemUsers, *fingerprintsCollection.ViewRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.ListRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.ViewRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.CreateRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.UpdateRule)
assert.Equal(t, isUserInSystemUsersNotReadonly, *fingerprintsCollection.DeleteRule)
@@ -216,8 +216,8 @@ func TestCollectionRulesShareAllSystems(t *testing.T) {
// fingerprints collection
fingerprintsCollection, err := hub.FindCollectionByNameOrId("fingerprints")
require.NoError(t, err, "Failed to find fingerprints collection")
assert.Equal(t, isUser, *fingerprintsCollection.ListRule)
assert.Equal(t, isUser, *fingerprintsCollection.ViewRule)
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.ListRule)
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.ViewRule)
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.CreateRule)
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.UpdateRule)
assert.Equal(t, isUserNotReadonly, *fingerprintsCollection.DeleteRule)
+1 -1
View File
@@ -6,8 +6,8 @@ import (
"log"
"os"
"path/filepath"
"uuid"
"github.com/google/uuid"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/pocketbase/dbx"
@@ -0,0 +1,46 @@
//go:build testing
package systems
import (
"testing"
"github.com/henrygd/beszel/internal/entities/container"
"github.com/pocketbase/dbx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateContainerRecordsPersistsImageUpdateAvailability(t *testing.T) {
_, app := newTestSystemWithHub(t)
const (
systemID = "system123"
containerID = "abcdef123456"
image = "nginx:latest"
)
data := &container.Stats{
Id: containerID,
Name: "web",
Image: image,
UpdateAvailable: true,
}
require.NoError(t, createContainerRecords(app, []*container.Stats{data}, systemID))
var record struct {
Image string `db:"image"`
UpdateAvailable bool `db:"updatable"`
}
require.NoError(t, app.DB().Select("image", "updatable").From("containers").
Where(dbx.HashExp{"id": containerID}).One(&record))
assert.Equal(t, image, record.Image)
assert.True(t, record.UpdateAvailable)
data.UpdateAvailable = false
require.NoError(t, createContainerRecords(app, []*container.Stats{data}, systemID))
require.NoError(t, app.DB().Select("image", "updatable").From("containers").
Where(dbx.HashExp{"id": containerID}).One(&record))
assert.Equal(t, image, record.Image)
assert.False(t, record.UpdateAvailable)
}
+17 -3
View File
@@ -272,7 +272,15 @@ func (sys *System) createRecords(data *system.CombinedData) (*core.Record, error
// update system record (do this last because it triggers alerts and we need above records to be inserted first)
systemRecord.Set("status", up)
systemRecord.Set("info", data.Info)
// Distinguish an idle GPU from a system without GPU data (#2312)
info := struct {
system.Info
GpuPct *float64 `json:"g,omitempty"`
}{Info: data.Info}
if len(data.Stats.GPUData) > 0 {
info.GpuPct = &data.Info.GpuPct
}
systemRecord.Set("info", info)
if err := txApp.SaveNoValidate(systemRecord); err != nil {
return err
}
@@ -322,6 +330,11 @@ func createSystemdStatsRecords(app core.App, data []*systemd.Service, systemId s
valueStrings := make([]string, 0, len(data))
for i, service := range data {
// Agent payloads can contain null entries. Reject the snapshot before
// executing any queries so existing service records remain intact.
if service == nil {
return fmt.Errorf("null systemd service at index %d", i)
}
suffix := fmt.Sprintf("%d", i)
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:state%[1]s}, {:sub%[1]s}, {:cpu%[1]s}, {:cpuPeak%[1]s}, {:memory%[1]s}, {:memPeak%[1]s}, {:updated})", suffix))
params["id"+suffix] = makeStableHashId(systemId, service.Name)
@@ -363,7 +376,7 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
valueStrings := make([]string, 0, len(data))
for i, container := range data {
suffix := fmt.Sprintf("%d", i)
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:image%[1]s}, {:ports%[1]s}, {:status%[1]s}, {:health%[1]s}, {:cpu%[1]s}, {:memory%[1]s}, {:net%[1]s}, {:updated})", suffix))
valueStrings = append(valueStrings, fmt.Sprintf("({:id%[1]s}, {:system}, {:name%[1]s}, {:image%[1]s}, {:ports%[1]s}, {:status%[1]s}, {:health%[1]s}, {:cpu%[1]s}, {:memory%[1]s}, {:net%[1]s}, {:updateAvailable%[1]s}, {:updated})", suffix))
params["id"+suffix] = container.Id
params["name"+suffix] = container.Name
params["image"+suffix] = container.Image
@@ -377,9 +390,10 @@ func createContainerRecords(app core.App, data []*container.Stats, systemId stri
netBytes = uint64((container.NetworkSent + container.NetworkRecv) * 1024 * 1024)
}
params["net"+suffix] = netBytes
params["updateAvailable"+suffix] = container.UpdateAvailable
}
queryString := fmt.Sprintf(
"INSERT INTO containers (id, system, name, image, ports, status, health, cpu, memory, net, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, image = excluded.image, ports = excluded.ports, status = excluded.status, health = excluded.health, cpu = excluded.cpu, memory = excluded.memory, net = excluded.net, updated = excluded.updated",
"INSERT INTO containers (id, system, name, image, ports, status, health, cpu, memory, net, updatable, updated) VALUES %s ON CONFLICT(id) DO UPDATE SET system = excluded.system, name = excluded.name, image = excluded.image, ports = excluded.ports, status = excluded.status, health = excluded.health, cpu = excluded.cpu, memory = excluded.memory, net = excluded.net, updatable = excluded.updatable, updated = excluded.updated",
strings.Join(valueStrings, ","),
)
_, err := app.DB().NewQuery(queryString).Bind(params).Execute()
+44
View File
@@ -0,0 +1,44 @@
//go:build testing
package systems
import (
"testing"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCreateRecordsGPUUtilization(t *testing.T) {
sys, app := newTestSystemWithHub(t)
for _, tc := range []struct {
name string
gpu bool
usage float64
}{
{"no GPU", false, 0},
{"active GPU", true, 42.5},
{"idle GPU", true, 0},
{"GPU removed", false, 0},
} {
t.Run(tc.name, func(t *testing.T) {
data := &system.CombinedData{Info: system.Info{GpuPct: tc.usage, Cpu: 12.5}}
if tc.gpu {
data.Stats.GPUData = map[string]system.GPUData{"0": {Name: "GPU", Usage: tc.usage}}
}
_, err := sys.createRecords(data)
require.NoError(t, err)
record, err := app.FindRecordById("systems", sys.Id)
require.NoError(t, err)
var info map[string]any
require.NoError(t, record.UnmarshalJSONField("info", &info))
assert.Equal(t, 12.5, info["cpu"])
if tc.gpu {
assert.Equal(t, tc.usage, info["g"])
} else {
assert.NotContains(t, info, "g")
}
})
}
}
+18 -11
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"sync"
"time"
"github.com/henrygd/beszel/internal/hub/ws"
@@ -42,13 +43,17 @@ var errSystemExists = errors.New("system exists")
// SystemManager manages a collection of monitored systems and their connections.
// It handles system lifecycle, status updates, and maintains both SSH and WebSocket connections.
type SystemManager struct {
hub hubLike // Hub interface for database and alert operations
systems *store.Store[string, *System] // Thread-safe store of active systems
sshConfig *ssh.ClientConfig // SSH client configuration for system connections
smartFetchMap *expirymap.ExpiryMap[smartFetchState] // Stores last SMART fetch time/result; TTL is only for cleanup
zfsFetchMap *expirymap.ExpiryMap[zfsFetchState] // Stores last ZFS fetch time/result; TTL is only for cleanup
ctx context.Context // Cancelled when the app terminates
cancel context.CancelFunc // Cancels ctx and all child system contexts
hub hubLike // Hub interface for database and alert operations
systems *store.Store[string, *System] // Thread-safe store of active systems
sshConfig *ssh.ClientConfig // SSH client configuration for system connections
smartFetchMap *expirymap.ExpiryMap[smartFetchState] // Stores last SMART fetch time/result; TTL is only for cleanup
zfsFetchMap *expirymap.ExpiryMap[zfsFetchState] // Stores last ZFS fetch time/result; TTL is only for cleanup
realtimeMutex sync.Mutex // Protects all realtime worker and subscription state
activeSubscriptions map[string]*subscriptionInfo // Realtime subscriptions keyed by system ID
realtimeWorkerStop chan struct{} // Stops the current realtime worker generation
realtimeWorkerRun bool // Whether a realtime worker has been started
ctx context.Context // Cancelled when the app terminates
cancel context.CancelFunc // Cancels ctx and all child system contexts
}
// hubLike defines the interface requirements for the hub dependency.
@@ -67,10 +72,11 @@ type hubLike interface {
// The hub must implement the hubLike interface to provide database and alert functionality.
func NewSystemManager(hub hubLike) *SystemManager {
sm := &SystemManager{
systems: store.New(map[string]*System{}),
hub: hub,
smartFetchMap: expirymap.New[smartFetchState](time.Hour),
zfsFetchMap: expirymap.New[zfsFetchState](time.Hour),
systems: store.New(map[string]*System{}),
hub: hub,
smartFetchMap: expirymap.New[smartFetchState](time.Hour),
zfsFetchMap: expirymap.New[zfsFetchState](time.Hour),
activeSubscriptions: make(map[string]*subscriptionInfo),
}
sm.ctx, sm.cancel = context.WithCancel(context.Background())
return sm
@@ -138,6 +144,7 @@ func (sm *SystemManager) bindEventHooks() {
// onTerminate cancels SystemManager context on app shutdown
func (sm *SystemManager) onTerminate(e *core.TerminateEvent) error {
sm.cancel()
sm.stopRealtimeWorker()
return e.Next()
}
+135 -64
View File
@@ -3,25 +3,27 @@ package systems
import (
"encoding/json"
"strings"
"sync"
"time"
"github.com/henrygd/beszel/internal/common"
"github.com/henrygd/beszel/internal/hub/utils"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/subscriptions"
)
type subscriptionInfo struct {
subscription string
connectedClients uint8
connectedClients int
fetching bool
}
var (
activeSubscriptions = make(map[string]*subscriptionInfo)
workerRunning bool
tickerStopChan chan struct{}
realtimeMutex sync.Mutex
)
type realtimeFetch struct {
systemID string
subscription string
info *subscriptionInfo
}
// onRealtimeConnectRequest handles client connection events for realtime subscriptions.
// It cleans up existing subscriptions when a client connects.
@@ -38,6 +40,19 @@ func (sm *SystemManager) onRealtimeConnectRequest(e *core.RealtimeConnectRequest
// onRealtimeSubscribeRequest handles client subscription events for realtime metrics.
// It tracks new subscriptions and unsubscriptions to manage the realtime worker lifecycle.
func (sm *SystemManager) onRealtimeSubscribeRequest(e *core.RealtimeSubscribeRequestEvent) error {
// Parse with PocketBase's own subscription parser before changing the real
// client. Reject the entire request if any metrics target is inaccessible.
requested := subscriptions.NewDefaultClient()
requested.Subscribe(e.Subscriptions...)
for topic, options := range requested.Subscriptions() {
if !strings.HasPrefix(topic, "rt_metrics") {
continue
}
system, err := sm.GetSystem(options.Query["system"])
if err != nil || !system.HasUser(e.App, e.Auth) {
return e.NotFoundError("", nil)
}
}
oldSubs := e.Client.Subscriptions()
// after e.Next() is the result of the subscribe request
err := e.Next()
@@ -47,14 +62,7 @@ func (sm *SystemManager) onRealtimeSubscribeRequest(e *core.RealtimeSubscribeReq
for k, options := range newSubs {
if _, ok := oldSubs[k]; !ok {
if strings.HasPrefix(k, "rt_metrics") {
systemId := options.Query["system"]
if _, ok := activeSubscriptions[systemId]; !ok {
activeSubscriptions[systemId] = &subscriptionInfo{
subscription: k,
}
}
activeSubscriptions[systemId].connectedClients += 1
sm.onRealtimeSubscriptionAdded()
sm.addRealtimeSubscription(options.Query["system"], k)
}
}
}
@@ -68,72 +76,76 @@ func (sm *SystemManager) onRealtimeSubscribeRequest(e *core.RealtimeSubscribeReq
return err
}
// onRealtimeSubscriptionAdded initializes or starts the realtime worker when the first subscription is added.
// It ensures only one worker runs at a time.
func (sm *SystemManager) onRealtimeSubscriptionAdded() {
realtimeMutex.Lock()
defer realtimeMutex.Unlock()
// addRealtimeSubscription tracks a subscriber and starts a worker if necessary.
func (sm *SystemManager) addRealtimeSubscription(systemID, subscription string) {
sm.realtimeMutex.Lock()
defer sm.realtimeMutex.Unlock()
// Start the worker if it's not already running
if !workerRunning {
workerRunning = true
// Create a new stop channel for this worker instance
tickerStopChan = make(chan struct{})
go sm.startRealtimeWorker()
if sm.activeSubscriptions == nil {
sm.activeSubscriptions = make(map[string]*subscriptionInfo)
}
info, ok := sm.activeSubscriptions[systemID]
if !ok {
info = &subscriptionInfo{subscription: subscription}
sm.activeSubscriptions[systemID] = info
}
info.connectedClients++
if !sm.realtimeWorkerRun {
sm.realtimeWorkerRun = true
stop := make(chan struct{})
sm.realtimeWorkerStop = stop
go sm.startRealtimeWorker(stop)
}
}
// checkSubscriptions stops the realtime worker when there are no active subscriptions.
// This prevents unnecessary resource usage when no clients are listening for realtime data.
func (sm *SystemManager) checkSubscriptions() {
if !workerRunning || len(activeSubscriptions) > 0 {
// stopRealtimeWorker stops the current worker generation, if any.
func (sm *SystemManager) stopRealtimeWorker() {
sm.realtimeMutex.Lock()
defer sm.realtimeMutex.Unlock()
sm.stopRealtimeWorkerLocked()
}
func (sm *SystemManager) stopRealtimeWorkerLocked() {
if !sm.realtimeWorkerRun {
return
}
realtimeMutex.Lock()
defer realtimeMutex.Unlock()
// Signal the worker to stop
if tickerStopChan != nil {
select {
case tickerStopChan <- struct{}{}:
default:
}
}
// Mark worker as stopped (will be reset when next subscription comes in)
workerRunning = false
close(sm.realtimeWorkerStop)
sm.realtimeWorkerStop = nil
sm.realtimeWorkerRun = false
}
// removeRealtimeSubscription removes a realtime subscription and checks if the worker should be stopped.
// It only processes subscriptions with the "rt_metrics" prefix and triggers cleanup when subscriptions are removed.
func (sm *SystemManager) removeRealtimeSubscription(subscription string, options subscriptions.SubscriptionOptions) {
if strings.HasPrefix(subscription, "rt_metrics") {
systemId := options.Query["system"]
if info, ok := activeSubscriptions[systemId]; ok {
info.connectedClients -= 1
systemID := options.Query["system"]
sm.realtimeMutex.Lock()
if info, ok := sm.activeSubscriptions[systemID]; ok {
info.connectedClients--
if info.connectedClients <= 0 {
delete(activeSubscriptions, systemId)
delete(sm.activeSubscriptions, systemID)
}
}
sm.checkSubscriptions()
if len(sm.activeSubscriptions) == 0 {
sm.stopRealtimeWorkerLocked()
}
sm.realtimeMutex.Unlock()
}
}
// startRealtimeWorker runs the main loop for fetching realtime data from agents.
// It continuously fetches system data and broadcasts it to subscribed clients via WebSocket.
func (sm *SystemManager) startRealtimeWorker() {
func (sm *SystemManager) startRealtimeWorker(stop <-chan struct{}) {
sm.fetchRealtimeDataAndNotify()
tick := time.Tick(1 * time.Second)
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-tickerStopChan:
case <-stop:
return
case <-tick:
if len(activeSubscriptions) == 0 {
return
}
case <-ticker.C:
sm.fetchRealtimeDataAndNotify()
}
}
@@ -141,27 +153,79 @@ func (sm *SystemManager) startRealtimeWorker() {
// fetchRealtimeDataAndNotify fetches realtime data for all active subscriptions and notifies the clients.
func (sm *SystemManager) fetchRealtimeDataAndNotify() {
for systemId, info := range activeSubscriptions {
system, err := sm.GetSystem(systemId)
for _, fetch := range sm.claimRealtimeFetches() {
system, err := sm.GetSystem(fetch.systemID)
if err != nil {
sm.finishRealtimeFetch(fetch)
continue
}
go func() {
go func(fetch realtimeFetch) {
defer sm.finishRealtimeFetch(fetch)
data, err := system.fetchDataFromAgent(common.DataRequestOptions{CacheTimeMs: 1000})
if err != nil {
return
}
bytes, err := json.Marshal(data)
if err == nil {
notify(sm.hub, info.subscription, bytes)
notify(sm.hub, system, fetch.subscription, bytes)
}
}()
}(fetch)
}
}
// claimRealtimeFetches takes a stable snapshot and marks each selected system as
// in flight. Slow agents are skipped on later ticks until their fetch completes.
func (sm *SystemManager) claimRealtimeFetches() []realtimeFetch {
sm.realtimeMutex.Lock()
defer sm.realtimeMutex.Unlock()
fetches := make([]realtimeFetch, 0, len(sm.activeSubscriptions))
for systemID, info := range sm.activeSubscriptions {
if info.fetching {
continue
}
info.fetching = true
fetches = append(fetches, realtimeFetch{
systemID: systemID,
subscription: info.subscription,
info: info,
})
}
return fetches
}
func (sm *SystemManager) finishRealtimeFetch(fetch realtimeFetch) {
sm.realtimeMutex.Lock()
defer sm.realtimeMutex.Unlock()
// A subscription may have been removed and recreated while the old request
// was running. Only release the exact entry claimed by this request.
if info := sm.activeSubscriptions[fetch.systemID]; info == fetch.info {
info.fetching = false
}
}
// notify broadcasts realtime data to all clients subscribed to a specific subscription.
// It iterates through all connected clients and sends the data only to those with matching subscriptions.
func notify(app core.App, subscription string, data []byte) error {
// Custom topics bypass collection rules, so check current access for every
// recipient, including clients whose authentication or membership was revoked.
func notify(app core.App, system *System, subscription string, data []byte) error {
shareAll, _ := utils.GetEnv("SHARE_ALL_SYSTEMS")
members := make(map[string]struct{})
if shareAll != "true" {
// Refresh once per broadcast so membership changes take effect on the
// next update without querying the database for every recipient.
var recordData struct{ Users string }
if err := app.DB().NewQuery("SELECT users FROM systems WHERE id={:id}").
Bind(dbx.Params{"id": system.Id}).One(&recordData); err != nil {
return err
}
var userIDs []string
if err := json.Unmarshal([]byte(recordData.Users), &userIDs); err != nil {
return err
}
for _, id := range userIDs {
members[id] = struct{}{}
}
}
message := subscriptions.Message{
Name: subscription,
Data: data,
@@ -170,6 +234,13 @@ func notify(app core.App, subscription string, data []byte) error {
if !client.HasSubscription(subscription) {
continue
}
auth, _ := client.Get(apis.RealtimeClientAuthKey).(*core.Record)
if auth == nil {
continue
}
if _, member := members[auth.Id]; shareAll != "true" && !member {
continue
}
client.Send(message)
}
return nil
@@ -0,0 +1,229 @@
package systems
import (
"testing"
"time"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
pbtests "github.com/pocketbase/pocketbase/tests"
"github.com/pocketbase/pocketbase/tools/hook"
"github.com/pocketbase/pocketbase/tools/store"
"github.com/pocketbase/pocketbase/tools/subscriptions"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRealtimeAuthorization(t *testing.T) {
t.Setenv("SHARE_ALL_SYSTEMS", "false")
t.Setenv("BESZEL_HUB_SHARE_ALL_SYSTEMS", "")
app, err := pbtests.NewTestApp(t.TempDir())
require.NoError(t, err)
t.Cleanup(app.Cleanup)
_, err = app.DB().NewQuery(`CREATE TABLE IF NOT EXISTS systems (id TEXT PRIMARY KEY, users TEXT)`).Execute()
require.NoError(t, err)
_, err = app.DB().NewQuery(`INSERT INTO systems (id, users) VALUES ('target', '["member"]')`).Execute()
require.NoError(t, err)
member := core.NewRecord(core.NewAuthCollection("users"))
member.Id = "member"
outsider := core.NewRecord(member.Collection())
outsider.Id = "outsider"
system := &System{Id: "target"}
sm := newRealtimeTestManager()
sm.systems.Set(system.Id, system)
// Keep the lifecycle bookkeeping active without starting an agent worker.
sm.realtimeWorkerRun = true
sm.realtimeWorkerStop = make(chan struct{})
t.Cleanup(sm.stopRealtimeWorker)
topic := `rt_metrics?options={"query":{"system":"target"}}`
for _, tc := range []struct {
name string
auth *core.Record
topic string
share bool
allowed bool
}{
{"guest", nil, topic, false, false},
{"outsider", outsider, topic, false, false},
{"member", member, topic, false, true},
{"missing system", member, `rt_metrics`, false, false},
{"unknown system", member, `rt_metrics?options={"query":{"system":"missing"}}`, false, false},
{"malformed options", member, `rt_metrics?options=invalid`, false, false},
{"prefix variant", outsider, `rt_metrics_extra?options={"query":{"system":"target"}}`, false, false},
{"shared outsider", outsider, topic, true, true},
{"shared guest", nil, topic, true, false},
{"other topic", nil, "systems/*", false, true},
} {
t.Run(tc.name, func(t *testing.T) {
if tc.share {
t.Setenv("BESZEL_HUB_SHARE_ALL_SYSTEMS", "true")
}
client := subscriptions.NewDefaultClient()
client.Subscribe("existing")
e := &core.RealtimeSubscribeRequestEvent{
RequestEvent: &core.RequestEvent{App: app, Auth: tc.auth},
Client: client, Subscriptions: []string{tc.topic},
}
called := false
h := &hook.Hook[*core.RealtimeSubscribeRequestEvent]{}
h.BindFunc(sm.onRealtimeSubscribeRequest)
err := h.Trigger(e, func(e *core.RealtimeSubscribeRequestEvent) error {
called = true
client.Unsubscribe()
client.Subscribe(e.Subscriptions...)
return nil
})
if tc.allowed {
require.NoError(t, err)
assert.True(t, called)
} else {
require.Error(t, err)
assert.False(t, called)
assert.True(t, client.HasSubscription("existing"))
assert.False(t, client.HasSubscription(tc.topic))
}
})
}
t.Run("broadcast checks current access", func(t *testing.T) {
client := subscriptions.NewDefaultClient()
client.Subscribe(topic)
app.SubscriptionsBroker().Register(client)
defer app.SubscriptionsBroker().Unregister(client.Id())
secondClient := subscriptions.NewDefaultClient()
secondClient.Subscribe(topic)
app.SubscriptionsBroker().Register(secondClient)
defer app.SubscriptionsBroker().Unregister(secondClient.Id())
check := func(auth *core.Record, allowed bool) {
t.Helper()
client.Set(apis.RealtimeClientAuthKey, auth)
secondClient.Set(apis.RealtimeClientAuthKey, auth)
done := make(chan struct{})
go func() {
notify(app, system, topic, []byte(`{"cpu":42}`))
close(done)
}()
// Even on failure, drain pending sends and join the broadcaster before
// unregistering clients, which closes their channels.
defer func() {
for {
select {
case <-client.Channel():
case <-secondClient.Channel():
case <-done:
return
}
}
}()
var received [2]int
timer := time.NewTimer(time.Second)
defer timer.Stop()
for {
select {
case msg := <-client.Channel():
received[0]++
assert.Equal(t, topic, msg.Name)
case msg := <-secondClient.Channel():
received[1]++
assert.Equal(t, topic, msg.Name)
case <-done:
want := [2]int{}
if allowed {
want = [2]int{1, 1}
}
assert.Equal(t, want, received)
return
case <-timer.C:
t.Fatal("broadcast did not finish")
}
}
}
check(nil, false)
check(outsider, false)
check(member, true)
_, err := app.DB().NewQuery(`UPDATE systems SET users = '[]'`).Execute()
require.NoError(t, err)
check(member, false)
t.Setenv("BESZEL_HUB_SHARE_ALL_SYSTEMS", "true")
check(outsider, true)
check(nil, false)
})
}
func newRealtimeTestManager() *SystemManager {
return &SystemManager{
systems: store.New(map[string]*System{}),
activeSubscriptions: make(map[string]*subscriptionInfo),
}
}
func TestRealtimeFetchesDoNotOverlapPerSystem(t *testing.T) {
sm := newRealtimeTestManager()
sm.activeSubscriptions["one"] = &subscriptionInfo{subscription: "rt_metrics_one"}
sm.activeSubscriptions["two"] = &subscriptionInfo{subscription: "rt_metrics_two"}
first := sm.claimRealtimeFetches()
require.Len(t, first, 2)
assert.Empty(t, sm.claimRealtimeFetches())
sm.finishRealtimeFetch(first[0])
next := sm.claimRealtimeFetches()
require.Len(t, next, 1)
assert.Equal(t, first[0].systemID, next[0].systemID)
sm.finishRealtimeFetch(first[1])
sm.finishRealtimeFetch(next[0])
}
func TestFinishingOldRealtimeFetchDoesNotReleaseReplacement(t *testing.T) {
sm := newRealtimeTestManager()
oldInfo := &subscriptionInfo{subscription: "old"}
sm.activeSubscriptions["system"] = oldInfo
fetch := sm.claimRealtimeFetches()[0]
newInfo := &subscriptionInfo{subscription: "new", fetching: true}
sm.activeSubscriptions["system"] = newInfo
sm.finishRealtimeFetch(fetch)
assert.True(t, newInfo.fetching)
}
func TestRealtimeSubscriptionLifecycle(t *testing.T) {
sm := newRealtimeTestManager()
options := subscriptions.SubscriptionOptions{Query: map[string]string{"system": "system"}}
sm.addRealtimeSubscription("system", "rt_metrics")
sm.addRealtimeSubscription("system", "rt_metrics")
sm.realtimeMutex.Lock()
firstStop := sm.realtimeWorkerStop
assert.True(t, sm.realtimeWorkerRun)
assert.Equal(t, 2, sm.activeSubscriptions["system"].connectedClients)
sm.realtimeMutex.Unlock()
sm.removeRealtimeSubscription("rt_metrics", options)
sm.realtimeMutex.Lock()
assert.True(t, sm.realtimeWorkerRun)
assert.Equal(t, 1, sm.activeSubscriptions["system"].connectedClients)
sm.realtimeMutex.Unlock()
sm.removeRealtimeSubscription("rt_metrics", options)
sm.realtimeMutex.Lock()
assert.False(t, sm.realtimeWorkerRun)
assert.Empty(t, sm.activeSubscriptions)
sm.realtimeMutex.Unlock()
select {
case <-firstStop:
default:
t.Fatal("worker stop channel was not closed")
}
// A later subscription must get a new stop channel owned by its worker.
sm.addRealtimeSubscription("system", "rt_metrics")
sm.realtimeMutex.Lock()
secondStop := sm.realtimeWorkerStop
assert.NotEqual(t, firstStop, secondStop)
sm.realtimeMutex.Unlock()
sm.stopRealtimeWorker()
}
+27 -11
View File
@@ -32,13 +32,12 @@ func (sys *System) FetchAndSaveZfsPools(force bool) error {
sys.recordZfsFetchResult(err, 0)
return err
}
if zfsData == nil || !zfsData.Complete {
err = errIncompleteZfsData
sys.recordZfsFetchResult(err, 0)
return err
}
err = sys.saveZfsPools(zfsData)
sys.recordZfsFetchResult(err, len(zfsData.Pools))
poolCount := 0
if zfsData != nil {
poolCount = len(zfsData.Pools)
}
sys.recordZfsFetchResult(err, poolCount)
return err
}
@@ -79,7 +78,7 @@ func (sys *System) zfsFetchInterval() time.Duration {
// saveZfsPools saves ZFS pool detail data to the zfs_pools collection and
// removes records for pools no longer reported by a complete agent inventory.
func (sys *System) saveZfsPools(zfsData *zfs.ZfsData) error {
if zfsData == nil || !zfsData.Complete {
if zfsData == nil || (!zfsData.CanRefreshPool("zfs") && !zfsData.CanRefreshPool("b:")) {
return errIncompleteZfsData
}
@@ -89,10 +88,10 @@ func (sys *System) saveZfsPools(zfsData *zfs.ZfsData) error {
return err
}
return hub.RunInTransaction(func(txApp core.App) error {
err = hub.RunInTransaction(func(txApp core.App) error {
alive := make(map[string]bool, len(zfsData.Pools))
for _, pool := range zfsData.Pools {
if pool == nil {
if pool == nil || !zfsData.CanRefreshPool(pool.Name) {
continue
}
alive[pool.Name] = true
@@ -111,7 +110,7 @@ func (sys *System) saveZfsPools(zfsData *zfs.ZfsData) error {
return err
}
for _, record := range existing {
if !alive[record.GetString("name")] {
if name := record.GetString("name"); zfsData.CanRefreshPool(name) && !alive[name] {
if err := txApp.Delete(record); err != nil {
return err
}
@@ -119,6 +118,14 @@ func (sys *System) saveZfsPools(zfsData *zfs.ZfsData) error {
}
return nil
})
if err != nil {
return err
}
// Report partial failure only after committing healthy backend updates.
if !zfsData.Complete {
return errIncompleteZfsData
}
return nil
}
func (sys *System) upsertZfsPoolRecord(app core.App, collection *core.Collection, pool *zfs.PoolDetail) error {
@@ -135,10 +142,12 @@ func (sys *System) upsertZfsPoolRecord(app core.App, collection *core.Collection
record.Set("system", sys.Id)
record.Set("name", pool.Name)
record.Set("display_name", pool.DisplayName)
record.Set("health", pool.Health)
record.Set("size", pool.Size)
record.Set("alloc", pool.Alloc)
record.Set("free", pool.Free)
record.Set("raw", pool.Raw)
record.Set("scrub", pool.Scrub)
record.Set("vdevs", pool.Vdevs)
record.Set("datasets", pool.Datasets)
@@ -172,7 +181,9 @@ func (sys *System) syncZfsPoolHealth(app core.App, pools map[string]*system.ZfsP
record.Set("id", recordID)
record.Set("system", sys.Id)
record.Set("name", name)
record.Set("display_name", pool.DisplayName)
record.Set("health", pool.Health)
record.Set("raw", pool.Raw)
record.Set("size", uint64(pool.Total*gib))
record.Set("alloc", uint64(pool.Used*gib))
record.Set("free", uint64(max(pool.Total-pool.Used, 0)*gib))
@@ -181,10 +192,15 @@ func (sys *System) syncZfsPoolHealth(app core.App, pools map[string]*system.ZfsP
}
continue
}
if record.GetString("health") == pool.Health {
if record.GetString("health") == pool.Health && record.GetBool("raw") == pool.Raw && record.GetString("display_name") == pool.DisplayName {
continue
}
record.Set("display_name", pool.DisplayName)
record.Set("health", pool.Health)
record.Set("raw", pool.Raw)
record.Set("size", uint64(pool.Total*gib))
record.Set("alloc", uint64(pool.Used*gib))
record.Set("free", uint64(max(pool.Total-pool.Used, 0)*gib))
if err := app.SaveNoValidate(record); err != nil {
return fmt.Errorf("updating ZFS pool health %q: %w", name, err)
}
+77
View File
@@ -123,6 +123,44 @@ func TestSaveZfsPoolsIncompletePreservesRecords(t *testing.T) {
assert.Len(t, records, 1)
}
func TestSavePartialBackendInventory(t *testing.T) {
for _, healthy := range []string{"zfs", "btrfs"} {
t.Run(healthy, func(t *testing.T) {
sys, app := newTestSystemWithHub(t)
healthyKey, failedKey := "tank", "b:uuid"
if healthy == "btrfs" {
healthyKey, failedKey = failedKey, healthyKey
}
initial := &zfs.ZfsData{Complete: true, Pools: []*zfs.PoolDetail{
{Name: healthyKey, Alloc: 10}, {Name: failedKey, Alloc: 10},
}}
require.NoError(t, sys.saveZfsPools(initial))
failedID := makeStableHashId(sys.Id, failedKey)
before, err := app.FindRecordById("zfs_pools", failedID)
require.NoError(t, err)
partial := &zfs.ZfsData{CompleteBackends: []string{healthy}, Pools: []*zfs.PoolDetail{
{Name: healthyKey, Alloc: 20}, {Name: failedKey, Alloc: 99},
}}
assert.ErrorIs(t, sys.saveZfsPools(partial), errIncompleteZfsData)
fresh, err := app.FindRecordById("zfs_pools", makeStableHashId(sys.Id, healthyKey))
require.NoError(t, err)
assert.EqualValues(t, 20, fresh.GetInt("alloc"))
cached, err := app.FindRecordById("zfs_pools", failedID)
require.NoError(t, err)
assert.EqualValues(t, 10, cached.GetInt("alloc"))
assert.Equal(t, before.GetDateTime("details_updated"), cached.GetDateTime("details_updated"))
// An empty successful backend can prune, even while the other fails.
partial.Pools = nil
assert.ErrorIs(t, sys.saveZfsPools(partial), errIncompleteZfsData)
records, err := app.FindRecordsByFilter("zfs_pools", "system={:system}", "", 0, 0, map[string]any{"system": sys.Id})
require.NoError(t, err)
require.Len(t, records, 1)
assert.Equal(t, failedKey, records[0].GetString("name"))
require.NoError(t, sys.saveZfsPools(&zfs.ZfsData{Complete: true}))
})
}
}
func TestSyncZfsPoolHealthWritesOnlyTransitions(t *testing.T) {
sys, app := newTestSystemWithHub(t)
collection, err := app.FindCachedCollectionByNameOrId("zfs_pools")
@@ -151,3 +189,42 @@ func TestSyncZfsPoolHealthWritesOnlyTransitions(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, "DEGRADED", record.GetString("health"))
}
func TestZfsRawCapacityPersistence(t *testing.T) {
sys, app := newTestSystemWithHub(t)
require.NoError(t, sys.saveZfsPools(&zfs.ZfsData{Complete: true, Pools: []*zfs.PoolDetail{{Name: "btrfs", Size: 200, Alloc: 10, Raw: true}}}))
record, err := app.FindRecordById("zfs_pools", makeStableHashId(sys.Id, "btrfs"))
require.NoError(t, err)
require.True(t, record.GetBool("raw"))
require.NoError(t, sys.syncZfsPoolHealth(app, map[string]*system.ZfsPool{"btrfs": {Total: 1, Used: 0.25}}))
record, err = app.FindRecordById("zfs_pools", record.Id)
require.NoError(t, err)
assert.False(t, record.GetBool("raw"))
assert.EqualValues(t, 1024*1024*1024, record.GetInt("size"))
}
func TestBtrfsDisplayNameKeepsRecordIdentity(t *testing.T) {
sys, app := newTestSystemWithHub(t)
key := "b:11111111-1111-4111-8111-111111111111"
require.NoError(t, sys.syncZfsPoolHealth(app, map[string]*system.ZfsPool{
key: {DisplayName: "tank", Health: "ONLINE"},
"tank": {Health: "ONLINE"},
}))
id := makeStableHashId(sys.Id, key)
record, err := app.FindRecordById("zfs_pools", id)
require.NoError(t, err)
assert.Equal(t, "tank", record.GetString("display_name"))
require.NoError(t, sys.syncZfsPoolHealth(app, map[string]*system.ZfsPool{key: {DisplayName: "renamed", Health: "ONLINE"}}))
record, err = app.FindRecordById("zfs_pools", id)
require.NoError(t, err)
assert.Equal(t, key, record.GetString("name"))
assert.Equal(t, "renamed", record.GetString("display_name"))
require.NoError(t, sys.saveZfsPools(&zfs.ZfsData{Complete: true, Pools: []*zfs.PoolDetail{
{Name: key, DisplayName: "detail name", Health: "ONLINE"}, {Name: "tank", Health: "ONLINE"},
}}))
record, err = app.FindRecordById("zfs_pools", id)
require.NoError(t, err)
assert.Equal(t, "detail name", record.GetString("display_name"))
_, err = app.FindRecordById("zfs_pools", makeStableHashId(sys.Id, "tank"))
require.NoError(t, err)
}
@@ -3,9 +3,11 @@
package systems_test
import (
"encoding/json"
"testing"
"time"
"github.com/fxamacker/cbor/v2"
"github.com/henrygd/beszel/internal/entities/system"
"github.com/henrygd/beszel/internal/entities/systemd"
"github.com/henrygd/beszel/internal/hub/systems"
@@ -15,6 +17,42 @@ import (
"github.com/stretchr/testify/require"
)
func TestCreateRecordsRejectsNullSystemdService(t *testing.T) {
hub, user := tests.GetHubWithUser(t)
defer hub.Cleanup()
records, err := tests.CreateSystems(hub, 1, user.Id, "paused")
require.NoError(t, err)
sys, err := hub.GetSystemManager().GetSystem(records[0].Id)
require.NoError(t, err)
require.NoError(t, systems.CreateSystemdStatsRecords(hub, []*systemd.Service{
{Name: "existing.service", State: systemd.StatusFailed},
}, records[0].Id))
for _, services := range []string{`[null]`, `[{"name":"new.service"},null]`, `[null,{"name":"new.service"}]`} {
for _, encoding := range []string{"json", "cbor"} {
t.Run(encoding+"/"+services, func(t *testing.T) {
var data system.CombinedData
require.NoError(t, json.Unmarshal([]byte(`{"systemd":`+services+`}`), &data))
if encoding == "cbor" {
encoded, err := cbor.Marshal(data)
require.NoError(t, err)
data = system.CombinedData{}
require.NoError(t, cbor.Unmarshal(encoded, &data))
}
_, err := sys.CreateRecords(&data)
require.ErrorContains(t, err, "null systemd service")
var names []string
require.NoError(t, hub.DB().Select("name").From("systemd_services").
Where(dbx.HashExp{"system": records[0].Id}).Column(&names))
assert.Equal(t, []string{"existing.service"}, names)
count, err := hub.CountRecords("system_stats", dbx.HashExp{"system": records[0].Id})
require.NoError(t, err)
assert.Zero(t, count, "invalid snapshot must roll back system stats")
})
}
}
}
func TestCreateRecordsHandlesSystemdAlertLifecycle(t *testing.T) {
hub, user := tests.GetHubWithUser(t)
defer hub.Cleanup()
@@ -0,0 +1,27 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
c, err := app.FindCollectionByNameOrId("zfs_pools")
if err != nil {
return err
}
c.Fields.Add(&core.TextField{Name: "display_name"})
c.Fields.Add(&core.BoolField{Name: "raw"})
return app.Save(c)
}, func(app core.App) error {
c, err := app.FindCollectionByNameOrId("zfs_pools")
if err != nil {
return err
}
c.Fields.RemoveByName("display_name")
c.Fields.RemoveByName("raw")
return app.Save(c)
})
}
@@ -0,0 +1,24 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("containers")
if err != nil {
return err
}
collection.Fields.Add(&core.BoolField{Name: "updatable"})
return app.Save(collection)
}, func(app core.App) error {
collection, err := app.FindCollectionByNameOrId("containers")
if err != nil {
return err
}
collection.Fields.RemoveByName("updatable")
return app.Save(collection)
})
}
+14 -3
View File
@@ -198,6 +198,7 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats {
var fanSums map[string]uint64
fanCount := uint64(0)
zfsPoolCounts := make(map[string]uint64)
zfsCapacityCounts := make(map[string]uint64)
// Accumulate totals
for i := range records {
@@ -350,9 +351,19 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats {
}
pool := sum.ZfsPools[name]
if pool == nil {
pool = &system.ZfsPool{}
pool = &system.ZfsPool{HideUsage: value.HideUsage, HideIO: value.HideIO}
sum.ZfsPools[name] = pool
}
// Never average physical and usable capacity into the same value.
if pool.Raw != value.Raw {
pool.Total, pool.Used = 0, 0
zfsCapacityCounts[name] = 0
}
pool.HideUsage = pool.HideUsage && value.HideUsage
pool.HideIO = pool.HideIO && value.HideIO
pool.DisplayName = value.DisplayName
pool.Raw = value.Raw
zfsCapacityCounts[name]++
pool.Total += value.Total
pool.Used += value.Used
pool.ReadBytes += value.ReadBytes
@@ -476,8 +487,8 @@ func AverageSystemStatsSlice(records []system.Stats) system.Stats {
// Average ZFS pool stats.
for name, pool := range sum.ZfsPools {
entryCount := zfsPoolCounts[name]
pool.Total = twoDecimals(pool.Total / float64(entryCount))
pool.Used = twoDecimals(pool.Used / float64(entryCount))
pool.Total = twoDecimals(pool.Total / float64(zfsCapacityCounts[name]))
pool.Used = twoDecimals(pool.Used / float64(zfsCapacityCounts[name]))
pool.ReadBytes /= entryCount
pool.WriteBytes /= entryCount
}
@@ -889,3 +889,34 @@ func TestAverageContainerStatsSlice_ManyContainers(t *testing.T) {
assert.Equal(t, 35.0, result[2].Cpu)
assert.Equal(t, 45.0, result[3].Cpu)
}
func TestAverageSystemStatsSlice_ZfsCapacityModes(t *testing.T) {
for _, raw := range []bool{false, true} {
result := records.AverageSystemStatsSlice([]system.Stats{
{ZfsPools: map[string]*system.ZfsPool{"pool": {Total: 200, Used: 40, Raw: !raw, ReadBytes: 100}}},
{ZfsPools: map[string]*system.ZfsPool{"pool": {Total: 100, Used: 10, Raw: raw, ReadBytes: 300}}},
})
assert.Equal(t, &system.ZfsPool{Total: 100, Used: 10, Raw: raw, ReadBytes: 200}, result.ZfsPools["pool"])
}
}
func TestAverageSystemStatsSlice_ZfsDuplicateCharts(t *testing.T) {
for _, hide := range []bool{false, true} {
result := records.AverageSystemStatsSlice([]system.Stats{
{ZfsPools: map[string]*system.ZfsPool{"pool": {HideUsage: true, HideIO: true}}},
{ZfsPools: map[string]*system.ZfsPool{"pool": {HideUsage: hide, HideIO: hide}}},
})
assert.Equal(t, hide, result.ZfsPools["pool"].HideUsage)
assert.Equal(t, hide, result.ZfsPools["pool"].HideIO)
}
}
func TestAverageSystemStatsSlice_BtrfsDisplayName(t *testing.T) {
result := records.AverageSystemStatsSlice([]system.Stats{
{ZfsPools: map[string]*system.ZfsPool{"b:uuid": {DisplayName: "before", Used: 10}}},
{ZfsPools: map[string]*system.ZfsPool{"b:uuid": {DisplayName: "after", Used: 20}}},
})
require.Len(t, result.ZfsPools, 1)
assert.Equal(t, "after", result.ZfsPools["b:uuid"].DisplayName)
assert.Equal(t, float64(15), result.ZfsPools["b:uuid"].Used)
}
@@ -4,6 +4,7 @@ import { cn, decimalString, formatBytes, hourWithSeconds } from "@/lib/utils"
import type { ContainerRecord } from "@/types"
import { ContainerHealth, ContainerHealthLabels } from "@/lib/enums"
import {
CircleArrowUpIcon,
ClockIcon,
ContainerIcon,
CpuIcon,
@@ -177,11 +178,25 @@ export const containerChartCols: ColumnDef<ContainerRecord>[] = [
header: ({ column }) => (
<HeaderButton column={column} name={t({ message: "Image", context: "Docker image" })} Icon={LayersIcon} />
),
cell: ({ getValue }) => {
cell: ({ getValue, row }) => {
const val = getValue() as string
return (
<div className="ms-1 xl:w-40 truncate" title={val}>
{val}
<div className="ms-1 xl:w-40 flex items-center gap-2">
<span className="truncate" title={val}>
{val}
</span>
{row.original.updatable && (
<Tooltip>
<TooltipTrigger
className="shrink-0 rounded-sm text-emerald-600 dark:text-emerald-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-label={t`Image update available`}
onClick={(event) => event.stopPropagation()}
>
<CircleArrowUpIcon className="size-4" aria-hidden="true" />
</TooltipTrigger>
<TooltipContent>{t`Image update available`}</TooltipContent>
</Tooltip>
)}
</div>
)
},
@@ -66,7 +66,7 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
function fetchData(systemId?: string) {
pb.collection<ContainerRecord>("containers")
.getList(0, 2000, {
fields: "id,name,image,ports,cpu,memory,net,health,status,system,updated",
fields: "id,name,image,updatable,ports,cpu,memory,net,health,status,system,updated",
filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
})
.then(({ items }) => {
+2 -2
View File
@@ -15,7 +15,7 @@ export default function () {
const page = useStore($router)
const [isFirstRun, setFirstRun] = useState(false)
const [authMethods, setAuthMethods] = useState<AuthMethodsList>()
const { theme } = useTheme()
const { resolvedTheme } = useTheme()
useEffect(() => {
document.title = t`Login` + " / Beszel"
@@ -54,7 +54,7 @@ export default function () {
<div
className="grid gap-5 w-full px-4 mx-auto"
// @ts-expect-error
style={{ maxWidth: "21.5em", "--border": theme == "light" ? "hsl(30, 8%, 70%)" : "hsl(220, 3%, 25%)" }}
style={{ maxWidth: "21.5em", "--border": resolvedTheme == "light" ? "hsl(30, 8%, 70%)" : "hsl(220, 3%, 25%)" }}
>
<div className="absolute top-3 right-3">
<ModeToggle />
@@ -8,7 +8,7 @@ import { useSystemData } from "./system/use-system-data"
import { CpuChart, ContainerCpuChart } from "./system/charts/cpu-charts"
import { MemoryChart, ContainerMemoryChart, SwapChart } from "./system/charts/memory-charts"
import { RootDiskCharts, ExtraFsCharts } from "./system/charts/disk-charts"
import { ZfsCharts } from "./system/charts/zfs-charts"
import { ZfsCharts } from "./system/charts/storage-pool-charts"
import { BandwidthChart, ContainerNetworkChart } from "./system/charts/network-charts"
import { TemperatureChart, FanChart, BatteryChart } from "./system/charts/sensor-charts"
import { GpuPowerChart, GpuCharts } from "./system/charts/gpu-charts"
@@ -95,7 +95,7 @@ export function ChartCard({
className,
}: {
title: string
description: string
description: React.ReactNode
children: React.ReactNode
grid?: boolean
empty?: boolean
@@ -3,6 +3,7 @@ import AreaChartDefault from "@/components/charts/area-chart"
import { decimalString, formatBytes, toFixedFloat } from "@/lib/utils"
import type { SystemStatsRecord } from "@/types"
import { ChartCard } from "../chart-card"
import { RawCapacityLabel } from "../raw-capacity-label"
import { Unit } from "@/lib/enums"
import { useStore } from "@nanostores/react"
import { $userSettings } from "@/lib/stores"
@@ -10,9 +11,11 @@ import type { SystemData } from "../use-system-data"
// Accessors for ZFS metrics
const poolUsage =
(name: string) =>
({ stats }: SystemStatsRecord) =>
stats?.z?.[name]?.du ?? 0
(name: string, raw: boolean) =>
({ stats }: SystemStatsRecord) => {
const pool = stats?.z?.[name]
return pool && !!pool.raw === raw ? pool.du : null
}
const poolRead =
(name: string) =>
({ stats }: SystemStatsRecord) =>
@@ -26,9 +29,10 @@ export function ZfsPoolUsageChart({ systemData, poolName }: { systemData: System
const { chartData, grid, dataEmpty } = systemData
const latest = chartData.systemStats.at(-1)?.stats
const pool = latest?.z?.[poolName]
if (!pool) {
if (!pool || pool.hu) {
return null
}
const displayName = pool.n || poolName
let poolTotal = pool.d
// round to nearest GB
if (poolTotal >= 100) {
@@ -39,8 +43,8 @@ export function ZfsPoolUsageChart({ systemData, poolName }: { systemData: System
<ChartCard
empty={dataEmpty}
grid={grid}
title={`${poolName} ${t`Usage`}`}
description={t`Usage of ZFS pool ${poolName}`}
title={`${displayName} ${t`Usage`}`}
description={pool.raw ? <RawCapacityLabel label={t`Raw usage of storage pool ${displayName}`} /> : t`Usage of storage pool ${displayName}`}
>
<AreaChartDefault
chartData={chartData}
@@ -57,7 +61,7 @@ export function ZfsPoolUsageChart({ systemData, poolName }: { systemData: System
dataPoints={[
{
label: t`Pool Usage`,
dataKey: poolUsage(poolName),
dataKey: poolUsage(poolName, !!pool.raw),
color: 4,
opacity: 0.4,
},
@@ -70,15 +74,16 @@ export function ZfsPoolUsageChart({ systemData, poolName }: { systemData: System
export function ZfsPoolIOChart({ systemData, poolName }: { systemData: SystemData; poolName: string }) {
const { chartData, grid, dataEmpty } = systemData
const userSettings = useStore($userSettings)
if (!chartData.systemStats?.length) {
if (!chartData.systemStats?.length || chartData.systemStats.at(-1)?.stats.z?.[poolName]?.hi) {
return null
}
const displayName = chartData.systemStats.at(-1)?.stats.z?.[poolName]?.n || poolName
return (
<ChartCard
empty={dataEmpty}
grid={grid}
title={`${poolName} I/O`}
description={t`Throughput of ZFS pool ${poolName}`}
title={`${displayName} I/O`}
description={t`Throughput of storage pool ${displayName}`}
>
<AreaChartDefault
chartData={chartData}
@@ -114,12 +119,15 @@ export function ZfsPoolIOChart({ systemData, poolName }: { systemData: SystemDat
export function ZfsCharts({ systemData }: { systemData: SystemData }) {
const latest = systemData.chartData.systemStats?.at(-1)?.stats
const pools = latest?.z ?? {}
if (Object.keys(pools).length === 0) {
const visiblePools = Object.keys(pools)
.filter((name) => !pools[name].hu || !pools[name].hi)
.sort((a, b) => (pools[a].n || a).localeCompare(pools[b].n || b, undefined, { numeric: true }) || a.localeCompare(b))
if (visiblePools.length === 0) {
return null
}
return (
<div className="grid xl:grid-cols-2 gap-4">
{Object.keys(pools).map((poolName) => (
{visiblePools.map((poolName) => (
<div key={poolName} className="contents">
<ZfsPoolUsageChart systemData={systemData} poolName={poolName} />
<ZfsPoolIOChart systemData={systemData} poolName={poolName} />
@@ -24,7 +24,7 @@ export function LazySmartTable({ systemId }: { systemId: string }) {
)
}
const ZfsTable = lazy(() => import("./zfs-table"))
const ZfsTable = lazy(() => import("./storage-pools-table"))
export function LazyZfsTable({ systemId }: { systemId: string }) {
const { isIntersecting, ref } = useIntersectionObserver({ rootMargin: "90px" })
@@ -0,0 +1,25 @@
import { t } from "@lingui/core/macro"
import { InfoIcon } from "lucide-react"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
export function RawCapacityLabel({ label = t`Raw` }: { label?: string }) {
return (
<span className="inline-flex items-center gap-1">
{label}
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
aria-label="About raw capacity"
className="inline-flex rounded-sm text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<InfoIcon className="size-3.5" aria-hidden="true" />
</button>
</TooltipTrigger>
<TooltipContent className="max-w-64">
{t`Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled.`}
</TooltipContent>
</Tooltip>
</span>
)
}
@@ -26,6 +26,7 @@ import {
CheckCircleIcon,
CircleAlertIcon,
ClockIcon,
DatabaseIcon,
HardDriveDownloadIcon,
HardDriveIcon,
HardDriveUploadIcon,
@@ -35,10 +36,13 @@ import {
RotateCwIcon,
XCircleIcon,
XIcon,
FolderTreeIcon,
} from "lucide-react"
import { useCallback, useEffect, useMemo, useState } from "react"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
const ZFS_POOL_FIELDS = "id,system,name,health,size,alloc,free,scrub,details_updated,updated"
import { RawCapacityLabel } from "./raw-capacity-label"
const ZFS_POOL_FIELDS = "id,system,name,display_name,health,size,alloc,free,raw,scrub,details_updated,updated"
/** Maps a zpool health string to a Badge variant. */
function healthVariant(health: string): "success" | "warning" | "danger" | "outline" {
@@ -81,13 +85,30 @@ function HeaderButton<T>({ column, name, Icon }: { column: Column<T>; name: stri
)
}
function poolType(pool: ZfsPoolRecord): string {
return pool.name.startsWith("b:") ? "Btrfs" : "ZFS"
}
const columns: ColumnDef<ZfsPoolRecord>[] = [
{
accessorKey: "name",
sortingFn: (a, b) => a.original.name.localeCompare(b.original.name),
header: ({ column }) => <HeaderButton column={column} name={`Pool`} Icon={HardDriveIcon} />,
id: "name",
accessorFn: (pool) => pool.display_name || pool.name,
header: ({ column }) => <HeaderButton column={column} name={`Pool`} Icon={DatabaseIcon} />,
cell: ({ getValue }) => <span className="font-medium ms-1.5">{getValue() as string}</span>,
},
{
id: "type",
accessorFn: poolType,
header: ({ column }) => <HeaderButton column={column} name={t`Type`} Icon={FolderTreeIcon} />,
cell: ({ getValue }) => {
const type = getValue() as string
return (
<Badge variant="outline" className={cn("border-transparent", type === "ZFS" ? "bg-blue-200 text-blue-800" : "bg-yellow-200 text-yellow-800")}>
{type}
</Badge>
)
},
},
{
accessorKey: "health",
sortingFn: (a, b) => a.original.health.localeCompare(b.original.health),
@@ -102,21 +123,21 @@ const columns: ColumnDef<ZfsPoolRecord>[] = [
accessorFn: (record) => record.size,
invertSorting: true,
header: ({ column }) => <HeaderButton column={column} name={t`Capacity`} Icon={BinaryIcon} />,
cell: ({ getValue }) => <span className="ms-1.5 tabular-nums">{formatCapacity(getValue() as number)}</span>,
cell: ({ getValue, row }) => <span className="ms-1.5 tabular-nums">{formatCapacity(getValue() as number)}{row.original.raw ? ` (${t`Raw`})` : ""}</span>,
},
{
id: "used",
accessorFn: (record) => record.alloc,
invertSorting: true,
header: ({ column }) => <HeaderButton column={column} name={t`Used`} Icon={HardDriveDownloadIcon} />,
cell: ({ getValue }) => <span className="ms-1.5 tabular-nums">{formatCapacity(getValue() as number)}</span>,
cell: ({ getValue, row }) => <span className="ms-1.5 tabular-nums">{formatCapacity(getValue() as number)}{row.original.raw ? ` (${t`Raw`})` : ""}</span>,
},
{
id: "free",
accessorFn: (record) => record.free,
invertSorting: true,
header: ({ column }) => <HeaderButton column={column} name={t({ message: `Free`, context: "Free space" })} Icon={HardDriveUploadIcon} />,
cell: ({ getValue }) => <span className="ms-1.5 tabular-nums">{formatCapacity(getValue() as number)}</span>,
cell: ({ getValue, row }) => <span className="ms-1.5 tabular-nums">{row.original.raw ? "-" : formatCapacity(getValue() as number)}</span>,
},
{
id: "scrub",
@@ -201,7 +222,7 @@ const datasetColumns: ColumnDef<ZfsDataset>[] = [
{
accessorKey: "name",
sortingFn: (a, b) => a.original.name.localeCompare(b.original.name),
header: ({ column }) => <HeaderButton column={column} name={`Dataset`} Icon={HardDriveIcon} />,
header: ({ column }) => <HeaderButton column={column} name={`Dataset`} Icon={DatabaseIcon} />,
cell: ({ getValue }) => <span className="font-mono text-xs">{getValue() as string}</span>,
},
{
@@ -310,6 +331,7 @@ function PoolSheet({
onOpenChange: (open: boolean) => void
}) {
const [pool, setPool] = useState<ZfsPoolRecord | null>(null)
const titleRef = useRef<HTMLHeadingElement>(null)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
@@ -342,23 +364,30 @@ function PoolSheet({
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="w-full sm:max-w-220 gap-0 overflow-y-auto">
<SheetContent
className="w-full sm:max-w-220 gap-0 overflow-y-auto"
onOpenAutoFocus={(event) => {
event.preventDefault()
titleRef.current?.focus()
}}
>
<SheetHeader className="mb-0 border-b">
<SheetTitle className="flex items-center gap-2">
{pool ? pool.name : `ZFS Pool`}
<SheetTitle ref={titleRef} tabIndex={-1} className="flex items-center gap-2 outline-none">
{pool ? (pool.display_name || pool.name) : `Storage Pool`}
{pool && <Badge variant={healthVariantValue}>{health}</Badge>}
</SheetTitle>
<SheetDescription className="flex flex-wrap items-center gap-x-2 gap-y-1">
{pool?.size ? formatCapacity(pool.size) : null}
{pool?.raw && <RawCapacityLabel />}
{pool?.alloc ? (
<>
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
<span>
<Trans>Used</Trans>: {formatCapacity(pool.alloc)}
<Trans>Used</Trans>: {formatCapacity(pool.alloc)}{pool.raw ? ` (${t`Raw`})` : ""}
</span>
</>
) : null}
{pool?.free ? (
{pool?.free && !pool.raw ? (
<>
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
<span>
@@ -555,6 +584,7 @@ export default function ZfsTable({ systemId }: { systemId?: string }) {
const table = useReactTable({
data: zfsPools || ([] as ZfsPoolRecord[]),
columns: tableColumns,
initialState: { sorting: [{ id: "name", desc: false }] },
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
@@ -562,7 +592,7 @@ export default function ZfsTable({ systemId }: { systemId?: string }) {
onGlobalFilterChange: setGlobalFilter,
globalFilterFn: (row, _columnId, filterValue) => {
const pool = row.original
const searchString = `${pool.name} ${pool.health ?? ""}`.toLowerCase()
const searchString = `${pool.display_name ?? ""} ${pool.name} ${poolType(pool)} ${pool.health ?? ""}`.toLowerCase()
return (filterValue as string)
.toLowerCase()
.split(" ")
@@ -587,7 +617,7 @@ export default function ZfsTable({ systemId }: { systemId?: string }) {
<CardHeader className="p-0 mb-3 sm:mb-4">
<div className="grid md:flex gap-x-5 gap-y-3 w-full items-end">
<div className="px-2 sm:px-1">
<CardTitle className="mb-2">ZFS</CardTitle>
<CardTitle className="mb-2">Storage Pools</CardTitle>
<CardDescription className="flex">
<Trans>Click on a pool to view vdev and dataset details.</Trans>
</CardDescription>
@@ -171,6 +171,7 @@ export function useSystemData(id: string) {
// get stats when system "changes." (Not just system to system,
// also when new info comes in via systemManager realtime connection, indicating an update)
useEffect(() => {
const requestId = ++statsRequestId.current
if (!system.id || !chartTime || chartTime === "1m") {
return
}
@@ -179,7 +180,6 @@ export function useSystemData(id: string) {
const { expectedInterval } = chartTimeData[chartTime]
const ss_cache_key = `${systemId}_${chartTime}_system_stats`
const cs_cache_key = `${systemId}_${chartTime}_container_stats`
const requestId = ++statsRequestId.current
const cachedSystemStats = cache.get(ss_cache_key) as SystemStatsRecord[] | undefined
const cachedContainerData = cache.get(cs_cache_key) as ChartData["containerData"] | undefined
@@ -203,7 +203,7 @@ export function useSystemData(id: string) {
getStats<SystemStatsRecord>("system_stats", systemId, chartTime),
getStats<ContainerStatsRecord>("container_stats", systemId, chartTime),
]).then(([systemStats, containerStats]) => {
// If another request has been made since this one, ignore the results
// Ignore responses for a previous system or chart time
if (requestId !== statsRequestId.current) {
return
}
@@ -193,7 +193,7 @@ export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<Syste
header: sortableHeader,
},
{
accessorFn: ({ info }) => info.g || undefined,
accessorFn: ({ info }) => info.g,
id: "gpu",
name: () => "GPU",
cell: (info) => {
+17 -10
View File
@@ -1,6 +1,7 @@
import { createContext, useContext, useEffect, useState } from "react"
type Theme = "dark" | "light" | "system"
type ResolvedTheme = "dark" | "light"
type ThemeProviderProps = {
children: React.ReactNode
@@ -10,11 +11,13 @@ type ThemeProviderProps = {
type ThemeProviderState = {
theme: Theme
resolvedTheme: ResolvedTheme
setTheme: (theme: Theme) => void
}
const initialState: ThemeProviderState = {
theme: "system",
resolvedTheme: "light",
setTheme: () => null,
}
@@ -27,24 +30,28 @@ export function ThemeProvider({
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState<Theme>(() => (localStorage.getItem(storageKey) as Theme) || defaultTheme)
const [systemDark, setSystemDark] = useState(() => window.matchMedia("(prefers-color-scheme: dark)").matches)
useEffect(() => {
const media = window.matchMedia("(prefers-color-scheme: dark)")
const onChange = (event: MediaQueryListEvent) => setSystemDark(event.matches)
media.addEventListener("change", onChange)
return () => media.removeEventListener("change", onChange)
}, [])
const resolvedTheme = theme === "system" ? (systemDark ? "dark" : "light") : theme
useEffect(() => {
const root = window.document.documentElement
root.classList.remove("light", "dark")
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
root.classList.add(systemTheme)
return
}
root.classList.add(theme)
}, [theme])
root.classList.add(resolvedTheme)
}, [resolvedTheme])
const value = {
theme,
resolvedTheme,
setTheme: (theme: Theme) => {
localStorage.setItem(storageKey, theme)
setTheme(theme)
+18 -2
View File
@@ -4,7 +4,7 @@ import { basePath } from "@/components/router"
import { toast } from "@/components/ui/use-toast"
import type { ChartTimes, UserSettings } from "@/types"
import { $alerts, $allSystemsById, $allSystemsByName, $userSettings } from "./stores"
import { chartTimeData } from "./utils"
import { chartTimeData, debounce } from "./utils"
/** PocketBase JS Client */
export const pb = new PocketBase(basePath)
@@ -12,7 +12,7 @@ export const pb = new PocketBase(basePath)
export const isAdmin = () => pb.authStore.record?.role === "admin"
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
export const verifyAuth = () => {
const verifyAuth = () => {
pb.collection("users")
.authRefresh()
.catch(() => {
@@ -25,6 +25,22 @@ export const verifyAuth = () => {
})
}
const verifyAuthDebounced = debounce(verifyAuth, 100)
// verify the session whenever any API request returns a 4xx response (e.g. an
// expired JWT). The auth-refresh endpoint is excluded to avoid a loop, since
// it returns 401 itself when the token is no longer valid.
pb.afterSend = (response, data) => {
if (
(response.status === 401 || response.status === 403) &&
pb.authStore.token &&
!response.url.includes("auth-refresh")
) {
verifyAuthDebounced()
}
return data
}
/** Logs the user out by clearing the auth store and unsubscribing from realtime updates. */
export function logOut() {
$allSystemsByName.set({})
+1 -6
View File
@@ -1,6 +1,6 @@
/** biome-ignore-all lint/suspicious/noAssignInExpressions: it's fine :) */
import type { PreinitializedMapStore } from "nanostores"
import { pb, verifyAuth } from "@/lib/api"
import { pb } from "@/lib/api"
import {
$allSystemsById,
$allSystemsByName,
@@ -167,11 +167,6 @@ export async function subscribe() {
export async function refresh() {
try {
const records = await fetchSystems()
if (!records.length) {
// No systems found, verify authentication
verifyAuth()
return
}
for (const record of records) {
add(record)
}
+58 -42
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ar\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 19:32\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
@@ -97,7 +97,7 @@ msgstr "5 دقائق"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "إجراءات"
@@ -196,7 +196,7 @@ msgstr "هل أنت متأكد؟"
msgid "Automatic copy requires a secure context."
msgstr "النسخ التلقائي يتطلب سياقًا آمنًا."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "المتاح"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "القدرات"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "السعة"
@@ -391,14 +391,14 @@ msgstr "تحقق من خدمة المراقبة الخاصة بك"
msgid "Check your notification service"
msgstr "تحقق من خدمة الإشعارات الخاصة بك"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "أخطاء المجموع الاختباري"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "مسح"
@@ -411,7 +411,7 @@ msgstr "انقر على حاوية لعرض مزيد من المعلومات."
msgid "Click on a device to view more information."
msgstr "انقر على جهاز لعرض مزيد من المعلومات."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "انقر على مجموعة تخزين لعرض تفاصيل vdev ومجموعة البيانات."
@@ -503,7 +503,7 @@ msgstr "نسخ الاسم"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "نسخ المفتاح العام"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -869,14 +869,14 @@ msgstr "فشل: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "المراوح"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "لمدة <0>{min}</0> {min, plural, one {دقيقة} other {دقائق}}
msgid "Forgot password?"
msgstr "هل نسيت كلمة المرور؟"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "المساحة الحرة"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "شبكة"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "الصحة"
@@ -1146,7 +1146,7 @@ msgstr "استخدام الذاكرة للحاويات"
msgid "Model"
msgstr "الموديل"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "نقطة الربط"
@@ -1185,7 +1185,7 @@ msgstr "وحدة الشبكة"
msgid "No"
msgstr "لا"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "لا تتوفر بيانات تفصيلية لمجموعة التخزين هذه."
@@ -1212,7 +1212,7 @@ msgstr "لا توجد سمات S.M.A.R.T. متاحة لهذا الجهاز."
msgid "No systems found."
msgstr "لم يتم العثور على أنظمة."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "لا شيء"
@@ -1255,7 +1255,7 @@ msgstr "كلمة مرور لمرة واحدة"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "فتح القائمة"
@@ -1346,6 +1346,10 @@ msgstr "دائم"
msgid "Persistence"
msgstr "الاستمرارية"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "مساحة الجهاز الفعلية. السعة الفعلية القابلة للاستخدام غير معروفة. تم تعطيل تنبيهات استخدام أقراص المجموعة."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "يرجى <0>تكوين خادم SMTP</0> لضمان تسليم التنبيهات."
@@ -1379,11 +1383,11 @@ msgstr "يرجى الاطلاع على <0>التوثيق</0> للحصول على
msgid "Please sign in to your account"
msgstr "يرجى تسجيل الدخول إلى حسابك"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "صحة مجموعة التخزين"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "استخدام مجموعة التخزين"
@@ -1416,7 +1420,7 @@ msgstr "تم بدء العملية"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "المفتاح العام"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "عمق الدور"
msgid "Quiet Hours"
msgstr "ساعات الهدوء"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "الخام"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "الاستخدام الخام لمجموعة التخزين {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "ساعات الهدوء"
msgid "Read"
msgstr "قراءة"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "أخطاء القراءة"
@@ -1454,7 +1469,7 @@ msgstr "تم الاستلام"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "تحديث"
@@ -1643,7 +1658,7 @@ msgstr "وقت البدء"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "الحالة"
@@ -1671,7 +1686,7 @@ msgstr "استخدام التبديل"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "تبديل السمة"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "النظام"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "سرعات مراوح النظام (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "سيؤدي هذا إلى حذف جميع السجلات المحددة
msgid "Throughput of {extraFsName}"
msgstr "معدل نقل {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "معدل نقل البيانات لمجموعة ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "معدل نقل مجموعة التخزين {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1897,6 +1912,7 @@ msgstr "يتم التفعيل عندما يتجاوز استخدام أي قرص
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "النوع"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "رمز مميز عالمي"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "غير معروفة"
@@ -1945,7 +1961,7 @@ msgstr "تحديث"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "تم التحديث"
@@ -1968,20 +1984,20 @@ msgstr "مدة التشغيل"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "الاستخدام"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "استخدام مجموعة ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "استخدام مجموعة التخزين {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "مستخدم"
@@ -2058,7 +2074,7 @@ msgstr "أمر ويندوز"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "أمر ويندوز"
msgid "Write"
msgstr "كتابة"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "أخطاء الكتابة"
+61 -45
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: bg\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Bulgarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -97,7 +97,7 @@ msgstr "5 минути"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Действия"
@@ -196,7 +196,7 @@ msgstr "Сигурни ли сте?"
msgid "Automatic copy requires a secure context."
msgstr "Автоматичното копиране изисква защитен контескт."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Налично"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Възможности"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Капацитет"
@@ -391,14 +391,14 @@ msgstr "Проверете мониторинг услугата си"
msgid "Check your notification service"
msgstr "Провери услугата си за удостоверяване"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Грешки в контролната сума"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Изчисти"
@@ -411,7 +411,7 @@ msgstr "Кликнете върху контейнер, за да видите
msgid "Click on a device to view more information."
msgstr "Кликнете върху устройство, за да видите повече информация."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Щракнете върху пул, за да видите подробности за vdev и наборите от данни."
@@ -503,7 +503,7 @@ msgstr "Копирай име"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Копирай публичния ключ"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -869,14 +869,14 @@ msgstr "Неуспешни: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Вентилатори"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "За <0>{min}</0> {min, plural, one {минута} other {минути}}
msgid "Forgot password?"
msgstr "Забравена парола?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Свободно"
@@ -928,7 +928,7 @@ msgstr "Глобален"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
@@ -948,13 +948,13 @@ msgid "Grid"
msgstr "Мрежово"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Здраве"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -1146,7 +1146,7 @@ msgstr "Използване на паметта от контейнерите"
msgid "Model"
msgstr "Модел"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Точка на монтиране"
@@ -1185,7 +1185,7 @@ msgstr "Единица за измерване на скорост"
msgid "No"
msgstr "Не"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Няма подробни данни за този пул."
@@ -1212,7 +1212,7 @@ msgstr "Няма налични S.M.A.R.T. атрибути за това уст
msgid "No systems found."
msgstr "Няма намерени системи."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Няма"
@@ -1255,7 +1255,7 @@ msgstr "Еднократна парола"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Отвори менюто"
@@ -1346,6 +1346,10 @@ msgstr "Постоянен"
msgid "Persistence"
msgstr "Устойчивост"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Физическо пространство на устройството. Действителният използваем капацитет е неизвестен. Сигналите за използване на диска на пула са изключени."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Моля <0>конфигурурай SMTP сървър</0> за да се подсигуриш, че тревогите са доставени."
@@ -1379,11 +1383,11 @@ msgstr "Моля виж <0>документацията</0> за инструк
msgid "Please sign in to your account"
msgstr "Моля влез в акаунта ти"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Състояние на пула"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Използване на пула"
@@ -1416,7 +1420,7 @@ msgstr "Процесът стартира"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Публичен ключ"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Дълбочина на опашката"
msgid "Quiet Hours"
msgstr "Тихи часове"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Сурово"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Сурово използване на пула {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Тихи часове"
msgid "Read"
msgstr "Прочети"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Грешки при четене"
@@ -1454,7 +1469,7 @@ msgstr "Получени"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Опресни"
@@ -1643,7 +1658,7 @@ msgstr "Начален час"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Състояние"
@@ -1671,7 +1686,7 @@ msgstr "Използване на swap"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Смени темата"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Система"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Скорости на вентилаторите на системата (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "Това ще доведе до трайно изтриване на в
msgid "Throughput of {extraFsName}"
msgstr "Пропускателна способност на {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Пропускателна способност на ZFS пул {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Пропускателна способност на пула {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1819,7 +1834,7 @@ msgstr "Общо изпратени данни за всеки интерфей
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Общо време, прекарано в четене/запис (може да надвиши 100%)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "Задейства се, когато употребата на няко
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Тип"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Универсален тоукън"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Неизвестна"
@@ -1945,7 +1961,7 @@ msgstr "Актуализирай"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Актуализирано"
@@ -1968,20 +1984,20 @@ msgstr "Време на работа"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Употреба"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Използване на ZFS пул {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Използване на пула {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Използвани"
@@ -2058,7 +2074,7 @@ msgstr "Команда Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Команда Windows"
msgid "Write"
msgstr "Запиши"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Грешки при запис"
+64 -48
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: cs\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
@@ -61,7 +61,7 @@ msgstr "1 hodina"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 hodin"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 dní"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Akce"
@@ -154,7 +154,7 @@ msgstr "Po nastavení proměnných prostředí restartujte hub Beszel, aby se zm
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Jste si jistý?"
msgid "Automatic copy requires a secure context."
msgstr "Automatická kopie vyžaduje zabezpečený kontext."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Dostupné"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Schopnosti"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapacita"
@@ -391,14 +391,14 @@ msgstr "Zkontrolujte svou monitorovací službu"
msgid "Check your notification service"
msgstr "Zkontrolujte službu upozornění"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Chyby kontrolního součtu"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Vymazat"
@@ -411,7 +411,7 @@ msgstr "Klikněte na kontejner pro zobrazení dalších informací."
msgid "Click on a device to view more information."
msgstr "Klikněte na zařízení pro zobrazení dalších informací."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Kliknutím na fond zobrazíte podrobnosti o vdev a datových sadách."
@@ -503,7 +503,7 @@ msgstr "Kopírovat název"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Zkopírovat veřejný klíč"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -647,7 +647,7 @@ msgstr "Popis"
#: src/components/containers-table/containers-table.tsx
msgid "Detail"
msgstr "Detail"
msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Device"
@@ -869,14 +869,14 @@ msgstr "Neúspěšné: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Ventilátory"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "Za <0>{min}</0> {min, plural, one {minutu} few {minuty} other {minut}}"
msgid "Forgot password?"
msgstr "Zapomněli jste heslo?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Volné"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Mřížka"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Zdraví"
@@ -1146,7 +1146,7 @@ msgstr "Využití paměti kontejnery"
msgid "Model"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Bod připojení"
@@ -1185,7 +1185,7 @@ msgstr "Síťová jednotka"
msgid "No"
msgstr "Ne"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Pro tento fond nejsou k dispozici podrobné údaje."
@@ -1212,7 +1212,7 @@ msgstr "Pro toto zařízení nejsou k dispozici žádné atributy S.M.A.R.T."
msgid "No systems found."
msgstr "Nenalezeny žádné systémy."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Žádné"
@@ -1255,7 +1255,7 @@ msgstr "Jednorázové heslo"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Otevřít menu"
@@ -1346,6 +1346,10 @@ msgstr "Trvalý"
msgid "Persistence"
msgstr "Trvalost"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fyzické místo na zařízení. Skutečná využitelná kapacita není známa. Upozornění na využití disků fondu jsou vypnutá."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,11 +1383,11 @@ msgstr "Instrukce naleznete v <0>dokumentaci</0>."
msgid "Please sign in to your account"
msgstr "Přihlaste se prosím k vašemu účtu"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Stav fondu"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Využití fondu"
@@ -1416,7 +1420,7 @@ msgstr "Proces spuštěn"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Veřejný klíč"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Hloubka fronty"
msgid "Quiet Hours"
msgstr "Tiché hodiny"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Hrubé"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Hrubé využití fondu {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Tiché hodiny"
msgid "Read"
msgstr "Číst"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Chyby čtení"
@@ -1454,7 +1469,7 @@ msgstr "Přijato"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Aktualizovat"
@@ -1643,7 +1658,7 @@ msgstr "Čas začátku"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Stav"
@@ -1671,7 +1686,7 @@ msgstr "Swap využití"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Přepnout motiv"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Systém"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Rychlosti ventilátorů systému (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "Tímto trvale odstraníte všechny vybrané záznamy z databáze."
msgid "Throughput of {extraFsName}"
msgstr "Propustnost {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Propustnost fondu ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Propustnost fondu {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1819,7 +1834,7 @@ msgstr "Celkový odeslaný objem dat pro každé rozhraní"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Celkový čas strávený čtením/zápisem (může přesáhnout 100 %)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "Spustí se, když využití disku překročí prahovou hodnotu"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Typ"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Univerzální token"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Neznámá"
@@ -1945,7 +1961,7 @@ msgstr "Aktualizovat"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Aktualizováno"
@@ -1968,20 +1984,20 @@ msgstr "Doba provozu"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Využití"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Využití fondu ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Využití fondu {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Využito"
@@ -2058,7 +2074,7 @@ msgstr "Windows příkaz"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows příkaz"
msgid "Write"
msgstr "Psát"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Chyby zápisu"
+84 -68
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: da\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} minut} other {{countString} minutter}
#: src/components/routes/system/charts/disk-charts.tsx
msgid "{diskName} I/O"
msgstr "{diskName} I/O"
msgstr ""
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
@@ -97,7 +97,7 @@ msgstr "5 minutter"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Handlinger"
@@ -154,7 +154,7 @@ msgstr "Efter indstilling af miljøvariablerne skal du genstarte din Beszel-hub
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Er du sikker?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisk kopiering kræver en sikker kontekst."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Tilgængelig"
@@ -300,7 +300,7 @@ msgstr "Binær"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -309,7 +309,7 @@ msgstr "Opstartstilstand"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Funktioner"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapacitet"
@@ -348,7 +348,7 @@ msgstr "Forsigtig - muligt tab af data"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Tjek din overvågningstjeneste"
msgid "Check your notification service"
msgstr "Tjek din notifikationstjeneste"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Kontrolsumfejl"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Ryd"
@@ -411,7 +411,7 @@ msgstr "Klik på en container for at se mere information."
msgid "Click on a device to view more information."
msgstr "Klik på en enhed for at se flere oplysninger."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Klik på en pool for at se detaljer om vdev og datasæt."
@@ -447,7 +447,7 @@ msgstr "Forbindelsen er nede"
#: src/lib/alerts.ts
msgid "Container"
msgstr "Container"
msgstr ""
#: src/lib/alerts.ts
msgid "Container Health"
@@ -503,7 +503,7 @@ msgstr "Kopier navn"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Kopiér offentlig nøgle"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "Kerne"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -542,7 +542,7 @@ msgstr "CPU-I/O-ventetid (IOWait)"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "CPU Peak"
msgstr ""
#: src/lib/alerts.ts
msgid "CPU Steal Time"
@@ -661,7 +661,7 @@ msgstr "Aflader"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
@@ -732,7 +732,7 @@ msgstr "Rediger {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -826,7 +826,7 @@ msgstr "Eksporter din nuværende systemkonfiguration."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,18 +869,18 @@ msgstr "Mislykkedes: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Blæsere"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
msgstr "Filter..."
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint"
@@ -888,7 +888,7 @@ msgstr "Fingeraftryk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -898,8 +898,8 @@ msgstr "For <0>{min}</0> {min, plural, one {minut} other {minutter}}"
msgid "Forgot password?"
msgstr "Glemt adgangskode?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Ledig"
@@ -924,7 +924,7 @@ msgstr "Generelt"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Gitter"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Sundhed"
@@ -1091,7 +1091,7 @@ msgstr "Loginforsøg mislykkedes"
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
msgid "Logs"
msgstr "Logs"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
@@ -1144,9 +1144,9 @@ msgstr "Containeres hukommelsesforbrug"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Monteringspunkt"
@@ -1161,7 +1161,7 @@ msgstr "Navn"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Net"
msgstr "Net"
msgstr ""
#: src/components/routes/system/charts/network-charts.tsx
msgid "Network traffic of containers"
@@ -1185,7 +1185,7 @@ msgstr "Netværksenhed"
msgid "No"
msgstr "Nej"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Ingen detaljerede data for denne pool."
@@ -1212,7 +1212,7 @@ msgstr "Ingen S.M.A.R.T.-attributter tilgængelige for denne enhed."
msgid "No systems found."
msgstr "Ingen systemer fundet."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Ingen"
@@ -1255,7 +1255,7 @@ msgstr "Engangsadgangskode"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Åbn menu"
@@ -1311,7 +1311,7 @@ msgstr "Tidligere"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Pause"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Paused"
@@ -1346,6 +1346,10 @@ msgstr ""
msgid "Persistence"
msgstr "Vedholdenhed"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fysisk enhedsplads. Den reelle brugbare kapacitet er ukendt. Advarsler om diskforbrug for poolen er deaktiveret."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,17 +1383,17 @@ msgstr "Se <0>dokumentationen</0> for instruktioner."
msgid "Please sign in to your account"
msgstr "Log venligst ind på din konto"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Poolstatus"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Poolforbrug"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
@@ -1416,7 +1420,7 @@ msgstr "Proces startet"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Offentlig nøgle"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Kødybde"
msgid "Quiet Hours"
msgstr "Stille timer"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Rå"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Råt forbrug af lagerpool {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Stille timer"
msgid "Read"
msgstr "Læs"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Læsefejl"
@@ -1454,7 +1469,7 @@ msgstr "Modtaget"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Opdater"
@@ -1501,7 +1516,7 @@ msgstr "Genoptag"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1643,7 +1658,7 @@ msgstr "Starttid"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Tilstand"
@@ -1654,7 +1669,7 @@ msgstr "Tilstand"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
@@ -1671,7 +1686,7 @@ msgstr "Swap forbrug"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Skift tema"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1686,11 +1701,11 @@ msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Systemblæserhastigheder (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1698,7 +1713,7 @@ msgstr "Gennemsnitlig system belastning over tid"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Systemd Services"
msgstr ""
#: src/components/navbar.tsx
msgid "Systems"
@@ -1742,7 +1757,7 @@ msgstr "Temperaturer i systemsensorer"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1772,9 +1787,9 @@ msgstr "Dette vil permanent slette alle poster fra databasen."
msgid "Throughput of {extraFsName}"
msgstr "Gennemløb af {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Gennemløb for ZFS-pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Gennemløb af lagerpool {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1819,7 +1834,7 @@ msgstr "Samlet sendt data for hver interface"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Samlet tid brugt på læsning/skrivning (kan overstige 100 %)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,8 +1912,9 @@ msgstr "Udløser når brugen af en disk overstiger en tærskel"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Type"
msgstr ""
#: src/lib/alerts.ts
msgid "Unhealthy"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Universalnøgle"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Ukendt"
@@ -1945,7 +1961,7 @@ msgstr "Opdater"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Opdateret"
@@ -1968,20 +1984,20 @@ msgstr "Oppetid"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Forbrug"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Forbrug af ZFS-pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Forbrug af lagerpool {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Brugt"
@@ -2058,7 +2074,7 @@ msgstr "Windows-kommando"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows-kommando"
msgid "Write"
msgstr "Skriv"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Skrivefejl"
+78 -62
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 20:36\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -97,7 +97,7 @@ msgstr "5 Min"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Aktionen"
@@ -142,7 +142,7 @@ msgstr "Breite des Hauptlayouts anpassen"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -154,7 +154,7 @@ msgstr "Starten Sie nach dem Festlegen der Umgebungsvariablen Ihren Beszel-Hub n
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Bist du sicher?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisches Kopieren erfordert einen sicheren Kontext."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Verfügbar"
@@ -248,7 +248,7 @@ msgstr "Durchschnittliche Auslastung der GPU-Engines"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Backups"
msgstr "Backups"
msgstr ""
#: src/components/routes/system/charts/network-charts.tsx
#: src/lib/alerts.ts
@@ -300,7 +300,7 @@ msgstr "Binär"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -309,7 +309,7 @@ msgstr "Boot-Zustand"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Fähigkeiten"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapazität"
@@ -348,7 +348,7 @@ msgstr "Vorsicht - potenzieller Datenverlust"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Überprüfen Sie Ihren Überwachungsdienst"
msgid "Check your notification service"
msgstr "Überprüfe deinen Benachrichtigungsdienst"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Prüfsummenfehler"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Löschen"
@@ -411,7 +411,7 @@ msgstr "Klicke auf einen Container, um weitere Informationen zu sehen."
msgid "Click on a device to view more information."
msgstr "Klicke auf ein Gerät, um weitere Informationen zu sehen."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Klicke auf einen Pool, um Details zu vdevs und Datensätzen anzuzeigen."
@@ -447,7 +447,7 @@ msgstr "Verbindung unterbrochen"
#: src/lib/alerts.ts
msgid "Container"
msgstr "Container"
msgstr ""
#: src/lib/alerts.ts
msgid "Container Health"
@@ -530,7 +530,7 @@ msgstr "Kern"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -826,7 +826,7 @@ msgstr "Exportiere die aktuelle Systemkonfiguration."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -875,12 +875,12 @@ msgstr "Lüfter"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
msgstr "Filter..."
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint"
@@ -898,8 +898,8 @@ msgstr "Für <0>{min}</0> {min, plural, one {Minute} other {Minuten}}"
msgid "Forgot password?"
msgstr "Passwort vergessen?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Frei"
@@ -924,7 +924,7 @@ msgstr "Allgemein"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
@@ -948,13 +948,13 @@ msgid "Grid"
msgstr "Raster"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Gesundheit"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -972,7 +972,7 @@ msgstr "Homebrew-Befehl"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -1009,7 +1009,7 @@ msgstr "Wenn du das Passwort für dein Administratorkonto verloren hast, kannst
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Image"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -1146,7 +1146,7 @@ msgstr "Speichernutzung der Container"
msgid "Model"
msgstr "Modell"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Einhängepunkt"
@@ -1156,7 +1156,7 @@ msgstr "Einhängepunkt"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name"
msgstr "Name"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -1185,7 +1185,7 @@ msgstr "Netzwerkeinheit"
msgid "No"
msgstr "Nein"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Für diesen Pool sind keine Detaildaten verfügbar."
@@ -1212,7 +1212,7 @@ msgstr "Für dieses Gerät sind keine S.M.A.R.T.-Attribute verfügbar."
msgid "No systems found."
msgstr "Keine Systeme gefunden."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Keine"
@@ -1255,7 +1255,7 @@ msgstr "Einmalpasswort"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Menü öffnen"
@@ -1311,7 +1311,7 @@ msgstr "Vergangen"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Pause"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Paused"
@@ -1346,6 +1346,10 @@ msgstr "Dauerhaft"
msgid "Persistence"
msgstr "Persistenz"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Physischer Gerätespeicherplatz. Die tatsächlich nutzbare Kapazität ist unbekannt. Warnungen zur Pool-Festplattennutzung sind deaktiviert."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,22 +1383,22 @@ msgstr "In der <0>Dokumentation</0> findest du weitere Anweisungen."
msgid "Please sign in to your account"
msgstr "Bitte melde dich bei deinem Konto an"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Pool-Zustand"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Pool-Auslastung"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
@@ -1432,10 +1436,21 @@ msgstr "Warteschlangentiefe"
msgid "Quiet Hours"
msgstr "Ruhezeiten"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Roh"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Rohauslastung des Pools {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Ruhezeiten"
msgid "Read"
msgstr "Lesen"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Lesefehler"
@@ -1454,7 +1469,7 @@ msgstr "Empfangen"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Aktualisieren"
@@ -1501,7 +1516,7 @@ msgstr "Fortsetzen"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1643,7 +1658,7 @@ msgstr "Startzeit"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Status"
@@ -1654,7 +1669,7 @@ msgstr "Status"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
@@ -1686,11 +1701,11 @@ msgstr "Design wechseln"
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Systemlüftergeschwindigkeiten (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1715,7 +1730,7 @@ msgstr "Tabelle"
#: src/components/routes/system/info-bar.tsx
msgctxt "Tabs system layout option"
msgid "Tabs"
msgstr "Tabs"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
@@ -1742,7 +1757,7 @@ msgstr "Temperaturen der Systemsensoren"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1772,9 +1787,9 @@ msgstr "Dadurch werden alle ausgewählten Datensätze dauerhaft aus der Datenban
msgid "Throughput of {extraFsName}"
msgstr "Durchsatz von {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Durchsatz des ZFS-Pools {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Durchsatz des Pools {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "An E-Mail(s)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1897,6 +1912,7 @@ msgstr "Löst aus, wenn die Nutzung einer Festplatte einen Schwellenwert übersc
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Typ"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Universeller Token"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Unbekannt"
@@ -1945,7 +1961,7 @@ msgstr "Aktualisieren"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Aktualisiert"
@@ -1968,20 +1984,20 @@ msgstr "Betriebszeit"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Nutzung"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Auslastung des ZFS-Pools {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Auslastung des Pools {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Verwendet"
@@ -2058,7 +2074,7 @@ msgstr "Windows-Befehl"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows-Befehl"
msgid "Write"
msgstr "Schreiben"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Schreibfehler"
+61 -45
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: el\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -52,7 +52,7 @@ msgstr "I/O {diskName}"
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
msgstr "{threads, plural, one {# thread} other {# threads}}"
msgstr ""
#: src/lib/utils.ts
msgid "1 hour"
@@ -97,7 +97,7 @@ msgstr "5 λ"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Ενέργειες"
@@ -196,7 +196,7 @@ msgstr "Είστε βέβαιοι;"
msgid "Automatic copy requires a secure context."
msgstr "Η αυτόματη αντιγραφή απαιτεί ασφαλές περιβάλλον."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Διαθέσιμο"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Δυνατότητες"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Χωρητικότητα"
@@ -391,14 +391,14 @@ msgstr "Ελέγξτε την υπηρεσία παρακολούθησής σα
msgid "Check your notification service"
msgstr "Ελέγξτε την υπηρεσία ειδοποιήσεών σας"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Σφάλματα αθροίσματος ελέγχου"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Εκκαθάριση"
@@ -411,7 +411,7 @@ msgstr "Κάντε κλικ σε ένα κοντέινερ για να δείτ
msgid "Click on a device to view more information."
msgstr "Κάντε κλικ σε μια συσκευή για να δείτε περισσότερες πληροφορίες."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Κάντε κλικ σε ένα pool για να δείτε λεπτομέρειες για τα vdev και τα σύνολα δεδομένων."
@@ -447,11 +447,11 @@ msgstr "Η σύνδεση είναι εκτός λειτουργίας"
#: src/lib/alerts.ts
msgid "Container"
msgstr ""
msgstr "Κοντέινερ"
#: src/lib/alerts.ts
msgid "Container Health"
msgstr ""
msgstr "Υγεία κοντέινερ"
#: src/components/routes/system.tsx
msgid "Containers"
@@ -530,7 +530,7 @@ msgstr "Βασικά"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -732,7 +732,7 @@ msgstr "Επεξεργασία {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -875,8 +875,8 @@ msgstr "Ανεμιστήρες"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "Για <0>{min}</0> {min, plural, one {λεπτό} other {λεπτά}}"
msgid "Forgot password?"
msgstr "Ξεχάσατε τον κωδικό πρόσβασης;"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Ελεύθερο"
@@ -928,7 +928,7 @@ msgstr "Καθολικό"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
@@ -948,13 +948,13 @@ msgid "Grid"
msgstr "Πλέγμα"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Υγεία"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -1146,7 +1146,7 @@ msgstr "Χρήση μνήμης των κοντέινερ"
msgid "Model"
msgstr "Μοντέλο"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Σημείο προσάρτησης"
@@ -1185,7 +1185,7 @@ msgstr "Μονάδα δικτύου"
msgid "No"
msgstr "Όχι"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Δεν υπάρχουν λεπτομερή δεδομένα για αυτό το pool."
@@ -1212,7 +1212,7 @@ msgstr "Δεν υπάρχουν διαθέσιμα χαρακτηριστικά
msgid "No systems found."
msgstr "Δεν βρέθηκαν συστήματα."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Κανένα"
@@ -1255,7 +1255,7 @@ msgstr "Κωδικός μίας χρήσης"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Άνοιγμα μενού"
@@ -1346,6 +1346,10 @@ msgstr "Μόνιμο"
msgid "Persistence"
msgstr "Διατήρηση"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Φυσικός χώρος συσκευής. Η πραγματική ωφέλιμη χωρητικότητα είναι άγνωστη. Οι ειδοποιήσεις χρήσης δίσκων του pool είναι απενεργοποιημένες."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Παρακαλώ <0>ρυθμίστε έναν διακομιστή SMTP</0> για να διασφαλίσετε την παράδοση των ειδοποιήσεων."
@@ -1379,11 +1383,11 @@ msgstr "Ανατρέξτε στην <0>τεκμηρίωση</0> για οδηγ
msgid "Please sign in to your account"
msgstr "Συνδεθείτε στον λογαριασμό σας"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Κατάσταση pool"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Χρήση pool"
@@ -1432,10 +1436,21 @@ msgstr "Βάθος ουράς"
msgid "Quiet Hours"
msgstr "Ώρες σίγασης"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Ακατέργαστο"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Ακατέργαστη χρήση του pool αποθήκευσης {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Ώρες σίγασης"
msgid "Read"
msgstr "Ανάγνωση"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Σφάλματα ανάγνωσης"
@@ -1454,7 +1469,7 @@ msgstr "Λήφθηκαν"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Ανανέωση"
@@ -1643,7 +1658,7 @@ msgstr "Ώρα έναρξης"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Κατάσταση"
@@ -1772,9 +1787,9 @@ msgstr "Αυτό θα διαγράψει οριστικά όλες τις επι
msgid "Throughput of {extraFsName}"
msgstr "Ρυθμός μεταφοράς του {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Ρυθμός διαμεταγωγής του ZFS pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Ρυθμός μεταφοράς του pool αποθήκευσης {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1897,12 +1912,13 @@ msgstr "Ενεργοποιείται όταν η χρήση οποιουδήπο
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Τύπος"
#: src/lib/alerts.ts
msgid "Unhealthy"
msgstr ""
msgstr "Μη υγιές"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Καθολικό διακριτικό"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Άγνωστο"
@@ -1945,7 +1961,7 @@ msgstr "Ενημέρωση"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Ενημερώθηκε"
@@ -1968,20 +1984,20 @@ msgstr "Χρόνος λειτουργίας"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Χρήση"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Χρήση του ZFS pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Χρήση του pool αποθήκευσης {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Χρησιμοποιείται"
@@ -2058,7 +2074,7 @@ msgstr "Εντολή Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Εντολή Windows"
msgid "Write"
msgstr "Εγγραφή"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Σφάλματα εγγραφής"
+52 -36
View File
@@ -92,7 +92,7 @@ msgstr "5 min"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Actions"
@@ -191,7 +191,7 @@ msgstr "Are you sure?"
msgid "Automatic copy requires a secure context."
msgstr "Automatic copy requires a secure context."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Available"
@@ -333,7 +333,7 @@ msgid "Capabilities"
msgstr "Capabilities"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Capacity"
@@ -386,14 +386,14 @@ msgstr "Check your monitoring service"
msgid "Check your notification service"
msgstr "Check your notification service"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Checksum errors"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Clear"
@@ -406,7 +406,7 @@ msgstr "Click on a container to view more information."
msgid "Click on a device to view more information."
msgstr "Click on a device to view more information."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Click on a pool to view vdev and dataset details."
@@ -870,8 +870,8 @@ msgstr "Fans"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -893,8 +893,8 @@ msgstr "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgid "Forgot password?"
msgstr "Forgot password?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Free"
@@ -943,7 +943,7 @@ msgid "Grid"
msgstr "Grid"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Health"
@@ -1141,7 +1141,7 @@ msgstr "Memory usage of containers"
msgid "Model"
msgstr "Model"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Mountpoint"
@@ -1180,7 +1180,7 @@ msgstr "Network unit"
msgid "No"
msgstr "No"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "No detail data for this pool."
@@ -1207,7 +1207,7 @@ msgstr "No S.M.A.R.T. attributes available for this device."
msgid "No systems found."
msgstr "No systems found."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "None"
@@ -1250,7 +1250,7 @@ msgstr "One-time password"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Open menu"
@@ -1341,6 +1341,10 @@ msgstr "Permanent"
msgid "Persistence"
msgstr "Persistence"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1374,11 +1378,11 @@ msgstr "Please see <0>the documentation</0> for instructions."
msgid "Please sign in to your account"
msgstr "Please sign in to your account"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Pool Health"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Pool Usage"
@@ -1427,10 +1431,21 @@ msgstr "Queue Depth"
msgid "Quiet Hours"
msgstr "Quiet Hours"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Raw"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Raw usage of storage pool {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1438,7 +1453,7 @@ msgstr "Quiet Hours"
msgid "Read"
msgstr "Read"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Read errors"
@@ -1449,7 +1464,7 @@ msgstr "Received"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Refresh"
@@ -1638,7 +1653,7 @@ msgstr "Start Time"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "State"
@@ -1767,9 +1782,9 @@ msgstr "This will permanently delete all selected records from the database."
msgid "Throughput of {extraFsName}"
msgstr "Throughput of {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Throughput of ZFS pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Throughput of storage pool {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1892,6 +1907,7 @@ msgstr "Triggers when usage of any disk exceeds a threshold"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Type"
@@ -1914,7 +1930,7 @@ msgid "Universal token"
msgstr "Universal token"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Unknown"
@@ -1940,7 +1956,7 @@ msgstr "Update"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Updated"
@@ -1963,20 +1979,20 @@ msgstr "Uptime"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Usage"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Usage of ZFS pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Usage of storage pool {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Used"
@@ -2053,7 +2069,7 @@ msgstr "Windows command"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2061,7 +2077,7 @@ msgstr "Windows command"
msgid "Write"
msgstr "Write"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Write errors"
+74 -58
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -61,7 +61,7 @@ msgstr "1 hora"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 horas"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 días"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Acciones"
@@ -196,7 +196,7 @@ msgstr "¿Estás seguro?"
msgid "Automatic copy requires a secure context."
msgstr "La copia automática requiere un contexto seguro."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Disponible"
@@ -258,7 +258,7 @@ msgstr "Ancho de banda"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Capacidades"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Capacidad"
@@ -348,7 +348,7 @@ msgstr "Precaución - posible pérdida de datos"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Compruebe su servicio de monitorización"
msgid "Check your notification service"
msgstr "Verifica tu servicio de notificaciones"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Errores de suma de comprobación"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Limpiar"
@@ -411,7 +411,7 @@ msgstr "Haz clic en un contenedor para ver más información."
msgid "Click on a device to view more information."
msgstr "Haz clic en un dispositivo para ver más información."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Haz clic en un pool para ver los detalles de los vdev y los conjuntos de datos."
@@ -503,7 +503,7 @@ msgstr "Copiar nombre"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Copiar clave pública"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "Núcleo"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -783,7 +783,7 @@ msgstr "Efímero"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error"
msgstr "Error"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Example:"
@@ -826,7 +826,7 @@ msgstr "Exporta la configuración actual de sus sistemas."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,14 +869,14 @@ msgstr "Fallidos: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Ventiladores"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -888,7 +888,7 @@ msgstr "Huella dactilar"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -898,8 +898,8 @@ msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
msgid "Forgot password?"
msgstr "¿Olvidaste tu contraseña?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Libre"
@@ -920,11 +920,11 @@ msgstr "Llena"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/layout.tsx
msgid "General"
msgstr "General"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Cuadrícula"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Estado"
@@ -1146,7 +1146,7 @@ msgstr "Uso de memoria de los contenedores"
msgid "Model"
msgstr "Modelo"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Punto de montaje"
@@ -1183,9 +1183,9 @@ msgstr "Unidad de red"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "No"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "No hay datos detallados para este pool."
@@ -1212,7 +1212,7 @@ msgstr "No hay atributos S.M.A.R.T. disponibles para este dispositivo."
msgid "No systems found."
msgstr "No se encontraron sistemas."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Ninguno"
@@ -1255,7 +1255,7 @@ msgstr "Contraseña de un solo uso"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Abrir menú"
@@ -1346,6 +1346,10 @@ msgstr "Permanente"
msgid "Persistence"
msgstr "Persistencia"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Espacio físico del dispositivo. La capacidad útil real se desconoce. Las alertas de uso de disco del pool están desactivadas."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Por favor, <0>configura un servidor SMTP</0> para asegurar que las alertas sean entregadas."
@@ -1379,11 +1383,11 @@ msgstr "Por favor, consulta <0>la documentación</0> para obtener instrucciones.
msgid "Please sign in to your account"
msgstr "Por favor, inicia sesión en tu cuenta"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Estado del pool"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Uso del pool"
@@ -1416,7 +1420,7 @@ msgstr "Proceso iniciado"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Clave pública"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Profundidad de cola"
msgid "Quiet Hours"
msgstr "Horas de silencio"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Bruto"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Uso bruto del pool de almacenamiento {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Horas de silencio"
msgid "Read"
msgstr "Lectura"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Errores de lectura"
@@ -1454,7 +1469,7 @@ msgstr "Recibido"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Actualizar"
@@ -1643,7 +1658,7 @@ msgstr "Hora de inicio"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Estado"
@@ -1671,7 +1686,7 @@ msgstr "Uso de swap"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Cambiar tema"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Sistema"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Velocidades de los ventiladores del sistema (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "Esto eliminará permanentemente todos los registros seleccionados de la
msgid "Throughput of {extraFsName}"
msgstr "Rendimiento de {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Rendimiento del pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Rendimiento del pool de almacenamiento {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "A correo(s)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1806,7 +1821,7 @@ msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conex
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1819,12 +1834,12 @@ msgstr "Datos totales enviados por cada interfaz"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Tiempo total dedicado a lectura/escritura (puede superar el 100 %)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total: {0}"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
@@ -1897,6 +1912,7 @@ msgstr "Se activa cuando el uso de cualquier disco supera un umbral"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Tipo"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Token universal"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Desconocida"
@@ -1945,7 +1961,7 @@ msgstr "Actualizar"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Actualizado"
@@ -1968,20 +1984,20 @@ msgstr "Tiempo de actividad"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Uso"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Uso del pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Uso del pool de almacenamiento {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Usado"
@@ -2058,7 +2074,7 @@ msgstr "Comando Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Comando Windows"
msgid "Write"
msgstr "Escritura"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Errores de escritura"
+59 -43
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: fa\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Persian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -97,7 +97,7 @@ msgstr "۵ دقیقه"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "عملیات"
@@ -196,7 +196,7 @@ msgstr "آیا مطمئن هستید؟"
msgid "Automatic copy requires a secure context."
msgstr "کپی خودکار نیاز به یک زمینه امن دارد."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "در دسترس"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "قابلیت‌ها"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "ظرفیت"
@@ -391,14 +391,14 @@ msgstr "سرویس نظارتی خود را بررسی کنید"
msgid "Check your notification service"
msgstr "سرویس اطلاع‌رسانی خود را بررسی کنید"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "خطاهای مجموع بررسی"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "پاک کردن"
@@ -411,7 +411,7 @@ msgstr "برای مشاهده اطلاعات بیشتر روی کانتینر ک
msgid "Click on a device to view more information."
msgstr "برای مشاهده اطلاعات بیشتر روی دستگاه کلیک کنید."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "برای مشاهده جزئیات vdev و مجموعه‌داده‌ها روی یک استخر کلیک کنید."
@@ -503,7 +503,7 @@ msgstr "کپی نام"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "کپی کردن کلید عمومی"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -869,14 +869,14 @@ msgstr "ناموفق: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "فن‌ها"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "برای <0>{min}</0> {min, plural, one {دقیقه} other {دقیقه}}
msgid "Forgot password?"
msgstr "رمز عبور را فراموش کرده‌اید؟"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "آزاد"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "جدول"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "سلامتی"
@@ -1146,7 +1146,7 @@ msgstr "میزان استفاده حافظه کانتینرها"
msgid "Model"
msgstr "مدل"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "نقطه اتصال"
@@ -1185,7 +1185,7 @@ msgstr "واحد شبکه"
msgid "No"
msgstr "خیر"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "دادهٔ جزئی برای این استخر موجود نیست."
@@ -1212,7 +1212,7 @@ msgstr "هیچ ویژگی S.M.A.R.T برای این دستگاه موجود نی
msgid "No systems found."
msgstr "هیچ سیستمی یافت نشد."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "هیچ‌کدام"
@@ -1255,7 +1255,7 @@ msgstr "رمز عبور یک‌بار مصرف"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "باز کردن منو"
@@ -1346,6 +1346,10 @@ msgstr "دائمی"
msgid "Persistence"
msgstr "ماندگاری"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "فضای فیزیکی دستگاه. ظرفیت واقعی قابل استفاده نامشخص است. هشدارهای استفاده از دیسک استخر غیرفعال هستند."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "لطفاً برای اطمینان از تحویل هشدارها، یک <0>سرور SMTP پیکربندی کنید</0>."
@@ -1379,11 +1383,11 @@ msgstr "لطفاً برای دستورالعمل‌ها به <0>مستندات</
msgid "Please sign in to your account"
msgstr "لطفاً به حساب کاربری خود وارد شوید"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "سلامت استخر"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "استفاده از استخر"
@@ -1416,7 +1420,7 @@ msgstr "فرآیند شروع شد"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "کلید عمومی"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "عمق صف"
msgid "Quiet Hours"
msgstr "ساعات آرام"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "خام"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "استفاده خام از استخر ذخیره‌سازی {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "ساعات آرام"
msgid "Read"
msgstr "خواندن"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "خطاهای خواندن"
@@ -1454,7 +1469,7 @@ msgstr "دریافت شد"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "تازه‌سازی"
@@ -1643,7 +1658,7 @@ msgstr "زمان شروع"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "وضعیت"
@@ -1671,7 +1686,7 @@ msgstr "میزان استفاده از Swap"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "تغییر تم"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "سیستم"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "سرعت فن‌های سیستم (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "این کار تمام رکوردهای انتخاب شده را برا
msgid "Throughput of {extraFsName}"
msgstr "توان عملیاتی {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "توان عملیاتی استخر ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "توان عملیاتی استخر ذخیره‌سازی {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1819,7 +1834,7 @@ msgstr "داده‌های کل ارسال شده برای هر رابط"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "کل زمان صرف‌شده برای خواندن/نوشتن (ممکن است از ۱۰۰٪ بیشتر شود)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "هنگامی که استفاده از هر دیسکی از یک آستا
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "نوع"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "توکن جهانی"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "ناشناخته"
@@ -1945,7 +1961,7 @@ msgstr "به‌روزرسانی"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "به‌روزرسانی شد"
@@ -1968,20 +1984,20 @@ msgstr "آپتایم"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "میزان استفاده"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "استفاده از استخر ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "استفاده از استخر ذخیره‌سازی {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "استفاده شده"
@@ -2058,7 +2074,7 @@ msgstr "دستور Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "دستور Windows"
msgid "Write"
msgstr "نوشتن"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "خطاهای نوشتن"
+92 -76
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:42\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
@@ -52,7 +52,7 @@ msgstr "E/S {diskName}"
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
msgstr "{threads, plural, one {# thread} other {# threads}}"
msgstr ""
#: src/lib/utils.ts
msgid "1 hour"
@@ -61,11 +61,11 @@ msgstr "1 heure"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
msgstr "1 minute"
msgstr ""
#: src/lib/utils.ts
msgid "1 week"
@@ -78,7 +78,7 @@ msgstr "12 heures"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,23 +91,23 @@ msgstr "30 jours"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Actions"
msgstr ""
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/heartbeat.tsx
#: src/components/routes/settings/quiet-hours.tsx
msgid "Active"
msgstr "Active"
msgstr ""
#: src/components/active-alerts.tsx
msgid "Active Alerts"
@@ -142,7 +142,7 @@ msgstr "Ajuster la largeur de la mise en page principale"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -154,7 +154,7 @@ msgstr "Après avoir défini les variables d'environnement, redémarrez votre hu
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Êtes-vous sûr ?"
msgid "Automatic copy requires a secure context."
msgstr "La copie automatique nécessite un contexte sécurisé."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Disponible"
@@ -258,7 +258,7 @@ msgstr "Bande passante"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
@@ -300,7 +300,7 @@ msgstr "Binaire"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -309,7 +309,7 @@ msgstr "État de démarrage"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Capacités"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Capacité"
@@ -348,7 +348,7 @@ msgstr "Attention - perte de données potentielle"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -360,7 +360,7 @@ msgstr "Modifier les options générales de l'application."
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Charge"
msgstr "Charge"
msgstr ""
#. Context: Battery state
#: src/lib/i18n.ts
@@ -391,14 +391,14 @@ msgstr "Vérifiez votre service de surveillance"
msgid "Check your notification service"
msgstr "Vérifiez votre service de notification"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Erreurs de somme de contrôle"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Effacer"
@@ -411,7 +411,7 @@ msgstr "Cliquez sur un conteneur pour voir plus d'informations."
msgid "Click on a device to view more information."
msgstr "Cliquez sur un appareil pour voir plus d'informations."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Cliquez sur un pool pour afficher les détails des vdev et des jeux de données."
@@ -503,7 +503,7 @@ msgstr "Copier le nom"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Copier la clé publique"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "Cœur"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -614,7 +614,7 @@ msgstr "État actuel"
#. Power Cycles
#: src/components/routes/system/smart-table.tsx
msgid "Cycles"
msgstr "Cycles"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
@@ -643,7 +643,7 @@ msgstr "Supprimer l'empreinte"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Description"
msgstr ""
#: src/components/containers-table/containers-table.tsx
msgid "Detail"
@@ -696,7 +696,7 @@ msgstr "Entrée/Sortie réseau Docker"
#: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation"
msgstr "Documentation"
msgstr ""
#. Context: System is down
#: src/components/alerts-history-columns.tsx
@@ -732,7 +732,7 @@ msgstr "Modifier {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -826,7 +826,7 @@ msgstr "Exportez la configuration actuelle de vos systèmes."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,14 +869,14 @@ msgstr "Échec : {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Ventilateurs"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "Pendant <0>{min}</0> {min, plural, one {minute} other {minutes}}"
msgid "Forgot password?"
msgstr "Mot de passe oublié ?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Espace libre"
@@ -924,11 +924,11 @@ msgstr "Général"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Grille"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Santé"
@@ -1009,7 +1009,7 @@ msgstr "Si vous avez perdu le mot de passe de votre compte administrateur, vous
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Image"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -1113,7 +1113,7 @@ msgstr "Guide pour une installation manuelle"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 min"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
@@ -1146,7 +1146,7 @@ msgstr "Utilisation de la mémoire des conteneurs"
msgid "Model"
msgstr "Modèle"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Point de montage"
@@ -1185,7 +1185,7 @@ msgstr "Unité réseau"
msgid "No"
msgstr "Non"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Aucune donnée détaillée disponible pour ce pool."
@@ -1212,7 +1212,7 @@ msgstr "Aucun attribut S.M.A.R.T. disponible pour cet appareil."
msgid "No systems found."
msgstr "Aucun système trouvé."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Aucun"
@@ -1220,7 +1220,7 @@ msgstr "Aucun"
#: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/notifications.tsx
msgid "Notifications"
msgstr "Notifications"
msgstr ""
#: src/lib/alerts.ts
msgid "Notifications may include recent container log excerpts."
@@ -1255,7 +1255,7 @@ msgstr "Mot de passe à usage unique"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Ouvrir le menu"
@@ -1276,7 +1276,7 @@ msgstr "Écraser les alertes existantes"
#: src/components/command-palette.tsx
#: src/components/command-palette.tsx
msgid "Page"
msgstr "Page"
msgstr ""
#. placeholder {0}: table.getState().pagination.pageIndex + 1
#. placeholder {1}: table.getPageCount()
@@ -1311,7 +1311,7 @@ msgstr "Passé"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Pause"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Paused"
@@ -1340,12 +1340,16 @@ msgstr "Pourcentage de temps passé dans chaque état"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Permanent"
msgstr "Permanent"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Persistence"
msgstr "Persistance"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Espace physique de l'appareil. La capacité réellement utilisable est inconnue. Les alertes d'utilisation des disques du pool sont désactivées."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,22 +1383,22 @@ msgstr "Veuillez consulter <0>la documentation</0> pour les instructions."
msgid "Please sign in to your account"
msgstr "Veuillez vous connecter à votre compte"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "État du pool"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Utilisation du pool"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
msgid "Ports"
msgstr "Ports"
msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
@@ -1416,7 +1420,7 @@ msgstr "Processus démarré"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Clé publique"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Profondeur de file d'attente"
msgid "Quiet Hours"
msgstr "Heures calmes"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Brut"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Utilisation brute du pool de stockage {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Heures calmes"
msgid "Read"
msgstr "Lecture"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Erreurs de lecture"
@@ -1454,7 +1469,7 @@ msgstr "Reçu"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Actualiser"
@@ -1599,7 +1614,7 @@ msgstr "Détails du service"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Services"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
@@ -1643,7 +1658,7 @@ msgstr "Heure de début"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "État"
@@ -1671,7 +1686,7 @@ msgstr "Utilisation du swap"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Changer de thème"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Système"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Vitesses des ventilateurs du système (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "Ceci supprimera définitivement tous les enregistrements sélectionnés
msgid "Throughput of {extraFsName}"
msgstr "Débit de {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Débit du pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Débit du pool de stockage {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "Aux email(s)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1806,7 +1821,7 @@ msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connex
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1897,8 +1912,9 @@ msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Type"
msgstr ""
#: src/lib/alerts.ts
msgid "Unhealthy"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Token universel"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Inconnue"
@@ -1945,7 +1961,7 @@ msgstr "Mettre à jour"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Mis à jour"
@@ -1968,20 +1984,20 @@ msgstr "Temps de fonctionnement"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Utilisation"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Utilisation du pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Utilisation du pool de stockage {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Utilisé"
@@ -2058,7 +2074,7 @@ msgstr "Commande Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Commande Windows"
msgid "Write"
msgstr "Écriture"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Erreurs d’écriture"
+60 -44
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: he\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==2 ? 1 : 2);\n"
@@ -97,7 +97,7 @@ msgstr "5 דק'"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "פעולות"
@@ -196,7 +196,7 @@ msgstr "האם אתה בטוח?"
msgid "Automatic copy requires a secure context."
msgstr "העתקה אוטומטית דורשת הקשר מאובטח."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "זמין"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "יכולות"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "קיבולת"
@@ -391,14 +391,14 @@ msgstr "בדוק את שירות הניטור שלך"
msgid "Check your notification service"
msgstr "בדוק את שירות ההתראות שלך"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "שגיאות סכום ביקורת"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "נקה"
@@ -411,7 +411,7 @@ msgstr "לחץ על קונטיינר כדי לצפות במידע נוסף."
msgid "Click on a device to view more information."
msgstr "לחץ על התקן כדי לצפות במידע נוסף."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "לחצו על מאגר כדי להציג פרטי vdev ומערכי נתונים."
@@ -503,7 +503,7 @@ msgstr "העתק שם"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "העתק מפתח ציבורי"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "ליבה"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -869,14 +869,14 @@ msgstr "נכשל: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "מאווררים"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "למשך <0>{min}</0> {min, plural, one {דקה} other {דקות}}"
msgid "Forgot password?"
msgstr "שכחת סיסמה?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "פנוי"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "רשת"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "בריאות"
@@ -1146,7 +1146,7 @@ msgstr "שימוש בזיכרון של קונטיינרים"
msgid "Model"
msgstr "דגם"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "נקודת עגינה"
@@ -1185,7 +1185,7 @@ msgstr "יחידת רשת"
msgid "No"
msgstr "לא"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "אין נתונים מפורטים עבור מאגר זה."
@@ -1212,7 +1212,7 @@ msgstr "אין מאפייני S.M.A.R.T. זמינים עבור התקן זה."
msgid "No systems found."
msgstr "לא נמצאו מערכות."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "ללא"
@@ -1255,7 +1255,7 @@ msgstr "סיסמה חד-פעמית"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "פתח תפריט"
@@ -1346,6 +1346,10 @@ msgstr "קבוע"
msgid "Persistence"
msgstr "עקביות"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "שטח פיזי של ההתקן. הקיבולת האמיתית הניתנת לשימוש אינה ידועה. התראות השימוש בדיסקים של המאגר מושבתות."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "אנא <0>הגדר שרת SMTP</0> כדי להבטיח שהתראות יישלחו."
@@ -1379,11 +1383,11 @@ msgstr "אנא ראה <0>את התיעוד</0> להוראות."
msgid "Please sign in to your account"
msgstr "אנא התחבר לחשבון שלך"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "מצב המאגר"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "שימוש במאגר"
@@ -1416,7 +1420,7 @@ msgstr "תהליך התחיל"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "מפתח ציבורי"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "עומק תור"
msgid "Quiet Hours"
msgstr "שעות שקט"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "גולמי"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "שימוש גולמי במאגר האחסון {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "שעות שקט"
msgid "Read"
msgstr "קריאה"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "שגיאות קריאה"
@@ -1454,7 +1469,7 @@ msgstr "התקבל"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "רענן"
@@ -1643,7 +1658,7 @@ msgstr "זמן התחלה"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "מצב"
@@ -1671,7 +1686,7 @@ msgstr "שימוש ב-Swap"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "החלף ערכת נושא"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "מערכת"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "מהירויות מאווררי המערכת (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "פעולה זו תמחק לצמיתות את כל הרשומות שנב
msgid "Throughput of {extraFsName}"
msgstr "תפוקה של {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "תפוקת מאגר ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "תפוקה של מאגר האחסון {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "לאימייל(ים)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1897,6 +1912,7 @@ msgstr "מופעל כאשר שימוש בכל דיסק עולה על סף"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "סוג"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "token אוניברסלי"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "לא ידוע"
@@ -1945,7 +1961,7 @@ msgstr "עדכן"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "עודכן"
@@ -1968,20 +1984,20 @@ msgstr "זמן פעילות"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "שימוש"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "שימוש במאגר ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "שימוש במאגר האחסון {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "בשימוש"
@@ -2058,7 +2074,7 @@ msgstr "פקודת Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "פקודת Windows"
msgid "Write"
msgstr "כתיבה"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "שגיאות כתיבה"
+69 -53
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: hr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Croatian\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
@@ -97,7 +97,7 @@ msgstr "5 minuta"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Akcije"
@@ -142,7 +142,7 @@ msgstr "Prilagodite širinu glavnog rasporeda"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -154,7 +154,7 @@ msgstr "Nakon postavljanja varijabli okruženja, ponovno pokrenite svoj Beszel h
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Jeste li sigurni?"
msgid "Automatic copy requires a secure context."
msgstr "Automatsko kopiranje zahtijeva siguran kontekst."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Dostupno"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Mogućnosti"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapacitet"
@@ -348,7 +348,7 @@ msgstr "Oprez - mogući gubitak podataka"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Provjerite svoju uslugu nadzora"
msgid "Check your notification service"
msgstr "Provjerite svoju obavještajnu uslugu"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Pogreške kontrolnog zbroja"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Očisti"
@@ -411,7 +411,7 @@ msgstr "Kliknite na spremnik za prikaz više informacija."
msgid "Click on a device to view more information."
msgstr "Kliknite na uređaj da biste vidjeli više informacija."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Kliknite na spremište za prikaz pojedinosti o vdevovima i skupovima podataka."
@@ -503,7 +503,7 @@ msgstr "Kopiraj naziv"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Kopiraj javni ključ"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -661,7 +661,7 @@ msgstr "Prazni se"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
@@ -732,7 +732,7 @@ msgstr "Uredi {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -869,14 +869,14 @@ msgstr "Neuspjelo: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Ventilatori"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "Za <0>{min}</0> {min, plural, one {minutu} other {minute}}"
msgid "Forgot password?"
msgstr "Zaboravljena lozinka?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Slobodno"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Rešetka"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Zdravlje"
@@ -972,7 +972,7 @@ msgstr "Homebrew naredba"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -1146,7 +1146,7 @@ msgstr "Upotreba memorije spremnika"
msgid "Model"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Točka montiranja"
@@ -1185,7 +1185,7 @@ msgstr "Mjerna jedinica za mrežu"
msgid "No"
msgstr "Ne"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Nema detaljnih podataka za ovo spremište."
@@ -1212,7 +1212,7 @@ msgstr "Nema dostupnih S.M.A.R.T. atributa za ovaj uređaj."
msgid "No systems found."
msgstr "Nije pronađen nijedan sustav."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Nema"
@@ -1255,7 +1255,7 @@ msgstr "Jednokratna lozinka"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Otvori meni"
@@ -1346,6 +1346,10 @@ msgstr "Trajan"
msgid "Persistence"
msgstr "Postojanost"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fizički prostor uređaja. Stvarni iskoristivi kapacitet nije poznat. Upozorenja o iskorištenosti diskova spremišta su onemogućena."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,17 +1383,17 @@ msgstr "Molimo provjerite <0>dokumentaciju</0> za upute."
msgid "Please sign in to your account"
msgstr "Molimo prijavite se u svoj račun"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Stanje spremišta"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Iskorištenost spremišta"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
@@ -1416,7 +1420,7 @@ msgstr "Proces pokrenut"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Javni ključ"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Dubina reda"
msgid "Quiet Hours"
msgstr "Tihi sati"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Sirovo"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Sirova iskorištenost spremišta {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Tihi sati"
msgid "Read"
msgstr "Pročitaj"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Pogreške čitanja"
@@ -1454,7 +1469,7 @@ msgstr "Primljeno"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Osvježi"
@@ -1643,7 +1658,7 @@ msgstr "Vrijeme početka"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Stanje"
@@ -1654,7 +1669,7 @@ msgstr "Stanje"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
@@ -1671,7 +1686,7 @@ msgstr "Swap Iskorištenost"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Promijeni temu"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Sustav"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Brzine ventilatora sustava (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1725,7 +1740,7 @@ msgstr "Zadaci"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Temp"
msgstr "Temp"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
@@ -1772,9 +1787,9 @@ msgstr "Ovom radnjom će se trajno izbrisati svi odabrani zapisi iz baze podatak
msgid "Throughput of {extraFsName}"
msgstr "Protok {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Propusnost ZFS spremišta {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Protok spremišta {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "Primaoci emaila"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1819,7 +1834,7 @@ msgstr "Ukupni podaci poslani za svako sučelje"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Ukupno vrijeme utrošeno na čitanje/pisanje (može prelaziti 100 %)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "Pokreće se kada iskorištenost bilo kojeg diska premaši prag"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Vrsta"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Sveopći token"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Nepoznato"
@@ -1945,7 +1961,7 @@ msgstr "Ažuriraj"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Ažurirano"
@@ -1968,20 +1984,20 @@ msgstr "Vrijeme rada"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Iskorištenost"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Iskorištenost ZFS spremišta {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Iskorištenost spremišta {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Iskorišteno"
@@ -2058,7 +2074,7 @@ msgstr "Windows naredba"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows naredba"
msgid "Write"
msgstr "Piši"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Pogreške zapisivanja"
+67 -51
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: hu\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} perc} few {{countString} perc} many {
#: src/components/routes/system/charts/disk-charts.tsx
msgid "{diskName} I/O"
msgstr "{diskName} I/O"
msgstr ""
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
@@ -97,7 +97,7 @@ msgstr "5 perc"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Műveletek"
@@ -196,7 +196,7 @@ msgstr "Biztos vagy benne?"
msgid "Automatic copy requires a secure context."
msgstr "Az automatikus másolás biztonságos környezetet igényel."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Elérhető"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Képességek"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapacitás"
@@ -348,7 +348,7 @@ msgstr "Figyelem - potenciális adatvesztés"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Ellenőrizze a megfigyelő szolgáltatást"
msgid "Check your notification service"
msgstr "Ellenőrizd az értesítési szolgáltatásodat"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Ellenőrzőösszeg-hibák"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Törlés"
@@ -411,7 +411,7 @@ msgstr "Kattintson egy konténerre a további információk megtekintéséhez."
msgid "Click on a device to view more information."
msgstr "Kattintson egy eszközre további információk megtekintéséhez."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Kattintson egy tárkészletre a vdev- és adatkészlet-részletek megtekintéséhez."
@@ -503,7 +503,7 @@ msgstr "Név másolása"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Nyilvános kulcs másolása"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "Mag"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -732,7 +732,7 @@ msgstr "Szerkesztés {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -826,7 +826,7 @@ msgstr "Exportálja a jelenlegi rendszerkonfigurációt."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,14 +869,14 @@ msgstr "Sikertelen: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Ventilátorok"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -888,7 +888,7 @@ msgstr "Ujjlenyomat"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -898,8 +898,8 @@ msgstr "<0>{min}</0> {min, plural, one {percig} other {percig}}"
msgid "Forgot password?"
msgstr "Elfelejtette a jelszavát?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Szabad"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Rács"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Egészség"
@@ -1146,7 +1146,7 @@ msgstr "Konténerek memóriahasználata"
msgid "Model"
msgstr "Modell"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Csatolási pont"
@@ -1185,7 +1185,7 @@ msgstr "Sávszélesség mértékegysége"
msgid "No"
msgstr "Nem"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Ehhez a tárkészlethez nem érhetők el részletes adatok."
@@ -1212,7 +1212,7 @@ msgstr "Ehhez az eszközhöz nem állnak rendelkezésre S.M.A.R.T. attribútumok
msgid "No systems found."
msgstr "Nem található rendszer."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Nincs"
@@ -1255,7 +1255,7 @@ msgstr "Egyszeri jelszó"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Menü megnyitása"
@@ -1346,6 +1346,10 @@ msgstr "Állandó"
msgid "Persistence"
msgstr "Tartósság"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fizikai eszközterület. A valódi használható kapacitás ismeretlen. A készlet lemezhasználati riasztásai le vannak tiltva."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,17 +1383,17 @@ msgstr "Kérjük, nézze meg <0>a dokumentációt</0> az utasításokért."
msgid "Please sign in to your account"
msgstr "Kérjük, jelentkezzen be a fiókjába"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Tárkészlet állapota"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Tárkészlet kihasználtsága"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
@@ -1416,7 +1420,7 @@ msgstr "Folyamat elindítva"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Nyilvános kulcs"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Várakozási sor mélysége"
msgid "Quiet Hours"
msgstr "Csendes órák"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Nyers"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "A(z) {displayName} tárolókészlet nyers kihasználtsága"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Csendes órák"
msgid "Read"
msgstr "Olvasás"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Olvasási hibák"
@@ -1454,7 +1469,7 @@ msgstr "Fogadott"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Frissítés"
@@ -1643,7 +1658,7 @@ msgstr "Kezdési idő"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Állapot"
@@ -1671,7 +1686,7 @@ msgstr "Swap használat"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Téma váltása"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Rendszer"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Rendszerventilátorok sebessége (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "Ez véglegesen törli az összes kijelölt bejegyzést az adatbázisból
msgid "Throughput of {extraFsName}"
msgstr "A {extraFsName} átviteli teljesítménye"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "{poolName} ZFS-tárkészlet átviteli sebessége"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "A(z) {displayName} tárolókészlet átviteli teljesítménye"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "E-mailben"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1819,7 +1834,7 @@ msgstr "Összes elküldött adat minden interfészenként"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Olvasással/írással töltött teljes idő (meghaladhatja a 100%-ot)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "Riaszt, ha a lemezhasználat túllép egy küszöbértéket"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Típus"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Univerzális token"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Ismeretlen"
@@ -1945,7 +1961,7 @@ msgstr "Frissítés"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Frissítve"
@@ -1968,20 +1984,20 @@ msgstr "Üzemidő"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Használat"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "{poolName} ZFS-tárkészlet kihasználtsága"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "A(z) {displayName} tárolókészlet kihasználtsága"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Felhasznált"
@@ -2058,7 +2074,7 @@ msgstr "Windows parancs"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows parancs"
msgid "Write"
msgstr "Írás"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Írási hibák"
+62 -46
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: id\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Indonesian\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -97,7 +97,7 @@ msgstr "5 mnt"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Aksi"
@@ -142,7 +142,7 @@ msgstr "Sesuaikan lebar layar utama"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -196,7 +196,7 @@ msgstr "Apakah anda yakin?"
msgid "Automatic copy requires a secure context."
msgstr "Copy memerlukan https."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Tersedia"
@@ -313,7 +313,7 @@ msgstr "Byte (KB/s, MB/s, GB/s)"
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffers"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Kapabilitas"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapasitas"
@@ -348,7 +348,7 @@ msgstr "Perhatian - potensi kehilangan data"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Periksa layanan pemantauan Anda"
msgid "Check your notification service"
msgstr "Periksa jasa penyedia notifikasi anda"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Kesalahan checksum"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Bersihkan"
@@ -411,7 +411,7 @@ msgstr "Klik pada kontainer untuk melihat informasi lebih banyak."
msgid "Click on a device to view more information."
msgstr "Klik pada perangkat untuk melihat informasi lebih banyak."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Klik pool untuk melihat detail vdev dan dataset."
@@ -503,7 +503,7 @@ msgstr "Salin nama"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Salin kunci publik"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -869,14 +869,14 @@ msgstr "Gagal: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Kipas"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "Untuk <0>{min}</0> {min, plural, one {menit} other {menit}}"
msgid "Forgot password?"
msgstr "Lupa kata sandi?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Kosong"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Kartu"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Kesehatan"
@@ -1146,7 +1146,7 @@ msgstr "Penggunaan memori container"
msgid "Model"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Titik kait"
@@ -1185,7 +1185,7 @@ msgstr "Unit jaringan"
msgid "No"
msgstr "Tidak"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Tidak ada data terperinci yang tersedia untuk pool ini."
@@ -1212,7 +1212,7 @@ msgstr "Tidak ada atribut S.M.A.R.T. yang tersedia untuk perangkat ini."
msgid "No systems found."
msgstr "Sistem tidak ditemukan."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Tidak ada"
@@ -1255,7 +1255,7 @@ msgstr "Kata sandi sekali pakai (OTP)"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Buka menu"
@@ -1346,6 +1346,10 @@ msgstr "Permanen"
msgid "Persistence"
msgstr "Tetap berlaku"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Ruang perangkat fisik. Kapasitas sebenarnya yang dapat digunakan tidak diketahui. Peringatan penggunaan disk pool dinonaktifkan."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Silakan <0>konfigurasi server SMTP</0> untuk memastikan peringatan dikirimkan."
@@ -1379,11 +1383,11 @@ msgstr "Silakan lihat <0>dokumentasi</0> untuk instruksi."
msgid "Please sign in to your account"
msgstr "Silakan masuk ke akun anda"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Kesehatan pool"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Penggunaan pool"
@@ -1416,7 +1420,7 @@ msgstr "Proses dimulai"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Kunci publik"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Kedalaman Antrian"
msgid "Quiet Hours"
msgstr "Jam Tenang"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Mentah"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Penggunaan mentah pool penyimpanan {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Jam Tenang"
msgid "Read"
msgstr "Baca"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Kesalahan baca"
@@ -1454,7 +1469,7 @@ msgstr "Diterima"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Muat ulang"
@@ -1643,7 +1658,7 @@ msgstr "Waktu Mulai"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Status"
@@ -1671,7 +1686,7 @@ msgstr "Penggunaan Swap"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Ganti tema"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Sistem"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Kecepatan kipas sistem (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "Ini akan menghapus secara permanen semua record yang dipilih dari databa
msgid "Throughput of {extraFsName}"
msgstr "Throughput dari {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Throughput pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Laju transfer pool penyimpanan {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1819,7 +1834,7 @@ msgstr "Total data yang dikirim untuk setiap antarmuka"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Total waktu yang dihabiskan untuk baca/tulis (dapat melebihi 100%)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "Dipicu ketika penggunaan disk apa pun melebihi ambang batas"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Tipe"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Token universal"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Tidak diketahui"
@@ -1945,7 +1961,7 @@ msgstr "Perbarui"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Diperbarui"
@@ -1968,20 +1984,20 @@ msgstr "Waktu aktif"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Penggunaan"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Penggunaan pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Penggunaan pool penyimpanan {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Digunakan"
@@ -2058,7 +2074,7 @@ msgstr "Perintah Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Perintah Windows"
msgid "Write"
msgstr "Tulis"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Kesalahan tulis"
+78 -62
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: it\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -61,7 +61,7 @@ msgstr "1 ora"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 ore"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 giorni"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Azioni"
@@ -196,7 +196,7 @@ msgstr "Sei sicuro?"
msgid "Automatic copy requires a secure context."
msgstr "La copia automatica richiede un contesto sicuro."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Disponibile"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Funzionalità"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Capacità"
@@ -348,7 +348,7 @@ msgstr "Attenzione - possibile perdita di dati"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Controlla il tuo servizio di monitoraggio"
msgid "Check your notification service"
msgstr "Controlla il tuo servizio di notifica"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Errori di checksum"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Cancella"
@@ -411,7 +411,7 @@ msgstr "Fare clic su un contenitore per visualizzare ulteriori informazioni."
msgid "Click on a device to view more information."
msgstr "Fare clic su un dispositivo per visualizzare più informazioni."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Fai clic su un pool per visualizzare i dettagli di vdev e dataset."
@@ -447,7 +447,7 @@ msgstr "La connessione è interrotta"
#: src/lib/alerts.ts
msgid "Container"
msgstr "Container"
msgstr ""
#: src/lib/alerts.ts
msgid "Container Health"
@@ -503,7 +503,7 @@ msgstr "Copia nome"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Copia chiave pubblica"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "Interne"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -679,7 +679,7 @@ msgstr "Utilizzo del disco di {extraFsName}"
#: src/components/routes/system/info-bar.tsx
msgctxt "Layout display options"
msgid "Display"
msgstr "Display"
msgstr ""
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Docker CPU Usage"
@@ -732,7 +732,7 @@ msgstr "Modifica {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -826,7 +826,7 @@ msgstr "Esporta la configurazione attuale dei tuoi sistemi."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,14 +869,14 @@ msgstr "Fallito: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Ventole"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -888,7 +888,7 @@ msgstr "Impronta digitale"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -898,8 +898,8 @@ msgstr "Per <0>{min}</0> {min, plural, one {minuto} other {minuti}}"
msgid "Forgot password?"
msgstr "Password dimenticata?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Libero"
@@ -928,7 +928,7 @@ msgstr "Globale"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
@@ -948,13 +948,13 @@ msgid "Grid"
msgstr "Griglia"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Stato"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -972,7 +972,7 @@ msgstr "Comando Homebrew"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -1113,7 +1113,7 @@ msgstr "Istruzioni di configurazione manuale"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 min"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
@@ -1146,7 +1146,7 @@ msgstr "Utilizzo della memoria dei container"
msgid "Model"
msgstr "Modello"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Punto di montaggio"
@@ -1183,9 +1183,9 @@ msgstr "Unità rete"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "No"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Nessun dato dettagliato disponibile per questo pool."
@@ -1212,7 +1212,7 @@ msgstr "Nessun attributo S.M.A.R.T. disponibile per questo dispositivo."
msgid "No systems found."
msgstr "Nessun sistema trovato."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Nessuno"
@@ -1255,7 +1255,7 @@ msgstr "Password monouso"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Apri menu"
@@ -1291,7 +1291,7 @@ msgstr "Pagine / Impostazioni"
#: src/components/login/auth-form.tsx
#: src/components/login/auth-form.tsx
msgid "Password"
msgstr "Password"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Password must be at least 8 characters."
@@ -1346,6 +1346,10 @@ msgstr "Permanente"
msgid "Persistence"
msgstr "Persistenza"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Spazio fisico del dispositivo. La capacità effettiva utilizzabile è sconosciuta. Gli avvisi di utilizzo del disco del pool sono disabilitati."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,11 +1383,11 @@ msgstr "Si prega di consultare <0>la documentazione</0> per le istruzioni."
msgid "Please sign in to your account"
msgstr "Si prega di accedere al proprio account"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Stato del pool"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Utilizzo del pool"
@@ -1416,7 +1420,7 @@ msgstr "Processo avviato"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Chiave pubblica"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Profondità coda"
msgid "Quiet Hours"
msgstr "Ore silenziose"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Grezzo"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Utilizzo grezzo del pool di archiviazione {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Ore silenziose"
msgid "Read"
msgstr "Lettura"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Errori di lettura"
@@ -1454,7 +1469,7 @@ msgstr "Ricevuto"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Aggiorna"
@@ -1501,7 +1516,7 @@ msgstr "Riprendi"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1643,7 +1658,7 @@ msgstr "Ora di inizio"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Stato"
@@ -1671,7 +1686,7 @@ msgstr "Utilizzo Swap"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Cambia tema"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Sistema"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Velocità delle ventole di sistema (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1742,7 +1757,7 @@ msgstr "Temperature dei sensori di sistema"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1772,9 +1787,9 @@ msgstr "Questo eliminerà permanentemente tutti i record selezionati dal databas
msgid "Throughput of {extraFsName}"
msgstr "Throughput di {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Throughput del pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Throughput del pool di archiviazione {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "A email(s)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1819,7 +1834,7 @@ msgstr "Dati totali inviati per ogni interfaccia"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Tempo totale dedicato a lettura/scrittura (può superare il 100%)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "Attiva quando l'utilizzo di un disco supera una soglia"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Tipo"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Token universale"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Sconosciuta"
@@ -1945,7 +1961,7 @@ msgstr "Aggiorna"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Aggiornato"
@@ -1968,20 +1984,20 @@ msgstr "Tempo di attività"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Utilizzo"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Utilizzo del pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Utilizzo del pool di archiviazione {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Utilizzato"
@@ -2058,7 +2074,7 @@ msgstr "Comando Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Comando Windows"
msgid "Write"
msgstr "Scrittura"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Errori di scrittura"
+59 -43
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ja\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -97,7 +97,7 @@ msgstr "5分"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "アクション"
@@ -196,7 +196,7 @@ msgstr "よろしいですか?"
msgid "Automatic copy requires a secure context."
msgstr "自動コピーには安全なコンテキストが必要です。"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "利用可能"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "機能"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "容量"
@@ -391,14 +391,14 @@ msgstr "監視サービスを確認する"
msgid "Check your notification service"
msgstr "通知サービスを確認してください"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "チェックサムエラー"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "クリア"
@@ -411,7 +411,7 @@ msgstr "詳細情報を表示するにはコンテナをクリックしてくだ
msgid "Click on a device to view more information."
msgstr "詳細情報を表示するにはデバイスをクリックしてください。"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "プールをクリックして、vdev とデータセットの詳細を表示します。"
@@ -503,7 +503,7 @@ msgstr "名前をコピーする"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "公開鍵をコピー"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -869,14 +869,14 @@ msgstr "失敗: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "ファン"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "<0>{min}</0> {min, plural, one {分} other {分}}の間"
msgid "Forgot password?"
msgstr "パスワードをお忘れですか?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "空き"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "グリッド"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "ヘルス"
@@ -1146,7 +1146,7 @@ msgstr "コンテナのメモリ使用量"
msgid "Model"
msgstr "モデル"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "マウントポイント"
@@ -1185,7 +1185,7 @@ msgstr "ネットワーク単位"
msgid "No"
msgstr "いいえ"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "このプールの詳細データはありません。"
@@ -1212,7 +1212,7 @@ msgstr "このデバイスのS.M.A.R.T.属性は利用できません。"
msgid "No systems found."
msgstr "システムが見つかりませんでした。"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "なし"
@@ -1255,7 +1255,7 @@ msgstr "ワンタイムパスワード"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "メニューを開く"
@@ -1346,6 +1346,10 @@ msgstr "永久"
msgid "Persistence"
msgstr "永続性"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "物理デバイス容量。実際に使用可能な容量は不明です。プールのディスク使用量アラートは無効になっています。"
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "アラートが配信されるように<0>SMTPサーバーを設定</0>してください。"
@@ -1379,11 +1383,11 @@ msgstr "手順については<0>ドキュメント</0>を参照してくださ
msgid "Please sign in to your account"
msgstr "アカウントにサインインしてください"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "プールの健全性"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "プール使用量"
@@ -1416,7 +1420,7 @@ msgstr "プロセス開始"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "公開鍵"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "キューの深さ"
msgid "Quiet Hours"
msgstr "サイレント時間"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "生"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "ストレージプール {displayName} の生使用量"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "サイレント時間"
msgid "Read"
msgstr "読み取り"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "読み取りエラー"
@@ -1454,7 +1469,7 @@ msgstr "受信"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "更新"
@@ -1643,7 +1658,7 @@ msgstr "開始時間"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "状態"
@@ -1671,7 +1686,7 @@ msgstr "スワップ使用量"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "テーマを切り替え"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "システム"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "システムファン速度 (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "これにより、選択したすべてのレコードがデータベー
msgid "Throughput of {extraFsName}"
msgstr "{extraFsName}のスループット"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "ZFS プール {poolName} のスループット"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "ストレージプール {displayName} のスループット"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1819,7 +1834,7 @@ msgstr "各インターフェースの総送信データ量"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "読み取り/書き込みに費やした合計時間 (100% を超える場合があります)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "ディスクの使用量がしきい値を超えたときにトリガー
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "タイプ"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "ユニバーサルトークン"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "不明"
@@ -1945,7 +1961,7 @@ msgstr "更新"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "更新済み"
@@ -1968,20 +1984,20 @@ msgstr "稼働時間"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "使用量"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "ZFS プール {poolName} の使用量"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "ストレージプール {displayName} の使用量"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "使用中"
@@ -2058,7 +2074,7 @@ msgstr "Windows コマンド"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows コマンド"
msgid "Write"
msgstr "書き込み"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "書き込みエラー"
+61 -45
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ko\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -48,7 +48,7 @@ msgstr "{count, plural, one {{countString} 분} few {{countString} 분} many {{c
#: src/components/routes/system/charts/disk-charts.tsx
msgid "{diskName} I/O"
msgstr "{diskName} I/O"
msgstr ""
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
@@ -97,7 +97,7 @@ msgstr "5분"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "작업"
@@ -196,7 +196,7 @@ msgstr "확실합니까?"
msgid "Automatic copy requires a secure context."
msgstr "자동 복사는 안전한 컨텍스트가 필요합니다."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "사용 가능"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "권한"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "용량"
@@ -391,14 +391,14 @@ msgstr "모니터링 서비스 확인"
msgid "Check your notification service"
msgstr "알림 서비스를 확인하세요."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "체크섬 오류"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "지우기"
@@ -411,7 +411,7 @@ msgstr "더 많은 정보를 보려면 컨테이너를 클릭하세요."
msgid "Click on a device to view more information."
msgstr "더 많은 정보를 보려면 장치를 클릭하세요."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "풀을 클릭하여 vdev 및 데이터 세트 세부 정보를 확인하세요."
@@ -503,7 +503,7 @@ msgstr "이름 복사"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "공개 키 복사"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "코어"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -869,14 +869,14 @@ msgstr "실패: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "<0>{min}</0> {min, plural, one {분} other {분}} 동안"
msgid "Forgot password?"
msgstr "비밀번호를 잊으셨나요?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "여유 공간"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "그리드"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "상태"
@@ -1146,7 +1146,7 @@ msgstr "컨테이너 메모리 사용량"
msgid "Model"
msgstr "모델"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "마운트 지점"
@@ -1185,7 +1185,7 @@ msgstr "네트워크 단위"
msgid "No"
msgstr "아니오"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "이 풀에 대한 상세 데이터가 없습니다."
@@ -1212,7 +1212,7 @@ msgstr "이 장치에 사용할 수 있는 S.M.A.R.T. 속성이 없습니다."
msgid "No systems found."
msgstr "시스템을 찾을 수 없습니다."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "없음"
@@ -1255,7 +1255,7 @@ msgstr "OTP"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "메뉴 열기"
@@ -1346,6 +1346,10 @@ msgstr "영구적"
msgid "Persistence"
msgstr "지속성"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "물리적 장치 공간. 실제 사용 가능한 용량을 알 수 없습니다. 풀 디스크 사용량 알림이 비활성화되어 있습니다."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "알림이 전달되도록 <0>SMTP 서버를 구성</0>하세요."
@@ -1379,11 +1383,11 @@ msgstr "사용법은 <0>문서</0>를 참조하세요."
msgid "Please sign in to your account"
msgstr "계정에 로그인하세요."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "풀 상태"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "풀 사용량"
@@ -1416,7 +1420,7 @@ msgstr "프로세스 시작됨"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "공개 키"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "대기열 깊이"
msgid "Quiet Hours"
msgstr "조용한 시간"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "원시"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "스토리지 풀 {displayName}의 원시 사용량"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "조용한 시간"
msgid "Read"
msgstr "읽기"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "읽기 오류"
@@ -1454,7 +1469,7 @@ msgstr "수신됨"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "새로고침"
@@ -1643,7 +1658,7 @@ msgstr "시작 시간"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "상태"
@@ -1671,7 +1686,7 @@ msgstr "스왑 사용량"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "테마 전환"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "시스템"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "시스템 팬 속도 (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "선택한 모든 레코드를 데이터베이스에서 영구적으로
msgid "Throughput of {extraFsName}"
msgstr "{extraFsName}의 처리량"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "ZFS 풀 {poolName} 처리량"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "스토리지 풀 {displayName} 처리량"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1819,7 +1834,7 @@ msgstr "각 인터페이스별 총합 업로드 데이터량"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "읽기/쓰기에 소요된 총 시간 (100%를 초과할 수 있음)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "디스크 사용량이 임계값을 초과할 때 트리거됩니다."
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "유형"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "범용 토큰"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "알 수 없음"
@@ -1945,7 +1961,7 @@ msgstr "업데이트"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "업데이트됨"
@@ -1968,20 +1984,20 @@ msgstr "가동시간"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "사용량"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "ZFS 풀 {poolName} 사용량"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "스토리지 풀 {displayName} 사용량"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "사용됨"
@@ -2058,7 +2074,7 @@ msgstr "Windows 명령어"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows 명령어"
msgid "Write"
msgstr "쓰기"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "쓰기 오류"
+79 -63
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: nl\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -52,7 +52,7 @@ msgstr "I/O van {diskName}"
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
msgstr "{threads, plural, one {# thread} other {# threads}}"
msgstr ""
#: src/lib/utils.ts
msgid "1 hour"
@@ -69,7 +69,7 @@ msgstr "1 minuut"
#: src/lib/utils.ts
msgid "1 week"
msgstr "1 week"
msgstr ""
#: src/lib/utils.ts
msgid "12 hours"
@@ -97,7 +97,7 @@ msgstr "5 minuten"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Acties"
@@ -154,7 +154,7 @@ msgstr "Start na het instellen van de omgevingsvariabelen je Beszel-hub opnieuw
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Weet je het zeker?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisch kopiëren vereist een veilige context."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Beschikbaar"
@@ -258,7 +258,7 @@ msgstr "Bandbreedte"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
@@ -300,7 +300,7 @@ msgstr "Binair"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -309,11 +309,11 @@ msgstr "Opstartstatus"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffers"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Mogelijkheden"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Capaciteit"
@@ -348,7 +348,7 @@ msgstr "Opgelet - potentieel gegevensverlies"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Controleer je monitoringservice"
msgid "Check your notification service"
msgstr "Controleer je meldingsservice"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Checksumfouten"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Wissen"
@@ -411,7 +411,7 @@ msgstr "Klik op een container om meer informatie te zien."
msgid "Click on a device to view more information."
msgstr "Klik op een apparaat om meer informatie te bekijken."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Klik op een pool om details over vdevs en datasets te bekijken."
@@ -447,7 +447,7 @@ msgstr "Verbinding is niet actief"
#: src/lib/alerts.ts
msgid "Container"
msgstr "Container"
msgstr ""
#: src/lib/alerts.ts
msgid "Container Health"
@@ -455,7 +455,7 @@ msgstr "Containergezondheid"
#: src/components/routes/system.tsx
msgid "Containers"
msgstr "Containers"
msgstr ""
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
@@ -530,7 +530,7 @@ msgstr "Kern"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -826,7 +826,7 @@ msgstr "Exporteer je huidige systeemconfiguratie."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -875,8 +875,8 @@ msgstr "Ventilatoren"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -888,7 +888,7 @@ msgstr "Vingerafdruk"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -898,8 +898,8 @@ msgstr "Voor <0>{min}</0> {min, plural, one {minuut} other {minuten}}"
msgid "Forgot password?"
msgstr "Wachtwoord vergeten?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Vrij"
@@ -928,7 +928,7 @@ msgstr "Globaal"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
@@ -948,13 +948,13 @@ msgid "Grid"
msgstr "Raster"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Gezondheid"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -1009,7 +1009,7 @@ msgstr "Als je het wachtwoord voor je beheerdersaccount bent kwijtgeraakt, kan j
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Image"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -1017,7 +1017,7 @@ msgstr "Inactief"
#: src/components/routes/settings/heartbeat.tsx
msgid "Interval"
msgstr "Interval"
msgstr ""
#: src/components/login/auth-form.tsx
msgid "Invalid email address."
@@ -1113,7 +1113,7 @@ msgstr "Handmatige installatie-instructies"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 min"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
@@ -1144,9 +1144,9 @@ msgstr "Geheugengebruik van containers"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Koppelpunt"
@@ -1185,7 +1185,7 @@ msgstr "Netwerk eenheid"
msgid "No"
msgstr "Nee"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Geen gedetailleerde gegevens beschikbaar voor deze pool."
@@ -1212,7 +1212,7 @@ msgstr "Geen S.M.A.R.T. kenmerken beschikbaar voor dit apparaat."
msgid "No systems found."
msgstr "Geen systemen gevonden."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Geen"
@@ -1255,7 +1255,7 @@ msgstr "Eenmalig wachtwoord"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Menu openen"
@@ -1346,6 +1346,10 @@ msgstr "Blijvend"
msgid "Persistence"
msgstr "Persistentie"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fysieke apparatuurruimte. De werkelijk bruikbare capaciteit is onbekend. Waarschuwingen voor pool-schijfgebruik zijn uitgeschakeld."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,11 +1383,11 @@ msgstr "Bekijk <0>de documentatie</0> voor instructies."
msgid "Please sign in to your account"
msgstr "Meld je aan bij je account"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Poolstatus"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Poolgebruik"
@@ -1432,10 +1436,21 @@ msgstr "Wachtrijdiepte"
msgid "Quiet Hours"
msgstr "Stille uren"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Ruw"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Ruw gebruik van opslagpool {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Stille uren"
msgid "Read"
msgstr "Lezen"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Leesfouten"
@@ -1454,7 +1469,7 @@ msgstr "Ontvangen"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Vernieuwen"
@@ -1501,7 +1516,7 @@ msgstr "Hervatten"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1599,7 +1614,7 @@ msgstr "Servicedetails"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Services"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors."
@@ -1643,7 +1658,7 @@ msgstr "Starttijd"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Status"
@@ -1654,7 +1669,7 @@ msgstr "Status"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
@@ -1742,7 +1757,7 @@ msgstr "Temperatuur van systeem sensoren"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1772,9 +1787,9 @@ msgstr "Dit zal alle geselecteerde records verwijderen uit de database."
msgid "Throughput of {extraFsName}"
msgstr "Doorvoer van {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Doorvoer van ZFS-pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Doorvoer van opslagpool {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "Naar e-mail(s)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1832,7 +1847,7 @@ msgstr "Geactiveerd door"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Triggers"
msgstr ""
#: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold"
@@ -1897,8 +1912,9 @@ msgstr "Triggert wanneer het gebruik van een schijf een drempelwaarde overschrij
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Type"
msgstr ""
#: src/lib/alerts.ts
msgid "Unhealthy"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Universele token"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Onbekend"
@@ -1945,7 +1961,7 @@ msgstr "Bijwerken"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Bijgewerkt"
@@ -1968,20 +1984,20 @@ msgstr "Actief"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Gebruik"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Gebruik van ZFS-pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Gebruik van opslagpool {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Gebruikt"
@@ -2058,7 +2074,7 @@ msgstr "Windows-commando"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows-commando"
msgid "Write"
msgstr "Schrijven"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Schrijffouten"
+84 -68
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: no\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Norwegian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -61,7 +61,7 @@ msgstr "1 time"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 timer"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 dager"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Handlinger"
@@ -142,7 +142,7 @@ msgstr "Juster bredden på hovedlayouten"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -154,7 +154,7 @@ msgstr "Etter å ha angitt miljøvariablene, start Beszel-huben på nytt for at
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Er du sikker?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisk kopiering krever en sikker kontekst."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Tilgjengelig"
@@ -300,7 +300,7 @@ msgstr "Binær"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -309,7 +309,7 @@ msgstr "Oppstartstilstand"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Kapabiliteter"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapasitet"
@@ -348,7 +348,7 @@ msgstr "Advarsel - potensielt tap av data"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Sjekk overvåkingstjenesten din"
msgid "Check your notification service"
msgstr "Sjekk din meldingstjeneste"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Kontrollsumfeil"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Tøm"
@@ -411,7 +411,7 @@ msgstr "Klikk på en container for å se mer informasjon."
msgid "Click on a device to view more information."
msgstr "Klikk på en enhet for å se mer informasjon."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Klikk på en pool for å se detaljer om vdev-er og datasett."
@@ -447,7 +447,7 @@ msgstr "Tilkoblingen er nede"
#: src/lib/alerts.ts
msgid "Container"
msgstr "Container"
msgstr ""
#: src/lib/alerts.ts
msgid "Container Health"
@@ -503,7 +503,7 @@ msgstr "Kopier navn"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Kopier offentlig nøkkel"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "Kjerne"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -661,7 +661,7 @@ msgstr "Lader ut"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
@@ -826,7 +826,7 @@ msgstr "Eksporter din nåværende systemkonfigurasjon"
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,18 +869,18 @@ msgstr "Mislyktes: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Vifter"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
msgstr "Filter..."
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Fingerprint"
@@ -898,8 +898,8 @@ msgstr "I <0>{min}</0> {min, plural, one {minutt} other {minutter}}"
msgid "Forgot password?"
msgstr "Glemt passord?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Ledig"
@@ -924,11 +924,11 @@ msgstr "Generelt"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Rutenett"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Helse"
@@ -1009,7 +1009,7 @@ msgstr "Dersom du har mistet passordet til admin-kontoen kan du nullstille det m
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Docker image"
msgid "Image"
msgstr "Image"
msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Inactive"
@@ -1146,7 +1146,7 @@ msgstr "Minnebruk for containere"
msgid "Model"
msgstr "Modell"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Monteringspunkt"
@@ -1185,7 +1185,7 @@ msgstr "Nettverksenhet"
msgid "No"
msgstr "Nei"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Ingen detaljerte data er tilgjengelige for denne poolen."
@@ -1212,7 +1212,7 @@ msgstr "Ingen S.M.A.R.T.-attributter tilgjengelig for denne enheten."
msgid "No systems found."
msgstr "Ingen systemer funnet."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Ingen"
@@ -1255,7 +1255,7 @@ msgstr "Engangspassord"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Åpne meny"
@@ -1311,7 +1311,7 @@ msgstr "Fortid"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Pause"
msgstr "Pause"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Paused"
@@ -1340,12 +1340,16 @@ msgstr "Prosentandel av tid brukt i hver tilstand"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Permanent"
msgstr "Permanent"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Persistence"
msgstr "Vedvarenhet"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fysisk enhetsplass. Den faktiske brukbare kapasiteten er ukjent. Varsler om diskbruk for poolen er deaktivert."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,17 +1383,17 @@ msgstr "Vennligst se <0>dokumentasjonen</0> for instrukser."
msgid "Please sign in to your account"
msgstr "Vennligst logg inn på kontoen din"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Pooltilstand"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Poolbruk"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
@@ -1416,7 +1420,7 @@ msgstr "Prosess startet"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Offentlig nøkkel"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Kødybde"
msgid "Quiet Hours"
msgstr "Stille timer"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Rå"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Rå bruk av lagringspool {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Stille timer"
msgid "Read"
msgstr "Lesing"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Lesefeil"
@@ -1454,7 +1469,7 @@ msgstr "Mottatt"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Oppdater"
@@ -1643,7 +1658,7 @@ msgstr "Starttid"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Tilstand"
@@ -1654,7 +1669,7 @@ msgstr "Tilstand"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
@@ -1671,7 +1686,7 @@ msgstr "Swap-bruk"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Bytt tema"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1686,11 +1701,11 @@ msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Systemviftehastigheter (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1725,7 +1740,7 @@ msgstr "Oppgaver"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Temp"
msgstr "Temp"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
@@ -1742,7 +1757,7 @@ msgstr "Temperaturer på system-sensorer"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1772,9 +1787,9 @@ msgstr "Dette vil permanent slette alle valgte oppføringer fra databasen."
msgid "Throughput of {extraFsName}"
msgstr "Gjennomstrømning av {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Gjennomstrømning for ZFS-pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Gjennomstrømning av lagringspool {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "Til e-postadresse(r)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1897,8 +1912,9 @@ msgstr "Slår inn når forbruk av hvilken som helst disk overstiger en grensever
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Type"
msgstr ""
#: src/lib/alerts.ts
msgid "Unhealthy"
@@ -1916,10 +1932,10 @@ msgstr "Enhetspreferanser"
#: src/components/command-palette.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Universal token"
msgstr "Universal token"
msgstr ""
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Ukjent"
@@ -1945,7 +1961,7 @@ msgstr "Oppdater"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Oppdatert"
@@ -1968,20 +1984,20 @@ msgstr "Oppetid"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Forbruk"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Bruk av ZFS-pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Bruk av lagringspool {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Brukt"
@@ -2058,7 +2074,7 @@ msgstr "Windows-kommando"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows-kommando"
msgid "Write"
msgstr "Skriving"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Skrivefeil"
+74 -58
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pl\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
@@ -61,7 +61,7 @@ msgstr "1 godzina"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 godzin"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 dni"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Akcje"
@@ -142,7 +142,7 @@ msgstr "Dostosuj szerokość widoku"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -154,7 +154,7 @@ msgstr "Po ustawieniu zmiennych środowiskowych zrestartuj Beszel hub, aby zmian
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Czy jesteś pewien?"
msgid "Automatic copy requires a secure context."
msgstr "Automatyczne kopiowanie wymaga bezpiecznego kontekstu."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Dostępne"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Możliwości"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Pojemność"
@@ -391,14 +391,14 @@ msgstr "Sprawdź usługę monitorowania"
msgid "Check your notification service"
msgstr "Sprawdź swój serwis powiadomień"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Błędy sumy kontrolnej"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Wyczyść"
@@ -411,7 +411,7 @@ msgstr "Wybierz kontener, aby wyświetlić więcej informacji."
msgid "Click on a device to view more information."
msgstr "Wybierz urządzenie, aby wyświetlić więcej informacji."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Kliknij pulę, aby wyświetlić szczegóły vdevów i zestawów danych."
@@ -503,7 +503,7 @@ msgstr "Kopiuj nazwę"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Kopiuj klucz publiczny"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -826,7 +826,7 @@ msgstr "Eksportuj aktualną konfigurację systemów."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,14 +869,14 @@ msgstr "Nieudane: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Wentylatory"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "Na <0>{min}</0> {min, plural, one {minutę} other {minut}}"
msgid "Forgot password?"
msgstr "Zapomniałeś hasła?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Wolne"
@@ -928,7 +928,7 @@ msgstr "Globalny"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
@@ -948,13 +948,13 @@ msgid "Grid"
msgstr "Siatka"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Kondycja"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -1042,7 +1042,7 @@ msgstr "Cykl życia"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "limit"
msgstr ""
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "Load Average"
@@ -1144,9 +1144,9 @@ msgstr "Zużycie pamięci przez kontenery"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Punkt montowania"
@@ -1185,7 +1185,7 @@ msgstr "Jednostka sieciowa"
msgid "No"
msgstr "Nie"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Brak szczegółowych danych dla tej puli."
@@ -1212,7 +1212,7 @@ msgstr "Brak dostępnych atrybutów S.M.A.R.T. dla tego urządzenia."
msgid "No systems found."
msgstr "Nie znaleziono systemów."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Brak"
@@ -1255,7 +1255,7 @@ msgstr "Hasło jednorazowe"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Otwórz menu"
@@ -1346,6 +1346,10 @@ msgstr "Stały"
msgid "Persistence"
msgstr "Trwałość"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fizyczne miejsce na urządzeniu. Rzeczywista pojemność użytkowa jest nieznana. Alerty użycia dysków puli są wyłączone."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Proszę <0>skonfigurować serwer SMTP</0>, aby zapewnić dostarczanie powiadomień."
@@ -1379,17 +1383,17 @@ msgstr "Proszę zapoznać się z <0>dokumentacją</0>."
msgid "Please sign in to your account"
msgstr "Zaloguj się na swoje konto"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Stan puli"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Użycie puli"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
@@ -1416,7 +1420,7 @@ msgstr "Proces uruchomiony"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Klucz publiczny"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Głębokość kolejki"
msgid "Quiet Hours"
msgstr "Godziny ciszy"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Surowe"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Surowe użycie puli magazynu {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Godziny ciszy"
msgid "Read"
msgstr "Odczyt"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Błędy odczytu"
@@ -1454,7 +1469,7 @@ msgstr "Otrzymane"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Odśwież"
@@ -1501,7 +1516,7 @@ msgstr "Wznów"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1643,7 +1658,7 @@ msgstr "Czas rozpoczęcia"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Stan"
@@ -1654,7 +1669,7 @@ msgstr "Stan"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
@@ -1671,7 +1686,7 @@ msgstr "Użycie pamięci wymiany"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Zmień motyw"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1686,11 +1701,11 @@ msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Prędkości wentylatorów systemu (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1742,7 +1757,7 @@ msgstr "Temperatury czujników systemowych."
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
msgstr "Test <0>URL</0>"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Test heartbeat"
@@ -1772,9 +1787,9 @@ msgstr "Spowoduje to trwałe usunięcie wszystkich zaznaczonych rekordów z bazy
msgid "Throughput of {extraFsName}"
msgstr "Przepustowość {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Przepustowość puli ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Przepustowość puli magazynu {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "Do e-mail(ów)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1897,6 +1912,7 @@ msgstr "Wyzwalane, gdy wykorzystanie któregokolwiek dysku przekroczy ustalony p
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Typ"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Uniwersalny token"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Nieznana"
@@ -1945,7 +1961,7 @@ msgstr "Aktualizuj"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Zaktualizowano"
@@ -1968,20 +1984,20 @@ msgstr "Czas pracy"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Wykorzystanie"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Użycie puli ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Użycie puli magazynu {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Używane"
@@ -2058,7 +2074,7 @@ msgstr "Polecenie Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Polecenie Windows"
msgid "Write"
msgstr "Zapis"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Błędy zapisu"
+80 -64
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pt\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -52,7 +52,7 @@ msgstr "E/S de {diskName}"
#: src/components/routes/system/info-bar.tsx
msgid "{threads, plural, one {# thread} other {# threads}}"
msgstr "{threads, plural, one {# thread} other {# threads}}"
msgstr ""
#: src/lib/utils.ts
msgid "1 hour"
@@ -61,7 +61,7 @@ msgstr "1 hora"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 horas"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 dias"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Ações"
@@ -142,7 +142,7 @@ msgstr "Ajustar a largura do layout principal"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -196,7 +196,7 @@ msgstr "Tem certeza?"
msgid "Automatic copy requires a secure context."
msgstr "A cópia automática requer um contexto seguro."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Disponível"
@@ -258,7 +258,7 @@ msgstr "Largura de Banda"
#. Battery label in systems table header
#: src/components/systems-table/systems-table-columns.tsx
msgid "Bat"
msgstr "Bat"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/components/routes/system/charts/sensor-charts.tsx
@@ -300,7 +300,7 @@ msgstr "Binário"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
@@ -309,11 +309,11 @@ msgstr "Estado de inicialização"
#: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)"
msgstr "Bytes (KB/s, MB/s, GB/s)"
msgstr ""
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Cache / Buffers"
msgstr "Cache / Buffers"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Capacidades"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Capacidade"
@@ -348,7 +348,7 @@ msgstr "Cuidado - possível perda de dados"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Verifique o seu serviço de monitorização"
msgid "Check your notification service"
msgstr "Verifique seu serviço de notificação"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Erros de checksum"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Limpar"
@@ -411,7 +411,7 @@ msgstr "Clique num contentor para ver mais informações."
msgid "Click on a device to view more information."
msgstr "Clique em um dispositivo para ver mais informações."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Clique num pool para ver os detalhes de vdev e dataset."
@@ -503,7 +503,7 @@ msgstr "Copiar nome"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Copiar chave pública"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "Núcleos"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -732,7 +732,7 @@ msgstr "Editar {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -826,7 +826,7 @@ msgstr "Exporte a configuração atual dos seus sistemas."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,14 +869,14 @@ msgstr "Falhou: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Ventoinhas"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -888,7 +888,7 @@ msgstr "Impressão digital"
#: src/components/routes/system/smart-table.tsx
msgid "Firmware"
msgstr "Firmware"
msgstr ""
#: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -898,8 +898,8 @@ msgstr "Por <0>{min}</0> {min, plural, one {minuto} other {minutos}}"
msgid "Forgot password?"
msgstr "Esqueceu a senha?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Livre"
@@ -924,11 +924,11 @@ msgstr "Geral"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Grade"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Saúde"
@@ -972,7 +972,7 @@ msgstr "Comando Homebrew"
#: src/components/add-system.tsx
msgid "Host / IP"
msgstr "Host / IP"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "HTTP Method"
@@ -1091,7 +1091,7 @@ msgstr "Tentativa de login falhou"
#: src/components/containers-table/containers-table.tsx
#: src/components/navbar.tsx
msgid "Logs"
msgstr "Logs"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
@@ -1146,7 +1146,7 @@ msgstr "Utilização de memória dos contentores"
msgid "Model"
msgstr "Modelo"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Ponto de montagem"
@@ -1185,7 +1185,7 @@ msgstr "Unidade de rede"
msgid "No"
msgstr "Não"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Não existem dados detalhados para este pool."
@@ -1212,7 +1212,7 @@ msgstr "Nenhum atributo S.M.A.R.T. disponível para este dispositivo."
msgid "No systems found."
msgstr "Nenhum sistema encontrado."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Nenhum"
@@ -1255,7 +1255,7 @@ msgstr "Senha de uso único"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Abrir menu"
@@ -1346,6 +1346,10 @@ msgstr "Permanente"
msgid "Persistence"
msgstr "Persistência"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Espaço físico do dispositivo. A capacidade útil real é desconhecida. Os alertas de utilização do disco do pool estão desativados."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,11 +1383,11 @@ msgstr "Por favor, veja <0>a documentação</0> para instruções."
msgid "Please sign in to your account"
msgstr "Por favor, entre na sua conta"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Estado do pool"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Utilização do pool"
@@ -1416,7 +1420,7 @@ msgstr "Processo iniciado"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Chave pública"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Profundidade da fila"
msgid "Quiet Hours"
msgstr "Horas Silenciosas"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Bruto"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Utilização bruta do pool de armazenamento {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Horas Silenciosas"
msgid "Read"
msgstr "Ler"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Erros de leitura"
@@ -1454,7 +1469,7 @@ msgstr "Recebido"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Atualizar"
@@ -1643,7 +1658,7 @@ msgstr "Hora de Início"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Estado"
@@ -1671,7 +1686,7 @@ msgstr "Uso de Swap"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Mudar tema"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Sistema"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Velocidades das ventoinhas do sistema (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1725,7 +1740,7 @@ msgstr "Tarefas"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Temp"
msgstr "Temp"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
@@ -1772,9 +1787,9 @@ msgstr "Isso excluirá permanentemente todos os registros selecionados do banco
msgid "Throughput of {extraFsName}"
msgstr "Taxa de transferência de {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Débito do pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Taxa de transferência do pool de armazenamento {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1787,7 +1802,7 @@ msgstr "Para email(s)"
#: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Token"
msgstr "Token"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/layout.tsx
@@ -1806,7 +1821,7 @@ msgstr "Tokens e impressões digitais são usados para autenticar conexões WebS
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1824,7 +1839,7 @@ msgstr "Tempo total gasto em leitura/escrita (pode exceder 100%)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total: {0}"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
@@ -1897,6 +1912,7 @@ msgstr "Dispara quando o uso de qualquer disco excede um limite"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Tipo"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Token universal"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Desconhecida"
@@ -1945,7 +1961,7 @@ msgstr "Atualizar"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Atualizado"
@@ -1968,20 +1984,20 @@ msgstr "Tempo de Atividade"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Uso"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Utilização do pool ZFS {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Utilização do pool de armazenamento {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Usado"
@@ -2058,7 +2074,7 @@ msgstr "Comando Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Comando Windows"
msgid "Write"
msgstr "Escrever"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Erros de escrita"
+64 -48
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ro\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 19:06\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Romanian\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
@@ -61,7 +61,7 @@ msgstr "1 oră"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 ore"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 zile"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Acțiuni"
@@ -142,7 +142,7 @@ msgstr "Reglaţi lăţimea aspectului principal"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -154,7 +154,7 @@ msgstr "După setarea variabilelor de mediu, reporniţi centrul Beszel pentru ca
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Ești sigur?"
msgid "Automatic copy requires a secure context."
msgstr "Copierea automată necesită un context securizat."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr ""
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Capabilități"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Capacitate"
@@ -348,7 +348,7 @@ msgstr "Atenție - posibilă pierdere de date"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr ""
msgid "Check your notification service"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr ""
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr ""
@@ -411,7 +411,7 @@ msgstr ""
msgid "Click on a device to view more information."
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr ""
@@ -530,7 +530,7 @@ msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -732,7 +732,7 @@ msgstr "Editează {foo}"
#: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx
msgid "Email"
msgstr "Email"
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Email notifications"
@@ -875,8 +875,8 @@ msgstr ""
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr ""
msgid "Forgot password?"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr ""
@@ -924,7 +924,7 @@ msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr ""
@@ -1144,9 +1144,9 @@ msgstr "Utilizarea memoriei de către containere"
#. Device model
#: src/components/routes/system/smart-table.tsx
msgid "Model"
msgstr "Model"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr ""
@@ -1185,7 +1185,7 @@ msgstr ""
msgid "No"
msgstr "Nu"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr ""
@@ -1212,7 +1212,7 @@ msgstr ""
msgid "No systems found."
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr ""
@@ -1255,7 +1255,7 @@ msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr ""
@@ -1346,6 +1346,10 @@ msgstr ""
msgid "Persistence"
msgstr ""
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr ""
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr ""
@@ -1379,17 +1383,17 @@ msgstr ""
msgid "Please sign in to your account"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr ""
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr ""
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
@@ -1432,10 +1436,21 @@ msgstr ""
msgid "Quiet Hours"
msgstr ""
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr ""
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr ""
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr ""
msgid "Read"
msgstr "Citit"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr ""
@@ -1454,7 +1469,7 @@ msgstr "Primit"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Actualizează"
@@ -1643,7 +1658,7 @@ msgstr ""
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr ""
@@ -1772,8 +1787,8 @@ msgstr ""
msgid "Throughput of {extraFsName}"
msgstr ""
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr ""
#: src/components/routes/settings/general.tsx
@@ -1806,7 +1821,7 @@ msgstr ""
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1824,7 +1839,7 @@ msgstr ""
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total: {0}"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
@@ -1897,6 +1912,7 @@ msgstr ""
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Tip"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr ""
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr ""
@@ -1945,7 +1961,7 @@ msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr ""
@@ -1968,20 +1984,20 @@ msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Utilizare"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr ""
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Utilizat"
@@ -2058,7 +2074,7 @@ msgstr ""
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr ""
msgid "Write"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr ""
+62 -46
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ru\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
@@ -97,7 +97,7 @@ msgstr "5 мин"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Действия"
@@ -196,7 +196,7 @@ msgstr "Вы уверены?"
msgid "Automatic copy requires a secure context."
msgstr "Автоматическое копирование требует безопасного контекста."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Доступно"
@@ -234,7 +234,7 @@ msgstr "Среднее время от очереди до завершения
#: src/components/routes/system/charts/cpu-charts.tsx
msgid "Average system-wide CPU utilization"
msgstr "Среднее использование CPU по всей системе"
msgstr "Среднее использование CPU в системе"
#. placeholder {0}: gpu.n
#: src/components/routes/system/charts/gpu-charts.tsx
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Возможности"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Емкость"
@@ -391,14 +391,14 @@ msgstr "Проверьте ваш сервис мониторинга"
msgid "Check your notification service"
msgstr "Проверьте ваш сервис уведомлений"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Ошибки контрольной суммы"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Очистить"
@@ -411,7 +411,7 @@ msgstr "Нажмите на контейнер для просмотра доп
msgid "Click on a device to view more information."
msgstr "Нажмите на устройство для просмотра дополнительной информации."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Нажмите на пул, чтобы просмотреть сведения о vdev и dataset."
@@ -875,8 +875,8 @@ msgstr "Вентиляторы"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "На <0>{min}</0> {min, plural, one {минуту} other {минут}}"
msgid "Forgot password?"
msgstr "Забыли пароль?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Свободно"
@@ -928,11 +928,11 @@ msgstr "Глобально"
#: src/components/routes/system.tsx
msgid "GPU"
msgstr "GPU"
msgstr ""
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Engines"
msgstr "GPU движки"
msgstr "Ядра GPU"
#: src/components/routes/system/charts/gpu-charts.tsx
msgid "GPU Power Draw"
@@ -948,13 +948,13 @@ msgid "Grid"
msgstr "Сетка"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Здоровье"
#: src/components/routes/settings/layout.tsx
msgid "Heartbeat"
msgstr "Heartbeat"
msgstr ""
#: src/components/routes/settings/heartbeat.tsx
msgid "Heartbeat Monitoring"
@@ -1146,7 +1146,7 @@ msgstr "Использование памяти контейнерами"
msgid "Model"
msgstr "Модель"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Точка монтирования"
@@ -1185,7 +1185,7 @@ msgstr "Единицы измерения скорости сети"
msgid "No"
msgstr "Нет"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Подробные данные для этого пула отсутствуют."
@@ -1212,7 +1212,7 @@ msgstr "Для этого устройства нет доступных атр
msgid "No systems found."
msgstr "Системы не найдены."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Нет"
@@ -1255,7 +1255,7 @@ msgstr "Одноразовый пароль"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Открыть меню"
@@ -1346,6 +1346,10 @@ msgstr "Постоянный"
msgid "Persistence"
msgstr "Устойчивость"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Физическое пространство устройства. Реальная полезная ёмкость неизвестна. Оповещения об использовании дисков пула отключены."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Пожалуйста, <0>настройте SMTP-сервер</0>, чтобы гарантировать доставку оповещений."
@@ -1379,11 +1383,11 @@ msgstr "Пожалуйста, смотрите <0>документацию</0>
msgid "Please sign in to your account"
msgstr "Пожалуйста, войдите в свою учетную запись"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Состояние пула"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Использование пула"
@@ -1404,7 +1408,7 @@ msgstr "Включение питания"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Precise utilization at the recorded time"
msgstr "Точное использование в записанное время"
msgstr "Использование по времени"
#: src/components/routes/settings/general.tsx
msgid "Preferred Language"
@@ -1432,10 +1436,21 @@ msgstr "Глубина очереди"
msgid "Quiet Hours"
msgstr "Тихие часы"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Сырое"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Сырое использование пула хранения {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Тихие часы"
msgid "Read"
msgstr "Чтение"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Ошибки чтения"
@@ -1454,7 +1469,7 @@ msgstr "Получено"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Обновить"
@@ -1501,7 +1516,7 @@ msgstr "Возобновить"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Корневой"
msgstr "Системный"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1643,7 +1658,7 @@ msgstr "Время начала"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Состояние"
@@ -1662,7 +1677,7 @@ msgstr "Подсостояние"
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap space used by the system"
msgstr "Используемое системой пространство подкачки"
msgstr "Размер файла подкачки системы"
#: src/components/routes/system/charts/memory-charts.tsx
msgid "Swap Usage"
@@ -1694,7 +1709,7 @@ msgstr "Скорость вращения системных вентилято
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
msgstr "Средняя загрузка системы за время"
msgstr "Средняя загрузка системы по времени"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
@@ -1738,7 +1753,7 @@ msgstr "Единицы измерения температуры"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Temperatures of system sensors"
msgstr "Температуры датчиков системы"
msgstr "Температура датчиков системы"
#: src/components/routes/settings/notifications.tsx
msgid "Test <0>URL</0>"
@@ -1772,9 +1787,9 @@ msgstr "Это навсегда удалит все выбранные запи
msgid "Throughput of {extraFsName}"
msgstr "Пропускная способность {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Пропускная способность ZFS-пула {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Пропускная способность пула хранения {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1897,6 +1912,7 @@ msgstr "Срабатывает, когда нагрузка на любой из
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Тип"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Универсальный токен"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Неизвестно"
@@ -1945,7 +1961,7 @@ msgstr "Обновить"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Обновлено"
@@ -1968,20 +1984,20 @@ msgstr "Время работы"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Использование"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Использование ZFS-пула {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Использование пула хранения {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Использовано"
@@ -2058,7 +2074,7 @@ msgstr "Команда Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Команда Windows"
msgid "Write"
msgstr "Запись"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Ошибки записи"
+63 -47
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: sl\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \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"
@@ -61,7 +61,7 @@ msgstr "1 ura"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 ur"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 dni"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Dejanja"
@@ -196,7 +196,7 @@ msgstr "Ali ste prepričani?"
msgid "Automatic copy requires a secure context."
msgstr "Za samodejno kopiranje je potreben varen kontekst."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Na voljo"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Zmožnosti"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapaciteta"
@@ -391,14 +391,14 @@ msgstr "Preverite svojo storitev za spremljanje"
msgid "Check your notification service"
msgstr "Preverite storitev obveščanja"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Napake kontrolne vsote"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Počisti"
@@ -411,7 +411,7 @@ msgstr "Kliknite na kontejner za več informacij."
msgid "Click on a device to view more information."
msgstr "Kliknite na napravo za več informacij."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Kliknite pool za ogled podrobnosti o vdev in dataset."
@@ -503,7 +503,7 @@ msgstr "Kopiraj ime"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Kopiraj javni ključ"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -869,14 +869,14 @@ msgstr "Neuspešno: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Ventilatorji"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "Za <0>{min}</0> {min, plural, one {minuto} other {minut}}"
msgid "Forgot password?"
msgstr "Pozabljeno geslo?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Prosto"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Mreža"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Zdravje"
@@ -1146,7 +1146,7 @@ msgstr "Poraba pomnilnika vsebnikov"
msgid "Model"
msgstr ""
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Točka priklopa"
@@ -1185,7 +1185,7 @@ msgstr "Enota omrežja"
msgid "No"
msgstr "Ne"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Za ta pool ni podrobnih podatkov."
@@ -1212,7 +1212,7 @@ msgstr "Za to napravo ni na voljo atributov S.M.A.R.T."
msgid "No systems found."
msgstr "Ne najdem sistema."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Brez"
@@ -1255,7 +1255,7 @@ msgstr "Enkratno geslo"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Odpri menu"
@@ -1346,6 +1346,10 @@ msgstr "Trajen"
msgid "Persistence"
msgstr "Vztrajnost"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fizični prostor naprave. Dejanska uporabna zmogljivost ni znana. Opozorila o uporabi diskov poola so onemogočena."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "<0>Nastavite strežnik SMTP</0>, da zagotovite dostavo opozoril."
@@ -1379,11 +1383,11 @@ msgstr "Za navodila glejte <0>dokumentacijo</0>."
msgid "Please sign in to your account"
msgstr "Prijavite se v svoj račun"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Stanje poola"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Uporaba poola"
@@ -1416,7 +1420,7 @@ msgstr "Proces začet"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Javni ključ"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Globina čakalne vrste"
msgid "Quiet Hours"
msgstr "Tihi čas"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Surovo"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Surova uporaba shranjevalnega poola {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Tihi čas"
msgid "Read"
msgstr "Preberano"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Napake pri branju"
@@ -1454,7 +1469,7 @@ msgstr "Prejeto"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Osveži"
@@ -1643,7 +1658,7 @@ msgstr "Čas začetka"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Stanje"
@@ -1671,7 +1686,7 @@ msgstr "Swap uporaba"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Zamenjaj temo"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1690,7 +1705,7 @@ msgstr "Sistemsko"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Hitrosti ventilatorjev sistema (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1725,7 +1740,7 @@ msgstr "Naloge"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Temp"
msgstr "Temp"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
@@ -1772,9 +1787,9 @@ msgstr "To bo trajno izbrisalo vse izbrane zapise iz zbirke podatkov."
msgid "Throughput of {extraFsName}"
msgstr "Prepustnost {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Prepustnost ZFS poola {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Prepustnost shranjevalnega poola {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1819,7 +1834,7 @@ msgstr "Skupni poslani podatki za vsak vmesnik"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Skupni čas, porabljen za branje/pisanje (lahko preseže 100 %)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "Sproži se, ko uporaba katerega koli diska preseže prag"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Vrsta"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Univerzalni žeton"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Neznana"
@@ -1945,7 +1961,7 @@ msgstr "Posodobi"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Posodobljeno"
@@ -1968,20 +1984,20 @@ msgstr "Čas delovanja"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Uporaba"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Uporaba ZFS poola {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Uporaba shranjevalnega poola {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Uporabljeno"
@@ -2058,7 +2074,7 @@ msgstr "Ukaz Windows"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Ukaz Windows"
msgid "Write"
msgstr "Pisanje"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Napake pri zapisovanju"
+55 -39
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: sr\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Serbian (Cyrillic)\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"
@@ -97,7 +97,7 @@ msgstr "5 мин"
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Акције"
@@ -196,7 +196,7 @@ msgstr "Да ли сте сигурни?"
msgid "Automatic copy requires a secure context."
msgstr "Аутоматско копирање захтева безбедан контекст."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Доступно"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Могућности"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Капацитет"
@@ -391,14 +391,14 @@ msgstr "Proverite svoju uslugu monitoringa"
msgid "Check your notification service"
msgstr "Проверите ваш сервис за обавештавања"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Грешке контролне суме"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Обриши"
@@ -411,7 +411,7 @@ msgstr "Кликните на контејнер да видите више ин
msgid "Click on a device to view more information."
msgstr "Кликните на уређај да видите више информација."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Кликните на pool да бисте видели детаље о vdev и dataset."
@@ -875,8 +875,8 @@ msgstr "Вентилатори"
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "За <0>{min}</0> {min, plural, one {минуту} few {минута} ot
msgid "Forgot password?"
msgstr "Заборављена лозинка?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Слободно"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Мрежа"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Здравље"
@@ -1146,7 +1146,7 @@ msgstr "Коришћење меморије контејнера"
msgid "Model"
msgstr "Модел"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Тачка монтирања"
@@ -1185,7 +1185,7 @@ msgstr "Мрежна јединица"
msgid "No"
msgstr "Не"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Нема детаљних података за овај pool."
@@ -1212,7 +1212,7 @@ msgstr "Нема доступних S.M.A.R.T. атрибута за овај у
msgid "No systems found."
msgstr "Нису пронађени системи."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Нема"
@@ -1255,7 +1255,7 @@ msgstr "Једнократна лозинка"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Отвори мени"
@@ -1346,6 +1346,10 @@ msgstr "Трајан"
msgid "Persistence"
msgstr "Упорност"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Физички простор уређаја. Стварни искористиви капацитет није познат. Упозорења о искоришћености дискова pool-а су онемогућена."
#: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Молимо вас <0>конфигуришите SMTP сервер</0> да бисте осигурали да се упозорења испоручују."
@@ -1379,11 +1383,11 @@ msgstr "Молимо вас погледајте <0>документацију</
msgid "Please sign in to your account"
msgstr "Молимо вас да се пријавите на ваш налог"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Стање pool-а"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Коришћење pool-а"
@@ -1432,10 +1436,21 @@ msgstr "Дубина реда"
msgid "Quiet Hours"
msgstr "Тихи сати"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Сирово"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Сирова искоришћеност pool-а за складиштење {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Тихи сати"
msgid "Read"
msgstr "Читање"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Грешке при читању"
@@ -1454,7 +1469,7 @@ msgstr "Примљено"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Освежи"
@@ -1501,7 +1516,7 @@ msgstr "Настави"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token"
@@ -1643,7 +1658,7 @@ msgstr "Време почетка"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Стање"
@@ -1690,7 +1705,7 @@ msgstr "Систем"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Брзине вентилатора система (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1772,9 +1787,9 @@ msgstr "Ово ће трајно избрисати све изабране за
msgid "Throughput of {extraFsName}"
msgstr "Проток {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Пропусност ZFS pool-а {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Проток pool-а за складиштење {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1897,6 +1912,7 @@ msgstr "Окида се када употреба било ког диска п
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Тип"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Универзални токен"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Непознато"
@@ -1945,7 +1961,7 @@ msgstr "Ажурирај"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Ажурирано"
@@ -1968,20 +1984,20 @@ msgstr "Време рада"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Употреба"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Коришћење ZFS pool-а {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Искоришћеност pool-а за складиштење {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Коришћено"
@@ -2058,7 +2074,7 @@ msgstr "Windows команда"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows команда"
msgid "Write"
msgstr "Писање"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Грешке при писању"
+78 -62
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: sv\n"
"Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-09-02 21:43\n"
"PO-Revision-Date: 2026-09-10 00:33\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -61,7 +61,7 @@ msgstr "1 timme"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "1 min"
msgstr "1 min"
msgstr ""
#: src/lib/utils.ts
msgid "1 minute"
@@ -78,7 +78,7 @@ msgstr "12 timmar"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "15 min"
msgstr "15 min"
msgstr ""
#: src/lib/utils.ts
msgid "24 hours"
@@ -91,13 +91,13 @@ msgstr "30 dagar"
#. Load average
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "5 min"
msgstr "5 min"
msgstr ""
#. Table column
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Actions"
msgstr "Åtgärder"
@@ -142,7 +142,7 @@ msgstr "Justera bredden på huvudlayouten"
#: src/components/command-palette.tsx
#: src/components/navbar.tsx
msgid "Admin"
msgstr "Admin"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
@@ -154,7 +154,7 @@ msgstr "Efter att du har ställt in miljövariablerna, starta om din Beszel-hubb
#: src/components/systems-table/systems-table-columns.tsx
msgid "Agent"
msgstr "Agent"
msgstr ""
#: src/components/command-palette.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
@@ -196,7 +196,7 @@ msgstr "Är du säker?"
msgid "Automatic copy requires a secure context."
msgstr "Automatisk kopiering kräver en säker kontext."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Disk space available"
msgid "Available"
msgstr "Tillgängligt"
@@ -338,7 +338,7 @@ msgid "Capabilities"
msgstr "Förmågor"
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Capacity"
msgstr "Kapacitet"
@@ -348,7 +348,7 @@ msgstr "Varning - potentiell dataförlust"
#: src/components/routes/settings/general.tsx
msgid "Celsius (°C)"
msgstr "Celsius (°C)"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Change display units for metrics."
@@ -391,14 +391,14 @@ msgstr "Kontrollera din övervakningstjänst"
msgid "Check your notification service"
msgstr "Kontrollera din aviseringstjänst"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Checksum errors"
msgstr "Kontrollsummefel"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Clear"
msgstr "Rensa"
@@ -411,7 +411,7 @@ msgstr "Klicka på en behållare för att visa mer information."
msgid "Click on a device to view more information."
msgstr "Klicka på en enhet för att visa mer information."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Click on a pool to view vdev and dataset details."
msgstr "Klicka på en pool för att visa detaljer om vdev och dataset."
@@ -447,7 +447,7 @@ msgstr "Ej ansluten"
#: src/lib/alerts.ts
msgid "Container"
msgstr "Container"
msgstr ""
#: src/lib/alerts.ts
msgid "Container Health"
@@ -503,7 +503,7 @@ msgstr "Kopiera namn"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy public key"
msgstr ""
msgstr "Kopiera publik nyckel"
#: src/components/copy-to-clipboard.tsx
msgid "Copy text"
@@ -530,7 +530,7 @@ msgstr "Kärna"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "CPU"
msgstr "CPU"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
@@ -661,7 +661,7 @@ msgstr "Urladdar"
#: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Disk"
msgstr "Disk"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Disk unit"
@@ -826,7 +826,7 @@ msgstr "Exportera din nuvarande systemkonfiguration."
#: src/components/routes/settings/general.tsx
msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)"
msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
@@ -869,14 +869,14 @@ msgstr "Misslyckades: {0}"
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "Fans"
msgstr ""
msgstr "Fläktar"
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/chart-card.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx
msgid "Filter..."
@@ -898,8 +898,8 @@ msgstr "Under <0>{min}</0> {min, plural, one {minut} other {minuter}}"
msgid "Forgot password?"
msgstr "Glömt lösenordet?"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgctxt "Free space"
msgid "Free"
msgstr "Ledigt"
@@ -914,7 +914,7 @@ msgstr "FreeBSD kommando"
#: src/components/routes/system/info-bar.tsx
#: src/lib/i18n.ts
msgid "Full"
msgstr "Full"
msgstr ""
#. Context: General settings
#: src/components/routes/settings/general.tsx
@@ -924,7 +924,7 @@ msgstr "Allmänt"
#: src/components/routes/settings/quiet-hours.tsx
msgid "Global"
msgstr "Global"
msgstr ""
#: src/components/routes/system.tsx
msgid "GPU"
@@ -948,7 +948,7 @@ msgid "Grid"
msgstr "Rutnät"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Health"
msgstr "Hälsa"
@@ -1029,7 +1029,7 @@ msgstr "Språk"
#: src/components/systems-table/systems-table.tsx
msgid "Layout"
msgstr "Layout"
msgstr ""
#: src/components/routes/settings/general.tsx
msgid "Layout width"
@@ -1113,7 +1113,7 @@ msgstr "Manuella installationsinstruktioner"
#. Chart select field. Please try to keep this short.
#: src/components/routes/system/chart-card.tsx
msgid "Max 1 min"
msgstr "Max 1 min"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/info-bar.tsx
@@ -1146,7 +1146,7 @@ msgstr "Minnesanvändning för containrar"
msgid "Model"
msgstr "Modell"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Mountpoint"
msgstr "Monteringspunkt"
@@ -1185,7 +1185,7 @@ msgstr "Nätverksenhet"
msgid "No"
msgstr "Nej"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "No detail data for this pool."
msgstr "Det finns inga detaljerade data för denna pool."
@@ -1212,7 +1212,7 @@ msgstr "Inga S.M.A.R.T.-attribut tillgängliga för den här enheten."
msgid "No systems found."
msgstr "Inga system hittades."
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "None"
msgstr "Ingen"
@@ -1255,7 +1255,7 @@ msgstr "Engångslösenord"
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Open menu"
msgstr "Öppna menyn"
@@ -1346,6 +1346,10 @@ msgstr ""
msgid "Persistence"
msgstr "Beständighet"
#: src/components/routes/system/raw-capacity-label.tsx
msgid "Physical device space. True usable capacity is unknown. Pool disk usage alerts are disabled."
msgstr "Fysiskt enhetsutrymme. Den verkliga användbara kapaciteten är okänd. Varningar om diskanvändning för poolen är inaktiverade."
#: src/components/routes/settings/notifications.tsx
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."
@@ -1379,17 +1383,17 @@ msgstr "Vänligen se <0>dokumentationen</0> för instruktioner."
msgid "Please sign in to your account"
msgstr "Vänligen logga in på ditt konto"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Pool Health"
msgstr "Poolstatus"
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Pool Usage"
msgstr "Poolanvändning"
#: src/components/add-system.tsx
msgid "Port"
msgstr "Port"
msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgctxt "Container ports"
@@ -1416,7 +1420,7 @@ msgstr "Process startad"
#: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Public key"
msgstr ""
msgstr "Publik nyckel"
#. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx
@@ -1432,10 +1436,21 @@ msgstr "Kö-djup"
msgid "Quiet Hours"
msgstr "Tysta timmar"
#: src/components/routes/system/raw-capacity-label.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Raw"
msgstr "Rå"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Raw usage of storage pool {displayName}"
msgstr "Rå användning av lagringspool {displayName}"
#. Disk read
#. Disk read
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -1443,7 +1458,7 @@ msgstr "Tysta timmar"
msgid "Read"
msgstr "Läs"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Read errors"
msgstr "Läsfel"
@@ -1454,7 +1469,7 @@ msgstr "Mottaget"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Refresh"
msgstr "Uppdatera"
@@ -1643,7 +1658,7 @@ msgstr "Starttid"
#. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State"
msgstr "Tillstånd"
@@ -1654,7 +1669,7 @@ msgstr "Tillstånd"
#: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts
msgid "Status"
msgstr "Status"
msgstr ""
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
@@ -1671,7 +1686,7 @@ msgstr "Swap-användning"
#: src/components/mode-toggle.tsx
#: src/components/mode-toggle.tsx
msgid "Switch theme"
msgstr ""
msgstr "Byt tema"
#: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx
@@ -1686,11 +1701,11 @@ msgstr ""
#: src/components/systems-table/systems-table-columns.tsx
#: src/lib/alerts.ts
msgid "System"
msgstr "System"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
msgid "System fan speeds (RPM)"
msgstr ""
msgstr "Systemfläkthastigheter (RPM)"
#: src/components/routes/system/charts/load-average-chart.tsx
msgid "System load averages over time"
@@ -1725,7 +1740,7 @@ msgstr "Uppgifter"
#: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx
msgid "Temp"
msgstr "Temp"
msgstr ""
#: src/components/routes/system/charts/sensor-charts.tsx
#: src/lib/alerts.ts
@@ -1772,9 +1787,9 @@ msgstr "Detta kommer permanent att ta bort alla valda poster från databasen."
msgid "Throughput of {extraFsName}"
msgstr "Genomströmning av {extraFsName}"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Throughput of ZFS pool {poolName}"
msgstr "Genomströmning för ZFS-pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Throughput of storage pool {displayName}"
msgstr "Genomströmning av lagringspool {displayName}"
#: src/components/routes/settings/general.tsx
msgid "Time format"
@@ -1806,7 +1821,7 @@ msgstr "Tokens och fingeravtryck används för att autentisera WebSocket-anslutn
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
msgstr ""
#: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface"
@@ -1819,7 +1834,7 @@ msgstr "Totalt skickad data för varje gränssnitt"
#: src/components/routes/system/disk-io-sheet.tsx
msgctxt "Disk I/O"
msgid "Total time spent on read/write (can exceed 100%)"
msgstr ""
msgstr "Total tid för läsning/skrivning (kan överstiga 100 %)"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
@@ -1897,6 +1912,7 @@ msgstr "Utlöses när användningen av någon disk överskrider ett tröskelvär
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/settings/quiet-hours.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Type"
msgstr "Typ"
@@ -1919,7 +1935,7 @@ msgid "Universal token"
msgstr "Universell nyckel"
#. Context: Battery state
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/lib/i18n.ts
msgid "Unknown"
msgstr "Okänd"
@@ -1945,7 +1961,7 @@ msgstr "Uppdatera"
#: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated"
msgstr "Uppdaterad"
@@ -1968,20 +1984,20 @@ msgstr "Drifttid"
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/gpu-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage"
msgstr "Användning"
#: src/components/routes/system/charts/zfs-charts.tsx
msgid "Usage of ZFS pool {poolName}"
msgstr "Användning av ZFS-pool {poolName}"
#: src/components/routes/system/charts/storage-pool-charts.tsx
msgid "Usage of storage pool {displayName}"
msgstr "Användning av lagringspool {displayName}"
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/charts/memory-charts.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Used"
msgstr "Använt"
@@ -2058,7 +2074,7 @@ msgstr "Windows-kommando"
#. Disk write
#. Disk write
#: src/components/routes/system/charts/disk-charts.tsx
#: src/components/routes/system/charts/zfs-charts.tsx
#: src/components/routes/system/charts/storage-pool-charts.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
#: src/components/routes/system/disk-io-sheet.tsx
@@ -2066,7 +2082,7 @@ msgstr "Windows-kommando"
msgid "Write"
msgstr "Skriv"
#: src/components/routes/system/zfs-table.tsx
#: src/components/routes/system/storage-pools-table.tsx
msgid "Write errors"
msgstr "Skrivfel"

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