Compare commits

...

89 Commits

Author SHA1 Message Date
Pavel Pikta
4d05bfdff0 feat: add crossorigin attribute to manifest link (#1457)
Signed-off-by: Pavel Pikta <pavel_pikta@epam.com>
2025-11-26 19:41:54 -05:00
henrygd
0388401a9e change layout of extra disks in all systems table (#1409) 2025-11-25 16:23:48 -05:00
henrygd
162c548010 quiet hours refactoring: change 'future' to 'inactive' 2025-11-24 19:12:35 -05:00
henrygd
888b4a57e5 add quiet hours to silence alerts during specific time periods (#265) 2025-11-24 17:35:28 -05:00
henrygd
26d367b188 add clear button to filter inputs in all systems and containers tables (#1447) 2025-11-19 17:58:58 -05:00
henrygd
ca4988951f add SKIP_SYSTEMD env var (#1448) 2025-11-19 17:21:30 -05:00
zjkal
c7a50dd74d fix: Fix issue where the Add System button is visible to read-only users (#1442)
移除按钮的hidden类并提前检查只读用户状态返回null
2025-11-19 16:38:37 -05:00
Frederik Ring
00fbf5c9c3 Font ligatures create unwanted artifacts in random ids (#1434) 2025-11-19 16:36:48 -05:00
henrygd
4bfe9dd5ad add missing systemd methods for nonlinux 2025-11-14 17:28:40 -05:00
henrygd
e159a75b79 update language files 2025-11-14 17:24:51 -05:00
henrygd
a69686125e release 0.16.1 2025-11-14 16:39:24 -05:00
henrygd
3eb025ded2 make sure distroless image gets :latest tag in workflow 2025-11-14 16:21:17 -05:00
henrygd
1d0e646094 update changelog and go deps 2025-11-14 16:05:36 -05:00
henrygd
32c8e047e3 update cpu / container axis datamax calculations 2025-11-14 15:45:18 -05:00
henrygd
3650482b09 refactor: move getRootMountPoint to disk.go 2025-11-14 14:06:46 -05:00
Arush Wadhawan
79adfd2c0d fix: detect and handle immutable filesystems like Fedora Silverblue (#1405) 2025-11-14 14:03:26 -05:00
Sven van Ginkel
779dcc62aa chore: update actions to lock issues and skip PRs (#1419) 2025-11-14 13:58:13 -05:00
henrygd
abe39c1a0a update bun.lockb to text-based bun.lock 2025-11-14 13:54:40 -05:00
henrygd
bd41ad813c change alert history pagination to use local storage instead of saving to user settings 2025-11-14 13:54:23 -05:00
Arush Wadhawan
77fe63fb63 feat: add alert history page size preference with persistence (#1404) 2025-11-14 13:37:46 -05:00
henrygd
f61ba202d8 remove matrix from list of notification services that support title param (#1406) 2025-11-14 13:27:23 -05:00
henrygd
e1067fa1a3 make layout width adjustable 2025-11-13 18:50:47 -05:00
henrygd
0a3eb898ae truncate device name in smart table (#1416) 2025-11-13 16:41:15 -05:00
evrial
6c33e9dc93 Set a dynamic upper domain on the YAxis for container chart (#1412) 2025-11-13 16:28:37 -05:00
henrygd
f8ed6ce705 refactor: fix nan when net value is undefined in systems table 2025-11-13 16:25:21 -05:00
henrygd
f64478b75e add SERVICE_PATTERNS env var (#1153) 2025-11-13 16:11:24 -05:00
henrygd
854a3697d7 add services column to all systems table 2025-11-13 15:09:48 -05:00
henrygd
b7915b9d0e release 0.16.0 2025-11-12 16:11:08 -05:00
henrygd
4443b606f6 update go deps 2025-11-12 16:08:21 -05:00
henrygd
6c20a98651 update translations 2025-11-12 15:29:30 -05:00
henrygd
b722ccc5bc show additional disk percentages in systems table (#1365) 2025-11-12 14:15:45 -05:00
hank
db0315394b New translations 2025-11-12 13:12:05 -05:00
henrygd
a7ef1235f4 specify latest tag for non-alpine agent image
also change capitalization for gpu alert
2025-11-11 16:18:54 -05:00
henrygd
f64a361c60 add EXCLUDE_SMART env var (#1392) 2025-11-11 16:05:00 -05:00
henrygd
aaa788bc2f add gpu usage alerts 2025-11-11 12:38:47 -05:00
henrygd
3eede6bead fix containers and systemd tables when using system name in URL 2025-11-10 17:43:11 -05:00
henrygd
02abfbcb54 change alert link to use system ID instead of name 2025-11-10 17:31:11 -05:00
henrygd
01d20562f0 add basic systemd service monitoring (#1153)
Co-authored-by: Shelby Tucker <shelby.tucker@gmail.com>
2025-11-10 17:04:51 -05:00
henrygd
cbe6ee6499 fix battery nil pointer error (#1353) 2025-11-10 15:16:50 -05:00
henrygd
9a61ea8356 improve perf of filter bar 2025-11-07 22:29:59 -05:00
henrygd
1af7ff600f embed smartctl in the windows binary (#1362) 2025-11-05 13:03:47 -05:00
henrygd
02d594cc82 release 0.15.4 2025-11-04 17:23:42 -05:00
henrygd
7d0b5c1c67 update language files 2025-11-04 17:18:57 -05:00
Thiago Alves Cavalcante
d3dc8a7af0 new Portuguese translations 2025-11-04 17:18:07 -05:00
henrygd
d67fefe7c5 new spanish translations by dtornerte 2025-11-04 17:17:02 -05:00
henrygd
4d364c5e4d update language files 2025-11-04 17:06:51 -05:00
henrygd
954400ea45 fix intel_gpu_top parsing when engine instance id is in column (#1230) 2025-11-04 16:02:20 -05:00
henrygd
04b6067e64 add a total line to the tooltip of charts with multiple values #1280
Co-authored-by: Titouan V <titouan.verhille@gmail.com>
2025-11-04 15:41:24 -05:00
henrygd
d77ee5554f add fallback paths for smartctl lookup (#1362, #1363) 2025-11-04 14:06:28 -05:00
henrygd
2e034bdead refactor containers table to fix clock issue causing no results (#1337) 2025-11-04 13:18:34 -05:00
henrygd
fc0947aa04 fix windows extra disk backslash issue (#1361) 2025-11-03 17:42:08 -05:00
henrygd
1d546a4091 update nvidia dockerfile to build latest smartmontools (#1335) 2025-11-02 17:13:47 -05:00
henrygd
f60b3bbbfb release 0.15.3 2025-11-01 16:04:02 -04:00
henrygd
8e99b9f1ad update shoutrrr and gopsutil deps 2025-11-01 14:31:41 -04:00
henrygd
fa5ed2bc11 update translations 2025-11-01 14:09:10 -04:00
henrygd
21d961ab97 sync language files 2025-11-01 13:50:53 -04:00
henrygd
aaa93b84d2 add hebrew + new cpu charts refactoring 2025-11-01 13:34:30 -04:00
henrygd
6a562ce03b add more cpu metrics (#1356)
- adds monitoring for cpu state time and per-core usage

Co-authored-by: Sven van Ginkel <svenvanginkel@icloud.com>
2025-11-01 12:57:58 -04:00
henrygd
3dbc48727e add INTEL_GPU_DEVICE env var (#1285) 2025-11-01 11:12:05 -04:00
henrygd
85ac2e5e9a update env var name to EXCLUDE_CONTAINERS #1352 2025-10-30 19:30:01 -04:00
Sven van Ginkel
af6bd4e505 [Feature] Add env var to exclude containers from being monitored (#1352) 2025-10-30 19:02:09 -04:00
Gabay
e54c4b3499 New translations en.po (Hebrew) 2025-10-30 16:50:14 -04:00
henrygd
078c88f825 add hebrew machine translations 2025-10-30 16:45:33 -04:00
henrygd
85169b6c5e improve parsing of edge case smart power on times (#1347) 2025-10-30 16:32:06 -04:00
henrygd
d0ff8ee2c0 fix disk i/o values in longer charts (#1355) 2025-10-30 14:17:56 -04:00
henrygd
e898768997 fix battery nil pointer error #1353 2025-10-30 12:52:33 -04:00
henrygd
0f5b504f23 release 0.15.2 2025-10-29 01:18:15 -04:00
henrygd
365d291393 improve smart device detection (#1345)
also fix virtual device filtering
2025-10-29 01:16:58 -04:00
henrygd
3dbab24c0f improve identification of smart drive types (#1345) 2025-10-28 22:37:47 -04:00
henrygd
1f67fb7c8d release 0.15.1 2025-10-28 19:30:36 -04:00
henrygd
219e09fc78 update language files 2025-10-28 18:41:39 -04:00
henrygd
cd9c2bd9ab update logs in smart.go
also change max execution time to 2 sec
2025-10-28 17:34:49 -04:00
henrygd
9f969d843c update changelog 2025-10-28 16:52:55 -04:00
henrygd
b22a6472fc missed staging this earlier :) 2025-10-28 16:44:34 -04:00
henrygd
d231ace28e fix SHARE_ALL_SYSTEMS not working for Containers
#1334
2025-10-28 16:25:29 -04:00
henrygd
473cb7f437 merge SMART_DEVICES with devices returned from smartctl scan 2025-10-28 15:38:47 -04:00
henrygd
783ed9f456 cache smartctl scan results for 10 min w/ force option
also add support for sntrealtek
2025-10-28 14:01:45 -04:00
henrygd
9a9a89ee50 handle when power on smart attribute is a string like 0h+0m+0.000s 2025-10-28 13:44:31 -04:00
henrygd
5122d0341d fix S.M.A.R.T. wrong disk is renderer in the DiskSheet table #1336 2025-10-28 12:55:38 -04:00
zjkal
81731689da A small translation error has been fixed (#1343) 2025-10-28 11:09:10 -04:00
henrygd
b3e9857448 add SMART_DEVICES env var (#373, #1335)
also iterate through parsers to try to find a match if type is not defined.
2025-10-27 15:26:29 -04:00
henrygd
2eda9eb0e3 add support for scsi and sntasmedia smart data (#373, #1335) 2025-10-27 14:39:12 -04:00
henrygd
82a5df5048 add secondsToString function 2025-10-27 14:14:17 -04:00
Sven van Ginkel
f11564a7ac Skip virtual disks (#1332) 2025-10-27 11:44:21 -04:00
Sven van Ginkel
9df4d29236 Add sorting to the smart table (#1333) 2025-10-27 11:43:23 -04:00
henrygd
1452817423 update readme 2025-10-26 14:09:14 -04:00
AuthorShin
c57e496f5e Added Container to Supported metrics list on readme.md (#1323) 2025-10-26 14:04:42 -04:00
henrygd
6287f7003c fix text contrast when container details disabled (#1324) 2025-10-26 11:41:21 -04:00
henrygd
37037b1f4e update changelog 2025-10-26 11:34:13 -04:00
120 changed files with 20215 additions and 1250 deletions

View File

@@ -10,6 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 5
matrix: matrix:
include: include:
# henrygd/beszel # henrygd/beszel
@@ -24,19 +25,18 @@ jobs:
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }} type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# henrygd/beszel-agent # henrygd/beszel-agent:alpine
- image: henrygd/beszel-agent - image: henrygd/beszel-agent
dockerfile: ./internal/dockerfile_agent dockerfile: ./internal/dockerfile_agent_alpine
registry: docker.io registry: docker.io
username_secret: DOCKERHUB_USERNAME username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN password_secret: DOCKERHUB_TOKEN
tags: | tags: |
type=raw,value=edge type=raw,value=alpine
type=semver,pattern={{version}} type=semver,pattern={{version}}-alpine
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}-alpine
type=semver,pattern={{major}} type=semver,pattern={{major}}-alpine
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# henrygd/beszel-agent-nvidia # henrygd/beszel-agent-nvidia
- image: henrygd/beszel-agent-nvidia - image: henrygd/beszel-agent-nvidia
@@ -66,18 +66,6 @@ jobs:
type=semver,pattern={{major}} type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }} type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
# henrygd/beszel-agent:alpine
- image: henrygd/beszel-agent
dockerfile: ./internal/dockerfile_agent_alpine
registry: docker.io
username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN
tags: |
type=raw,value=alpine
type=semver,pattern={{version}}-alpine
type=semver,pattern={{major}}.{{minor}}-alpine
type=semver,pattern={{major}}-alpine
# ghcr.io/henrygd/beszel # ghcr.io/henrygd/beszel
- image: ghcr.io/${{ github.repository }}/beszel - image: ghcr.io/${{ github.repository }}/beszel
dockerfile: ./internal/dockerfile_hub dockerfile: ./internal/dockerfile_hub
@@ -99,6 +87,7 @@ jobs:
password_secret: GITHUB_TOKEN password_secret: GITHUB_TOKEN
tags: | tags: |
type=raw,value=edge type=raw,value=edge
type=raw,value=latest
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}} type=semver,pattern={{major}}
@@ -144,6 +133,19 @@ jobs:
type=semver,pattern={{major}}.{{minor}}-alpine type=semver,pattern={{major}}.{{minor}}-alpine
type=semver,pattern={{major}}-alpine type=semver,pattern={{major}}-alpine
# henrygd/beszel-agent (keep at bottom so it gets built after :alpine and gets the latest tag)
- image: henrygd/beszel-agent
dockerfile: ./internal/dockerfile_agent
registry: docker.io
username_secret: DOCKERHUB_USERNAME
password_secret: DOCKERHUB_TOKEN
tags: |
type=raw,value=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value={{sha}},enable=${{ github.ref_type != 'tag' }}
permissions: permissions:
contents: read contents: read
packages: write packages: write

View File

@@ -10,12 +10,25 @@ permissions:
pull-requests: write pull-requests: write
jobs: jobs:
lock-inactive:
name: Lock Inactive Issues
runs-on: ubuntu-24.04
steps:
- uses: klaasnicolaas/action-inactivity-lock@v1.1.3
id: lock
with:
days-inactive-issues: 14
lock-reason-issues: ""
# Action can not skip PRs, set it to 100 years to cover it.
days-inactive-prs: 36524
lock-reason-prs: ""
close-stale: close-stale:
name: Close Stale Issues name: Close Stale Issues
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Close Stale Issues - name: Close Stale Issues
uses: actions/stale@v9 uses: actions/stale@v10
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -32,6 +45,8 @@ jobs:
# Timing # Timing
days-before-issue-stale: 14 days-before-issue-stale: 14
days-before-issue-close: 7 days-before-issue-close: 7
# Action can not skip PRs, set it to 100 years to cover it.
days-before-pr-stale: 36524
# Labels # Labels
stale-issue-label: 'stale' stale-issue-label: 'stale'

View File

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

View File

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

View File

@@ -43,6 +43,7 @@ type Agent struct {
dataDir string // Directory for persisting data dataDir string // Directory for persisting data
keys []gossh.PublicKey // SSH public keys keys []gossh.PublicKey // SSH public keys
smartManager *SmartManager // Manages SMART data smartManager *SmartManager // Manages SMART data
systemdManager *systemdManager // Manages systemd services
} }
// NewAgent creates a new agent with the given data directory for persisting data. // NewAgent creates a new agent with the given data directory for persisting data.
@@ -101,6 +102,11 @@ func NewAgent(dataDir ...string) (agent *Agent, err error) {
// initialize docker manager // initialize docker manager
agent.dockerManager = newDockerManager(agent) agent.dockerManager = newDockerManager(agent)
agent.systemdManager, err = newSystemdManager()
if err != nil {
slog.Debug("Systemd", "err", err)
}
agent.smartManager, err = NewSmartManager() agent.smartManager, err = NewSmartManager()
if err != nil { if err != nil {
slog.Debug("SMART", "err", err) slog.Debug("SMART", "err", err)
@@ -154,7 +160,20 @@ func (a *Agent) gatherStats(cacheTimeMs uint16) *system.CombinedData {
} }
} }
// skip updating systemd services if cache time is not the default 60sec interval
if a.systemdManager != nil && cacheTimeMs == 60_000 {
totalCount := uint16(a.systemdManager.getServiceStatsCount())
if totalCount > 0 {
numFailed := a.systemdManager.getFailedServiceCount()
data.Info.Services = []uint16{totalCount, numFailed}
}
if a.systemdManager.hasFreshStats {
data.SystemdServices = a.systemdManager.getServiceStats(nil, false)
}
}
data.Stats.ExtraFs = make(map[string]*system.FsStats) data.Stats.ExtraFs = make(map[string]*system.FsStats)
data.Info.ExtraFsPct = make(map[string]float64)
for name, stats := range a.fsStats { for name, stats := range a.fsStats {
if !stats.Root && stats.DiskTotal > 0 { if !stats.Root && stats.DiskTotal > 0 {
// Use custom name if available, otherwise use device name // Use custom name if available, otherwise use device name
@@ -163,6 +182,11 @@ func (a *Agent) gatherStats(cacheTimeMs uint16) *system.CombinedData {
key = stats.Name key = stats.Name
} }
data.Stats.ExtraFs[key] = stats data.Stats.ExtraFs[key] = stats
// Add percentages to Info struct for dashboard
if stats.DiskTotal > 0 {
pct := twoDecimals((stats.DiskUsed / stats.DiskTotal) * 100)
data.Info.ExtraFsPct[key] = pct
}
} }
} }
slog.Debug("Extra FS", "data", data.Stats.ExtraFs) slog.Debug("Extra FS", "data", data.Stats.ExtraFs)

View File

@@ -6,12 +6,15 @@ package battery
import ( import (
"errors" "errors"
"log/slog" "log/slog"
"math"
"github.com/distatus/battery" "github.com/distatus/battery"
) )
var systemHasBattery = false var (
var haveCheckedBattery = false systemHasBattery = false
haveCheckedBattery = false
)
// HasReadableBattery checks if the system has a battery and returns true if it does. // HasReadableBattery checks if the system has a battery and returns true if it does.
func HasReadableBattery() bool { func HasReadableBattery() bool {
@@ -21,7 +24,7 @@ func HasReadableBattery() bool {
haveCheckedBattery = true haveCheckedBattery = true
batteries, err := battery.GetAll() batteries, err := battery.GetAll()
for _, bat := range batteries { for _, bat := range batteries {
if bat.Full > 0 { if bat != nil && (bat.Full > 0 || bat.Design > 0) {
systemHasBattery = true systemHasBattery = true
break break
} }
@@ -49,21 +52,26 @@ func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
totalCharge := float64(0) totalCharge := float64(0)
errs, partialErrs := err.(battery.Errors) errs, partialErrs := err.(battery.Errors)
batteryState = math.MaxUint8
for i, bat := range batteries { for i, bat := range batteries {
if partialErrs && errs[i] != nil { if partialErrs && errs[i] != nil {
// if there were some errors, like missing data, skip it // if there were some errors, like missing data, skip it
continue continue
} }
if bat.Full == 0 { if bat == nil || bat.Full == 0 {
// skip batteries with no capacity. Charge is unlikely to ever be zero, but // skip batteries with no capacity. Charge is unlikely to ever be zero, but
// we can't guarantee that, so don't skip based on charge. // we can't guarantee that, so don't skip based on charge.
continue continue
} }
totalCapacity += bat.Full totalCapacity += bat.Full
totalCharge += bat.Current totalCharge += bat.Current
if bat.State.Raw >= 0 {
batteryState = uint8(bat.State.Raw)
}
} }
if totalCapacity == 0 { if totalCapacity == 0 || batteryState == math.MaxUint8 {
// for macs there's sometimes a ghost battery with 0 capacity // for macs there's sometimes a ghost battery with 0 capacity
// https://github.com/distatus/battery/issues/34 // https://github.com/distatus/battery/issues/34
// Instead of skipping over those batteries, we'll check for total 0 capacity // Instead of skipping over those batteries, we'll check for total 0 capacity
@@ -72,6 +80,5 @@ func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
} }
batteryPercent = uint8(totalCharge / totalCapacity * 100) batteryPercent = uint8(totalCharge / totalCapacity * 100)
batteryState = uint8(batteries[0].State.Raw)
return batteryPercent, batteryState, nil return batteryPercent, batteryState, nil
} }

View File

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

View File

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

View File

@@ -31,6 +31,7 @@ func (a *Agent) initializeDiskInfo() {
filesystem, _ := GetEnv("FILESYSTEM") filesystem, _ := GetEnv("FILESYSTEM")
efPath := "/extra-filesystems" efPath := "/extra-filesystems"
hasRoot := false hasRoot := false
isWindows := runtime.GOOS == "windows"
partitions, err := disk.Partitions(false) partitions, err := disk.Partitions(false)
if err != nil { if err != nil {
@@ -38,6 +39,13 @@ func (a *Agent) initializeDiskInfo() {
} }
slog.Debug("Disk", "partitions", partitions) slog.Debug("Disk", "partitions", partitions)
// trim trailing backslash for Windows devices (#1361)
if isWindows {
for i, p := range partitions {
partitions[i].Device = strings.TrimSuffix(p.Device, "\\")
}
}
// ioContext := context.WithValue(a.sensorsContext, // ioContext := context.WithValue(a.sensorsContext,
// common.EnvKey, common.EnvMap{common.HostProcEnvKey: "/tmp/testproc"}, // common.EnvKey, common.EnvMap{common.HostProcEnvKey: "/tmp/testproc"},
// ) // )
@@ -52,7 +60,7 @@ func (a *Agent) initializeDiskInfo() {
// Helper function to add a filesystem to fsStats if it doesn't exist // Helper function to add a filesystem to fsStats if it doesn't exist
addFsStat := func(device, mountpoint string, root bool, customName ...string) { addFsStat := func(device, mountpoint string, root bool, customName ...string) {
var key string var key string
if runtime.GOOS == "windows" { if isWindows {
key = device key = device
} else { } else {
key = filepath.Base(device) key = filepath.Base(device)
@@ -87,6 +95,9 @@ func (a *Agent) initializeDiskInfo() {
} }
} }
// Get the appropriate root mount point for this system
rootMountPoint := a.getRootMountPoint()
// Use FILESYSTEM env var to find root filesystem // Use FILESYSTEM env var to find root filesystem
if filesystem != "" { if filesystem != "" {
for _, p := range partitions { for _, p := range partitions {
@@ -130,7 +141,7 @@ func (a *Agent) initializeDiskInfo() {
for _, p := range partitions { for _, p := range partitions {
// fmt.Println(p.Device, p.Mountpoint) // fmt.Println(p.Device, p.Mountpoint)
// Binary root fallback or docker root fallback // Binary root fallback or docker root fallback
if !hasRoot && (p.Mountpoint == "/" || (p.Mountpoint == "/etc/hosts" && strings.HasPrefix(p.Device, "/dev"))) { if !hasRoot && (p.Mountpoint == rootMountPoint || (p.Mountpoint == "/etc/hosts" && strings.HasPrefix(p.Device, "/dev"))) {
fs, match := findIoDevice(filepath.Base(p.Device), diskIoCounters, a.fsStats) fs, match := findIoDevice(filepath.Base(p.Device), diskIoCounters, a.fsStats)
if match { if match {
addFsStat(fs, p.Mountpoint, true) addFsStat(fs, p.Mountpoint, true)
@@ -166,8 +177,8 @@ func (a *Agent) initializeDiskInfo() {
// If no root filesystem set, use fallback // If no root filesystem set, use fallback
if !hasRoot { if !hasRoot {
rootDevice, _ := findIoDevice(filepath.Base(filesystem), diskIoCounters, a.fsStats) rootDevice, _ := findIoDevice(filepath.Base(filesystem), diskIoCounters, a.fsStats)
slog.Info("Root disk", "mountpoint", "/", "io", rootDevice) slog.Info("Root disk", "mountpoint", rootMountPoint, "io", rootDevice)
a.fsStats[rootDevice] = &system.FsStats{Root: true, Mountpoint: "/"} a.fsStats[rootDevice] = &system.FsStats{Root: true, Mountpoint: rootMountPoint}
} }
a.initializeDiskIoStats(diskIoCounters) a.initializeDiskIoStats(diskIoCounters)
@@ -304,3 +315,32 @@ func (a *Agent) updateDiskIo(cacheTimeMs uint16, systemStats *system.Stats) {
} }
} }
} }
// getRootMountPoint returns the appropriate root mount point for the system
// For immutable systems like Fedora Silverblue, it returns /sysroot instead of /
func (a *Agent) getRootMountPoint() string {
// 1. Check if /etc/os-release contains indicators of an immutable system
if osReleaseContent, err := os.ReadFile("/etc/os-release"); err == nil {
content := string(osReleaseContent)
if strings.Contains(content, "fedora") && strings.Contains(content, "silverblue") ||
strings.Contains(content, "coreos") ||
strings.Contains(content, "flatcar") ||
strings.Contains(content, "rhel-atomic") ||
strings.Contains(content, "centos-atomic") {
// Verify that /sysroot exists before returning it
if _, err := os.Stat("/sysroot"); err == nil {
return "/sysroot"
}
}
}
// 2. Check if /run/ostree is present (ostree-based systems like Silverblue)
if _, err := os.Stat("/run/ostree"); err == nil {
// Verify that /sysroot exists before returning it
if _, err := os.Stat("/sysroot"); err == nil {
return "/sysroot"
}
}
return "/"
}

View File

@@ -13,6 +13,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -53,6 +54,7 @@ type dockerManager struct {
buf *bytes.Buffer // Buffer to store and read response bodies buf *bytes.Buffer // Buffer to store and read response bodies
decoder *json.Decoder // Reusable JSON decoder that reads from buf decoder *json.Decoder // Reusable JSON decoder that reads from buf
apiStats *container.ApiStats // Reusable API stats object apiStats *container.ApiStats // Reusable API stats object
excludeContainers []string // Patterns to exclude containers by name
// Cache-time-aware tracking for CPU stats (similar to cpu.go) // Cache-time-aware tracking for CPU stats (similar to cpu.go)
// Maps cache time intervals to container-specific CPU usage tracking // Maps cache time intervals to container-specific CPU usage tracking
@@ -94,6 +96,19 @@ func (d *dockerManager) dequeue() {
} }
} }
// shouldExcludeContainer checks if a container name matches any exclusion pattern
func (dm *dockerManager) shouldExcludeContainer(name string) bool {
if len(dm.excludeContainers) == 0 {
return false
}
for _, pattern := range dm.excludeContainers {
if match, _ := path.Match(pattern, name); match {
return true
}
}
return false
}
// Returns stats for all running containers with cache-time-aware delta tracking // Returns stats for all running containers with cache-time-aware delta tracking
func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats, error) { func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats, error) {
resp, err := dm.client.Get("http://localhost/containers/json") resp, err := dm.client.Get("http://localhost/containers/json")
@@ -121,6 +136,13 @@ func (dm *dockerManager) getDockerStats(cacheTimeMs uint16) ([]*container.Stats,
for _, ctr := range dm.apiContainerList { for _, ctr := range dm.apiContainerList {
ctr.IdShort = ctr.Id[:12] ctr.IdShort = ctr.Id[:12]
// Skip this container if it matches the exclusion pattern
if dm.shouldExcludeContainer(ctr.Names[0][1:]) {
slog.Debug("Excluding container", "name", ctr.Names[0][1:])
continue
}
dm.validIds[ctr.IdShort] = struct{}{} dm.validIds[ctr.IdShort] = struct{}{}
// check if container is less than 1 minute old (possible restart) // check if container is less than 1 minute old (possible restart)
// note: can't use Created field because it's not updated on restart // note: can't use Created field because it's not updated on restart
@@ -503,6 +525,19 @@ func newDockerManager(a *Agent) *dockerManager {
userAgent: "Docker-Client/", userAgent: "Docker-Client/",
} }
// Read container exclusion patterns from environment variable
var excludeContainers []string
if excludeStr, set := GetEnv("EXCLUDE_CONTAINERS"); set && excludeStr != "" {
parts := strings.SplitSeq(excludeStr, ",")
for part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
excludeContainers = append(excludeContainers, trimmed)
}
}
slog.Info("EXCLUDE_CONTAINERS", "patterns", excludeContainers)
}
manager := &dockerManager{ manager := &dockerManager{
client: &http.Client{ client: &http.Client{
Timeout: timeout, Timeout: timeout,
@@ -512,6 +547,7 @@ func newDockerManager(a *Agent) *dockerManager {
sem: make(chan struct{}, 5), sem: make(chan struct{}, 5),
apiContainerList: []*container.ApiInfo{}, apiContainerList: []*container.ApiInfo{},
apiStats: &container.ApiStats{}, apiStats: &container.ApiStats{},
excludeContainers: excludeContainers,
// Initialize cache-time-aware tracking structures // Initialize cache-time-aware tracking structures
lastCpuContainer: make(map[uint16]map[string]uint64), lastCpuContainer: make(map[uint16]map[string]uint64),

View File

@@ -1099,3 +1099,107 @@ func TestAllocateBuffer(t *testing.T) {
}) })
} }
} }
func TestShouldExcludeContainer(t *testing.T) {
tests := []struct {
name string
containerName string
patterns []string
expected bool
}{
{
name: "empty patterns excludes nothing",
containerName: "any-container",
patterns: []string{},
expected: false,
},
{
name: "exact match - excluded",
containerName: "test-web",
patterns: []string{"test-web", "test-api"},
expected: true,
},
{
name: "exact match - not excluded",
containerName: "prod-web",
patterns: []string{"test-web", "test-api"},
expected: false,
},
{
name: "wildcard prefix match - excluded",
containerName: "test-web",
patterns: []string{"test-*"},
expected: true,
},
{
name: "wildcard prefix match - not excluded",
containerName: "prod-web",
patterns: []string{"test-*"},
expected: false,
},
{
name: "wildcard suffix match - excluded",
containerName: "myapp-staging",
patterns: []string{"*-staging"},
expected: true,
},
{
name: "wildcard suffix match - not excluded",
containerName: "myapp-prod",
patterns: []string{"*-staging"},
expected: false,
},
{
name: "wildcard both sides match - excluded",
containerName: "test-myapp-staging",
patterns: []string{"*-myapp-*"},
expected: true,
},
{
name: "wildcard both sides match - not excluded",
containerName: "prod-yourapp-live",
patterns: []string{"*-myapp-*"},
expected: false,
},
{
name: "multiple patterns - matches first",
containerName: "test-container",
patterns: []string{"test-*", "*-staging"},
expected: true,
},
{
name: "multiple patterns - matches second",
containerName: "myapp-staging",
patterns: []string{"test-*", "*-staging"},
expected: true,
},
{
name: "multiple patterns - no match",
containerName: "prod-web",
patterns: []string{"test-*", "*-staging"},
expected: false,
},
{
name: "mixed exact and wildcard - exact match",
containerName: "temp-container",
patterns: []string{"temp-container", "test-*"},
expected: true,
},
{
name: "mixed exact and wildcard - wildcard match",
containerName: "test-web",
patterns: []string{"temp-container", "test-*"},
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dm := &dockerManager{
excludeContainers: tt.patterns,
}
result := dm.shouldExcludeContainer(tt.containerName)
assert.Equal(t, tt.expected, result)
})
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

782
agent/smart_test.go Normal file
View File

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

40
agent/smart_windows.go Normal file
View File

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

View File

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

273
agent/systemd.go Normal file
View File

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

38
agent/systemd_nonlinux.go Normal file
View File

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

View File

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

158
agent/systemd_test.go Normal file
View File

@@ -0,0 +1,158 @@
//go:build linux && testing
package agent
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUnescapeServiceName(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"nginx.service", "nginx.service"}, // No escaping needed
{"test\\x2dwith\\x2ddashes.service", "test-with-dashes.service"}, // \x2d is dash
{"service\\x20with\\x20spaces.service", "service with spaces.service"}, // \x20 is space
{"mixed\\x2dand\\x2dnormal", "mixed-and-normal"}, // Mixed escaped and normal
{"no-escape-here", "no-escape-here"}, // No escape sequences
{"", ""}, // Empty string
{"\\x2d\\x2d", "--"}, // Multiple escapes
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
result := unescapeServiceName(test.input)
assert.Equal(t, test.expected, result)
})
}
}
func TestUnescapeServiceNameInvalid(t *testing.T) {
// Test invalid escape sequences - should return original string
invalidInputs := []string{
"invalid\\x", // Incomplete escape
"invalid\\xZZ", // Invalid hex
"invalid\\x2", // Incomplete hex
"invalid\\xyz", // Not a valid escape
}
for _, input := range invalidInputs {
t.Run(input, func(t *testing.T) {
result := unescapeServiceName(input)
assert.Equal(t, input, result, "Invalid escape sequences should return original string")
})
}
}
func TestGetServicePatterns(t *testing.T) {
tests := []struct {
name string
prefixedEnv string
unprefixedEnv string
expected []string
cleanupEnvVars bool
}{
{
name: "default when no env var set",
prefixedEnv: "",
unprefixedEnv: "",
expected: []string{"*.service"},
cleanupEnvVars: true,
},
{
name: "single pattern with prefixed env",
prefixedEnv: "nginx",
unprefixedEnv: "",
expected: []string{"nginx.service"},
cleanupEnvVars: true,
},
{
name: "single pattern with unprefixed env",
prefixedEnv: "",
unprefixedEnv: "nginx",
expected: []string{"nginx.service"},
cleanupEnvVars: true,
},
{
name: "prefixed env takes precedence",
prefixedEnv: "nginx",
unprefixedEnv: "apache",
expected: []string{"nginx.service"},
cleanupEnvVars: true,
},
{
name: "multiple patterns",
prefixedEnv: "nginx,apache,postgresql",
unprefixedEnv: "",
expected: []string{"nginx.service", "apache.service", "postgresql.service"},
cleanupEnvVars: true,
},
{
name: "patterns with .service suffix",
prefixedEnv: "nginx.service,apache.service",
unprefixedEnv: "",
expected: []string{"nginx.service", "apache.service"},
cleanupEnvVars: true,
},
{
name: "mixed patterns with and without suffix",
prefixedEnv: "nginx.service,apache,postgresql.service",
unprefixedEnv: "",
expected: []string{"nginx.service", "apache.service", "postgresql.service"},
cleanupEnvVars: true,
},
{
name: "patterns with whitespace",
prefixedEnv: " nginx , apache , postgresql ",
unprefixedEnv: "",
expected: []string{"nginx.service", "apache.service", "postgresql.service"},
cleanupEnvVars: true,
},
{
name: "empty patterns are skipped",
prefixedEnv: "nginx,,apache, ,postgresql",
unprefixedEnv: "",
expected: []string{"nginx.service", "apache.service", "postgresql.service"},
cleanupEnvVars: true,
},
{
name: "wildcard pattern",
prefixedEnv: "*nginx*,*apache*",
unprefixedEnv: "",
expected: []string{"*nginx*.service", "*apache*.service"},
cleanupEnvVars: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clean up any existing env vars
os.Unsetenv("BESZEL_AGENT_SERVICE_PATTERNS")
os.Unsetenv("SERVICE_PATTERNS")
// Set up environment variables
if tt.prefixedEnv != "" {
os.Setenv("BESZEL_AGENT_SERVICE_PATTERNS", tt.prefixedEnv)
}
if tt.unprefixedEnv != "" {
os.Setenv("SERVICE_PATTERNS", tt.unprefixedEnv)
}
// Run the function
result := getServicePatterns()
// Verify results
assert.Equal(t, tt.expected, result, "Patterns should match expected values")
// Cleanup
if tt.cleanupEnvVars {
os.Unsetenv("BESZEL_AGENT_SERVICE_PATTERNS")
os.Unsetenv("SERVICE_PATTERNS")
}
})
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

36
go.mod
View File

@@ -4,21 +4,22 @@ go 1.25.3
require ( require (
github.com/blang/semver v3.5.1+incompatible github.com/blang/semver v3.5.1+incompatible
github.com/coreos/go-systemd/v22 v22.6.0
github.com/distatus/battery v0.11.0 github.com/distatus/battery v0.11.0
github.com/fxamacker/cbor/v2 v2.9.0 github.com/fxamacker/cbor/v2 v2.9.0
github.com/gliderlabs/ssh v0.3.8 github.com/gliderlabs/ssh v0.3.8
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/lxzan/gws v1.8.9 github.com/lxzan/gws v1.8.9
github.com/nicholas-fedor/shoutrrr v0.11.0 github.com/nicholas-fedor/shoutrrr v0.12.0
github.com/pocketbase/dbx v1.11.0 github.com/pocketbase/dbx v1.11.0
github.com/pocketbase/pocketbase v0.31.0 github.com/pocketbase/pocketbase v0.33.0
github.com/shirou/gopsutil/v4 v4.25.9 github.com/shirou/gopsutil/v4 v4.25.10
github.com/spf13/cast v1.10.0 github.com/spf13/cast v1.10.0
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.43.0 golang.org/x/crypto v0.44.0
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -30,13 +31,14 @@ require (
github.com/dolthub/maphash v0.1.0 // indirect github.com/dolthub/maphash v0.1.0 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.9.0 // indirect github.com/ebitengine/purego v0.9.1 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gabriel-vasile/mimetype v1.4.11 // indirect
github.com/ganigeorgiev/fexpr v0.5.0 // indirect github.com/ganigeorgiev/fexpr v0.5.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
github.com/go-sql-driver/mysql v1.9.1 // indirect github.com/go-sql-driver/mysql v1.9.1 // indirect
github.com/godbus/dbus/v5 v5.2.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.1 // indirect github.com/klauspost/compress v1.18.1 // indirect
@@ -47,20 +49,20 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.11.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/image v0.32.0 // indirect golang.org/x/image v0.33.0 // indirect
golang.org/x/net v0.46.0 // indirect golang.org/x/net v0.47.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/oauth2 v0.33.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.37.0 // indirect golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.36.0 // indirect golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect golang.org/x/text v0.31.0 // indirect
howett.net/plist v1.0.1 // indirect howett.net/plist v1.0.1 // indirect
modernc.org/libc v1.66.10 // indirect modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.39.1 // indirect modernc.org/sqlite v1.40.0 // indirect
) )

104
go.sum
View File

@@ -9,6 +9,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -23,16 +25,16 @@ github.com/domodwyer/mailyak/v3 v3.6.2 h1:x3tGMsyFhTCaxp6ycgR0FE/bu5QiNp+hetUuCO
github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= github.com/domodwyer/mailyak/v3 v3.6.2/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk= github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE= github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@@ -49,6 +51,8 @@ github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtS
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -58,6 +62,8 @@ github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d h1:KJIErDwbSHjnp/SGzE
github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A=
@@ -79,10 +85,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= 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/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nicholas-fedor/shoutrrr v0.11.0 h1:hAMv2uM8OfFXkMHVP977elkP3Wgw5/YpVX5GxXQwiWA= github.com/nicholas-fedor/shoutrrr v0.12.0 h1:8mwJdfU+uBEybSymwQJMGl/grG7lvVUKbVSNxn3XvUI=
github.com/nicholas-fedor/shoutrrr v0.11.0/go.mod h1:0kRF9ral22xUn/0BlxfhLQUeJDTySCPsuNvaclyagb4= github.com/nicholas-fedor/shoutrrr v0.12.0/go.mod h1:WYiRalR4C43Qmd2zhPWGIFIxu633NB1hDM6Ap/DQcsA=
github.com/onsi/ginkgo/v2 v2.27.1 h1:0LJC8MpUSQnfnp4n/3W3GdlmJP3ENGF0ZPzjQGLPP7s= github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.1/go.mod h1:wmy3vCqiBjirARfVhAqFpYt8uvX0yaFe+GudAqqcCqA= github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -90,8 +96,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU= github.com/pocketbase/dbx v1.11.0 h1:LpZezioMfT3K4tLrqA55wWFw1EtH1pM4tzSVa7kgszU=
github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs= github.com/pocketbase/dbx v1.11.0/go.mod h1:xXRCIAKTHMgUCyCKZm55pUOdvFziJjQfXaWKhu2vhMs=
github.com/pocketbase/pocketbase v0.31.0 h1:JaOtSDytdA+a0r4689Mrjda4rmq+BaHgEJkPeOIydms= github.com/pocketbase/pocketbase v0.33.0 h1:v2EfiY3hxigzRJ/BwFuwVn0vUv7d2QQoD5zUFPaKR9o=
github.com/pocketbase/pocketbase v0.31.0/go.mod h1:p4a83n+DlBcTvvqhC7QDy0KDmQ2la2c6dgxdIBWwKiE= github.com/pocketbase/pocketbase v0.33.0/go.mod h1:9BEs+CRV7CrS+X5LfBh4bdJQsbzQAIklft3ovGe/c5A=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
@@ -99,8 +105,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU= github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA=
github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8= github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
@@ -112,75 +118,77 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
modernc.org/libc v1.67.0 h1:QzL4IrKab2OFmxA3/vRYl0tLXrIamwrhD6CKD4WBVjQ=
modernc.org/libc v1.67.0/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
@@ -189,8 +197,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4= modernc.org/sqlite v1.40.0 h1:bNWEDlYhNPAUdUdBzjAvn8icAs/2gaKlj4vM+tQ6KdQ=
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= modernc.org/sqlite v1.40.0/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -28,6 +28,7 @@ type AlertManager struct {
type AlertMessageData struct { type AlertMessageData struct {
UserID string UserID string
SystemID string
Title string Title string
Message string Message string
Link string Link string
@@ -40,13 +41,18 @@ type UserNotificationSettings struct {
} }
type SystemAlertStats struct { type SystemAlertStats struct {
Cpu float64 `json:"cpu"` Cpu float64 `json:"cpu"`
Mem float64 `json:"mp"` Mem float64 `json:"mp"`
Disk float64 `json:"dp"` Disk float64 `json:"dp"`
NetSent float64 `json:"ns"` NetSent float64 `json:"ns"`
NetRecv float64 `json:"nr"` NetRecv float64 `json:"nr"`
Temperatures map[string]float32 `json:"t"` GPU map[string]SystemAlertGPUData `json:"g"`
LoadAvg [3]float64 `json:"la"` Temperatures map[string]float32 `json:"t"`
LoadAvg [3]float64 `json:"la"`
}
type SystemAlertGPUData struct {
Usage float64 `json:"u"`
} }
type SystemAlertData struct { type SystemAlertData struct {
@@ -72,7 +78,6 @@ var supportsTitle = map[string]struct{}{
"ifttt": {}, "ifttt": {},
"join": {}, "join": {},
"lark": {}, "lark": {},
"matrix": {},
"ntfy": {}, "ntfy": {},
"opsgenie": {}, "opsgenie": {},
"pushbullet": {}, "pushbullet": {},
@@ -101,8 +106,81 @@ func (am *AlertManager) bindEvents() {
am.hub.OnRecordAfterDeleteSuccess("alerts").BindFunc(resolveHistoryOnAlertDelete) am.hub.OnRecordAfterDeleteSuccess("alerts").BindFunc(resolveHistoryOnAlertDelete)
} }
// IsNotificationSilenced checks if a notification should be silenced based on configured quiet hours
func (am *AlertManager) IsNotificationSilenced(userID, systemID string) bool {
// Query for quiet hours windows that match this user and system
// Include both global windows (system is null/empty) and system-specific windows
var filter string
var params dbx.Params
if systemID == "" {
// If no systemID provided, only check global windows
filter = "user={:user} AND system=''"
params = dbx.Params{"user": userID}
} else {
// Check both global and system-specific windows
filter = "user={:user} AND (system='' OR system={:system})"
params = dbx.Params{
"user": userID,
"system": systemID,
}
}
quietHourWindows, err := am.hub.FindAllRecords("quiet_hours", dbx.NewExp(filter, params))
if err != nil || len(quietHourWindows) == 0 {
return false
}
now := time.Now().UTC()
for _, window := range quietHourWindows {
windowType := window.GetString("type")
start := window.GetDateTime("start").Time()
end := window.GetDateTime("end").Time()
if windowType == "daily" {
// For daily recurring windows, extract just the time portion and compare
// The start/end are stored as full datetime but we only care about HH:MM
startHour, startMin, _ := start.Clock()
endHour, endMin, _ := end.Clock()
nowHour, nowMin, _ := now.Clock()
// Convert to minutes since midnight for easier comparison
startMinutes := startHour*60 + startMin
endMinutes := endHour*60 + endMin
nowMinutes := nowHour*60 + nowMin
// Handle case where window crosses midnight
if endMinutes < startMinutes {
// Window crosses midnight (e.g., 23:00 - 01:00)
if nowMinutes >= startMinutes || nowMinutes < endMinutes {
return true
}
} else {
// Normal case (e.g., 09:00 - 17:00)
if nowMinutes >= startMinutes && nowMinutes < endMinutes {
return true
}
}
} else {
// One-time window: check if current time is within the date range
if (now.After(start) || now.Equal(start)) && now.Before(end) {
return true
}
}
}
return false
}
// SendAlert sends an alert to the user // SendAlert sends an alert to the user
func (am *AlertManager) SendAlert(data AlertMessageData) error { func (am *AlertManager) SendAlert(data AlertMessageData) error {
// Check if alert is silenced
if am.IsNotificationSilenced(data.UserID, data.SystemID) {
am.hub.Logger().Info("Notification silenced", "user", data.UserID, "system", data.SystemID, "title", data.Title)
return nil
}
// get user settings // get user settings
record, err := am.hub.FindFirstRecordByFilter( record, err := am.hub.FindFirstRecordByFilter(
"user_settings", "user={:user}", "user_settings", "user={:user}",

View File

@@ -0,0 +1,426 @@
//go:build testing
// +build testing
package alerts_test
import (
"testing"
"testing/synctest"
"time"
"github.com/henrygd/beszel/internal/alerts"
beszelTests "github.com/henrygd/beszel/internal/tests"
"github.com/pocketbase/dbx"
"github.com/stretchr/testify/assert"
)
func TestAlertSilencedOneTime(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
assert.NoError(t, err)
system := systems[0]
// Create an alert
alert, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "CPU",
"system": system.Id,
"user": user.Id,
"value": 80,
"min": 1,
})
assert.NoError(t, err)
// Create a one-time quiet hours window (current time - 1 hour to current time + 1 hour)
now := time.Now().UTC()
startTime := now.Add(-1 * time.Hour)
endTime := now.Add(1 * time.Hour)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"system": system.Id,
"type": "one-time",
"start": startTime,
"end": endTime,
})
assert.NoError(t, err)
// Get alert manager
am := alerts.NewAlertManager(hub)
defer am.StopWorker()
// Test that alert is silenced
silenced := am.IsNotificationSilenced(user.Id, system.Id)
assert.True(t, silenced, "Alert should be silenced during active one-time window")
// Create a window that has already ended
pastStart := now.Add(-3 * time.Hour)
pastEnd := now.Add(-2 * time.Hour)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"system": system.Id,
"type": "one-time",
"start": pastStart,
"end": pastEnd,
})
assert.NoError(t, err)
// Should still be silenced because of the first window
silenced = am.IsNotificationSilenced(user.Id, system.Id)
assert.True(t, silenced, "Alert should still be silenced (past window doesn't affect active window)")
// Clear all windows and create a future window
_, err = hub.DB().NewQuery("DELETE FROM quiet_hours").Execute()
assert.NoError(t, err)
futureStart := now.Add(2 * time.Hour)
futureEnd := now.Add(3 * time.Hour)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"system": system.Id,
"type": "one-time",
"start": futureStart,
"end": futureEnd,
})
assert.NoError(t, err)
// Alert should NOT be silenced (window hasn't started yet)
silenced = am.IsNotificationSilenced(user.Id, system.Id)
assert.False(t, silenced, "Alert should not be silenced (window hasn't started)")
_ = alert
}
func TestAlertSilencedDaily(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
assert.NoError(t, err)
system := systems[0]
// Get alert manager
am := alerts.NewAlertManager(hub)
defer am.StopWorker()
// Get current hour and create a window that includes current time
now := time.Now().UTC()
currentHour := now.Hour()
currentMin := now.Minute()
// Create a window from 1 hour ago to 1 hour from now
startHour := (currentHour - 1 + 24) % 24
endHour := (currentHour + 1) % 24
// Create times with just the hours/minutes we want (date doesn't matter for daily)
startTime := time.Date(2000, 1, 1, startHour, currentMin, 0, 0, time.UTC)
endTime := time.Date(2000, 1, 1, endHour, currentMin, 0, 0, time.UTC)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"system": system.Id,
"type": "daily",
"start": startTime,
"end": endTime,
})
assert.NoError(t, err)
// Alert should be silenced (current time is within the daily window)
silenced := am.IsNotificationSilenced(user.Id, system.Id)
assert.True(t, silenced, "Alert should be silenced during active daily window")
// Clear windows and create one that doesn't include current time
_, err = hub.DB().NewQuery("DELETE FROM quiet_hours").Execute()
assert.NoError(t, err)
// Create a window from 6-12 hours from now
futureStartHour := (currentHour + 6) % 24
futureEndHour := (currentHour + 12) % 24
startTime = time.Date(2000, 1, 1, futureStartHour, 0, 0, 0, time.UTC)
endTime = time.Date(2000, 1, 1, futureEndHour, 0, 0, 0, time.UTC)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"system": system.Id,
"type": "daily",
"start": startTime,
"end": endTime,
})
assert.NoError(t, err)
// Alert should NOT be silenced
silenced = am.IsNotificationSilenced(user.Id, system.Id)
assert.False(t, silenced, "Alert should not be silenced (outside daily window)")
}
func TestAlertSilencedDailyMidnightCrossing(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
assert.NoError(t, err)
system := systems[0]
// Get alert manager
am := alerts.NewAlertManager(hub)
defer am.StopWorker()
// Create a window that crosses midnight: 22:00 - 02:00
startTime := time.Date(2000, 1, 1, 22, 0, 0, 0, time.UTC)
endTime := time.Date(2000, 1, 1, 2, 0, 0, 0, time.UTC)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"system": system.Id,
"type": "daily",
"start": startTime,
"end": endTime,
})
assert.NoError(t, err)
// Test with a time at 23:00 (should be silenced)
// We can't control the actual current time, but we can verify the logic
// by checking if the window was created correctly
windows, err := hub.FindAllRecords("quiet_hours", dbx.HashExp{
"user": user.Id,
"system": system.Id,
})
assert.NoError(t, err)
assert.Len(t, windows, 1, "Should have created 1 window")
window := windows[0]
assert.Equal(t, "daily", window.GetString("type"))
assert.Equal(t, 22, window.GetDateTime("start").Time().Hour())
assert.Equal(t, 2, window.GetDateTime("end").Time().Hour())
}
func TestAlertSilencedGlobal(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create multiple systems
systems, err := beszelTests.CreateSystems(hub, 3, user.Id, "up")
assert.NoError(t, err)
// Get alert manager
am := alerts.NewAlertManager(hub)
defer am.StopWorker()
// Create a global quiet hours window (no system specified)
now := time.Now().UTC()
startTime := now.Add(-1 * time.Hour)
endTime := now.Add(1 * time.Hour)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"type": "one-time",
"start": startTime,
"end": endTime,
// system field is empty/null for global windows
})
assert.NoError(t, err)
// All systems should be silenced
for _, system := range systems {
silenced := am.IsNotificationSilenced(user.Id, system.Id)
assert.True(t, silenced, "Alert should be silenced for system %s (global window)", system.Id)
}
// Even with a systemID that doesn't exist, should be silenced
silenced := am.IsNotificationSilenced(user.Id, "nonexistent-system")
assert.True(t, silenced, "Alert should be silenced for any system (global window)")
}
func TestAlertSilencedSystemSpecific(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create multiple systems
systems, err := beszelTests.CreateSystems(hub, 2, user.Id, "up")
assert.NoError(t, err)
system1 := systems[0]
system2 := systems[1]
// Get alert manager
am := alerts.NewAlertManager(hub)
defer am.StopWorker()
// Create a system-specific quiet hours window for system1 only
now := time.Now().UTC()
startTime := now.Add(-1 * time.Hour)
endTime := now.Add(1 * time.Hour)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"system": system1.Id,
"type": "one-time",
"start": startTime,
"end": endTime,
})
assert.NoError(t, err)
// System1 should be silenced
silenced := am.IsNotificationSilenced(user.Id, system1.Id)
assert.True(t, silenced, "Alert should be silenced for system1")
// System2 should NOT be silenced
silenced = am.IsNotificationSilenced(user.Id, system2.Id)
assert.False(t, silenced, "Alert should not be silenced for system2")
}
func TestAlertSilencedMultiUser(t *testing.T) {
hub, _ := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create two users
user1, err := beszelTests.CreateUser(hub, "user1@example.com", "password")
assert.NoError(t, err)
user2, err := beszelTests.CreateUser(hub, "user2@example.com", "password")
assert.NoError(t, err)
// Create a system accessible to both users
system, err := beszelTests.CreateRecord(hub, "systems", map[string]any{
"name": "shared-system",
"users": []string{user1.Id, user2.Id},
"host": "127.0.0.1",
})
assert.NoError(t, err)
// Get alert manager
am := alerts.NewAlertManager(hub)
defer am.StopWorker()
// Create a quiet hours window for user1 only
now := time.Now().UTC()
startTime := now.Add(-1 * time.Hour)
endTime := now.Add(1 * time.Hour)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user1.Id,
"system": system.Id,
"type": "one-time",
"start": startTime,
"end": endTime,
})
assert.NoError(t, err)
// User1 should be silenced
silenced := am.IsNotificationSilenced(user1.Id, system.Id)
assert.True(t, silenced, "Alert should be silenced for user1")
// User2 should NOT be silenced
silenced = am.IsNotificationSilenced(user2.Id, system.Id)
assert.False(t, silenced, "Alert should not be silenced for user2")
}
func TestAlertSilencedWithActualAlert(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
assert.NoError(t, err)
system := systems[0]
// Create a status alert
_, err = beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "Status",
"system": system.Id,
"user": user.Id,
"min": 1,
})
assert.NoError(t, err)
// Create user settings with email
userSettings, err := hub.FindFirstRecordByFilter("user_settings", "user={:user}", dbx.Params{"user": user.Id})
if err != nil || userSettings == nil {
userSettings, err = beszelTests.CreateRecord(hub, "user_settings", map[string]any{
"user": user.Id,
"settings": map[string]any{
"emails": []string{"test@example.com"},
},
})
assert.NoError(t, err)
}
// Create a quiet hours window
now := time.Now().UTC()
startTime := now.Add(-1 * time.Hour)
endTime := now.Add(1 * time.Hour)
_, err = beszelTests.CreateRecord(hub, "quiet_hours", map[string]any{
"user": user.Id,
"system": system.Id,
"type": "one-time",
"start": startTime,
"end": endTime,
})
assert.NoError(t, err)
// Get initial email count
initialEmailCount := hub.TestMailer.TotalSend()
// Trigger an alert by setting system to down
system.Set("status", "down")
err = hub.SaveNoValidate(system)
assert.NoError(t, err)
// Wait for the alert to be processed (1 minute + buffer)
time.Sleep(time.Second * 75)
synctest.Wait()
// Check that no email was sent (because alert is silenced)
finalEmailCount := hub.TestMailer.TotalSend()
assert.Equal(t, initialEmailCount, finalEmailCount, "No emails should be sent when alert is silenced")
// Clear quiet hours windows
_, err = hub.DB().NewQuery("DELETE FROM quiet_hours").Execute()
assert.NoError(t, err)
// Reset system to up, then down again
system.Set("status", "up")
err = hub.SaveNoValidate(system)
assert.NoError(t, err)
time.Sleep(100 * time.Millisecond)
system.Set("status", "down")
err = hub.SaveNoValidate(system)
assert.NoError(t, err)
// Wait for the alert to be processed
time.Sleep(time.Second * 75)
synctest.Wait()
// Now an email should be sent
newEmailCount := hub.TestMailer.TotalSend()
assert.Greater(t, newEmailCount, finalEmailCount, "Email should be sent when not silenced")
})
}
func TestAlertSilencedNoWindows(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
assert.NoError(t, err)
system := systems[0]
// Get alert manager
am := alerts.NewAlertManager(hub)
defer am.StopWorker()
// Without any quiet hours windows, alert should NOT be silenced
silenced := am.IsNotificationSilenced(user.Id, system.Id)
assert.False(t, silenced, "Alert should not be silenced when no windows exist")
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,11 +30,12 @@
"noUnusedFunctionParameters": "error", "noUnusedFunctionParameters": "error",
"noUnusedPrivateClassMembers": "error", "noUnusedPrivateClassMembers": "error",
"useExhaustiveDependencies": { "useExhaustiveDependencies": {
"level": "error", "level": "warn",
"options": { "options": {
"reportUnnecessaryDependencies": false "reportUnnecessaryDependencies": false
} }
}, },
"useUniqueElementIds": "off",
"noUnusedVariables": "error" "noUnusedVariables": "error"
}, },
"style": { "style": {
@@ -47,7 +48,8 @@
}, },
"suspicious": { "suspicious": {
"useAwait": "error", "useAwait": "error",
"noEvolvingTypes": "error" "noEvolvingTypes": "error",
"noArrayIndexKey": "off"
} }
} }
}, },

1008
internal/site/bun.lock Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -2,7 +2,7 @@
<html lang="en" dir="ltr"> <html lang="en" dir="ltr">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="manifest" href="./static/manifest.json" /> <link rel="manifest" href="./static/manifest.json" crossorigin="use-credentials" />
<link rel="icon" type="image/svg+xml" href="./static/icon.svg" /> <link rel="icon" type="image/svg+xml" href="./static/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />

View File

@@ -11,6 +11,7 @@ export default defineConfig({
"es", "es",
"fa", "fa",
"fr", "fr",
"he",
"hr", "hr",
"hu", "hu",
"it", "it",

View File

@@ -1,12 +1,12 @@
{ {
"name": "beszel", "name": "beszel",
"version": "0.15.0", "version": "0.16.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "beszel", "name": "beszel",
"version": "0.15.0", "version": "0.16.1",
"dependencies": { "dependencies": {
"@henrygd/queue": "^1.0.7", "@henrygd/queue": "^1.0.7",
"@henrygd/semaphore": "^0.0.2", "@henrygd/semaphore": "^0.0.2",
@@ -4807,9 +4807,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

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

View File

@@ -36,28 +36,31 @@ import { AppleIcon, DockerIcon, FreeBsdIcon, TuxIcon, WindowsIcon } from "./ui/i
import { InputCopy } from "./ui/input-copy" import { InputCopy } from "./ui/input-copy"
export function AddSystemButton({ className }: { className?: string }) { export function AddSystemButton({ className }: { className?: string }) {
const [open, setOpen] = useState(false) if (isReadOnlyUser()) {
const opened = useRef(false) return null
if (open) { }
opened.current = true const [open, setOpen] = useState(false)
} const opened = useRef(false)
if (open) {
opened.current = true
}
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
variant="outline" variant="outline"
className={cn("flex gap-1 max-xs:h-[2.4rem]", className, isReadOnlyUser() && "hidden")} className={cn("flex gap-1 max-xs:h-[2.4rem]", className)}
> >
<PlusIcon className="h-4 w-4 -ms-1" /> <PlusIcon className="h-4 w-4 -ms-1" />
<Trans> <Trans>
Add <span className="hidden sm:inline">System</span> Add <span className="hidden sm:inline">System</span>
</Trans> </Trans>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
{opened.current && <SystemDialog setOpen={setOpen} />} {opened.current && <SystemDialog setOpen={setOpen} />}
</Dialog> </Dialog>
) )
} }
/** /**

View File

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

View File

@@ -2,7 +2,7 @@
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
import { memo, useMemo } from "react" import { memo, useMemo } from "react"
import { Area, AreaChart, CartesianGrid, YAxis } from "recharts" import { Area, AreaChart, CartesianGrid, YAxis } from "recharts"
import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, xAxis } from "@/components/ui/chart" import { type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, pinnedAxisDomain, xAxis } from "@/components/ui/chart"
import { ChartType, Unit } from "@/lib/enums" import { ChartType, Unit } from "@/lib/enums"
import { $containerFilter, $userSettings } from "@/lib/stores" import { $containerFilter, $userSettings } from "@/lib/stores"
import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils" import { chartMargin, cn, decimalString, formatBytes, formatShortDate, toFixedFloat } from "@/lib/utils"
@@ -41,7 +41,7 @@ export default memo(function ContainerChart({
// tick formatter // tick formatter
if (chartType === ChartType.CPU) { if (chartType === ChartType.CPU) {
obj.tickFormatter = (value) => { obj.tickFormatter = (value) => {
const val = toFixedFloat(value, 2) + unit const val = `${toFixedFloat(value, 2)}%`
return updateYAxisWidth(val) return updateYAxisWidth(val)
} }
} else { } else {
@@ -78,7 +78,7 @@ export default memo(function ContainerChart({
return `${decimalString(value)} ${unit}` return `${decimalString(value)} ${unit}`
} }
} else { } else {
obj.toolTipFormatter = (item: any) => `${decimalString(item.value)} ${unit}` obj.toolTipFormatter = (item: any) => `${decimalString(item.value)}${unit}`
} }
// data function // data function
if (isNetChart) { if (isNetChart) {
@@ -124,6 +124,7 @@ export default memo(function ContainerChart({
<CartesianGrid vertical={false} /> <CartesianGrid vertical={false} />
<YAxis <YAxis
direction="ltr" direction="ltr"
domain={pinnedAxisDomain()}
orientation={chartData.orientation} orientation={chartData.orientation}
className="tracking-tighter" className="tracking-tighter"
width={yAxisWidth} width={yAxisWidth}
@@ -139,7 +140,7 @@ export default memo(function ContainerChart({
labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} labelFormatter={(_, data) => formatShortDate(data[0].payload.created)}
// @ts-expect-error // @ts-expect-error
itemSorter={(a, b) => b.value - a.value} itemSorter={(a, b) => b.value - a.value}
content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} />} content={<ChartTooltipContent filter={filter} contentFormatter={toolTipFormatter} showTotal={true} />}
/> />
{Object.keys(chartConfig).map((key) => { {Object.keys(chartConfig).map((key) => {
const filtered = filteredKeys.has(key) const filtered = filteredKeys.has(key)

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ import { Sheet, SheetTitle, SheetHeader, SheetContent, SheetDescription } from "
import { Dialog, DialogContent, DialogTitle } from "../ui/dialog" import { Dialog, DialogContent, DialogTitle } from "../ui/dialog"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { $allSystemsById } from "@/lib/stores" import { $allSystemsById } from "@/lib/stores"
import { MaximizeIcon, RefreshCwIcon } from "lucide-react" import { MaximizeIcon, RefreshCwIcon, XIcon } from "lucide-react"
import { Separator } from "../ui/separator" import { Separator } from "../ui/separator"
import { $router, Link } from "../router" import { $router, Link } from "../router"
import { listenKeys } from "nanostores" import { listenKeys } from "nanostores"
@@ -35,6 +35,7 @@ import { getPagePath } from "@nanostores/router"
const syntaxTheme = "github-dark-dimmed" const syntaxTheme = "github-dark-dimmed"
export default function ContainersTable({ systemId }: { systemId?: string }) { export default function ContainersTable({ systemId }: { systemId?: string }) {
const loadTime = Date.now()
const [data, setData] = useState<ContainerRecord[]>([]) const [data, setData] = useState<ContainerRecord[]>([])
const [sorting, setSorting] = useBrowserStorage<SortingState>( const [sorting, setSorting] = useBrowserStorage<SortingState>(
`sort-c-${systemId ? 1 : 0}`, `sort-c-${systemId ? 1 : 0}`,
@@ -47,56 +48,53 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
const [globalFilter, setGlobalFilter] = useState("") const [globalFilter, setGlobalFilter] = useState("")
useEffect(() => { useEffect(() => {
const pbOptions = { function fetchData(systemId?: string) {
fields: "id,name,image,cpu,memory,net,health,status,system,updated",
}
const fetchData = (lastXMs: number) => {
const updated = Date.now() - lastXMs
let filter: string
if (systemId) {
filter = pb.filter("system={:system} && updated > {:updated}", { system: systemId, updated })
} else {
filter = pb.filter("updated > {:updated}", { updated })
}
pb.collection<ContainerRecord>("containers") pb.collection<ContainerRecord>("containers")
.getList(0, 2000, { .getList(0, 2000, {
...pbOptions, fields: "id,name,image,cpu,memory,net,health,status,system,updated",
filter, filter: systemId ? pb.filter("system={:system}", { system: systemId }) : undefined,
}) })
.then(({ items }) => setData((curItems) => { .then(({ items }) => items.length && setData((curItems) => {
const containerIds = new Set(items.map(item => item.id)) const lastUpdated = Math.max(items[0].updated, items.at(-1)?.updated ?? 0)
const now = Date.now() const containerIds = new Set()
for (const item of curItems) { const newItems = []
if (!containerIds.has(item.id) && now - item.updated < 70_000) { for (const item of items) {
items.push(item) if (Math.abs(lastUpdated - item.updated) < 70_000) {
containerIds.add(item.id)
newItems.push(item)
} }
} }
return items for (const item of curItems) {
if (!containerIds.has(item.id) && lastUpdated - item.updated < 70_000) {
newItems.push(item)
}
}
return newItems
})) }))
} }
// initial load // initial load
fetchData(70_000) fetchData(systemId)
// if no systemId, poll every 10 seconds // if no systemId, pull system containers after every system update
if (!systemId) { if (!systemId) {
// poll every 10 seconds return $allSystemsById.listen((_value, _oldValue, systemId) => {
const intervalId = setInterval(() => fetchData(10_500), 10_000) // exclude initial load of systems
// clear interval on unmount if (Date.now() - loadTime > 500) {
return () => clearInterval(intervalId) fetchData(systemId)
}
})
} }
// if systemId, fetch containers after the system is updated // if systemId, fetch containers after the system is updated
return listenKeys($allSystemsById, [systemId], (_newSystems) => { return listenKeys($allSystemsById, [systemId], (_newSystems) => {
const changeTime = Date.now() fetchData(systemId)
setTimeout(() => fetchData(Date.now() - changeTime + 1000), 100)
}) })
}, []) }, [])
const table = useReactTable({ const table = useReactTable({
data, data,
columns: containerChartCols.filter(col => systemId ? col.id !== "system" : true), columns: containerChartCols.filter((col) => (systemId ? col.id !== "system" : true)),
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
@@ -149,12 +147,26 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
<Trans>Click on a container to view more information.</Trans> <Trans>Click on a container to view more information.</Trans>
</CardDescription> </CardDescription>
</div> </div>
<Input <div className="relative ms-auto w-full max-w-full md:w-64">
placeholder={t`Filter...`} <Input
value={globalFilter} placeholder={t`Filter...`}
onChange={(e) => setGlobalFilter(e.target.value)} value={globalFilter}
className="ms-auto px-4 w-full max-w-full md:w-64" onChange={(e) => setGlobalFilter(e.target.value)}
/> className="ps-4 pe-10 w-full"
/>
{globalFilter && (
<Button
type="button"
variant="ghost"
size="icon"
aria-label={t`Clear filter`}
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-muted-foreground"
onClick={() => setGlobalFilter("")}
>
<XIcon className="h-4 w-4" />
</Button>
)}
</div>
</div> </div>
</CardHeader> </CardHeader>
<div className="rounded-md"> <div className="rounded-md">
@@ -164,77 +176,78 @@ export default function ContainersTable({ systemId }: { systemId?: string }) {
) )
} }
const AllContainersTable = memo( const AllContainersTable = memo(function AllContainersTable({
function AllContainersTable({ table, rows, colLength }: { table: TableType<ContainerRecord>; rows: Row<ContainerRecord>[]; colLength: number }) { table,
// The virtualizer will need a reference to the scrollable container element rows,
const scrollRef = useRef<HTMLDivElement>(null) colLength,
const activeContainer = useRef<ContainerRecord | null>(null) }: {
const [sheetOpen, setSheetOpen] = useState(false) table: TableType<ContainerRecord>
const openSheet = (container: ContainerRecord) => { rows: Row<ContainerRecord>[]
activeContainer.current = container colLength: number
setSheetOpen(true) }) {
} // The virtualizer will need a reference to the scrollable container element
const scrollRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({ const activeContainer = useRef<ContainerRecord | null>(null)
count: rows.length, const [sheetOpen, setSheetOpen] = useState(false)
estimateSize: () => 54, const openSheet = (container: ContainerRecord) => {
getScrollElement: () => scrollRef.current, activeContainer.current = container
overscan: 5, setSheetOpen(true)
})
const virtualRows = virtualizer.getVirtualItems()
const paddingTop = Math.max(0, virtualRows[0]?.start ?? 0 - virtualizer.options.scrollMargin)
const paddingBottom = Math.max(0, virtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0))
return (
<div
className={cn(
"h-min max-h-[calc(100dvh-17rem)] max-w-full relative overflow-auto border rounded-md",
// don't set min height if there are less than 2 rows, do set if we need to display the empty state
(!rows.length || rows.length > 2) && "min-h-50"
)}
ref={scrollRef}
>
{/* add header height to table size */}
<div style={{ height: `${virtualizer.getTotalSize() + 48}px`, paddingTop, paddingBottom }}>
<table className="text-sm w-full h-full text-nowrap">
<ContainersTableHead table={table} />
<TableBody>
{rows.length ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index]
return (
<ContainerTableRow
key={row.id}
row={row}
virtualRow={virtualRow}
openSheet={openSheet}
/>
)
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
<Trans>No results.</Trans>
</TableCell>
</TableRow>
)}
</TableBody>
</table>
</div>
<ContainerSheet sheetOpen={sheetOpen} setSheetOpen={setSheetOpen} activeContainer={activeContainer} />
</div>
)
} }
)
const virtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
count: rows.length,
estimateSize: () => 54,
getScrollElement: () => scrollRef.current,
overscan: 5,
})
const virtualRows = virtualizer.getVirtualItems()
const paddingTop = Math.max(0, virtualRows[0]?.start ?? 0 - virtualizer.options.scrollMargin)
const paddingBottom = Math.max(0, virtualizer.getTotalSize() - (virtualRows[virtualRows.length - 1]?.end ?? 0))
return (
<div
className={cn(
"h-min max-h-[calc(100dvh-17rem)] max-w-full relative overflow-auto border rounded-md",
// don't set min height if there are less than 2 rows, do set if we need to display the empty state
(!rows.length || rows.length > 2) && "min-h-50"
)}
ref={scrollRef}
>
{/* add header height to table size */}
<div style={{ height: `${virtualizer.getTotalSize() + 48}px`, paddingTop, paddingBottom }}>
<table className="text-sm w-full h-full text-nowrap">
<ContainersTableHead table={table} />
<TableBody>
{rows.length ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index]
return <ContainerTableRow key={row.id} row={row} virtualRow={virtualRow} openSheet={openSheet} />
})
) : (
<TableRow>
<TableCell colSpan={colLength} className="h-37 text-center pointer-events-none">
<Trans>No results.</Trans>
</TableCell>
</TableRow>
)}
</TableBody>
</table>
</div>
<ContainerSheet sheetOpen={sheetOpen} setSheetOpen={setSheetOpen} activeContainer={activeContainer} />
</div>
)
})
async function getLogsHtml(container: ContainerRecord): Promise<string> { async function getLogsHtml(container: ContainerRecord): Promise<string> {
try { try {
const [{ highlighter }, logsHtml] = await Promise.all([import('@/lib/shiki'), pb.send<{ logs: string }>("/api/beszel/containers/logs", { const [{ highlighter }, logsHtml] = await Promise.all([
system: container.system, import("@/lib/shiki"),
container: container.id, pb.send<{ logs: string }>("/api/beszel/containers/logs", {
})]) system: container.system,
container: container.id,
}),
])
return logsHtml.logs ? highlighter.codeToHtml(logsHtml.logs, { lang: "log", theme: syntaxTheme }) : t`No results.` return logsHtml.logs ? highlighter.codeToHtml(logsHtml.logs, { lang: "log", theme: syntaxTheme }) : t`No results.`
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@@ -244,10 +257,13 @@ async function getLogsHtml(container: ContainerRecord): Promise<string> {
async function getInfoHtml(container: ContainerRecord): Promise<string> { async function getInfoHtml(container: ContainerRecord): Promise<string> {
try { try {
let [{ highlighter }, { info }] = await Promise.all([import('@/lib/shiki'), pb.send<{ info: string }>("/api/beszel/containers/info", { let [{ highlighter }, { info }] = await Promise.all([
system: container.system, import("@/lib/shiki"),
container: container.id, pb.send<{ info: string }>("/api/beszel/containers/info", {
})]) system: container.system,
container: container.id,
}),
])
try { try {
info = JSON.stringify(JSON.parse(info), null, 2) info = JSON.stringify(JSON.parse(info), null, 2)
} catch (_) { } } catch (_) { }
@@ -258,7 +274,15 @@ async function getInfoHtml(container: ContainerRecord): Promise<string> {
} }
} }
function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpen: boolean, setSheetOpen: (open: boolean) => void, activeContainer: RefObject<ContainerRecord | null> }) { function ContainerSheet({
sheetOpen,
setSheetOpen,
activeContainer,
}: {
sheetOpen: boolean
setSheetOpen: (open: boolean) => void
activeContainer: RefObject<ContainerRecord | null>
}) {
const container = activeContainer.current const container = activeContainer.current
if (!container) return null if (!container) return null
@@ -297,14 +321,14 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
useEffect(() => { useEffect(() => {
setLogsDisplay("") setLogsDisplay("")
setInfoDisplay(""); setInfoDisplay("")
if (!container) return if (!container) return
(async () => { ; (async () => {
const [logsHtml, infoHtml] = await Promise.all([getLogsHtml(container), getInfoHtml(container)]) const [logsHtml, infoHtml] = await Promise.all([getLogsHtml(container), getInfoHtml(container)])
setLogsDisplay(logsHtml) setLogsDisplay(logsHtml)
setInfoDisplay(infoHtml) setInfoDisplay(infoHtml)
setTimeout(scrollLogsToBottom, 20) setTimeout(scrollLogsToBottom, 20)
})() })()
}, [container]) }, [container])
return ( return (
@@ -328,7 +352,9 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
<SheetHeader> <SheetHeader>
<SheetTitle>{container.name}</SheetTitle> <SheetTitle>{container.name}</SheetTitle>
<SheetDescription className="flex flex-wrap items-center gap-x-2 gap-y-1"> <SheetDescription className="flex flex-wrap items-center gap-x-2 gap-y-1">
<Link className="hover:underline" href={getPagePath($router, "system", { id: container.system })}>{$allSystemsById.get()[container.system]?.name ?? ""}</Link> <Link className="hover:underline" href={getPagePath($router, "system", { id: container.system })}>
{$allSystemsById.get()[container.system]?.name ?? ""}
</Link>
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" /> <Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
{container.status} {container.status}
<Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" /> <Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
@@ -350,19 +376,20 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
disabled={isRefreshingLogs} disabled={isRefreshingLogs}
> >
<RefreshCwIcon <RefreshCwIcon
className={`size-4 transition-transform duration-300 ${isRefreshingLogs ? 'animate-spin' : ''}`} className={`size-4 transition-transform duration-300 ${isRefreshingLogs ? "animate-spin" : ""}`}
/> />
</Button> </Button>
<Button <Button variant="ghost" size="sm" onClick={() => setLogsFullscreenOpen(true)} className="h-8 w-8 p-0">
variant="ghost"
size="sm"
onClick={() => setLogsFullscreenOpen(true)}
className="h-8 w-8 p-0"
>
<MaximizeIcon className="size-4" /> <MaximizeIcon className="size-4" />
</Button> </Button>
</div> </div>
<div ref={logsContainerRef} className={cn("max-h-[calc(50dvh-10rem)] w-full overflow-auto p-3 rounded-md bg-gh-dark text-sm", !logsDisplay && ["animate-pulse", "h-full"])}> <div
ref={logsContainerRef}
className={cn(
"max-h-[calc(50dvh-10rem)] w-full overflow-auto p-3 rounded-md bg-gh-dark text-white text-sm",
!logsDisplay && ["animate-pulse", "h-full"]
)}
>
<div dangerouslySetInnerHTML={{ __html: logsDisplay }} /> <div dangerouslySetInnerHTML={{ __html: logsDisplay }} />
</div> </div>
<div className="flex items-center w-full"> <div className="flex items-center w-full">
@@ -376,15 +403,18 @@ function ContainerSheet({ sheetOpen, setSheetOpen, activeContainer }: { sheetOpe
<MaximizeIcon className="size-4" /> <MaximizeIcon className="size-4" />
</Button> </Button>
</div> </div>
<div className={cn("grow h-[calc(50dvh-4rem)] w-full overflow-auto p-3 rounded-md bg-gh-dark text-sm", !infoDisplay && "animate-pulse")}> <div
className={cn(
"grow h-[calc(50dvh-4rem)] w-full overflow-auto p-3 rounded-md bg-gh-dark text-white text-sm",
!infoDisplay && "animate-pulse"
)}
>
<div dangerouslySetInnerHTML={{ __html: infoDisplay }} /> <div dangerouslySetInnerHTML={{ __html: infoDisplay }} />
</div> </div>
</div> </div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
</> </>
) )
} }
@@ -406,39 +436,51 @@ function ContainersTableHead({ table }: { table: TableType<ContainerRecord> }) {
) )
} }
const ContainerTableRow = memo( const ContainerTableRow = memo(function ContainerTableRow({
function ContainerTableRow({ row,
row, virtualRow,
virtualRow, openSheet,
openSheet, }: {
}: { row: Row<ContainerRecord>
row: Row<ContainerRecord> virtualRow: VirtualItem
virtualRow: VirtualItem openSheet: (container: ContainerRecord) => void
openSheet: (container: ContainerRecord) => void }) {
}) { return (
return ( <TableRow
<TableRow data-state={row.getIsSelected() && "selected"}
data-state={row.getIsSelected() && "selected"} className="cursor-pointer transition-opacity"
className="cursor-pointer transition-opacity" onClick={() => openSheet(row.original)}
onClick={() => openSheet(row.original)} >
> {row.getVisibleCells().map((cell) => (
{row.getVisibleCells().map((cell) => ( <TableCell
<TableCell key={cell.id}
key={cell.id} className="py-0"
className="py-0" style={{
style={{ height: virtualRow.size,
height: virtualRow.size, }}
}} >
> {flexRender(cell.column.columnDef.cell, cell.getContext())}
{flexRender(cell.column.columnDef.cell, cell.getContext())} </TableCell>
</TableCell> ))}
))} </TableRow>
</TableRow> )
) })
}
)
function LogsFullscreenDialog({ open, onOpenChange, logsDisplay, containerName, onRefresh, isRefreshing }: { open: boolean, onOpenChange: (open: boolean) => void, logsDisplay: string, containerName: string, onRefresh: () => void | Promise<void>, isRefreshing: boolean }) { function LogsFullscreenDialog({
open,
onOpenChange,
logsDisplay,
containerName,
onRefresh,
isRefreshing,
}: {
open: boolean
onOpenChange: (open: boolean) => void
logsDisplay: string
containerName: string
onRefresh: () => void | Promise<void>
isRefreshing: boolean
}) {
const outerContainerRef = useRef<HTMLDivElement>(null) const outerContainerRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
@@ -471,16 +513,24 @@ function LogsFullscreenDialog({ open, onOpenChange, logsDisplay, containerName,
title={t`Refresh`} title={t`Refresh`}
aria-label={t`Refresh`} aria-label={t`Refresh`}
> >
<RefreshCwIcon <RefreshCwIcon className={`size-4 transition-transform duration-300 ${isRefreshing ? "animate-spin" : ""}`} />
className={`size-4 transition-transform duration-300 ${isRefreshing ? 'animate-spin' : ''}`}
/>
</button> </button>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) )
} }
function InfoFullscreenDialog({ open, onOpenChange, infoDisplay, containerName }: { open: boolean, onOpenChange: (open: boolean) => void, infoDisplay: string, containerName: string }) { function InfoFullscreenDialog({
open,
onOpenChange,
infoDisplay,
containerName,
}: {
open: boolean
onOpenChange: (open: boolean) => void
infoDisplay: string
containerName: string
}) {
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[calc(100vw-20px)] h-[calc(100dvh-20px)] max-w-none p-0 bg-gh-dark border-0 text-white"> <DialogContent className="w-[calc(100vw-20px)] h-[calc(100dvh-20px)] max-w-none p-0 bg-gh-dark border-0 text-white">

View File

@@ -7,6 +7,7 @@ import {
getFilteredRowModel, getFilteredRowModel,
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
type PaginationState,
type SortingState, type SortingState,
useReactTable, useReactTable,
type VisibilityState, type VisibilityState,
@@ -40,7 +41,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
import { useToast } from "@/components/ui/use-toast" import { useToast } from "@/components/ui/use-toast"
import { alertInfo } from "@/lib/alerts" import { alertInfo } from "@/lib/alerts"
import { pb } from "@/lib/api" import { pb } from "@/lib/api"
import { cn, formatDuration, formatShortDate } from "@/lib/utils" import { cn, formatDuration, formatShortDate, useBrowserStorage } from "@/lib/utils"
import type { AlertsHistoryRecord } from "@/types" import type { AlertsHistoryRecord } from "@/types"
import { alertsHistoryColumns } from "../../alerts-history-columns" import { alertsHistoryColumns } from "../../alerts-history-columns"
@@ -66,6 +67,12 @@ export default function AlertsHistoryDataTable() {
const [globalFilter, setGlobalFilter] = useState("") const [globalFilter, setGlobalFilter] = useState("")
const { toast } = useToast() const { toast } = useToast()
const [deleteOpen, setDeleteDialogOpen] = useState(false) const [deleteOpen, setDeleteDialogOpen] = useState(false)
// Store pagination preference in local storage
const [pagination, setPagination] = useBrowserStorage<PaginationState>("ah-pagination", {
pageIndex: 0,
pageSize: 10,
})
useEffect(() => { useEffect(() => {
let unsubscribe: (() => void) | undefined let unsubscribe: (() => void) | undefined
@@ -136,12 +143,14 @@ export default function AlertsHistoryDataTable() {
onColumnFiltersChange: setColumnFilters, onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility, onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection, onRowSelectionChange: setRowSelection,
onPaginationChange: setPagination,
state: { state: {
sorting, sorting,
columnFilters, columnFilters,
columnVisibility, columnVisibility,
rowSelection, rowSelection,
globalFilter, globalFilter,
pagination,
}, },
onGlobalFilterChange: setGlobalFilter, onGlobalFilterChange: setGlobalFilter,
globalFilterFn: (row, _columnId, filterValue) => { globalFilterFn: (row, _columnId, filterValue) => {
@@ -318,10 +327,10 @@ export default function AlertsHistoryDataTable() {
<Select <Select
value={`${table.getState().pagination.pageSize}`} value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => { onValueChange={(value) => {
table.setPageSize(Number(value)) table.setPageSize(Number(value));
}} }}
> >
<SelectTrigger className="w-[4.8em]" id="rows-per-page"> <SelectTrigger className="w-18" id="rows-per-page">
<SelectValue placeholder={table.getState().pagination.pageSize} /> <SelectValue placeholder={table.getState().pagination.pageSize} />
</SelectTrigger> </SelectTrigger>
<SelectContent side="top"> <SelectContent side="top">

View File

@@ -2,14 +2,17 @@
import { Trans, useLingui } from "@lingui/react/macro" import { Trans, useLingui } from "@lingui/react/macro"
import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from "lucide-react" import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from "lucide-react"
import { useState } from "react" import { useState } from "react"
import { useStore } from "@nanostores/react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Separator } from "@/components/ui/separator" import { Separator } from "@/components/ui/separator"
import Slider from "@/components/ui/slider"
import { HourFormat, Unit } from "@/lib/enums" import { HourFormat, Unit } from "@/lib/enums"
import { dynamicActivate } from "@/lib/i18n" import { dynamicActivate } from "@/lib/i18n"
import languages from "@/lib/languages" import languages from "@/lib/languages"
import { $userSettings } from "@/lib/stores"
import { chartTimeData, currentHour12 } from "@/lib/utils" import { chartTimeData, currentHour12 } from "@/lib/utils"
import type { UserSettings } from "@/types" import type { UserSettings } from "@/types"
import { saveSettings } from "./layout" import { saveSettings } from "./layout"
@@ -17,6 +20,8 @@ import { saveSettings } from "./layout"
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) { export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const { i18n } = useLingui() const { i18n } = useLingui()
const currentUserSettings = useStore($userSettings)
const layoutWidth = currentUserSettings.layoutWidth ?? 1480
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault() e.preventDefault()
@@ -73,6 +78,27 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
</Select> </Select>
</div> </div>
<Separator /> <Separator />
<div className="grid gap-2">
<div className="mb-2">
<h3 className="mb-1 text-lg font-medium">
<Trans>Layout width</Trans>
</h3>
<Label htmlFor="layoutWidth" className="text-sm text-muted-foreground leading-relaxed">
<Trans>Adjust the width of the main layout</Trans> ({layoutWidth}px)
</Label>
</div>
<Slider
id="layoutWidth"
name="layoutWidth"
value={[layoutWidth]}
onValueChange={(val) => $userSettings.setKey("layoutWidth", val[0])}
min={1000}
max={2000}
step={10}
className="w-full mb-1"
/>
</div>
<Separator />
<div className="grid gap-2"> <div className="grid gap-2">
<div className="mb-2"> <div className="mb-2">
<h3 className="mb-1 text-lg font-medium"> <h3 className="mb-1 text-lg font-medium">

View File

@@ -14,6 +14,7 @@ import { toast } from "@/components/ui/use-toast"
import { isAdmin, pb } from "@/lib/api" import { isAdmin, pb } from "@/lib/api"
import type { UserSettings } from "@/types" import type { UserSettings } from "@/types"
import { saveSettings } from "./layout" import { saveSettings } from "./layout"
import { QuietHours } from "./quiet-hours"
interface ShoutrrrUrlCardProps { interface ShoutrrrUrlCardProps {
url: string url: string
@@ -120,19 +121,32 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
</div> </div>
<Separator /> <Separator />
<div className="space-y-3"> <div className="space-y-3">
<div> <div className="grid grid-cols-1 sm:flex items-center justify-between gap-4">
<h3 className="mb-1 text-lg font-medium"> <div>
<Trans>Webhook / Push notifications</Trans> <h3 className="mb-1 text-lg font-medium">
</h3> <Trans>Webhook / Push notifications</Trans>
<p className="text-sm text-muted-foreground leading-relaxed"> </h3>
<Trans> <p className="text-sm text-muted-foreground leading-relaxed">
Beszel uses{" "} <Trans>
<a href="https://beszel.dev/guide/notifications" target="_blank" className="link" rel="noopener"> Beszel uses{" "}
Shoutrrr <a href="https://beszel.dev/guide/notifications" target="_blank" className="link" rel="noopener">
</a>{" "} Shoutrrr
to integrate with popular notification services. </a>{" "}
</Trans> to integrate with popular notification services.
</p> </Trans>
</p>
</div>
<Button
type="button"
variant="outline"
className="h-10 shrink-0"
onClick={addWebhook}
>
<PlusIcon className="size-4" />
<span className="ms-1">
<Trans>Add URL</Trans>
</span>
</Button>
</div> </div>
{webhooks.length > 0 && ( {webhooks.length > 0 && (
<div className="grid gap-2.5" id="webhooks"> <div className="grid gap-2.5" id="webhooks">
@@ -146,16 +160,10 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
))} ))}
</div> </div>
)} )}
<Button </div>
type="button" <Separator />
variant="outline" <div className="space-y-3">
size="sm" <QuietHours />
className="mt-2 flex items-center gap-1"
onClick={addWebhook}
>
<PlusIcon className="h-4 w-4 -ms-0.5" />
<Trans>Add URL</Trans>
</Button>
</div> </div>
<Separator /> <Separator />
<Button <Button
@@ -194,7 +202,7 @@ const ShoutrrrUrlCard = ({ url, onUrlChange, onRemove }: ShoutrrrUrlCardProps) =
} }
return ( return (
<Card className="bg-muted/40 p-2 md:p-3"> <Card className="bg-table-header p-2 md:p-3">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Input <Input
type="url" type="url"

View File

@@ -0,0 +1,530 @@
import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"
import { useStore } from "@nanostores/react"
import {
MoreHorizontalIcon,
PlusIcon,
Trash2Icon,
ServerIcon,
ClockIcon,
CalendarIcon,
ActivityIcon,
PenSquareIcon,
} from "lucide-react"
import { useEffect, useState } from "react"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { useToast } from "@/components/ui/use-toast"
import { pb } from "@/lib/api"
import { $systems } from "@/lib/stores"
import { formatShortDate } from "@/lib/utils"
import type { QuietHoursRecord, SystemRecord } from "@/types"
export function QuietHours() {
const [data, setData] = useState<QuietHoursRecord[]>([])
const [dialogOpen, setDialogOpen] = useState(false)
const [editingRecord, setEditingRecord] = useState<QuietHoursRecord | null>(null)
const { toast } = useToast()
const systems = useStore($systems)
useEffect(() => {
let unsubscribe: (() => void) | undefined
const pbOptions = {
expand: "system",
fields: "id,user,system,type,start,end,expand.system.name",
}
// Initial load
pb.collection<QuietHoursRecord>("quiet_hours")
.getList(0, 200, {
...pbOptions,
sort: "system",
})
.then(({ items }) => setData(items))
// Subscribe to changes
;(async () => {
unsubscribe = await pb.collection("quiet_hours").subscribe(
"*",
(e) => {
if (e.action === "create") {
setData((current) => [e.record as QuietHoursRecord, ...current])
}
if (e.action === "update") {
setData((current) => current.map((r) => (r.id === e.record.id ? (e.record as QuietHoursRecord) : r)))
}
if (e.action === "delete") {
setData((current) => current.filter((r) => r.id !== e.record.id))
}
},
pbOptions
)
})()
// Unsubscribe on unmount
return () => unsubscribe?.()
}, [])
const handleDelete = async (id: string) => {
try {
await pb.collection("quiet_hours").delete(id)
} catch (e: unknown) {
toast({
variant: "destructive",
title: t`Error`,
description: (e as Error).message || "Failed to delete quiet hours.",
})
}
}
const openEditDialog = (record: QuietHoursRecord) => {
setEditingRecord(record)
setDialogOpen(true)
}
const closeDialog = () => {
setDialogOpen(false)
setEditingRecord(null)
}
const formatDateTime = (record: QuietHoursRecord) => {
if (record.type === "daily") {
// For daily windows, show only time
const startTime = new Date(record.start).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })
const endTime = new Date(record.end).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })
return `${startTime} - ${endTime}`
}
// For one-time windows, show full date and time
const start = formatShortDate(record.start)
const end = formatShortDate(record.end)
return `${start} - ${end}`
}
const getWindowState = (record: QuietHoursRecord): "active" | "past" | "inactive" => {
const now = new Date()
if (record.type === "daily") {
// For daily windows, check if current time is within the window
const startDate = new Date(record.start)
const endDate = new Date(record.end)
// Get current time in local timezone
const currentMinutes = now.getHours() * 60 + now.getMinutes()
const startMinutes = startDate.getUTCHours() * 60 + startDate.getUTCMinutes()
const endMinutes = endDate.getUTCHours() * 60 + endDate.getUTCMinutes()
// Convert UTC to local time offset
const offset = now.getTimezoneOffset()
const localStartMinutes = (startMinutes - offset + 1440) % 1440
const localEndMinutes = (endMinutes - offset + 1440) % 1440
// Handle cases where window spans midnight
if (localStartMinutes <= localEndMinutes) {
return currentMinutes >= localStartMinutes && currentMinutes < localEndMinutes ? "active" : "inactive"
} else {
return currentMinutes >= localStartMinutes || currentMinutes < localEndMinutes ? "active" : "inactive"
}
} else {
// For one-time windows
const startDate = new Date(record.start)
const endDate = new Date(record.end)
if (now >= startDate && now < endDate) {
return "active"
} else if (now >= endDate) {
return "past"
} else {
return "inactive"
}
}
}
return (
<>
<div className="grid grid-cols-1 sm:flex items-center justify-between gap-4 mb-3">
<div>
<h3 className="mb-1 text-lg font-medium">
<Trans>Quiet hours</Trans>
</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
<Trans>
Schedule quiet hours where notifications will not be sent, such as during maintenance periods.
</Trans>
</p>
</div>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger asChild>
<Button variant="outline" className="h-10 shrink-0" onClick={() => setEditingRecord(null)}>
<PlusIcon className="size-4" />
<span className="ms-1">
<Trans>Add Quiet Hours</Trans>
</span>
</Button>
</DialogTrigger>
<QuietHoursDialog editingRecord={editingRecord} systems={systems} onClose={closeDialog} toast={toast} />
</Dialog>
</div>
{data.length > 0 && (
<div className="rounded-md border overflow-x-auto whitespace-nowrap">
<Table>
<TableHeader>
<TableRow className="border-border/50">
<TableHead className="px-4">
<span className="flex items-center gap-2">
<ServerIcon className="size-4" />
<Trans>System</Trans>
</span>
</TableHead>
<TableHead className="px-4">
<span className="flex items-center gap-2">
<ClockIcon className="size-4" />
<Trans>Type</Trans>
</span>
</TableHead>
<TableHead className="px-4">
<span className="flex items-center gap-2">
<CalendarIcon className="size-4" />
<Trans>Schedule</Trans>
</span>
</TableHead>
<TableHead className="px-4">
<span className="flex items-center gap-2">
<ActivityIcon className="size-4" />
<Trans>State</Trans>
</span>
</TableHead>
<TableHead className="px-4 text-right sr-only">
<Trans>Actions</Trans>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((record) => (
<TableRow key={record.id}>
<TableCell className="px-4 py-3">
{record.system ? record.expand?.system?.name || record.system : <Trans>All Systems</Trans>}
</TableCell>
<TableCell className="px-4 py-3">
{record.type === "daily" ? <Trans>Daily</Trans> : <Trans>One-time</Trans>}
</TableCell>
<TableCell className="px-4 py-3">{formatDateTime(record)}</TableCell>
<TableCell className="px-4 py-3">
{(() => {
const state = getWindowState(record)
const stateConfig = {
active: { label: <Trans>Active</Trans>, variant: "success" as const },
past: { label: <Trans>Past</Trans>, variant: "danger" as const },
inactive: { label: <Trans>Inactive</Trans>, variant: "default" as const },
}
const config = stateConfig[state]
return <Badge variant={config.variant}>{config.label}</Badge>
})()}
</TableCell>
<TableCell className="px-4 py-3 text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="size-8">
<span className="sr-only">
<Trans>Open menu</Trans>
</span>
<MoreHorizontalIcon className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => openEditDialog(record)}>
<PenSquareIcon className="me-2.5 size-4" />
<Trans>Edit</Trans>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleDelete(record.id)}>
<Trash2Icon className="me-2.5 size-4" />
<Trans>Delete</Trans>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</>
)
}
// Helper function to format Date as datetime-local string (YYYY-MM-DDTHH:mm) in local time
function formatDateTimeLocal(date: Date): string {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, "0")
const day = String(date.getDate()).padStart(2, "0")
const hours = String(date.getHours()).padStart(2, "0")
const minutes = String(date.getMinutes()).padStart(2, "0")
return `${year}-${month}-${day}T${hours}:${minutes}`
}
function QuietHoursDialog({
editingRecord,
systems,
onClose,
toast,
}: {
editingRecord: QuietHoursRecord | null
systems: SystemRecord[]
onClose: () => void
toast: ReturnType<typeof useToast>["toast"]
}) {
const [selectedSystem, setSelectedSystem] = useState(editingRecord?.system || "")
const [isGlobal, setIsGlobal] = useState(!editingRecord?.system)
const [windowType, setWindowType] = useState<"one-time" | "daily">(editingRecord?.type || "one-time")
const [startDateTime, setStartDateTime] = useState("")
const [endDateTime, setEndDateTime] = useState("")
const [startTime, setStartTime] = useState("")
const [endTime, setEndTime] = useState("")
useEffect(() => {
if (editingRecord) {
setSelectedSystem(editingRecord.system || "")
setIsGlobal(!editingRecord.system)
setWindowType(editingRecord.type)
if (editingRecord.type === "daily") {
// Extract time from datetime
const start = new Date(editingRecord.start)
const end = editingRecord.end ? new Date(editingRecord.end) : null
setStartTime(start.toTimeString().slice(0, 5))
setEndTime(end ? end.toTimeString().slice(0, 5) : "")
} else {
// For one-time, format as datetime-local (local time, not UTC)
const startDate = new Date(editingRecord.start)
const endDate = editingRecord.end ? new Date(editingRecord.end) : null
setStartDateTime(formatDateTimeLocal(startDate))
setEndDateTime(endDate ? formatDateTimeLocal(endDate) : "")
}
} else {
// Reset form with default dates: today at 12pm and 1pm
const today = new Date()
const noon = new Date(today)
noon.setHours(12, 0, 0, 0)
const onePm = new Date(today)
onePm.setHours(13, 0, 0, 0)
setSelectedSystem("")
setIsGlobal(true)
setWindowType("one-time")
setStartDateTime(formatDateTimeLocal(noon))
setEndDateTime(formatDateTimeLocal(onePm))
setStartTime("12:00")
setEndTime("13:00")
}
}, [editingRecord])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
try {
let startValue: string
let endValue: string | undefined
if (windowType === "daily") {
// For daily windows, convert local time to UTC
// Create a date with the time in local timezone, then convert to UTC
const startDate = new Date(`2000-01-01T${startTime}:00`)
startValue = startDate.toISOString()
if (endTime) {
const endDate = new Date(`2000-01-01T${endTime}:00`)
endValue = endDate.toISOString()
}
} else {
// For one-time windows, use the datetime values
startValue = new Date(startDateTime).toISOString()
endValue = endDateTime ? new Date(endDateTime).toISOString() : undefined
}
const data = {
user: pb.authStore.record?.id,
system: isGlobal ? undefined : selectedSystem,
type: windowType,
start: startValue,
end: endValue,
}
if (editingRecord) {
await pb.collection("quiet_hours").update(editingRecord.id, data)
toast({
title: t`Updated`,
description: t`Quiet hours have been updated.`,
})
} else {
await pb.collection("quiet_hours").create(data)
toast({
title: t`Created`,
description: t`Quiet hours have been created.`,
})
}
onClose()
} catch (e) {
toast({
variant: "destructive",
title: t`Error`,
description: t`Failed to save quiet hours.`,
})
}
}
return (
<DialogContent>
<DialogHeader>
<DialogTitle>{editingRecord ? <Trans>Edit Quiet Hours</Trans> : <Trans>Add Quiet Hours</Trans>}</DialogTitle>
<DialogDescription>
<Trans>Configure quiet hours where notifications will not be sent.</Trans>
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<Tabs value={isGlobal ? "global" : "system"} onValueChange={(value) => setIsGlobal(value === "global")}>
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="global">
<Trans>All Systems</Trans>
</TabsTrigger>
<TabsTrigger value="system">
<Trans>Specific System</Trans>
</TabsTrigger>
</TabsList>
<TabsContent value="system" className="mt-4 space-y-4">
<div className="grid gap-2">
<Label htmlFor="system">
<Trans>System</Trans>
</Label>
<Select value={selectedSystem} onValueChange={setSelectedSystem}>
<SelectTrigger id="system">
<SelectValue placeholder={t`Select a system`} />
</SelectTrigger>
<SelectContent>
{systems.map((system) => (
<SelectItem key={system.id} value={system.id}>
{system.name}
</SelectItem>
))}
</SelectContent>
</Select>
{/* Hidden input for native form validation */}
<input
className="sr-only"
type="text"
tabIndex={-1}
autoComplete="off"
value={selectedSystem}
onChange={() => {}}
required={!isGlobal}
/>
</div>
</TabsContent>
</Tabs>
<div className="grid gap-2">
<Label htmlFor="type">
<Trans>Type</Trans>
</Label>
<Select value={windowType} onValueChange={(value: "one-time" | "daily") => setWindowType(value)}>
<SelectTrigger id="type">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="one-time">
<Trans>One-time</Trans>
</SelectItem>
<SelectItem value="daily">
<Trans>Daily</Trans>
</SelectItem>
</SelectContent>
</Select>
</div>
{windowType === "one-time" ? (
<>
<div className="grid gap-2">
<Label htmlFor="start-datetime">
<Trans>Start Time</Trans>
</Label>
<Input
id="start-datetime"
type="datetime-local"
value={startDateTime}
onChange={(e) => setStartDateTime(e.target.value)}
min={formatDateTimeLocal(new Date(new Date().setHours(0, 0, 0, 0)))}
required
className="tabular-nums tracking-tighter"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="end-datetime">
<Trans>End Time</Trans>
</Label>
<Input
id="end-datetime"
type="datetime-local"
value={endDateTime}
onChange={(e) => setEndDateTime(e.target.value)}
min={startDateTime || formatDateTimeLocal(new Date())}
required
className="tabular-nums tracking-tighter"
/>
</div>
</>
) : (
<div className="grid gap-2 grid-cols-2">
<div>
<Label htmlFor="start-time">
<Trans>Start Time</Trans>
</Label>
<Input
className="tabular-nums tracking-tighter"
id="start-time"
type="time"
value={startTime}
onChange={(e) => setStartTime(e.target.value)}
required
/>
</div>
<div>
<Label htmlFor="end-time">
<Trans>End Time</Trans>
</Label>
<Input
className="tabular-nums tracking-tighter"
id="end-time"
type="time"
value={endTime}
onChange={(e) => setEndTime(e.target.value)}
required
/>
</div>
</div>
)}
<DialogFooter>
<Button type="button" variant="outline" onClick={onClose}>
<Trans>Cancel</Trans>
</Button>
<Button type="submit">{editingRecord ? <Trans>Update</Trans> : <Trans>Create</Trans>}</Button>
</DialogFooter>
</form>
</DialogContent>
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
/** biome-ignore-all lint/correctness/useHookAtTopLevel: <explanation> */
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { Trans, useLingui } from "@lingui/react/macro" import { Trans, useLingui } from "@lingui/react/macro"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
@@ -16,10 +17,12 @@ import {
PenBoxIcon, PenBoxIcon,
PlayCircleIcon, PlayCircleIcon,
ServerIcon, ServerIcon,
TerminalSquareIcon,
Trash2Icon, Trash2Icon,
WifiIcon, WifiIcon,
} from "lucide-react" } from "lucide-react"
import { memo, useMemo, useRef, useState } from "react" import { memo, useMemo, useRef, useState } from "react"
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"
import { isReadOnlyUser, pb } from "@/lib/api" import { isReadOnlyUser, pb } from "@/lib/api"
import { ConnectionType, connectionTypeLabels, MeterState, SystemStatus } from "@/lib/enums" import { ConnectionType, connectionTypeLabels, MeterState, SystemStatus } from "@/lib/enums"
import { $longestSystemNameLen, $userSettings } from "@/lib/stores" import { $longestSystemNameLen, $userSettings } from "@/lib/stores"
@@ -68,7 +71,7 @@ const STATUS_COLORS = {
* @param viewMode - "table" or "grid" * @param viewMode - "table" or "grid"
* @returns - Column definitions for the systems table * @returns - Column definitions for the systems table
*/ */
export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] { export function SystemsTableColumns(viewMode: "table" | "grid"): ColumnDef<SystemRecord>[] {
return [ return [
{ {
// size: 200, // size: 200,
@@ -133,7 +136,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
header: sortableHeader, header: sortableHeader,
}, },
{ {
accessorFn: ({ info }) => info.cpu, accessorFn: ({ info }) => info.cpu || undefined,
id: "cpu", id: "cpu",
name: () => t`CPU`, name: () => t`CPU`,
cell: TableCellWithMeter, cell: TableCellWithMeter,
@@ -142,7 +145,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
}, },
{ {
// accessorKey: "info.mp", // accessorKey: "info.mp",
accessorFn: ({ info }) => info.mp, accessorFn: ({ info }) => info.mp || undefined,
id: "memory", id: "memory",
name: () => t`Memory`, name: () => t`Memory`,
cell: TableCellWithMeter, cell: TableCellWithMeter,
@@ -150,15 +153,15 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
header: sortableHeader, header: sortableHeader,
}, },
{ {
accessorFn: ({ info }) => info.dp, accessorFn: ({ info }) => info.dp || undefined,
id: "disk", id: "disk",
name: () => t`Disk`, name: () => t`Disk`,
cell: TableCellWithMeter, cell: DiskCellWithMultiple,
Icon: HardDriveIcon, Icon: HardDriveIcon,
header: sortableHeader, header: sortableHeader,
}, },
{ {
accessorFn: ({ info }) => info.g, accessorFn: ({ info }) => info.g || undefined,
id: "gpu", id: "gpu",
name: () => "GPU", name: () => "GPU",
cell: TableCellWithMeter, cell: TableCellWithMeter,
@@ -171,9 +174,9 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
const sum = info.la?.reduce((acc, curr) => acc + curr, 0) const sum = info.la?.reduce((acc, curr) => acc + curr, 0)
// TODO: remove this in future release in favor of la array // TODO: remove this in future release in favor of la array
if (!sum) { if (!sum) {
return (info.l1 ?? 0) + (info.l5 ?? 0) + (info.l15 ?? 0) return (info.l1 ?? 0) + (info.l5 ?? 0) + (info.l15 ?? 0) || undefined
} }
return sum return sum || undefined
}, },
name: () => t({ message: "Load Avg", comment: "Short label for load average" }), name: () => t({ message: "Load Avg", comment: "Short label for load average" }),
size: 0, size: 0,
@@ -216,7 +219,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
}, },
}, },
{ {
accessorFn: ({ info }) => info.bb || (info.b || 0) * 1024 * 1024, accessorFn: ({ info }) => info.bb || (info.b || 0) * 1024 * 1024 || undefined,
id: "net", id: "net",
name: () => t`Net`, name: () => t`Net`,
size: 0, size: 0,
@@ -228,7 +231,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
if (sys.status === SystemStatus.Paused) { if (sys.status === SystemStatus.Paused) {
return null return null
} }
const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false) const { value, unit } = formatBytes((info.getValue() || 0) as number, true, userSettings.unitNet, false)
return ( return (
<span className="tabular-nums whitespace-nowrap"> <span className="tabular-nums whitespace-nowrap">
{decimalString(value, value >= 100 ? 1 : 2)} {unit} {decimalString(value, value >= 100 ? 1 : 2)} {unit}
@@ -258,11 +261,49 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
) )
}, },
}, },
{
accessorFn: ({ info }) => info.sv?.[0],
id: "services",
name: () => t`Services`,
size: 50,
Icon: TerminalSquareIcon,
header: sortableHeader,
hideSort: true,
sortingFn: (a, b) => {
// sort priorities: 1) failed services, 2) total services
const [totalCountA, numFailedA] = a.original.info.sv ?? [0, 0]
const [totalCountB, numFailedB] = b.original.info.sv ?? [0, 0]
if (numFailedA !== numFailedB) {
return numFailedA - numFailedB
}
return totalCountA - totalCountB
},
cell(info) {
const sys = info.row.original
const [totalCount, numFailed] = sys.info.sv ?? [0, 0]
if (sys.status !== SystemStatus.Up || totalCount === 0) {
return null
}
return (
<span className="tabular-nums whitespace-nowrap flex gap-1.5 items-center">
<span
className={cn("block size-2 rounded-full", {
[STATUS_COLORS[SystemStatus.Down]]: numFailed > 0,
[STATUS_COLORS[SystemStatus.Up]]: numFailed === 0,
})}
/>
{totalCount}{" "}
<span className="text-muted-foreground text-sm -ms-0.5">
({t`Failed`.toLowerCase()}: {numFailed})
</span>
</span>
)
},
},
{ {
accessorFn: ({ info }) => info.v, accessorFn: ({ info }) => info.v,
id: "agent", id: "agent",
name: () => t`Agent`, name: () => t`Agent`,
// invertSorting: true,
size: 50, size: 50,
Icon: WifiIcon, Icon: WifiIcon,
hideSort: true, hideSort: true,
@@ -354,6 +395,94 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
) )
} }
function DiskCellWithMultiple(info: CellContext<SystemRecord, unknown>) {
const { info: sysInfo, status, id } = info.row.original
const extraFs = Object.entries(sysInfo.efs ?? {})
if (extraFs.length === 0) {
return TableCellWithMeter(info)
}
const rootDiskPct = sysInfo.dp
// sort extra disks by percentage descending
extraFs.sort((a, b) => b[1] - a[1])
function getIndicatorColor(pct: number) {
const threshold = getMeterState(pct)
return (
(status !== SystemStatus.Up && STATUS_COLORS.paused) ||
(threshold === MeterState.Good && STATUS_COLORS.up) ||
(threshold === MeterState.Warn && STATUS_COLORS.pending) ||
STATUS_COLORS.down
)
}
function getMeterClass(pct: number) {
return cn("h-full", getIndicatorColor(pct))
}
return (
<Tooltip>
<TooltipTrigger asChild>
<Link
href={getPagePath($router, "system", { id })}
tabIndex={-1}
className="flex flex-col gap-0.5 w-full relative z-10"
>
<div className="flex gap-2 items-center tabular-nums tracking-tight">
<span className="min-w-8 shrink-0">{decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}%</span>
<span className="flex-1 min-w-8 flex items-center gap-0.5 px-1 justify-end bg-muted h-[1em] rounded-sm overflow-hidden relative">
{/* Root disk */}
<span
className={cn("absolute inset-0", getMeterClass(rootDiskPct))}
style={{ width: `${rootDiskPct}%` }}
></span>
{/* Extra disk indicators */}
{extraFs.map(([name, pct]) => (
<span
key={name}
className={cn("size-1.5 rounded-full shrink-0 outline-[0.5px] outline-muted", getIndicatorColor(pct))}
/>
))}
</span>
</div>
</Link>
</TooltipTrigger>
<TooltipContent side="right" className="max-w-xs pb-2">
<div className="grid gap-1">
<div className="grid gap-0.5">
<div className="text-[0.65rem] text-muted-foreground uppercase tracking-wide tabular-nums">
<Trans context="Root disk label">Root</Trans>
</div>
<div className="flex gap-2 items-center tabular-nums text-xs">
<span className="min-w-7">{decimalString(rootDiskPct, rootDiskPct >= 10 ? 1 : 2)}%</span>
<span className="flex-1 min-w-12 grid bg-muted h-2.5 rounded-sm overflow-hidden">
<span className={getMeterClass(rootDiskPct)} style={{ width: `${rootDiskPct}%` }}></span>
</span>
</div>
</div>
{extraFs.map(([name, pct]) => {
return (
<div key={name} className="grid gap-0.5">
<div className="text-[0.65rem] max-w-40 text-muted-foreground uppercase tracking-wide truncate">
{name}
</div>
<div className="flex gap-2 items-center tabular-nums text-xs">
<span className="min-w-7">{decimalString(pct, pct >= 10 ? 1 : 2)}%</span>
<span className="flex-1 min-w-12 grid bg-muted h-2.5 rounded-sm overflow-hidden">
<span className={getMeterClass(pct)} style={{ width: `${pct}%` }}></span>
</span>
</div>
</div>
)
})}
</div>
</TooltipContent>
</Tooltip>
)
}
export function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) { export function IndicatorDot({ system, className }: { system: SystemRecord; className?: ClassValue }) {
className ||= STATUS_COLORS[system.status as keyof typeof STATUS_COLORS] || "" className ||= STATUS_COLORS[system.status as keyof typeof STATUS_COLORS] || ""
return ( return (

View File

@@ -24,6 +24,7 @@ import {
LayoutGridIcon, LayoutGridIcon,
LayoutListIcon, LayoutListIcon,
Settings2Icon, Settings2Icon,
XIcon,
} from "lucide-react" } from "lucide-react"
import { memo, useEffect, useMemo, useRef, useState } from "react" import { memo, useEffect, useMemo, useRef, useState } from "react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
@@ -47,7 +48,7 @@ import type { SystemRecord } from "@/types"
import AlertButton from "../alerts/alert-button" import AlertButton from "../alerts/alert-button"
import { $router, Link } from "../router" import { $router, Link } from "../router"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns" import { SystemsTableColumns, ActionsButton, IndicatorDot } from "./systems-table-columns"
type ViewMode = "table" | "grid" type ViewMode = "table" | "grid"
type StatusFilter = "all" | SystemRecord["status"] type StatusFilter = "all" | SystemRecord["status"]
@@ -60,7 +61,7 @@ export default function SystemsTable() {
const upSystems = $upSystems.get() const upSystems = $upSystems.get()
const pausedSystems = $pausedSystems.get() const pausedSystems = $pausedSystems.get()
const { i18n, t } = useLingui() const { i18n, t } = useLingui()
const [filter, setFilter] = useState<string>() const [filter, setFilter] = useState<string>("")
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all") const [statusFilter, setStatusFilter] = useState<StatusFilter>("all")
const [sorting, setSorting] = useBrowserStorage<SortingState>( const [sorting, setSorting] = useBrowserStorage<SortingState>(
"sortMode", "sortMode",
@@ -145,7 +146,26 @@ export default function SystemsTable() {
</div> </div>
<div className="flex gap-2 ms-auto w-full md:w-80"> <div className="flex gap-2 ms-auto w-full md:w-80">
<Input placeholder={t`Filter...`} onChange={(e) => setFilter(e.target.value)} className="px-4" /> <div className="relative flex-1">
<Input
placeholder={t`Filter...`}
onChange={(e) => setFilter(e.target.value)}
value={filter}
className="ps-4 pe-10 w-full"
/>
{filter && (
<Button
type="button"
variant="ghost"
size="icon"
aria-label="Clear filter"
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7 text-muted-foreground"
onClick={() => setFilter("")}
>
<XIcon className="h-4 w-4" />
</Button>
)}
</div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline"> <Button variant="outline">
@@ -278,6 +298,7 @@ export default function SystemsTable() {
upSystemsLength, upSystemsLength,
downSystemsLength, downSystemsLength,
pausedSystemsLength, pausedSystemsLength,
filter,
]) ])
return ( return (

View File

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

View File

@@ -32,6 +32,9 @@
--chart-4: hsl(280 65% 60%); --chart-4: hsl(280 65% 60%);
--chart-5: hsl(340 75% 55%); --chart-5: hsl(340 75% 55%);
--table-header: hsl(225, 6%, 97%); --table-header: hsl(225, 6%, 97%);
--chart-saturation: 65%;
--chart-lightness: 50%;
--container: 1480px;
} }
.dark { .dark {
@@ -51,11 +54,13 @@
--accent: hsl(220 5% 15.5%); --accent: hsl(220 5% 15.5%);
--accent-foreground: hsl(220 2% 98%); --accent-foreground: hsl(220 2% 98%);
--destructive: hsl(0 62% 46%); --destructive: hsl(0 62% 46%);
--border: hsl(220 3% 16%); --border: hsl(220 3% 17%);
--input: hsl(220 4% 22%); --input: hsl(220 4% 22%);
--ring: hsl(220 4% 80%); --ring: hsl(220 4% 80%);
--table-header: hsl(220, 6%, 13%); --table-header: hsl(220, 6%, 13%);
--radius: 0.8rem; --radius: 0.8rem;
--chart-saturation: 60%;
--chart-lightness: 55%;
} }
@theme inline { @theme inline {
@@ -137,6 +142,7 @@
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-variant-ligatures: no-contextual;
} }
button { button {
@@ -145,7 +151,8 @@
} }
@utility container { @utility container {
@apply max-w-370 mx-auto px-4; max-width: var(--container);
@apply mx-auto px-4;
} }
@utility link { @utility link {
@@ -168,4 +175,4 @@
.recharts-yAxis { .recharts-yAxis {
@apply tabular-nums; @apply tabular-nums;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ar\n" "Language: ar\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-20 21:37\n" "PO-Revision-Date: 2025-10-30 21:52\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Arabic\n" "Language-Team: Arabic\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n" "Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# يوم} other {# أيام}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# ساعة} other {# ساعات}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# دقيقة} few {# دقائق} many {# دقيقة} other {# دقيقة}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "تم تحديد {0} من {1} صف" msgstr "تم تحديد {0} من {1} صف"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} يوم} other {{countString} أيام}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} ساعة} other {{countString} ساعات}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} دقيقة} few {{countString} دقائق} many {{countString} دقيقة} other {{countString} دقيقة}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 ساعة" msgstr "1 ساعة"
@@ -93,6 +90,10 @@ msgstr "نشط"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "التنبيهات النشطة" msgstr "التنبيهات النشطة"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "الحالة النشطة"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "إضافة <0>نظام</0>" msgstr "إضافة <0>نظام</0>"
@@ -113,11 +114,19 @@ msgstr "إضافة رابط"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "تعديل خيارات العرض للرسوم البيانية." msgstr "تعديل خيارات العرض للرسوم البيانية."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "تعديل عرض التخطيط الرئيسي"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "مسؤول" msgstr "مسؤول"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "بعد"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "وكيل" msgstr "وكيل"
@@ -203,6 +212,18 @@ msgstr "عرض النطاق الترددي"
msgid "Battery" msgid "Battery"
msgstr "البطارية" msgstr "البطارية"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "أصبح نشطًا"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "أصبح غير نشط"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "قبل"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "يدعم بيزيل بروتوكول OpenID Connect والعديد من مزوّدي المصادقة عبر بروتوكول OAuth2." msgstr "يدعم بيزيل بروتوكول OpenID Connect والعديد من مزوّدي المصادقة عبر بروتوكول OAuth2."
@@ -220,6 +241,10 @@ msgstr "ثنائي"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "بت (كيلوبت/ثانية، ميجابت/ثانية، جيجابت/ثانية)" msgstr "بت (كيلوبت/ثانية، ميجابت/ثانية، جيجابت/ثانية)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "حالة التمهيد"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "بايت (كيلوبايت/ثانية، ميجابايت/ثانية، ج
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "ذاكرة التخزين المؤقت / المخازن المؤقتة" msgstr "ذاكرة التخزين المؤقت / المخازن المؤقتة"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "يمكن إعادة التحميل"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "يمكن البدء"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "يمكن الإيقاف"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "إلغاء" msgstr "إلغاء"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "القدرات"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "السعة" msgstr "السعة"
@@ -309,6 +350,10 @@ msgstr "هيئ التنبيهات الواردة"
msgid "Confirm password" msgid "Confirm password"
msgstr "تأكيد كلمة المرور" msgstr "تأكيد كلمة المرور"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "التعارضات"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "الاتصال مقطوع" msgstr "الاتصال مقطوع"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "نسخ YAML" msgstr "نسخ YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "المعالج" msgstr "المعالج"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "نوى المعالج"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "ذروة المعالج"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "وقت المعالج"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "تفصيل وقت المعالج"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "استخدام وحدة المعالجة المركزية" msgstr "استخدام وحدة المعالجة المركزية"
@@ -427,6 +490,10 @@ msgstr "حذف"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "حذف البصمة" msgstr "حذف البصمة"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "الوصف"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "التفاصيل" msgstr "التفاصيل"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "إدخال/إخراج الشبكة للدوكر" msgstr "إدخال/إخراج الشبكة للدوكر"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "التوثيق" msgstr "التوثيق"
@@ -535,6 +603,7 @@ msgstr "أدخل كلمة المرور لمرة واحدة الخاصة بك."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "خطأ" msgstr "خطأ"
@@ -545,10 +614,18 @@ msgstr "خطأ"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "يتجاوز {0}{1} في آخر {2, plural, one {# دقيقة} other {# دقائق}}" msgstr "يتجاوز {0}{1} في آخر {2, plural, one {# دقيقة} other {# دقائق}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "معرف العملية الرئيسي للتنفيذ"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "سيتم حذف الأنظمة الحالية غير المعرفة في <0>config.yml</0>. يرجى عمل نسخ احتياطية بانتظام." msgstr "سيتم حذف الأنظمة الحالية غير المعرفة في <0>config.yml</0>. يرجى عمل نسخ احتياطية بانتظام."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "خرج نشطًا"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "تصدير" msgstr "تصدير"
@@ -565,6 +642,10 @@ msgstr "تصدير تكوين الأنظمة الحالية الخاصة بك."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "فهرنهايت (°ف)" msgstr "فهرنهايت (°ف)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "فشل"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "السمات الفاشلة:" msgstr "السمات الفاشلة:"
@@ -586,10 +667,16 @@ msgstr "فشل في إرسال إشعار الاختبار"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "فشل في تحديث التنبيه" msgstr "فشل في تحديث التنبيه"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "فشل: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "تصفية..." msgstr "تصفية..."
@@ -635,6 +722,10 @@ msgstr "محركات GPU"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "استهلاك طاقة وحدة معالجة الرسوميات" msgstr "استهلاك طاقة وحدة معالجة الرسوميات"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "استخدام وحدة معالجة الرسوميات"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "شبكة" msgstr "شبكة"
@@ -684,6 +775,19 @@ msgstr "اللغة"
msgid "Layout" msgid "Layout"
msgstr "التخطيط" msgstr "التخطيط"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "عرض التخطيط"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "دورة الحياة"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "الحد"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "متوسط التحميل" msgstr "متوسط التحميل"
@@ -705,6 +809,14 @@ msgstr "متوسط التحميل 5 دقائق"
msgid "Load Avg" msgid "Load Avg"
msgstr "متوسط التحميل" msgstr "متوسط التحميل"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "حالة التحميل"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "جاري التحميل..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "تسجيل الخروج" msgstr "تسجيل الخروج"
@@ -728,6 +840,10 @@ msgstr "السجلات"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "هل تبحث عن مكان لإنشاء التنبيهات؟ انقر على أيقونات الجرس <0/> في جدول الأنظمة." msgstr "هل تبحث عن مكان لإنشاء التنبيهات؟ انقر على أيقونات الجرس <0/> في جدول الأنظمة."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "معرف العملية الرئيسي"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "إدارة تفضيلات العرض والإشعارات." msgstr "إدارة تفضيلات العرض والإشعارات."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "الحد الأقصى دقيقة" msgstr "الحد الأقصى دقيقة"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "الذاكرة" msgstr "الذاكرة"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "حد الذاكرة"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "ذروة الذاكرة"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "الموديل"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "الاسم" msgstr "الاسم"
@@ -787,7 +916,14 @@ msgstr "حركة مرور الشبكة للواجهات العامة"
msgid "Network unit" msgid "Network unit"
msgstr "وحدة الشبكة" msgstr "وحدة الشبكة"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "لا"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "لم يتم العثور على نتائج." msgstr "لم يتم العثور على نتائج."
@@ -796,6 +932,7 @@ msgstr "لم يتم العثور على نتائج."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "لا توجد نتائج." msgstr "لا توجد نتائج."
@@ -836,6 +973,10 @@ msgstr "فتح القائمة"
msgid "Or continue with" msgid "Or continue with"
msgstr "أو المتابعة باستخدام" msgstr "أو المتابعة باستخدام"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "أخرى"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "الكتابة فوق التنبيهات الحالية" msgstr "الكتابة فوق التنبيهات الحالية"
@@ -884,6 +1025,15 @@ msgstr "متوقف مؤقتا"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "متوقف مؤقتا ({pausedSystemsLength})" msgstr "متوقف مؤقتا ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "متوسط الاستخدام لكل نواة"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "النسبة المئوية للوقت المقضي في كل حالة"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "يرجى <0>تكوين خادم SMTP</0> لضمان تسليم التنبيهات." msgstr "يرجى <0>تكوين خادم SMTP</0> لضمان تسليم التنبيهات."
@@ -935,6 +1085,10 @@ msgstr "الاستخدام الدقيق في الوقت المسجل"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "اللغة المفضلة" msgstr "اللغة المفضلة"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "تم بدء العملية"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "تم الاستلام"
msgid "Refresh" msgid "Refresh"
msgstr "تحديث" msgstr "تحديث"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "العلاقات"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "طلب كلمة مرور لمرة واحدة" msgstr "طلب كلمة مرور لمرة واحدة"
@@ -963,6 +1121,14 @@ msgstr "طلب كلمة مرور لمرة واحدة"
msgid "Request OTP" msgid "Request OTP"
msgstr "طلب OTP" msgstr "طلب OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "مطلوب من قبل"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "يتطلب"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "إعادة تعيين كلمة المرور" msgstr "إعادة تعيين كلمة المرور"
@@ -973,10 +1139,19 @@ msgstr "إعادة تعيين كلمة المرور"
msgid "Resolved" msgid "Resolved"
msgstr "تم حلها" msgstr "تم حلها"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "إعادة التشغيل"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "استئناف" msgstr "استئناف"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "الجذر"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "تدوير الرمز المميز" msgstr "تدوير الرمز المميز"
@@ -985,6 +1160,10 @@ msgstr "تدوير الرمز المميز"
msgid "Rows per page" msgid "Rows per page"
msgstr "صفوف لكل صفحة" msgstr "صفوف لكل صفحة"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "مقاييس وقت التشغيل"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "تفاصيل S.M.A.R.T." msgstr "تفاصيل S.M.A.R.T."
@@ -1026,6 +1205,14 @@ msgstr "تم الإرسال"
msgid "Serial Number" msgid "Serial Number"
msgstr "الرقم التسلسلي" msgstr "الرقم التسلسلي"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "تفاصيل الخدمة"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "الخدمات"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "تعيين عتبات النسبة المئوية لألوان العداد." msgstr "تعيين عتبات النسبة المئوية لألوان العداد."
@@ -1055,16 +1242,22 @@ msgstr "الترتيب حسب"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "الحالة" msgstr "الحالة"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "الحالة" msgstr "الحالة"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "الحالة الفرعية"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "مساحة التبديل المستخدمة من قبل النظام" msgstr "مساحة التبديل المستخدمة من قبل النظام"
@@ -1085,6 +1278,10 @@ msgstr "النظام"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "متوسط تحميل النظام مع مرور الوقت" msgstr "متوسط تحميل النظام مع مرور الوقت"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "خدمات systemd"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "الأنظمة" msgstr "الأنظمة"
@@ -1097,6 +1294,10 @@ msgstr "يمكن إدارة الأنظمة في ملف <0>config.yml</0> داخ
msgid "Table" msgid "Table"
msgstr "جدول" msgstr "جدول"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "المهام"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "تسمح الرموز المميزة للوكلاء بالاتصال و
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور." msgstr "تُستخدم الرموز المميزة والبصمات للمصادقة على اتصالات WebSocket إلى المحور."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "الإجمالي"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "إجمالي البيانات المستلمة لكل واجهة" msgstr "إجمالي البيانات المستلمة لكل واجهة"
@@ -1188,6 +1394,19 @@ msgstr "إجمالي البيانات المستلمة لكل واجهة"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "إجمالي البيانات المرسلة لكل واجهة" msgstr "إجمالي البيانات المرسلة لكل واجهة"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "الإجمالي: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "تم التفعيل بواسطة"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "المحفزات"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة دقيقة واحدة عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز متوسط التحميل لمدة دقيقة واحدة عتبة معينة"
@@ -1212,6 +1431,10 @@ msgstr "يتم التفعيل عندما يتجاوز الجمع بين الصع
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز استخدام وحدة المعالجة المركزية عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز استخدام وحدة المعالجة المركزية عتبة معينة"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز استخدام وحدة معالجة الرسوميات عتبة معينة"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "يتم التفعيل عندما يتجاوز استخدام الذاكرة عتبة معينة" msgstr "يتم التفعيل عندما يتجاوز استخدام الذاكرة عتبة معينة"
@@ -1228,6 +1451,10 @@ msgstr "يتم التفعيل عندما يتجاوز استخدام أي قرص
msgid "Type" msgid "Type"
msgstr "النوع" msgstr "النوع"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "ملف الوحدة"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "رمز مميز عالمي"
msgid "Unknown" msgid "Unknown"
msgstr "غير معروفة" msgstr "غير معروفة"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "غير محدود"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "قيد التشغيل ({upSystemsLength})" msgstr "قيد التشغيل ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "تم التحديث" msgstr "تم التحديث"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "يتم التحديث كل 10 دقائق."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "رفع" msgstr "رفع"
@@ -1269,6 +1506,7 @@ msgstr "مدة التشغيل"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "الاستخدام" msgstr "الاستخدام"
@@ -1294,6 +1532,7 @@ msgstr "القيمة"
msgid "View" msgid "View"
msgstr "عرض" msgstr "عرض"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "عرض المزيد" msgstr "عرض المزيد"
@@ -1314,6 +1553,10 @@ msgstr "في انتظار وجود سجلات كافية للعرض"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "هل تريد مساعدتنا في تحسين ترجماتنا؟ تحقق من <0>Crowdin</0> لمزيد من التفاصيل." msgstr "هل تريد مساعدتنا في تحسين ترجماتنا؟ تحقق من <0>Crowdin</0> لمزيد من التفاصيل."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "يريد"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "تحذير (%)" msgstr "تحذير (%)"
@@ -1350,6 +1593,12 @@ msgstr "تكوين YAML"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "تكوين YAML" msgstr "تكوين YAML"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "نعم"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "تم تحديث إعدادات المستخدم الخاصة بك." msgstr "تم تحديث إعدادات المستخدم الخاصة بك."

View File

@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# ден} other {# дни}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# час} other {# часа}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# минута} few {# минути} many {# минути} other {# минути}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} от {1} селектирани." msgstr "{0} от {1} селектирани."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} ден} other {{countString} дни}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} час} other {{countString} часа}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} минута} few {{countString} минути} many {{countString} минути} other {{countString} минути}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 час" msgstr "1 час"
@@ -93,6 +90,10 @@ msgstr "Активен"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Активни тревоги" msgstr "Активни тревоги"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Активно състояние"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Добави <0>Система</0>" msgstr "Добави <0>Система</0>"
@@ -113,11 +114,19 @@ msgstr "Добави URL"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Настрой опциите за показване на диаграмите." msgstr "Настрой опциите за показване на диаграмите."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Настройка ширината на основния макет"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Администратор" msgstr "Администратор"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "След"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Агент" msgstr "Агент"
@@ -203,6 +212,18 @@ msgstr "Bandwidth на мрежата"
msgid "Battery" msgid "Battery"
msgstr "Батерия" msgstr "Батерия"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Стана активен"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Стана неактивен"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Преди"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel поддържа OpenID Connect и много други OAuth2 доставчици за удостоверяване." msgstr "Beszel поддържа OpenID Connect и много други OAuth2 доставчици за удостоверяване."
@@ -220,6 +241,10 @@ msgstr "Двоичен код"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Бита (Kbps, Mbps, Gbps)" msgstr "Бита (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Състояние при зареждане"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Байта (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Кеш / Буфери" msgstr "Кеш / Буфери"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Може да се презареди"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Може да се стартира"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Може да се спре"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Откажи" msgstr "Откажи"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "Възможности"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Капацитет" msgstr "Капацитет"
@@ -309,6 +350,10 @@ msgstr "Настрой как получаваш нотификации за т
msgid "Confirm password" msgid "Confirm password"
msgstr "Потвърди парола" msgstr "Потвърди парола"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Конфликти"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "Връзката е прекъсната" msgstr "Връзката е прекъсната"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "Копирай YAML" msgstr "Копирай YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "Процесор" msgstr "Процесор"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU ядра"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "Пик на CPU"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "Време на CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Разбивка на времето на CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Употреба на процесор" msgstr "Употреба на процесор"
@@ -427,6 +490,10 @@ msgstr "Изтрий"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Изтрий пръстов отпечатък" msgstr "Изтрий пръстов отпечатък"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Описание"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Подробности" msgstr "Подробности"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "Мрежов I/O използван от docker" msgstr "Мрежов I/O използван от docker"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Документация" msgstr "Документация"
@@ -535,6 +603,7 @@ msgstr "Въведете Вашата еднократна парола."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Грешка" msgstr "Грешка"
@@ -545,10 +614,18 @@ msgstr "Грешка"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Надвишава {0}{1} в последните {2, plural, one {# минута} other {# минути}}" msgstr "Надвишава {0}{1} в последните {2, plural, one {# минута} other {# минути}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr ""
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Съществуващи системи които не са дефинирани в <0>config.yml</0> ще бъдат изтрити. Моля прави чести архиви." msgstr "Съществуващи системи които не са дефинирани в <0>config.yml</0> ще бъдат изтрити. Моля прави чести архиви."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Излезе активно"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Експортиране" msgstr "Експортиране"
@@ -565,6 +642,10 @@ msgstr "Експортирай конфигурацията на системи
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Фаренхайт (°F)" msgstr "Фаренхайт (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Неуспешно"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Неуспешни атрибути:" msgstr "Неуспешни атрибути:"
@@ -586,10 +667,16 @@ msgstr "Неуспешно изпрати тестова нотификация"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Неуспешно обнови тревога" msgstr "Неуспешно обнови тревога"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Неуспешни: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Филтрирай..." msgstr "Филтрирай..."
@@ -635,6 +722,10 @@ msgstr "GPU двигатели"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Консумация на ток от графична карта" msgstr "Консумация на ток от графична карта"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "Употреба на GPU"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Мрежово" msgstr "Мрежово"
@@ -684,6 +775,19 @@ msgstr "Език"
msgid "Layout" msgid "Layout"
msgstr "Подреждане" msgstr "Подреждане"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Ширина на макета"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Жизнен цикъл"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "лимит"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Средно натоварване" msgstr "Средно натоварване"
@@ -705,6 +809,14 @@ msgstr "Средно натоварване 5 минути"
msgid "Load Avg" msgid "Load Avg"
msgstr "Средно натоварване" msgstr "Средно натоварване"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Състояние на зареждане"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Зареждане..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Изход" msgstr "Изход"
@@ -728,6 +840,10 @@ msgstr "Логове"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Търсиш къде да създадеш тревоги? Натисни емотиконата за звънец <0/> в таблицата за системи." msgstr "Търсиш къде да създадеш тревоги? Натисни емотиконата за звънец <0/> в таблицата за системи."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr ""
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Управление на предпочитанията за показване и уведомяване." msgstr "Управление на предпочитанията за показване и уведомяване."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "Максимум 1 минута" msgstr "Максимум 1 минута"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Памет" msgstr "Памет"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Лимит на памет"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Пик на памет"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "Модел"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Име" msgstr "Име"
@@ -787,7 +916,14 @@ msgstr "Мрежов трафик на публични интерфейси"
msgid "Network unit" msgid "Network unit"
msgstr "Единица за измерване на скорост" msgstr "Единица за измерване на скорост"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "Не"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "Няма намерени резултати." msgstr "Няма намерени резултати."
@@ -796,6 +932,7 @@ msgstr "Няма намерени резултати."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Няма резултати." msgstr "Няма резултати."
@@ -836,6 +973,10 @@ msgstr "Отвори менюто"
msgid "Or continue with" msgid "Or continue with"
msgstr "Или продължи с" msgstr "Или продължи с"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Други"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Презапиши съществуващи тревоги" msgstr "Презапиши съществуващи тревоги"
@@ -884,6 +1025,15 @@ msgstr "На пауза"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "На пауза ({pausedSystemsLength})" msgstr "На пауза ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Средно използване на ядро"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Процент време, прекарано във всяко състояние"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Моля <0>конфигурурай SMTP сървър</0> за да се подсигуриш, че тревогите са доставени." msgstr "Моля <0>конфигурурай SMTP сървър</0> за да се подсигуриш, че тревогите са доставени."
@@ -935,6 +1085,10 @@ msgstr "Точно използване в записаното време"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Предпочитан език" msgstr "Предпочитан език"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Процесът стартира"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "Получени"
msgid "Refresh" msgid "Refresh"
msgstr "Опресни" msgstr "Опресни"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Връзки"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Заявка за еднократна парола" msgstr "Заявка за еднократна парола"
@@ -963,6 +1121,14 @@ msgstr "Заявка за еднократна парола"
msgid "Request OTP" msgid "Request OTP"
msgstr "Заявка OTP" msgstr "Заявка OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Изисква се от"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Изисква"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Нулиране на парола" msgstr "Нулиране на парола"
@@ -973,10 +1139,19 @@ msgstr "Нулиране на парола"
msgid "Resolved" msgid "Resolved"
msgstr "Решен" msgstr "Решен"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Рестартирания"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Възобнови" msgstr "Възобнови"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Корен"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Пресъздаване на идентификатора" msgstr "Пресъздаване на идентификатора"
@@ -985,6 +1160,10 @@ msgstr "Пресъздаване на идентификатора"
msgid "Rows per page" msgid "Rows per page"
msgstr "Редове на страница" msgstr "Редове на страница"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Метрики на изпълнение"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Детайли" msgstr "S.M.A.R.T. Детайли"
@@ -1026,6 +1205,14 @@ msgstr "Изпратени"
msgid "Serial Number" msgid "Serial Number"
msgstr "Сериен номер" msgstr "Сериен номер"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Детайли на услугата"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Услуги"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Задайте процентни прагове за цветовете на измервателните уреди." msgstr "Задайте процентни прагове за цветовете на измервателните уреди."
@@ -1055,16 +1242,22 @@ msgstr "Сортиране по"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "Състояние" msgstr "Състояние"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Статус" msgstr "Статус"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Подсъстояние"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Изполван swap от системата" msgstr "Изполван swap от системата"
@@ -1085,6 +1278,10 @@ msgstr "Система"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Средно натоварване на системата във времето" msgstr "Средно натоварване на системата във времето"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Услуги на systemd"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Системи" msgstr "Системи"
@@ -1097,6 +1294,10 @@ msgstr "Системите могат да бъдат управлявани в
msgid "Table" msgid "Table"
msgstr "Таблица" msgstr "Таблица"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Задачи"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "Токените позволяват на агентите да се с
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Токените и пръстовите отпечатъци се използват за удостоверяване на WebSocket връзките към концентратора." msgstr "Токените и пръстовите отпечатъци се използват за удостоверяване на WebSocket връзките към концентратора."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Общо"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Общо получени данни за всеки интерфейс" msgstr "Общо получени данни за всеки интерфейс"
@@ -1188,6 +1394,19 @@ msgstr "Общо получени данни за всеки интерфейс"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Общо изпратени данни за всеки интерфейс" msgstr "Общо изпратени данни за всеки интерфейс"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Общо: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Активиран от"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Активатори"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Задейства се, когато употребата на паметта за 1 минута надвиши зададен праг" msgstr "Задейства се, когато употребата на паметта за 1 минута надвиши зададен праг"
@@ -1212,6 +1431,10 @@ msgstr "Задейства се, когато комбинираното кач
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Задейства се, когато употребата на процесора надвиши зададен праг" msgstr "Задейства се, когато употребата на процесора надвиши зададен праг"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr ""
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Задейства се, когато употребата на паметта надвиши зададен праг" msgstr "Задейства се, когато употребата на паметта надвиши зададен праг"
@@ -1228,6 +1451,10 @@ msgstr "Задейства се, когато употребата на няко
msgid "Type" msgid "Type"
msgstr "Тип" msgstr "Тип"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "Файл на единица"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Универсален тоукън"
msgid "Unknown" msgid "Unknown"
msgstr "Неизвестна" msgstr "Неизвестна"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Неограничено"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Нагоре ({upSystemsLength})" msgstr "Нагоре ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Актуализирано" msgstr "Актуализирано"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Актуализира се на всеки 10 минути."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Качване" msgstr "Качване"
@@ -1269,6 +1506,7 @@ msgstr "Време на работа"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Употреба" msgstr "Употреба"
@@ -1294,6 +1532,7 @@ msgstr "Стойност"
msgid "View" msgid "View"
msgstr "Изглед" msgstr "Изглед"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Виж повече" msgstr "Виж повече"
@@ -1314,6 +1553,10 @@ msgstr "Изчаква се за достатъчно записи за пока
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Искаш да помогнеш да направиш преводите още по-добри? Провери нашия <0>Crowdin</0> за повече детайли." msgstr "Искаш да помогнеш да направиш преводите още по-добри? Провери нашия <0>Crowdin</0> за повече детайли."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Иска"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Предупреждение (%)" msgstr "Предупреждение (%)"
@@ -1350,6 +1593,12 @@ msgstr "YAML конфигурация"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML конфигурация" msgstr "YAML конфигурация"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Да"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Настройките за потребителя ти са обновени." msgstr "Настройките за потребителя ти са обновени."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: cs\n" "Language: cs\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-20 21:37\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Czech\n" "Language-Team: Czech\n"
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n" "Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# den} few {# dny} other {# dní}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# Hodina} few {# Hodiny} many {# Hodin} other {# Hodin}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuta} few {# minuty} many {# minut} other {# minut}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} z {1} vybraných řádků." msgstr "{0} z {1} vybraných řádků."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} den} few {{countString} dny} other {{countString} dní}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} Hodina} few {{countString} Hodiny} many {{countString} Hodin} other {{countString} Hodin}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuta} few {{countString} minuty} many {{countString} minut} other {{countString} minut}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 hodina" msgstr "1 hodina"
@@ -46,7 +43,7 @@ msgstr "1 hodina"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "1 min" msgid "1 min"
msgstr "1 min" msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 minute" msgid "1 minute"
@@ -63,7 +60,7 @@ msgstr "12 hodin"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "15 min" msgid "15 min"
msgstr "15 min" msgstr ""
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "24 hours" msgid "24 hours"
@@ -76,7 +73,7 @@ msgstr "30 dní"
#. Load average #. Load average
#: src/components/charts/load-average-chart.tsx #: src/components/charts/load-average-chart.tsx
msgid "5 min" msgid "5 min"
msgstr "5 min" msgstr ""
#. Table column #. Table column
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -93,6 +90,10 @@ msgstr "Aktivní"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Aktivní výstrahy" msgstr "Aktivní výstrahy"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Aktivní stav"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Přidat <0>Systém</0>" msgstr "Přidat <0>Systém</0>"
@@ -113,11 +114,19 @@ msgstr "Přidat URL"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Upravit možnosti zobrazení pro grafy." msgstr "Upravit možnosti zobrazení pro grafy."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Upravit šířku hlavního rozvržení"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Administrátor" msgstr "Administrátor"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "Po"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
@@ -203,6 +212,18 @@ msgstr "Přenos"
msgid "Battery" msgid "Battery"
msgstr "Baterie" msgstr "Baterie"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Stal se aktivním"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Stal se neaktivním"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Před"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel podporuje OpenID Connect a mnoho poskytovatelů OAuth2 ověřování." msgstr "Beszel podporuje OpenID Connect a mnoho poskytovatelů OAuth2 ověřování."
@@ -220,6 +241,10 @@ msgstr "Binární"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bity (Kbps, Mbps, Gbps)" msgstr "Bity (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Stav zavádění"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Byty (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / vyrovnávací paměť" msgstr "Cache / vyrovnávací paměť"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Může znovu načíst"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Může spustit"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Může zastavit"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Zrušit" msgstr "Zrušit"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "Schopnosti"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Kapacita" msgstr "Kapacita"
@@ -309,6 +350,10 @@ msgstr "Konfigurace způsobu přijímání upozornění."
msgid "Confirm password" msgid "Confirm password"
msgstr "Potvrdit heslo" msgstr "Potvrdit heslo"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Konflikty"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "Připojení je nedostupné" msgstr "Připojení je nedostupné"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "Kopírovat YAML" msgstr "Kopírovat YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "Procesor" msgstr "Procesor"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU jádra"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "Špička CPU"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "Čas CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Rozdělení času CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Využití procesoru" msgstr "Využití procesoru"
@@ -427,6 +490,10 @@ msgstr "Odstranit"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Smazat identifikátor" msgstr "Smazat identifikátor"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Popis"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detail" msgstr "Detail"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "Síťové I/O Dockeru" msgstr "Síťové I/O Dockeru"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentace" msgstr "Dokumentace"
@@ -507,7 +575,7 @@ msgstr "Upravit"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
#: src/components/login/otp-forms.tsx #: src/components/login/otp-forms.tsx
msgid "Email" msgid "Email"
msgstr "Email" msgstr "E-mail"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Email notifications" msgid "Email notifications"
@@ -535,6 +603,7 @@ msgstr "Zadejte Vaše jednorázové heslo."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Chyba" msgstr "Chyba"
@@ -545,10 +614,18 @@ msgstr "Chyba"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Překračuje {0}{1} za {2, plural, one {poslední # minutu} few {poslední # minuty} other {posledních # minut}}" msgstr "Překračuje {0}{1} za {2, plural, one {poslední # minutu} few {poslední # minuty} other {posledních # minut}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "Hlavní PID spuštění"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Stávající systémy, které nejsou definovány v <0>config.yml</0>, budou odstraněny. Provádějte pravidelné zálohování." msgstr "Stávající systémy, které nejsou definovány v <0>config.yml</0>, budou odstraněny. Provádějte pravidelné zálohování."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Ukončeno aktivně"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Exportovat" msgstr "Exportovat"
@@ -565,6 +642,10 @@ msgstr "Exportovat aktuální konfiguraci systémů."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheita (°F)" msgstr "Fahrenheita (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Selhalo"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Neúspěšné atributy:" msgstr "Neúspěšné atributy:"
@@ -586,10 +667,16 @@ msgstr "Nepodařilo se odeslat testovací oznámení"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Nepodařilo se aktualizovat upozornění" msgstr "Nepodařilo se aktualizovat upozornění"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Neúspěšné: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtr..." msgstr "Filtr..."
@@ -635,6 +722,10 @@ msgstr "GPU enginy"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Spotřeba energie GPU" msgstr "Spotřeba energie GPU"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "Využití GPU"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Mřížka" msgstr "Mřížka"
@@ -684,6 +775,19 @@ msgstr "Jazyk"
msgid "Layout" msgid "Layout"
msgstr "Rozvržení" msgstr "Rozvržení"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Šířka rozvržení"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Životní cyklus"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "limit"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Průměrné vytížení" msgstr "Průměrné vytížení"
@@ -705,6 +809,14 @@ msgstr "Průměrná zátěž 5m"
msgid "Load Avg" msgid "Load Avg"
msgstr "Prům. zatížení" msgstr "Prům. zatížení"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Stav načtení"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Načítání..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Odhlásit" msgstr "Odhlásit"
@@ -728,6 +840,10 @@ msgstr "Logy"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Hledáte místo kde vytvářet upozornění? Klikněte na ikonu zvonku <0/> v systémové tabulce." msgstr "Hledáte místo kde vytvářet upozornění? Klikněte na ikonu zvonku <0/> v systémové tabulce."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "Hlavní PID"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Správa nastavení zobrazení a oznámení." msgstr "Správa nastavení zobrazení a oznámení."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "Max. 1 min" msgstr "Max. 1 min"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Paměť" msgstr "Paměť"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Limit paměti"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Špička paměti"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "Model"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Název" msgstr "Název"
@@ -787,7 +916,14 @@ msgstr "Síťový provoz veřejných rozhraní"
msgid "Network unit" msgid "Network unit"
msgstr "Síťová jednotka" msgstr "Síťová jednotka"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "Ne"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "Nenalezeny žádné výskyty." msgstr "Nenalezeny žádné výskyty."
@@ -796,6 +932,7 @@ msgstr "Nenalezeny žádné výskyty."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Žádné výsledky." msgstr "Žádné výsledky."
@@ -836,6 +973,10 @@ msgstr "Otevřít menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Nebo pokračujte s" msgstr "Nebo pokračujte s"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Jiné"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Přepsat existující upozornění" msgstr "Přepsat existující upozornění"
@@ -884,6 +1025,15 @@ msgstr "Pozastaveno"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pozastaveno ({pausedSystemsLength})" msgstr "Pozastaveno ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Průměrné využití na jádro"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Procento času strávěného v každém stavu"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "<0>nakonfigurujte SMTP server</0> pro zajištění toho, aby byla upozornění doručena." msgstr "<0>nakonfigurujte SMTP server</0> pro zajištění toho, aby byla upozornění doručena."
@@ -935,6 +1085,10 @@ msgstr "Přesné využití v zaznamenaném čase"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Upřednostňovaný jazyk" msgstr "Upřednostňovaný jazyk"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Proces spuštěn"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "Přijato"
msgid "Refresh" msgid "Refresh"
msgstr "Aktualizovat" msgstr "Aktualizovat"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Vztahy"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Požádat o jednorázové heslo" msgstr "Požádat o jednorázové heslo"
@@ -963,6 +1121,14 @@ msgstr "Požádat o jednorázové heslo"
msgid "Request OTP" msgid "Request OTP"
msgstr "Požádat OTP" msgstr "Požádat OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Vyžadováno službou"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Vyžaduje"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Obnovit heslo" msgstr "Obnovit heslo"
@@ -973,10 +1139,19 @@ msgstr "Obnovit heslo"
msgid "Resolved" msgid "Resolved"
msgstr "Vyřešeno" msgstr "Vyřešeno"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Restarty"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Pokračovat" msgstr "Pokračovat"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Kořenový"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Změnit token" msgstr "Změnit token"
@@ -985,6 +1160,10 @@ msgstr "Změnit token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Řádků na stránku" msgstr "Řádků na stránku"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Metriky běhu"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Detaily" msgstr "S.M.A.R.T. Detaily"
@@ -1026,6 +1205,14 @@ msgstr "Odeslat"
msgid "Serial Number" msgid "Serial Number"
msgstr "Sériové číslo" msgstr "Sériové číslo"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Detaily služby"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Služby"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Nastavte procentuální prahové hodnoty pro barvy měřičů." msgstr "Nastavte procentuální prahové hodnoty pro barvy měřičů."
@@ -1055,16 +1242,22 @@ msgstr "Seřadit podle"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "Stav" msgstr "Stav"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Stav" msgstr "Stav"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Podstav"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Swap prostor využívaný systémem" msgstr "Swap prostor využívaný systémem"
@@ -1085,6 +1278,10 @@ msgstr "Systém"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Průměry zatížení systému v průběhu času" msgstr "Průměry zatížení systému v průběhu času"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Služby systemd"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Systémy" msgstr "Systémy"
@@ -1097,6 +1294,10 @@ msgstr "Systémy lze spravovat v souboru <0>config.yml</0> uvnitř datového adr
msgid "Table" msgid "Table"
msgstr "Tabulka" msgstr "Tabulka"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Úlohy"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "Tokeny umožňují agentům připojení a registraci. Otisky jsou stabil
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeny a otisky slouží k ověření připojení WebSocket k uzlu." msgstr "Tokeny a otisky slouží k ověření připojení WebSocket k uzlu."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Celkem"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Celkový přijatý objem dat pro každé rozhraní" msgstr "Celkový přijatý objem dat pro každé rozhraní"
@@ -1188,6 +1394,19 @@ msgstr "Celkový přijatý objem dat pro každé rozhraní"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Celkový odeslaný objem dat pro každé rozhraní" msgstr "Celkový odeslaný objem dat pro každé rozhraní"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Celkem: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Spuštěno službou"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Spouštěče"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Spustí se, když využití paměti během 1 minuty překročí prahovou hodnotu" msgstr "Spustí se, když využití paměti během 1 minuty překročí prahovou hodnotu"
@@ -1212,6 +1431,10 @@ msgstr "Spustí se, když kombinace up/down překročí prahovou hodnotu"
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Spustí se, když využití procesoru překročí prahovou hodnotu" msgstr "Spustí se, když využití procesoru překročí prahovou hodnotu"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "Spustí se, když využití GPU překročí prahovou hodnotu"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Spustí se, když využití paměti překročí prahovou hodnotu" msgstr "Spustí se, když využití paměti překročí prahovou hodnotu"
@@ -1228,6 +1451,10 @@ msgstr "Spustí se, když využití disku překročí prahovou hodnotu"
msgid "Type" msgid "Type"
msgstr "Typ" msgstr "Typ"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "Soubor jednotky"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Univerzální token"
msgid "Unknown" msgid "Unknown"
msgstr "Neznámá" msgstr "Neznámá"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Neomezeno"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Funkční ({upSystemsLength})" msgstr "Funkční ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Aktualizováno" msgstr "Aktualizováno"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Aktualizováno každých 10 minut."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Odeslání" msgstr "Odeslání"
@@ -1269,6 +1506,7 @@ msgstr "Doba provozu"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Využití" msgstr "Využití"
@@ -1294,6 +1532,7 @@ msgstr "Hodnota"
msgid "View" msgid "View"
msgstr "Zobrazení" msgstr "Zobrazení"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Zobrazit více" msgstr "Zobrazit více"
@@ -1314,6 +1553,10 @@ msgstr "Čeká se na dostatek záznamů k zobrazení"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Chcete nám pomoci s našimi překlady ještě lépe? Podívejte se na <0>Crowdin</0> pro více informací." msgstr "Chcete nám pomoci s našimi překlady ještě lépe? Podívejte se na <0>Crowdin</0> pro více informací."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Chce"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Varování (%)" msgstr "Varování (%)"
@@ -1350,6 +1593,12 @@ msgstr "YAML konfigurace"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML konfigurace" msgstr "YAML konfigurace"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Ano"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Vaše uživatelská nastavení byla aktualizována." msgstr "Vaše uživatelská nastavení byla aktualizována."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: da\n" "Language: da\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-25 10:58\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Danish\n" "Language-Team: Danish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dag} other {# dage}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# time} other {# timer}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minut} other {# minutter}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} af {1} række(r) valgt." msgstr "{0} af {1} række(r) valgt."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dag} other {{countString} dage}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} time} other {{countString} timer}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minut} other {{countString} minutter}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 time" msgstr "1 time"
@@ -93,6 +90,10 @@ msgstr "Aktiv"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Aktive Alarmer" msgstr "Aktive Alarmer"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Aktiv tilstand"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Tilføj <0>System</0>" msgstr "Tilføj <0>System</0>"
@@ -113,11 +114,19 @@ msgstr "Tilføj URL"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Juster visningsindstillinger for diagrammer." msgstr "Juster visningsindstillinger for diagrammer."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Juster bredden af hovedlayoutet"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Administrator" msgstr "Administrator"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "Efter"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
@@ -203,6 +212,18 @@ msgstr "Båndbredde"
msgid "Battery" msgid "Battery"
msgstr "Batteri" msgstr "Batteri"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Blev aktiv"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Blev inaktiv"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Før"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel understøtter OpenID Connect og mange OAuth2 godkendelsesudbydere." msgstr "Beszel understøtter OpenID Connect og mange OAuth2 godkendelsesudbydere."
@@ -220,6 +241,10 @@ msgstr "Binær"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)" msgstr "Bits (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Opstartstilstand"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Bytes (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Buffere" msgstr "Cache / Buffere"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Kan genindlæse"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Kan starte"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Kan stoppe"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Fortryd" msgstr "Fortryd"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr ""
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Kapacitet" msgstr "Kapacitet"
@@ -309,6 +350,10 @@ msgstr "Konfigurer hvordan du modtager advarselsmeddelelser."
msgid "Confirm password" msgid "Confirm password"
msgstr "Bekræft adgangskode" msgstr "Bekræft adgangskode"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr ""
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "Forbindelsen er nede" msgstr "Forbindelsen er nede"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "Kopier YAML" msgstr "Kopier YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU-kerner"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr ""
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU-tidsfordeling"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU forbrug" msgstr "CPU forbrug"
@@ -427,6 +490,10 @@ msgstr "Slet"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Slet fingeraftryk" msgstr "Slet fingeraftryk"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Beskrivelse"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detalje" msgstr "Detalje"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "Docker Netværk I/O" msgstr "Docker Netværk I/O"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentation" msgstr "Dokumentation"
@@ -535,6 +603,7 @@ msgstr "Indtast din engangsadgangskode."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Fejl" msgstr "Fejl"
@@ -545,10 +614,18 @@ msgstr "Fejl"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Overskrider {0}{1} i sidste {2, plural, one {# minut} other {# minutter}}" msgstr "Overskrider {0}{1} i sidste {2, plural, one {# minut} other {# minutter}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr ""
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Eksisterende systemer ikke defineret i <0>config.yml</0> vil blive slettet. Opret venligst regelmæssige sikkerhedskopier." msgstr "Eksisterende systemer ikke defineret i <0>config.yml</0> vil blive slettet. Opret venligst regelmæssige sikkerhedskopier."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Afsluttet aktiv"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Eksporter" msgstr "Eksporter"
@@ -565,6 +642,10 @@ msgstr "Eksporter din nuværende systemkonfiguration."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Mislykkedes"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Mislykkede attributter:" msgstr "Mislykkede attributter:"
@@ -586,10 +667,16 @@ msgstr "Afsendelse af testnotifikation mislykkedes"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Kunne ikke opdatere alarm" msgstr "Kunne ikke opdatere alarm"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Mislykkedes: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -635,6 +722,10 @@ msgstr "GPU-enheder"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Gpu Strøm Træk" msgstr "Gpu Strøm Træk"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "GPU-forbrug"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Gitter" msgstr "Gitter"
@@ -684,6 +775,19 @@ msgstr "Sprog"
msgid "Layout" msgid "Layout"
msgstr "Opstilling" msgstr "Opstilling"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Layoutbredde"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Livscyklus"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Belastning Gennemsnitlig" msgstr "Belastning Gennemsnitlig"
@@ -705,6 +809,14 @@ msgstr "Belastning Gennemsnitlig 5m"
msgid "Load Avg" msgid "Load Avg"
msgstr "Belastning gns." msgstr "Belastning gns."
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Indlæsningstilstand"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Indlæser..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Log ud" msgstr "Log ud"
@@ -728,6 +840,10 @@ msgstr "Logs"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Leder du i stedet for efter hvor du kan oprette alarmer? Klik på klokken <0/> ikoner i system tabellen." msgstr "Leder du i stedet for efter hvor du kan oprette alarmer? Klik på klokken <0/> ikoner i system tabellen."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr ""
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Administrer display og notifikationsindstillinger." msgstr "Administrer display og notifikationsindstillinger."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "Maks. 1 min" msgstr "Maks. 1 min"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Hukommelse" msgstr "Hukommelse"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Hukommelsesgrænse"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,13 +890,15 @@ msgstr "Model"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Navn" msgstr "Navn"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Net" msgid "Net"
msgstr "Net" msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Network traffic of docker containers" msgid "Network traffic of docker containers"
@@ -787,7 +916,14 @@ msgstr "Netværkstrafik af offentlige grænseflader"
msgid "Network unit" msgid "Network unit"
msgstr "Netværksenhed" msgstr "Netværksenhed"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "Nej"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "Ingen resultater fundet." msgstr "Ingen resultater fundet."
@@ -796,6 +932,7 @@ msgstr "Ingen resultater fundet."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Ingen resultater." msgstr "Ingen resultater."
@@ -836,6 +973,10 @@ msgstr "Åbn menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Eller fortsæt med" msgstr "Eller fortsæt med"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Andre"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Overskriv eksisterende alarmer" msgstr "Overskriv eksisterende alarmer"
@@ -884,6 +1025,15 @@ msgstr "Sat på pause"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Sat på pause ({pausedSystemsLength})" msgstr "Sat på pause ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Gennemsnitlig udnyttelse pr. kerne"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Procentdel af tid brugt i hver tilstand"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Konfigurer <0>en SMTP server</0> for at sikre at alarmer bliver leveret." msgstr "Konfigurer <0>en SMTP server</0> for at sikre at alarmer bliver leveret."
@@ -935,6 +1085,10 @@ msgstr "Præcis udnyttelse på det registrerede tidspunkt"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Foretrukket sprog" msgstr "Foretrukket sprog"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Proces startet"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "Modtaget"
msgid "Refresh" msgid "Refresh"
msgstr "Opdater" msgstr "Opdater"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Relationer"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Anmod om engangsadgangskode" msgstr "Anmod om engangsadgangskode"
@@ -963,6 +1121,14 @@ msgstr "Anmod om engangsadgangskode"
msgid "Request OTP" msgid "Request OTP"
msgstr "Anmod OTP" msgstr "Anmod OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Kræves af"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Kræver"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Nulstil adgangskode" msgstr "Nulstil adgangskode"
@@ -973,10 +1139,19 @@ msgstr "Nulstil adgangskode"
msgid "Resolved" msgid "Resolved"
msgstr "Løst" msgstr "Løst"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Genstarter"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Genoptag" msgstr "Genoptag"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Roter nøgle" msgstr "Roter nøgle"
@@ -985,6 +1160,10 @@ msgstr "Roter nøgle"
msgid "Rows per page" msgid "Rows per page"
msgstr "Rækker per side" msgstr "Rækker per side"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Køretidsmålinger"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T.-detaljer" msgstr "S.M.A.R.T.-detaljer"
@@ -1026,6 +1205,14 @@ msgstr "Sendt"
msgid "Serial Number" msgid "Serial Number"
msgstr "Serienummer" msgstr "Serienummer"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Tjenestedetaljer"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Tjenester"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Indstil procentvise tærskler for målerfarver." msgstr "Indstil procentvise tærskler for målerfarver."
@@ -1055,16 +1242,22 @@ msgstr "Sorter efter"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "Tilstand" msgstr "Tilstand"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Undertilstand"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Swap plads brugt af systemet" msgstr "Swap plads brugt af systemet"
@@ -1085,6 +1278,10 @@ msgstr "System"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Gennemsnitlig system belastning over tid" msgstr "Gennemsnitlig system belastning over tid"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Systemer" msgstr "Systemer"
@@ -1097,6 +1294,10 @@ msgstr "Systemer kan være administreres i filen <0>config.yml</0> i din datamap
msgid "Table" msgid "Table"
msgstr "Tabel" msgstr "Tabel"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Opgaver"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "Nøgler tillader agenter at oprette forbindelse og registrere. Fingeraft
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Nøgler og fingeraftryk bruges til at godkende WebSocket-forbindelser til hubben." msgstr "Nøgler og fingeraftryk bruges til at godkende WebSocket-forbindelser til hubben."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Samlet"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Samlet modtaget data for hver interface" msgstr "Samlet modtaget data for hver interface"
@@ -1188,6 +1394,19 @@ msgstr "Samlet modtaget data for hver interface"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Samlet sendt data for hver interface" msgstr "Samlet sendt data for hver interface"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "I alt: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Udløst af"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Udløsere"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Udløser når 1 minut belastning gennemsnit overstiger en tærskel" msgstr "Udløser når 1 minut belastning gennemsnit overstiger en tærskel"
@@ -1212,6 +1431,10 @@ msgstr "Udløses når de kombinerede op/ned overstiger en tærskel"
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Udløser når CPU-forbrug overstiger en tærskel" msgstr "Udløser når CPU-forbrug overstiger en tærskel"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr ""
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Udløser når hukommelsesforbruget overstiger en tærskel" msgstr "Udløser når hukommelsesforbruget overstiger en tærskel"
@@ -1228,6 +1451,10 @@ msgstr "Udløser når brugen af en disk overstiger en tærskel"
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr ""
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Universalnøgle"
msgid "Unknown" msgid "Unknown"
msgstr "Ukendt" msgstr "Ukendt"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr ""
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Oppe ({upSystemsLength})" msgstr "Oppe ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Opdateret" msgstr "Opdateret"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Opdateret hver 10. minut."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Overfør" msgstr "Overfør"
@@ -1269,6 +1506,7 @@ msgstr "Oppetid"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Forbrug" msgstr "Forbrug"
@@ -1294,6 +1532,7 @@ msgstr "Værdi"
msgid "View" msgid "View"
msgstr "Vis" msgstr "Vis"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Se mere" msgstr "Se mere"
@@ -1314,6 +1553,10 @@ msgstr "Venter på nok posteringer til at vise"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Vil du hjælpe os med at gøre vores oversættelser endnu bedre? Tjek <0>Crowdin</0> for flere detaljer." msgstr "Vil du hjælpe os med at gøre vores oversættelser endnu bedre? Tjek <0>Crowdin</0> for flere detaljer."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Ønsker"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Advarsel (%)" msgstr "Advarsel (%)"
@@ -1350,6 +1593,12 @@ msgstr "YAML Konfiguration"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML Konfiguration" msgstr "YAML Konfiguration"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Ja"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Dine brugerindstillinger er opdateret." msgstr "Dine brugerindstillinger er opdateret."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n" "Language: de\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-25 21:09\n" "PO-Revision-Date: 2025-10-28 22:59\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: German\n" "Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# Tag} other {# Tage}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# Stunde} other {# Stunden}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# Minute} other {# Minuten}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} von {1} Zeile(n) ausgewählt." msgstr "{0} von {1} Zeile(n) ausgewählt."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} Tag} other {{countString} Tage}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} Stunde} other {{countString} Stunden}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} Minute} other {{countString} Minuten}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 Stunde" msgstr "1 Stunde"
@@ -93,6 +90,10 @@ msgstr "Aktiv"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Aktive Warnungen" msgstr "Aktive Warnungen"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Aktiver Zustand"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "<0>System</0> hinzufügen" msgstr "<0>System</0> hinzufügen"
@@ -113,11 +114,19 @@ msgstr "URL hinzufügen"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Anzeigeoptionen für Diagramme anpassen." msgstr "Anzeigeoptionen für Diagramme anpassen."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Breite des Hauptlayouts anpassen"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "Nach"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
@@ -203,6 +212,18 @@ msgstr "Bandbreite"
msgid "Battery" msgid "Battery"
msgstr "Batterie" msgstr "Batterie"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Wurde aktiv"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Wurde inaktiv"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Vor"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel unterstützt OpenID Connect und viele OAuth2-Authentifizierungsanbieter." msgstr "Beszel unterstützt OpenID Connect und viele OAuth2-Authentifizierungsanbieter."
@@ -220,6 +241,10 @@ msgstr "Binär"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)" msgstr "Bits (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Boot-Zustand"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Bytes (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Puffer" msgstr "Cache / Puffer"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Kann neu laden"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Kann starten"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Kann stoppen"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "Fähigkeiten"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Kapazität" msgstr "Kapazität"
@@ -309,6 +350,10 @@ msgstr "Konfiguriere, wie du Warnbenachrichtigungen erhältst."
msgid "Confirm password" msgid "Confirm password"
msgstr "Passwort bestätigen" msgstr "Passwort bestätigen"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Konflikte"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "Verbindung unterbrochen" msgstr "Verbindung unterbrochen"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "YAML kopieren" msgstr "YAML kopieren"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU-Kerne"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "CPU-Spitze"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "CPU-Zeit"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU-Zeit-Aufschlüsselung"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU-Auslastung" msgstr "CPU-Auslastung"
@@ -427,6 +490,10 @@ msgstr "Löschen"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Fingerabdruck löschen" msgstr "Fingerabdruck löschen"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Beschreibung"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Details" msgstr "Details"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "Docker-Netzwerk-I/O" msgstr "Docker-Netzwerk-I/O"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentation" msgstr "Dokumentation"
@@ -535,6 +603,7 @@ msgstr "Geben Sie Ihr Einmalpasswort ein."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Fehler" msgstr "Fehler"
@@ -545,10 +614,18 @@ msgstr "Fehler"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Überschreitet {0}{1} in den letzten {2, plural, one {# Minute} other {# Minuten}}" msgstr "Überschreitet {0}{1} in den letzten {2, plural, one {# Minute} other {# Minuten}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "Ausführungs-Haupt-PID"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Bestehende Systeme, die nicht in der <0>config.yml</0> definiert sind, werden gelöscht. Bitte mache regelmäßige Backups." msgstr "Bestehende Systeme, die nicht in der <0>config.yml</0> definiert sind, werden gelöscht. Bitte mache regelmäßige Backups."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Beendet aktiv"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Exportieren" msgstr "Exportieren"
@@ -565,6 +642,10 @@ msgstr "Exportiere die aktuelle Systemkonfiguration."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Fehlgeschlagen"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Fehlgeschlagene Attribute:" msgstr "Fehlgeschlagene Attribute:"
@@ -586,10 +667,16 @@ msgstr "Testbenachrichtigung konnte nicht gesendet werden"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Warnung konnte nicht aktualisiert werden" msgstr "Warnung konnte nicht aktualisiert werden"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Fehlgeschlagen: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -600,7 +687,7 @@ msgstr "Fingerabdruck"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Firmware" msgid "Firmware"
msgstr "Firmware" msgstr "Firm­ware"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -635,6 +722,10 @@ msgstr "GPU-Engines"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU-Leistungsaufnahme" msgstr "GPU-Leistungsaufnahme"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "GPU-Auslastung"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Raster" msgstr "Raster"
@@ -684,6 +775,19 @@ msgstr "Sprache"
msgid "Layout" msgid "Layout"
msgstr "Anordnung" msgstr "Anordnung"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Layoutbreite"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Lebenszyklus"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "Limit"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Durchschnittliche Systemlast" msgstr "Durchschnittliche Systemlast"
@@ -705,6 +809,14 @@ msgstr "Durchschnittliche Systemlast 5 Min"
msgid "Load Avg" msgid "Load Avg"
msgstr "Systemlast" msgstr "Systemlast"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Ladezustand"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Lädt..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Abmelden" msgstr "Abmelden"
@@ -728,6 +840,10 @@ msgstr "Protokolle"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Du möchtest neue Warnungen erstellen? Klicke dafür auf die Glocken-<0/>-Symbole in der Systemtabelle." msgstr "Du möchtest neue Warnungen erstellen? Klicke dafür auf die Glocken-<0/>-Symbole in der Systemtabelle."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "Haupt-PID"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Anzeige- und Benachrichtigungseinstellungen verwalten." msgstr "Anzeige- und Benachrichtigungseinstellungen verwalten."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "Max 1 Min" msgstr "Max 1 Min"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Arbeitsspeicher" msgstr "Arbeitsspeicher"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Arbeitsspeicherlimit"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Arbeitsspeicher-Spitze"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "Modell"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -787,7 +916,14 @@ msgstr "Netzwerkverkehr der öffentlichen Schnittstellen"
msgid "Network unit" msgid "Network unit"
msgstr "Netzwerkeinheit" msgstr "Netzwerkeinheit"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "Nein"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "Keine Ergebnisse gefunden." msgstr "Keine Ergebnisse gefunden."
@@ -796,6 +932,7 @@ msgstr "Keine Ergebnisse gefunden."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Keine Ergebnisse." msgstr "Keine Ergebnisse."
@@ -836,6 +973,10 @@ msgstr "Menü öffnen"
msgid "Or continue with" msgid "Or continue with"
msgstr "Oder fortfahren mit" msgstr "Oder fortfahren mit"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Andere"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Bestehende Warnungen überschreiben" msgstr "Bestehende Warnungen überschreiben"
@@ -884,6 +1025,15 @@ msgstr "Pausiert"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pausiert ({pausedSystemsLength})" msgstr "Pausiert ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Durchschnittliche Auslastung pro Kern"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Prozentsatz der Zeit in jedem Zustand"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Bitte <0>konfiguriere einen SMTP-Server</0>, um sicherzustellen, dass Warnungen zugestellt werden." msgstr "Bitte <0>konfiguriere einen SMTP-Server</0>, um sicherzustellen, dass Warnungen zugestellt werden."
@@ -935,6 +1085,10 @@ msgstr "Genaue Nutzung zum aufgezeichneten Zeitpunkt"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Bevorzugte Sprache" msgstr "Bevorzugte Sprache"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Prozess gestartet"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "Empfangen"
msgid "Refresh" msgid "Refresh"
msgstr "Aktualisieren" msgstr "Aktualisieren"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Beziehungen"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Einmalpasswort anfordern" msgstr "Einmalpasswort anfordern"
@@ -963,6 +1121,14 @@ msgstr "Einmalpasswort anfordern"
msgid "Request OTP" msgid "Request OTP"
msgstr "OTP anfordern" msgstr "OTP anfordern"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Benötigt von"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Benötigt"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Passwort zurücksetzen" msgstr "Passwort zurücksetzen"
@@ -973,10 +1139,19 @@ msgstr "Passwort zurücksetzen"
msgid "Resolved" msgid "Resolved"
msgstr "Gelöst" msgstr "Gelöst"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Neustarts"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Fortsetzen" msgstr "Fortsetzen"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Token rotieren" msgstr "Token rotieren"
@@ -985,6 +1160,10 @@ msgstr "Token rotieren"
msgid "Rows per page" msgid "Rows per page"
msgstr "Zeilen pro Seite" msgstr "Zeilen pro Seite"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Laufzeitmetriken"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T.-Details" msgstr "S.M.A.R.T.-Details"
@@ -1026,6 +1205,14 @@ msgstr "Gesendet"
msgid "Serial Number" msgid "Serial Number"
msgstr "Seriennummer" msgstr "Seriennummer"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Servicedetails"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Dienste"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Prozentuale Schwellenwerte für Zählerfarben festlegen." msgstr "Prozentuale Schwellenwerte für Zählerfarben festlegen."
@@ -1055,16 +1242,22 @@ msgstr "Sortieren nach"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "Status" msgstr "Status"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Unterzustand"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Vom System genutzter Swap-Speicher" msgstr "Vom System genutzter Swap-Speicher"
@@ -1085,6 +1278,10 @@ msgstr "System"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Systemlastdurchschnitt im Zeitverlauf" msgstr "Systemlastdurchschnitt im Zeitverlauf"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Systemd-Dienste"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Systeme" msgstr "Systeme"
@@ -1097,6 +1294,10 @@ msgstr "Systeme können in einer <0>config.yml</0>-Datei im Datenverzeichnis ver
msgid "Table" msgid "Table"
msgstr "Tabelle" msgstr "Tabelle"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Aufgaben"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "Tokens ermöglichen es Agents, sich zu verbinden und zu registrieren. Fi
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren." msgstr "Tokens und Fingerabdrücke werden verwendet, um WebSocket-Verbindungen zum Hub zu authentifizieren."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Gesamt"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Empfangene Gesamtdatenmenge je Schnittstelle " msgstr "Empfangene Gesamtdatenmenge je Schnittstelle "
@@ -1188,6 +1394,19 @@ msgstr "Empfangene Gesamtdatenmenge je Schnittstelle "
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Gesendete Gesamtdatenmenge je Schnittstelle" msgstr "Gesendete Gesamtdatenmenge je Schnittstelle"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Gesamt: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Ausgelöst von"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Trigger"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Löst aus, wenn der Lastdurchschnitt der letzten Minute einen Schwellenwert überschreitet" msgstr "Löst aus, wenn der Lastdurchschnitt der letzten Minute einen Schwellenwert überschreitet"
@@ -1212,6 +1431,10 @@ msgstr "Löst aus, wenn die kombinierte Up- und Downloadrate einen Schwellenwert
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Löst aus, wenn die CPU-Auslastung einen Schwellenwert überschreitet" msgstr "Löst aus, wenn die CPU-Auslastung einen Schwellenwert überschreitet"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "Löst aus, wenn die GPU-Auslastung einen Schwellenwert überschreitet"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Löst aus, wenn die Arbeitsspeichernutzung einen Schwellenwert überschreitet" msgstr "Löst aus, wenn die Arbeitsspeichernutzung einen Schwellenwert überschreitet"
@@ -1228,6 +1451,10 @@ msgstr "Löst aus, wenn die Nutzung einer Festplatte einen Schwellenwert übersc
msgid "Type" msgid "Type"
msgstr "Typ" msgstr "Typ"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "Unit-Datei"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Universeller Token"
msgid "Unknown" msgid "Unknown"
msgstr "Unbekannt" msgstr "Unbekannt"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Unbegrenzt"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "aktiv ({upSystemsLength})" msgstr "aktiv ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Aktualisiert" msgstr "Aktualisiert"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Alle 10 Minuten aktualisiert."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Hochladen" msgstr "Hochladen"
@@ -1269,6 +1506,7 @@ msgstr "Betriebszeit"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Nutzung" msgstr "Nutzung"
@@ -1294,6 +1532,7 @@ msgstr "Wert"
msgid "View" msgid "View"
msgstr "Ansicht" msgstr "Ansicht"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Mehr anzeigen" msgstr "Mehr anzeigen"
@@ -1314,6 +1553,10 @@ msgstr "Warten auf genügend Datensätze zur Anzeige"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Möchtest du uns helfen, unsere Übersetzungen noch besser zu machen? Schau dir <0>Crowdin</0> für weitere Details an." msgstr "Möchtest du uns helfen, unsere Übersetzungen noch besser zu machen? Schau dir <0>Crowdin</0> für weitere Details an."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Möchte"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Warnung (%)" msgstr "Warnung (%)"
@@ -1350,6 +1593,12 @@ msgstr "YAML-Konfiguration"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML-Konfiguration" msgstr "YAML-Konfiguration"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Ja"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Deine Benutzereinstellungen wurden aktualisiert." msgstr "Deine Benutzereinstellungen wurden aktualisiert."

View File

@@ -13,27 +13,24 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# day} other {# days}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# hour} other {# hours}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} of {1} row(s) selected." msgstr "{0} of {1} row(s) selected."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} day} other {{countString} days}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} hour} other {{countString} hours}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 hour" msgstr "1 hour"
@@ -88,6 +85,10 @@ msgstr "Active"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Active Alerts" msgstr "Active Alerts"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Active state"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Add <0>System</0>" msgstr "Add <0>System</0>"
@@ -108,11 +109,19 @@ msgstr "Add URL"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Adjust display options for charts." msgstr "Adjust display options for charts."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Adjust the width of the main layout"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "After"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
@@ -198,6 +207,18 @@ msgstr "Bandwidth"
msgid "Battery" msgid "Battery"
msgstr "Battery" msgstr "Battery"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Became active"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Became inactive"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Before"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgstr "Beszel supports OpenID Connect and many OAuth2 authentication providers."
@@ -215,6 +236,10 @@ msgstr "Binary"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)" msgstr "Bits (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Boot state"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -224,11 +249,27 @@ msgstr "Bytes (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Buffers" msgstr "Cache / Buffers"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Can reload"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Can start"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Can stop"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Cancel" msgstr "Cancel"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "Capabilities"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Capacity" msgstr "Capacity"
@@ -304,6 +345,10 @@ msgstr "Configure how you receive alert notifications."
msgid "Confirm password" msgid "Confirm password"
msgstr "Confirm password" msgstr "Confirm password"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Conflicts"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "Connection is down" msgstr "Connection is down"
@@ -364,12 +409,30 @@ msgid "Copy YAML"
msgstr "Copy YAML" msgstr "Copy YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU Cores"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "CPU Peak"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "CPU time"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU Time Breakdown"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU Usage" msgstr "CPU Usage"
@@ -422,6 +485,10 @@ msgstr "Delete"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Delete fingerprint" msgstr "Delete fingerprint"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Description"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detail" msgstr "Detail"
@@ -470,6 +537,7 @@ msgid "Docker Network I/O"
msgstr "Docker Network I/O" msgstr "Docker Network I/O"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Documentation" msgstr "Documentation"
@@ -530,6 +598,7 @@ msgstr "Enter your one-time password."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Error" msgstr "Error"
@@ -540,10 +609,18 @@ msgstr "Error"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgstr "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "Exec main PID"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgstr "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Exited active"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Export" msgstr "Export"
@@ -560,6 +637,10 @@ msgstr "Export your current systems configuration."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Failed"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Failed Attributes:" msgstr "Failed Attributes:"
@@ -581,10 +662,16 @@ msgstr "Failed to send test notification"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Failed to update alert" msgstr "Failed to update alert"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Failed: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filter..." msgstr "Filter..."
@@ -630,6 +717,10 @@ msgstr "GPU Engines"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU Power Draw" msgstr "GPU Power Draw"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "GPU Usage"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Grid" msgstr "Grid"
@@ -679,6 +770,19 @@ msgstr "Language"
msgid "Layout" msgid "Layout"
msgstr "Layout" msgstr "Layout"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Layout width"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Lifecycle"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "limit"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Load Average" msgstr "Load Average"
@@ -700,6 +804,14 @@ msgstr "Load Average 5m"
msgid "Load Avg" msgid "Load Avg"
msgstr "Load Avg" msgstr "Load Avg"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Load state"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Loading..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Log Out" msgstr "Log Out"
@@ -723,6 +835,10 @@ msgstr "Logs"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgstr "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "Main PID"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Manage display and notification preferences." msgstr "Manage display and notification preferences."
@@ -738,10 +854,21 @@ msgid "Max 1 min"
msgstr "Max 1 min" msgstr "Max 1 min"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Memory" msgstr "Memory"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Memory limit"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Memory Peak"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -758,6 +885,8 @@ msgstr "Model"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@@ -782,7 +911,14 @@ msgstr "Network traffic of public interfaces"
msgid "Network unit" msgid "Network unit"
msgstr "Network unit" msgstr "Network unit"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "No"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "No results found." msgstr "No results found."
@@ -791,6 +927,7 @@ msgstr "No results found."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "No results." msgstr "No results."
@@ -831,6 +968,10 @@ msgstr "Open menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Or continue with" msgstr "Or continue with"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Other"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Overwrite existing alerts" msgstr "Overwrite existing alerts"
@@ -879,6 +1020,15 @@ msgstr "Paused"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Paused ({pausedSystemsLength})" msgstr "Paused ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Per-core average utilization"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Percentage of time spent in each state"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgstr "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
@@ -930,6 +1080,10 @@ msgstr "Precise utilization at the recorded time"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Preferred Language" msgstr "Preferred Language"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Process started"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -950,6 +1104,10 @@ msgstr "Received"
msgid "Refresh" msgid "Refresh"
msgstr "Refresh" msgstr "Refresh"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Relationships"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Request a one-time password" msgstr "Request a one-time password"
@@ -958,6 +1116,14 @@ msgstr "Request a one-time password"
msgid "Request OTP" msgid "Request OTP"
msgstr "Request OTP" msgstr "Request OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Required by"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Requires"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Reset Password" msgstr "Reset Password"
@@ -968,10 +1134,19 @@ msgstr "Reset Password"
msgid "Resolved" msgid "Resolved"
msgstr "Resolved" msgstr "Resolved"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Restarts"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Resume" msgstr "Resume"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Rotate token" msgstr "Rotate token"
@@ -980,6 +1155,10 @@ msgstr "Rotate token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Rows per page" msgstr "Rows per page"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Runtime Metrics"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Details" msgstr "S.M.A.R.T. Details"
@@ -1021,6 +1200,14 @@ msgstr "Sent"
msgid "Serial Number" msgid "Serial Number"
msgstr "Serial Number" msgstr "Serial Number"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Service Details"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Services"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Set percentage thresholds for meter colors." msgstr "Set percentage thresholds for meter colors."
@@ -1050,16 +1237,22 @@ msgstr "Sort By"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "State" msgstr "State"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Sub State"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Swap space used by the system" msgstr "Swap space used by the system"
@@ -1080,6 +1273,10 @@ msgstr "System"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "System load averages over time" msgstr "System load averages over time"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Systemd Services"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Systems" msgstr "Systems"
@@ -1092,6 +1289,10 @@ msgstr "Systems may be managed in a <0>config.yml</0> file inside your data dire
msgid "Table" msgid "Table"
msgstr "Table" msgstr "Table"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Tasks"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1175,6 +1376,11 @@ msgstr "Tokens allow agents to connect and register. Fingerprints are stable ide
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgstr "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Total data received for each interface" msgstr "Total data received for each interface"
@@ -1183,6 +1389,19 @@ msgstr "Total data received for each interface"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Total data sent for each interface" msgstr "Total data sent for each interface"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Triggered by"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Triggers"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Triggers when 1 minute load average exceeds a threshold" msgstr "Triggers when 1 minute load average exceeds a threshold"
@@ -1207,6 +1426,10 @@ msgstr "Triggers when combined up/down exceeds a threshold"
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Triggers when CPU usage exceeds a threshold" msgstr "Triggers when CPU usage exceeds a threshold"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "Triggers when GPU usage exceeds a threshold"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Triggers when memory usage exceeds a threshold" msgstr "Triggers when memory usage exceeds a threshold"
@@ -1223,6 +1446,10 @@ msgstr "Triggers when usage of any disk exceeds a threshold"
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "Unit file"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1238,6 +1465,11 @@ msgstr "Universal token"
msgid "Unknown" msgid "Unknown"
msgstr "Unknown" msgstr "Unknown"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Unlimited"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1249,9 +1481,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Up ({upSystemsLength})" msgstr "Up ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Updated" msgstr "Updated"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Updated every 10 minutes."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Upload" msgstr "Upload"
@@ -1264,6 +1501,7 @@ msgstr "Uptime"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Usage" msgstr "Usage"
@@ -1289,6 +1527,7 @@ msgstr "Value"
msgid "View" msgid "View"
msgstr "View" msgstr "View"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "View more" msgstr "View more"
@@ -1309,6 +1548,10 @@ msgstr "Waiting for enough records to display"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Want to help improve our translations? Check <0>Crowdin</0> for details." msgstr "Want to help improve our translations? Check <0>Crowdin</0> for details."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Wants"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Warning (%)" msgstr "Warning (%)"
@@ -1345,6 +1588,12 @@ msgstr "YAML Config"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML Configuration" msgstr "YAML Configuration"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Yes"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Your user settings have been updated." msgstr "Your user settings have been updated."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n" "Language: es\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-25 21:09\n" "PO-Revision-Date: 2025-11-04 22:13\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Spanish\n" "Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# día} other {# días}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# hora} other {# horas}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuto} other {# minutos}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} de {1} fila(s) seleccionada(s)." msgstr "{0} de {1} fila(s) seleccionada(s)."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} día} other {{countString} días}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} hora} other {{countString} horas}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuto} other {{countString} minutos}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 hora" msgstr "1 hora"
@@ -93,6 +90,10 @@ msgstr "Activo"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Alertas activas" msgstr "Alertas activas"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Estado activo"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Agregar <0>sistema</0>" msgstr "Agregar <0>sistema</0>"
@@ -113,11 +114,19 @@ msgstr "Agregar URL"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Ajustar las opciones de visualización para los gráficos." msgstr "Ajustar las opciones de visualización para los gráficos."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Ajustar el ancho del diseño principal"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Administrador" msgstr "Administrador"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "Después"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agente" msgstr "Agente"
@@ -126,7 +135,7 @@ msgstr "Agente"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Alert History" msgid "Alert History"
msgstr "Historial de Alertas" msgstr "Historial de alertas"
#: src/components/alerts/alert-button.tsx #: src/components/alerts/alert-button.tsx
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
@@ -145,7 +154,7 @@ msgstr "Todos los contenedores"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "All Systems" msgid "All Systems"
msgstr "Todos los Sistemas" msgstr "Todos los sistemas"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Are you sure you want to delete {name}?" msgid "Are you sure you want to delete {name}?"
@@ -203,6 +212,18 @@ msgstr "Ancho de banda"
msgid "Battery" msgid "Battery"
msgstr "Batería" msgstr "Batería"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Se activó"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Se desactivó"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Antes"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel admite OpenID Connect y muchos proveedores de autenticación OAuth2." msgstr "Beszel admite OpenID Connect y muchos proveedores de autenticación OAuth2."
@@ -220,6 +241,10 @@ msgstr "Binario"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (kbps, Mbps, Gbps)" msgstr "Bits (kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Estado de arranque"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Bytes (kB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Caché / Buffers" msgstr "Caché / Buffers"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Puede recargar"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Puede iniciar"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Puede detener"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Cancelar" msgstr "Cancelar"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "Capacidades"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Capacidad" msgstr "Capacidad"
@@ -309,6 +350,10 @@ msgstr "Configura cómo recibe las notificaciones de alertas."
msgid "Confirm password" msgid "Confirm password"
msgstr "Confirmar contraseña" msgstr "Confirmar contraseña"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Conflictos"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "La conexión está caída" msgstr "La conexión está caída"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "Copiar YAML" msgstr "Copiar YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Núcleos de CPU"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "Pico de CPU"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "Tiempo de CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Desglose de tiempo de CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Uso de CPU" msgstr "Uso de CPU"
@@ -427,6 +490,10 @@ msgstr "Eliminar"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Eliminar huella digital" msgstr "Eliminar huella digital"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Descripción"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detalle" msgstr "Detalle"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "E/S de red de Docker" msgstr "E/S de red de Docker"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Documentación" msgstr "Documentación"
@@ -535,6 +603,7 @@ msgstr "Ingrese su contraseña de un solo uso."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Error" msgstr "Error"
@@ -545,10 +614,18 @@ msgstr "Error"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Excede {0}{1} en el último {2, plural, one {# minuto} other {# minutos}}" msgstr "Excede {0}{1} en el último {2, plural, one {# minuto} other {# minutos}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "PID principal de ejecución"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Los sistemas existentes no definidos en <0>config.yml</0> serán eliminados. Por favor, haz copias de seguridad regularmente." msgstr "Los sistemas existentes no definidos en <0>config.yml</0> serán eliminados. Por favor, haz copias de seguridad regularmente."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Salió activo"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Exportar" msgstr "Exportar"
@@ -565,6 +642,10 @@ msgstr "Exporta la configuración actual de sus sistemas."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Fallido"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Atributos fallidos:" msgstr "Atributos fallidos:"
@@ -586,10 +667,16 @@ msgstr "Error al enviar la notificación de prueba"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Error al actualizar la alerta" msgstr "Error al actualizar la alerta"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Fallidos: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrar..." msgstr "Filtrar..."
@@ -635,6 +722,10 @@ msgstr "Motores GPU"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Consumo de energía de la GPU" msgstr "Consumo de energía de la GPU"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "Uso de GPU"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Cuadrícula" msgstr "Cuadrícula"
@@ -684,6 +775,19 @@ msgstr "Idioma"
msgid "Layout" msgid "Layout"
msgstr "Diseño" msgstr "Diseño"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Ancho del diseño"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Ciclo de vida"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "límite"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Carga media" msgstr "Carga media"
@@ -705,6 +809,14 @@ msgstr "Carga media 5m"
msgid "Load Avg" msgid "Load Avg"
msgstr "Carga media" msgstr "Carga media"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Estado de carga"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Cargando..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Cerrar sesión" msgstr "Cerrar sesión"
@@ -728,6 +840,10 @@ msgstr "Registros"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "¿Buscas dónde crear alertas? Haz clic en los iconos de campana <0/> en la tabla de sistemas." msgstr "¿Buscas dónde crear alertas? Haz clic en los iconos de campana <0/> en la tabla de sistemas."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "PID principal"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Administrar preferencias de visualización y notificaciones." msgstr "Administrar preferencias de visualización y notificaciones."
@@ -740,21 +856,32 @@ msgstr "Instrucciones manuales de configuración"
#. Chart select field. Please try to keep this short. #. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Max 1 min" msgid "Max 1 min"
msgstr "Máx 1 min" msgstr "Máx. 1 min"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Memoria" msgstr "Memoria"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Límite de memoria"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Pico de memoria"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
msgstr "Uso de Memoria" msgstr "Uso de memoria"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Memory usage of docker containers" msgid "Memory usage of docker containers"
msgstr "Uso de memoria de los contenedores de Docker" msgstr "Uso de memoria de los contenedores Docker"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Model" msgid "Model"
@@ -763,6 +890,8 @@ msgstr "Modelo"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
@@ -773,7 +902,7 @@ msgstr "Red"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Network traffic of docker containers" msgid "Network traffic of docker containers"
msgstr "Tráfico de red de los contenedores de Docker" msgstr "Tráfico de red de los contenedores Docker"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
@@ -787,7 +916,14 @@ msgstr "Tráfico de red de interfaces públicas"
msgid "Network unit" msgid "Network unit"
msgstr "Unidad de red" msgstr "Unidad de red"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "No"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "No se encontraron resultados." msgstr "No se encontraron resultados."
@@ -796,6 +932,7 @@ msgstr "No se encontraron resultados."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Sin resultados." msgstr "Sin resultados."
@@ -836,6 +973,10 @@ msgstr "Abrir menú"
msgid "Or continue with" msgid "Or continue with"
msgstr "O continuar con" msgstr "O continuar con"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Otro"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Sobrescribir alertas existentes" msgstr "Sobrescribir alertas existentes"
@@ -884,38 +1025,47 @@ msgstr "Pausado"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pausado ({pausedSystemsLength})" msgstr "Pausado ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Uso promedio por núcleo"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Porcentaje de tiempo dedicado a cada estado"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Por favor, <0>configure un servidor SMTP</0> para asegurar que las alertas sean entregadas." msgstr "Por favor, <0>configura un servidor SMTP</0> para asegurar que las alertas sean entregadas."
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Please check logs for more details." msgid "Please check logs for more details."
msgstr "Por favor, revise los registros para más detalles." msgstr "Por favor, revisa los registros para más detalles."
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Please check your credentials and try again" msgid "Please check your credentials and try again"
msgstr "Por favor, verifique sus credenciales e intente de nuevo" msgstr "Por favor, verifica tus credenciales e inténtalo de nuevo"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Please create an admin account" msgid "Please create an admin account"
msgstr "Por favor, cree una cuenta de administrador" msgstr "Por favor, crea una cuenta de administrador"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Please enable pop-ups for this site" msgid "Please enable pop-ups for this site"
msgstr "Por favor, habilite las ventanas emergentes para este sitio" msgstr "Por favor, habilita las ventanas emergentes para este sitio"
#: src/lib/api.ts #: src/lib/api.ts
msgid "Please log in again" msgid "Please log in again"
msgstr "Por favor, inicie sesión de nuevo" msgstr "Por favor, inicia sesión de nuevo"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Please see <0>the documentation</0> for instructions." msgid "Please see <0>the documentation</0> for instructions."
msgstr "Por favor, consulte <0>la documentación</0> para obtener instrucciones." msgstr "Por favor, consulta <0>la documentación</0> para obtener instrucciones."
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Please sign in to your account" msgid "Please sign in to your account"
msgstr "Por favor, inicie sesión en su cuenta" msgstr "Por favor, inicia sesión en tu cuenta"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Port" msgid "Port"
@@ -933,12 +1083,16 @@ msgstr "Utilización precisa en el momento registrado"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Idioma Preferido" msgstr "Idioma preferido"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Proceso iniciado"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
msgstr "Clave Pública" msgstr "Clave pública"
#. Disk read #. Disk read
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -955,6 +1109,10 @@ msgstr "Recibido"
msgid "Refresh" msgid "Refresh"
msgstr "Actualizar" msgstr "Actualizar"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Relaciones"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Solicitar contraseña de un solo uso" msgstr "Solicitar contraseña de un solo uso"
@@ -963,9 +1121,17 @@ msgstr "Solicitar contraseña de un solo uso"
msgid "Request OTP" msgid "Request OTP"
msgstr "Solicitar OTP" msgstr "Solicitar OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Requerido por"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Requiere"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Restablecer Contraseña" msgstr "Restablecer contraseña"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
@@ -973,10 +1139,19 @@ msgstr "Restablecer Contraseña"
msgid "Resolved" msgid "Resolved"
msgstr "Resuelto" msgstr "Resuelto"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Reinicios"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Reanudar" msgstr "Reanudar"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Raíz"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Rotar token" msgstr "Rotar token"
@@ -985,6 +1160,10 @@ msgstr "Rotar token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Filas por página" msgstr "Filas por página"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Métricas de tiempo de ejecución"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "Detalles S.M.A.R.T." msgstr "Detalles S.M.A.R.T."
@@ -995,16 +1174,16 @@ msgstr "Autoprueba S.M.A.R.T."
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save address using enter key or comma. Leave blank to disable email notifications." msgid "Save address using enter key or comma. Leave blank to disable email notifications."
msgstr "Guarde la dirección usando la tecla enter o coma. Deje en blanco para desactivar las notificaciones por correo." msgstr "Guarda la dirección usando la tecla enter o coma. Deja en blanco para desactivar las notificaciones por correo."
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Save Settings" msgid "Save Settings"
msgstr "Guardar Configuración" msgstr "Guardar configuración"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Save system" msgid "Save system"
msgstr "Guardar Sistema" msgstr "Guardar sistema"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Search" msgid "Search"
@@ -1016,7 +1195,7 @@ msgstr "Buscar sistemas o configuraciones..."
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "See <0>notification settings</0> to configure how you receive alerts." msgid "See <0>notification settings</0> to configure how you receive alerts."
msgstr "Consulte <0>configuración de notificaciones</0> para configurar cómo recibe alertas." msgstr "Consulta <0>configuración de notificaciones</0> para configurar cómo recibe alertas."
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Sent" msgid "Sent"
@@ -1026,6 +1205,14 @@ msgstr "Enviado"
msgid "Serial Number" msgid "Serial Number"
msgstr "Número de serie" msgstr "Número de serie"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Detalles del servicio"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Servicios"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Establecer umbrales de porcentaje para los colores de los medidores." msgstr "Establecer umbrales de porcentaje para los colores de los medidores."
@@ -1055,23 +1242,29 @@ msgstr "Ordenar por"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "Estado" msgstr "Estado"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Estado" msgstr "Estado"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Subestado"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Espacio de swap utilizado por el sistema" msgstr "Espacio de swap utilizado por el sistema"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap Usage" msgid "Swap Usage"
msgstr "Uso de Swap" msgstr "Uso de swap"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
@@ -1085,18 +1278,26 @@ msgstr "Sistema"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Promedios de carga del sistema a lo largo del tiempo" msgstr "Promedios de carga del sistema a lo largo del tiempo"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Servicios de systemd"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Sistemas" msgstr "Sistemas"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Systems may be managed in a <0>config.yml</0> file inside your data directory." msgid "Systems may be managed in a <0>config.yml</0> file inside your data directory."
msgstr "Los sistemas pueden ser gestionados en un archivo <0>config.yml</0> dentro de su directorio de datos." msgstr "Los sistemas pueden ser gestionados en un archivo <0>config.yml</0> dentro de tu directorio de datos."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Table" msgid "Table"
msgstr "Tabla" msgstr "Tabla"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Tareas"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1126,7 +1327,7 @@ msgstr "Notificación de prueba enviada"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Then log into the backend and reset your user account password in the users table." msgid "Then log into the backend and reset your user account password in the users table."
msgstr "Luego inicie sesión en el backend y restablezca la contraseña de su cuenta de usuario en la tabla de usuarios." msgstr "Luego inicia sesión en el backend y restablece la contraseña de tu cuenta de usuario en la tabla de usuarios."
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database." msgid "This action cannot be undone. This will permanently delete all current records for {name} from the database."
@@ -1170,7 +1371,7 @@ msgstr "Token"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens & Fingerprints" msgid "Tokens & Fingerprints"
msgstr "Tokens y Huellas Digitales" msgstr "Tokens y huellas digitales"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection." msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection."
@@ -1180,6 +1381,11 @@ msgstr "Los tokens permiten que los agentes se conecten y registren. Las huellas
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub." msgstr "Los tokens y las huellas digitales se utilizan para autenticar las conexiones WebSocket al hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Datos totales recibidos por cada interfaz" msgstr "Datos totales recibidos por cada interfaz"
@@ -1188,6 +1394,19 @@ msgstr "Datos totales recibidos por cada interfaz"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Datos totales enviados por cada interfaz" msgstr "Datos totales enviados por cada interfaz"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Activado por"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Activadores"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Se activa cuando la carga media de 1 minuto supera un umbral" msgstr "Se activa cuando la carga media de 1 minuto supera un umbral"
@@ -1212,6 +1431,10 @@ msgstr "Se activa cuando la suma de subida/bajada supera un umbral"
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Se activa cuando el uso de CPU supera un umbral" msgstr "Se activa cuando el uso de CPU supera un umbral"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "Se activa cuando el uso de GPU supera un umbral"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Se activa cuando el uso de memoria supera un umbral" msgstr "Se activa cuando el uso de memoria supera un umbral"
@@ -1228,6 +1451,10 @@ msgstr "Se activa cuando el uso de cualquier disco supera un umbral"
msgid "Type" msgid "Type"
msgstr "Tipo" msgstr "Tipo"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "Archivo de unidad"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Token universal"
msgid "Unknown" msgid "Unknown"
msgstr "Desconocida" msgstr "Desconocida"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Ilimitado"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Activo ({upSystemsLength})" msgstr "Activo ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Actualizado" msgstr "Actualizado"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Actualizado cada 10 minutos."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Cargar" msgstr "Cargar"
@@ -1269,6 +1506,7 @@ msgstr "Tiempo de actividad"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Uso" msgstr "Uso"
@@ -1294,13 +1532,14 @@ msgstr "Valor"
msgid "View" msgid "View"
msgstr "Vista" msgstr "Vista"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Ver más" msgstr "Ver más"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "View your 200 most recent alerts." msgid "View your 200 most recent alerts."
msgstr "Ver sus 200 alertas más recientes." msgstr "Ver tus 200 alertas más recientes."
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Visible Fields" msgid "Visible Fields"
@@ -1312,7 +1551,11 @@ msgstr "Esperando suficientes registros para mostrar"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "¿Quieres ayudarnos a mejorar nuestras traducciones? Consulta <0>Crowdin</0> para más detalles." msgstr "¿Quieres ayudar a mejorar nuestras traducciones? Consulta <0>Crowdin</0> para más detalles."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Desea"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
@@ -1350,6 +1593,12 @@ msgstr "Configuración YAML"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "Configuración YAML" msgstr "Configuración YAML"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Sí"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Su configuración de usuario ha sido actualizada." msgstr "Tu configuración de usuario ha sido actualizada."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: fa\n" "Language: fa\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-20 21:37\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Persian\n" "Language-Team: Persian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# روز} other {# روز}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# ساعت} other {# ساعت}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# دقیقه} few {# دقیقه} many {# دقیقه} other {# دقیقه}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} از {1} ردیف انتخاب شده است." msgstr "{0} از {1} ردیف انتخاب شده است."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} روز} other {{countString} روز}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} ساعت} other {{countString} ساعت}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} دقیقه} few {{countString} دقیقه} many {{countString} دقیقه} other {{countString} دقیقه}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "۱ ساعت" msgstr "۱ ساعت"
@@ -93,6 +90,10 @@ msgstr "فعال"
msgid "Active Alerts" msgid "Active Alerts"
msgstr " هشدارهای فعال" msgstr " هشدارهای فعال"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "وضعیت فعال"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "افزودن <0>سیستم</0>" msgstr "افزودن <0>سیستم</0>"
@@ -113,11 +114,19 @@ msgstr "افزودن آدرس اینترنتی"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "تنظیم گزینه‌های نمایش برای نمودارها." msgstr "تنظیم گزینه‌های نمایش برای نمودارها."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "تنظیم عرض چیدمان اصلی"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "مدیر" msgstr "مدیر"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "بعد از"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "عامل" msgstr "عامل"
@@ -203,6 +212,18 @@ msgstr "پهنای باند"
msgid "Battery" msgid "Battery"
msgstr "باتری" msgstr "باتری"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "فعال شد"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "غیرفعال شد"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "قبل از"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "بِزل از OpenID Connect و بسیاری از ارائه‌دهندگان احراز هویت OAuth2 پشتیبانی می‌کند." msgstr "بِزل از OpenID Connect و بسیاری از ارائه‌دهندگان احراز هویت OAuth2 پشتیبانی می‌کند."
@@ -220,6 +241,10 @@ msgstr "دودویی"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "بیت (کیلوبیت بر ثانیه، مگابیت بر ثانیه، گیگابیت بر ثانیه)" msgstr "بیت (کیلوبیت بر ثانیه، مگابیت بر ثانیه، گیگابیت بر ثانیه)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "وضعیت بوت"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "بایت (کیلوبایت بر ثانیه، مگابایت بر ثان
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "حافظه پنهان / بافرها" msgstr "حافظه پنهان / بافرها"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "می‌تواند بارگذاری مجدد شود"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "می‌تواند شروع شود"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "می‌تواند متوقف شود"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "لغو" msgstr "لغو"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "قابلیت‌ها"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "ظرفیت" msgstr "ظرفیت"
@@ -309,6 +350,10 @@ msgstr "نحوه دریافت هشدارهای اطلاع‌رسانی را پی
msgid "Confirm password" msgid "Confirm password"
msgstr "تأیید رمز عبور" msgstr "تأیید رمز عبور"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "تعارض‌ها"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "اتصال قطع است" msgstr "اتصال قطع است"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "کپی YAML" msgstr "کپی YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "پردازنده" msgstr "پردازنده"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "هسته‌های CPU"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "حداکثر CPU"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "زمان CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "تجزیه زمان CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "میزان استفاده از پردازنده" msgstr "میزان استفاده از پردازنده"
@@ -427,6 +490,10 @@ msgstr "حذف"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "حذف اثر انگشت" msgstr "حذف اثر انگشت"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "توضیحات"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "جزئیات" msgstr "جزئیات"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "ورودی/خروجی شبکه داکر" msgstr "ورودی/خروجی شبکه داکر"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "مستندات" msgstr "مستندات"
@@ -535,6 +603,7 @@ msgstr "رمز عبور یک‌بار مصرف خود را وارد کنید."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "خطا" msgstr "خطا"
@@ -545,10 +614,18 @@ msgstr "خطا"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "در {2, plural, one {# دقیقه} other {# دقیقه}} گذشته از {0}{1} بیشتر است" msgstr "در {2, plural, one {# دقیقه} other {# دقیقه}} گذشته از {0}{1} بیشتر است"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr ""
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "سیستم‌های موجود که در <0>config.yml</0> تعریف نشده‌اند حذف خواهند شد. لطفاً به طور منظم پشتیبان‌گیری کنید." msgstr "سیستم‌های موجود که در <0>config.yml</0> تعریف نشده‌اند حذف خواهند شد. لطفاً به طور منظم پشتیبان‌گیری کنید."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "خروج فعال"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "خروجی گرفتن" msgstr "خروجی گرفتن"
@@ -565,6 +642,10 @@ msgstr "پیکربندی سیستم‌های فعلی خود را خارج کن
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "فارنهایت (°F)" msgstr "فارنهایت (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "ناموفق"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "ویژگی‌های ناموفق:" msgstr "ویژگی‌های ناموفق:"
@@ -586,10 +667,16 @@ msgstr "ارسال اعلان آزمایشی ناموفق بود"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "به‌روزرسانی هشدار ناموفق بود" msgstr "به‌روزرسانی هشدار ناموفق بود"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "ناموفق: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "فیلتر..." msgstr "فیلتر..."
@@ -635,6 +722,10 @@ msgstr "موتورهای GPU"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "مصرف برق پردازنده گرافیکی" msgstr "مصرف برق پردازنده گرافیکی"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "میزان استفاده از GPU"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "جدول" msgstr "جدول"
@@ -684,6 +775,19 @@ msgstr "زبان"
msgid "Layout" msgid "Layout"
msgstr "طرح‌بندی" msgstr "طرح‌بندی"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "عرض چیدمان"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "چرخه حیات"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "میانگین بار" msgstr "میانگین بار"
@@ -705,6 +809,14 @@ msgstr "میانگین بار ۵ دقیقه"
msgid "Load Avg" msgid "Load Avg"
msgstr "میانگین بار" msgstr "میانگین بار"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "وضعیت بارگذاری"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "در حال بارگذاری..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "خروج" msgstr "خروج"
@@ -728,6 +840,10 @@ msgstr "لاگ‌ها"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "به دنبال جایی برای ایجاد هشدار هستید؟ روی آیکون‌های زنگ <0/> در جدول سیستم‌ها کلیک کنید." msgstr "به دنبال جایی برای ایجاد هشدار هستید؟ روی آیکون‌های زنگ <0/> در جدول سیستم‌ها کلیک کنید."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr ""
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "مدیریت تنظیمات نمایش و اعلان‌ها." msgstr "مدیریت تنظیمات نمایش و اعلان‌ها."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "حداکثر ۱ دقیقه" msgstr "حداکثر ۱ دقیقه"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "حافظه" msgstr "حافظه"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "محدودیت حافظه"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "حداکثر حافظه"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "مدل"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "نام" msgstr "نام"
@@ -787,7 +916,14 @@ msgstr "ترافیک شبکه رابط‌های عمومی"
msgid "Network unit" msgid "Network unit"
msgstr "واحد شبکه" msgstr "واحد شبکه"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "خیر"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "هیچ نتیجه‌ای یافت نشد." msgstr "هیچ نتیجه‌ای یافت نشد."
@@ -796,6 +932,7 @@ msgstr "هیچ نتیجه‌ای یافت نشد."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "نتیجه‌ای یافت نشد." msgstr "نتیجه‌ای یافت نشد."
@@ -836,6 +973,10 @@ msgstr "باز کردن منو"
msgid "Or continue with" msgid "Or continue with"
msgstr "یا ادامه با" msgstr "یا ادامه با"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "سایر"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "بازنویسی هشدارهای موجود" msgstr "بازنویسی هشدارهای موجود"
@@ -884,6 +1025,15 @@ msgstr "مکث شده"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "مکث شده ({pausedSystemsLength})" msgstr "مکث شده ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "میانگین استفاده در هر هسته"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "درصد زمان صرف شده در هر حالت"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "لطفاً برای اطمینان از تحویل هشدارها، یک <0>سرور SMTP پیکربندی کنید</0>." msgstr "لطفاً برای اطمینان از تحویل هشدارها، یک <0>سرور SMTP پیکربندی کنید</0>."
@@ -935,6 +1085,10 @@ msgstr "میزان دقیق استفاده در زمان ثبت شده"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "زبان ترجیحی" msgstr "زبان ترجیحی"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "فرآیند شروع شد"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "دریافت شد"
msgid "Refresh" msgid "Refresh"
msgstr "تازه‌سازی" msgstr "تازه‌سازی"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "روابط"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "درخواست رمز عبور یک‌بار مصرف" msgstr "درخواست رمز عبور یک‌بار مصرف"
@@ -963,6 +1121,14 @@ msgstr "درخواست رمز عبور یک‌بار مصرف"
msgid "Request OTP" msgid "Request OTP"
msgstr "درخواست OTP" msgstr "درخواست OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "مورد نیاز توسط"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "نیازمند"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "بازنشانی رمز عبور" msgstr "بازنشانی رمز عبور"
@@ -973,10 +1139,19 @@ msgstr "بازنشانی رمز عبور"
msgid "Resolved" msgid "Resolved"
msgstr "حل شده" msgstr "حل شده"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "راه‌اندازی مجدد"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "ادامه" msgstr "ادامه"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "چرخش توکن" msgstr "چرخش توکن"
@@ -985,6 +1160,10 @@ msgstr "چرخش توکن"
msgid "Rows per page" msgid "Rows per page"
msgstr "ردیف در هر صفحه" msgstr "ردیف در هر صفحه"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "معیارهای زمان اجرا"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "جزئیات S.M.A.R.T" msgstr "جزئیات S.M.A.R.T"
@@ -1026,6 +1205,14 @@ msgstr "ارسال شد"
msgid "Serial Number" msgid "Serial Number"
msgstr "شماره سریال" msgstr "شماره سریال"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "جزئیات سرویس"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "سرویس‌ها"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "آستانه های درصدی را برای رنگ های متر تنظیم کنید." msgstr "آستانه های درصدی را برای رنگ های متر تنظیم کنید."
@@ -1055,16 +1242,22 @@ msgstr "مرتب‌سازی بر اساس"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "وضعیت" msgstr "وضعیت"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "وضعیت" msgstr "وضعیت"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "وضعیت فرعی"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "فضای Swap استفاده شده توسط سیستم" msgstr "فضای Swap استفاده شده توسط سیستم"
@@ -1085,6 +1278,10 @@ msgstr "سیستم"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "میانگین بار سیستم در طول زمان" msgstr "میانگین بار سیستم در طول زمان"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "سیستم‌ها" msgstr "سیستم‌ها"
@@ -1097,6 +1294,10 @@ msgstr "سیستم‌ها ممکن است در یک فایل <0>config.yml</0>
msgid "Table" msgid "Table"
msgstr "جدول" msgstr "جدول"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "وظایف"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "توکن‌ها به عامل‌ها اجازه اتصال و ثبت‌
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند." msgstr "توکن‌ها و اثرات انگشت برای احراز هویت اتصالات WebSocket به هاب استفاده می‌شوند."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "کل"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "داده‌های کل دریافت شده برای هر رابط" msgstr "داده‌های کل دریافت شده برای هر رابط"
@@ -1188,6 +1394,19 @@ msgstr "داده‌های کل دریافت شده برای هر رابط"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "داده‌های کل ارسال شده برای هر رابط" msgstr "داده‌های کل ارسال شده برای هر رابط"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "کل: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "فعال شده توسط"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "محرک‌ها"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "هنگامی که میانگین بار ۱ دقیقه‌ای از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که میانگین بار ۱ دقیقه‌ای از یک آستانه فراتر رود، فعال می‌شود"
@@ -1212,6 +1431,10 @@ msgstr "هنگامی که مجموع بالا/پایین از یک آستانه
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "هنگامی که میزان استفاده از CPU از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که میزان استفاده از CPU از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "هنگامی که میزان استفاده از GPU از یک آستانه فراتر رود، فعال می‌شود"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "هنگامی که میزان استفاده از حافظه از یک آستانه فراتر رود، فعال می‌شود" msgstr "هنگامی که میزان استفاده از حافظه از یک آستانه فراتر رود، فعال می‌شود"
@@ -1228,6 +1451,10 @@ msgstr "هنگامی که استفاده از هر دیسکی از یک آستا
msgid "Type" msgid "Type"
msgstr "نوع" msgstr "نوع"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr ""
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "توکن جهانی"
msgid "Unknown" msgid "Unknown"
msgstr "ناشناخته" msgstr "ناشناخته"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "نامحدود"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "فعال ({upSystemsLength})" msgstr "فعال ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "به‌روزرسانی شد" msgstr "به‌روزرسانی شد"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "هر ۱۰ دقیقه به‌روزرسانی می‌شود."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "آپلود" msgstr "آپلود"
@@ -1269,6 +1506,7 @@ msgstr "آپتایم"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "میزان استفاده" msgstr "میزان استفاده"
@@ -1294,6 +1532,7 @@ msgstr "مقدار"
msgid "View" msgid "View"
msgstr "مشاهده" msgstr "مشاهده"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "مشاهده بیشتر" msgstr "مشاهده بیشتر"
@@ -1314,6 +1553,10 @@ msgstr "در انتظار رکوردهای کافی برای نمایش"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "می‌خواهید به ما کمک کنید تا ترجمه‌های خود را بهتر کنیم؟ برای جزئیات بیشتر به <0>Crowdin</0> مراجعه کنید." msgstr "می‌خواهید به ما کمک کنید تا ترجمه‌های خود را بهتر کنیم؟ برای جزئیات بیشتر به <0>Crowdin</0> مراجعه کنید."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "می‌خواهد"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "هشدار (%)" msgstr "هشدار (%)"
@@ -1350,6 +1593,12 @@ msgstr "پیکربندی YAML"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "پیکربندی YAML" msgstr "پیکربندی YAML"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "بله"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "تنظیمات کاربری شما به‌روزرسانی شد." msgstr "تنظیمات کاربری شما به‌روزرسانی شد."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n" "Language: fr\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-25 20:53\n" "PO-Revision-Date: 2025-11-11 19:25\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: French\n" "Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# jour} other {# jours}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# heure} other {# heures}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} sur {1} ligne(s) sélectionnée(s)." msgstr "{0} sur {1} ligne(s) sélectionnée(s)."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} jour} other {{countString} jours}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} heure} other {{countString} heures}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 heure" msgstr "1 heure"
@@ -93,6 +90,10 @@ msgstr "Active"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Alertes actives" msgstr "Alertes actives"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "État actif"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Ajouter <0>un Système</0>" msgstr "Ajouter <0>un Système</0>"
@@ -113,11 +114,19 @@ msgstr "Ajouter lURL"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Ajuster les options d'affichage pour les graphiques." msgstr "Ajuster les options d'affichage pour les graphiques."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Ajuster la largeur de la mise en page principale"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "Après"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
@@ -203,6 +212,18 @@ msgstr "Bande passante"
msgid "Battery" msgid "Battery"
msgstr "Batterie" msgstr "Batterie"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Devenu actif"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Devenu inactif"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Avant"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel prend en charge OpenID Connect et de nombreux fournisseurs d'authentification OAuth2." msgstr "Beszel prend en charge OpenID Connect et de nombreux fournisseurs d'authentification OAuth2."
@@ -220,6 +241,10 @@ msgstr "Binaire"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bits (Kbps, Mbps, Gbps)" msgstr "Bits (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "État de démarrage"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Bytes (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Tampons" msgstr "Cache / Tampons"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Peut recharger"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Peut démarrer"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Peut arrêter"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Annuler" msgstr "Annuler"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "Capacités"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Capacité" msgstr "Capacité"
@@ -309,6 +350,10 @@ msgstr "Configurez comment vous recevez les notifications d'alerte."
msgid "Confirm password" msgid "Confirm password"
msgstr "Confirmer le mot de passe" msgstr "Confirmer le mot de passe"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Conflits"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "Connexion interrompue" msgstr "Connexion interrompue"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "Copier YAML" msgstr "Copier YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Cœurs CPU"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "Pic CPU"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "Temps CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Répartition du temps CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Utilisation du CPU" msgstr "Utilisation du CPU"
@@ -427,6 +490,10 @@ msgstr "Supprimer"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Supprimer l'empreinte" msgstr "Supprimer l'empreinte"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Description"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Détail" msgstr "Détail"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "Entrée/Sortie réseau Docker" msgstr "Entrée/Sortie réseau Docker"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Documentation" msgstr "Documentation"
@@ -535,6 +603,7 @@ msgstr "Entrez votre mot de passe à usage unique."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Erreur" msgstr "Erreur"
@@ -545,10 +614,18 @@ msgstr "Erreur"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Dépasse {0}{1} dans {2, plural, one {la dernière # minute} other {les dernières # minutes}}" msgstr "Dépasse {0}{1} dans {2, plural, one {la dernière # minute} other {les dernières # minutes}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "PID principal d'exécution"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Les systèmes existants non définis dans <0>config.yml</0> seront supprimés. Veuillez faire des sauvegardes régulières." msgstr "Les systèmes existants non définis dans <0>config.yml</0> seront supprimés. Veuillez faire des sauvegardes régulières."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Sorti actif"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Exporter" msgstr "Exporter"
@@ -565,6 +642,10 @@ msgstr "Exportez la configuration actuelle de vos systèmes."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Échoué"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Attributs défaillants :" msgstr "Attributs défaillants :"
@@ -586,10 +667,16 @@ msgstr "Échec de l'envoi de la notification de test"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Échec de la mise à jour de l'alerte" msgstr "Échec de la mise à jour de l'alerte"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Échec : {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtrer..." msgstr "Filtrer..."
@@ -635,6 +722,10 @@ msgstr "Moteurs GPU"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Consommation du GPU" msgstr "Consommation du GPU"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "Utilisation GPU"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Grille" msgstr "Grille"
@@ -684,6 +775,19 @@ msgstr "Langue"
msgid "Layout" msgid "Layout"
msgstr "Disposition" msgstr "Disposition"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Largeur de la mise en page"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Cycle de vie"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "limite"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Charge moyenne" msgstr "Charge moyenne"
@@ -705,6 +809,14 @@ msgstr "Charge moyenne 5m"
msgid "Load Avg" msgid "Load Avg"
msgstr "Charge moy." msgstr "Charge moy."
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "État de charge"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Chargement..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Déconnexion" msgstr "Déconnexion"
@@ -728,6 +840,10 @@ msgstr "Journaux"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Vous cherchez plutôt où créer des alertes ? Cliquez sur les icônes de cloche <0/> dans le tableau des systèmes." msgstr "Vous cherchez plutôt où créer des alertes ? Cliquez sur les icônes de cloche <0/> dans le tableau des systèmes."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "PID principal"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Gérer les préférences d'affichage et de notification." msgstr "Gérer les préférences d'affichage et de notification."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "Max 1 min" msgstr "Max 1 min"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Mémoire" msgstr "Mémoire"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Limite mémoire"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Pic mémoire"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "Modèle"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
@@ -787,7 +916,14 @@ msgstr "Trafic réseau des interfaces publiques"
msgid "Network unit" msgid "Network unit"
msgstr "Unité réseau" msgstr "Unité réseau"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "Non"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "Aucun résultat trouvé." msgstr "Aucun résultat trouvé."
@@ -796,6 +932,7 @@ msgstr "Aucun résultat trouvé."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Aucun résultat." msgstr "Aucun résultat."
@@ -836,6 +973,10 @@ msgstr "Ouvrir le menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Ou continuer avec" msgstr "Ou continuer avec"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Autre"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Écraser les alertes existantes" msgstr "Écraser les alertes existantes"
@@ -884,6 +1025,15 @@ msgstr "En pause"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Mis en pause ({pausedSystemsLength})" msgstr "Mis en pause ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Utilisation moyenne par cœur"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Pourcentage de temps passé dans chaque état"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Veuillez <0>configurer un serveur SMTP</0> pour garantir la livraison des alertes." msgstr "Veuillez <0>configurer un serveur SMTP</0> pour garantir la livraison des alertes."
@@ -935,6 +1085,10 @@ msgstr "Utilisation précise au moment enregistré"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Langue préférée" msgstr "Langue préférée"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Processus démarré"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "Reçu"
msgid "Refresh" msgid "Refresh"
msgstr "Actualiser" msgstr "Actualiser"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Relations"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Demander un mot de passe à usage unique" msgstr "Demander un mot de passe à usage unique"
@@ -963,6 +1121,14 @@ msgstr "Demander un mot de passe à usage unique"
msgid "Request OTP" msgid "Request OTP"
msgstr "Demander OTP" msgstr "Demander OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Requis par"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Requiert"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Réinitialiser le mot de passe" msgstr "Réinitialiser le mot de passe"
@@ -971,12 +1137,21 @@ msgstr "Réinitialiser le mot de passe"
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Resolved" msgid "Resolved"
msgstr "Résolution" msgstr "Résolu"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Redémarrages"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Reprendre" msgstr "Reprendre"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Racine"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Faire tourner le token" msgstr "Faire tourner le token"
@@ -985,6 +1160,10 @@ msgstr "Faire tourner le token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Lignes par page" msgstr "Lignes par page"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Métriques d'exécution"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "Détails S.M.A.R.T." msgstr "Détails S.M.A.R.T."
@@ -1026,6 +1205,14 @@ msgstr "Envoyé"
msgid "Serial Number" msgid "Serial Number"
msgstr "Numéro de série" msgstr "Numéro de série"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Détails du service"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Services"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Définir des seuils de pourcentage pour les couleurs des compteurs." msgstr "Définir des seuils de pourcentage pour les couleurs des compteurs."
@@ -1055,16 +1242,22 @@ msgstr "Trier par"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "État" msgstr "État"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Statut" msgstr "Statut"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Sous-état"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Espace Swap utilisé par le système" msgstr "Espace Swap utilisé par le système"
@@ -1085,6 +1278,10 @@ msgstr "Système"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Charges moyennes du système dans le temps" msgstr "Charges moyennes du système dans le temps"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Services systemd"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Systèmes" msgstr "Systèmes"
@@ -1097,6 +1294,10 @@ msgstr "Les systèmes peuvent être gérés dans un fichier <0>config.yml</0> à
msgid "Table" msgid "Table"
msgstr "Tableau" msgstr "Tableau"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Tâches"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "Les tokens permettent aux agents de se connecter et de s'enregistrer. Le
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub." msgstr "Les tokens et les empreintes sont utilisés pour authentifier les connexions WebSocket vers le hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Total"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Données totales reçues pour chaque interface" msgstr "Données totales reçues pour chaque interface"
@@ -1188,6 +1394,19 @@ msgstr "Données totales reçues pour chaque interface"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Données totales envoyées pour chaque interface" msgstr "Données totales envoyées pour chaque interface"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Total : {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Déclenché par"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Déclencheurs"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Se déclenche lorsque la charge moyenne sur 1 minute dépasse un seuil" msgstr "Se déclenche lorsque la charge moyenne sur 1 minute dépasse un seuil"
@@ -1212,6 +1431,10 @@ msgstr "Déclenchement lorsque le montant/descendant combinée dépasse un seuil
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Déclenchement lorsque l'utilisation du CPU dépasse un seuil" msgstr "Déclenchement lorsque l'utilisation du CPU dépasse un seuil"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "Déclenchement lorsque l'utilisation du GPU dépasse un seuil"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Déclenchement lorsque l'utilisation de la mémoire dépasse un seuil" msgstr "Déclenchement lorsque l'utilisation de la mémoire dépasse un seuil"
@@ -1228,6 +1451,10 @@ msgstr "Déclenchement lorsque l'utilisation de tout disque dépasse un seuil"
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "Fichier unité"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Token universel"
msgid "Unknown" msgid "Unknown"
msgstr "Inconnue" msgstr "Inconnue"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Illimité"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Joignable ({upSystemsLength})" msgstr "Joignable ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Mis à jour" msgstr "Mis à jour"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Mis à jour toutes les 10 minutes."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Téléverser" msgstr "Téléverser"
@@ -1269,6 +1506,7 @@ msgstr "Temps de fonctionnement"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Utilisation" msgstr "Utilisation"
@@ -1294,6 +1532,7 @@ msgstr "Valeur"
msgid "View" msgid "View"
msgstr "Vue" msgstr "Vue"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Voir plus" msgstr "Voir plus"
@@ -1314,6 +1553,10 @@ msgstr "En attente de suffisamment d'enregistrements à afficher"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Vous voulez nous aider à améliorer nos traductions ? Consultez <0>Crowdin</0> pour plus de détails." msgstr "Vous voulez nous aider à améliorer nos traductions ? Consultez <0>Crowdin</0> pour plus de détails."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Souhaite"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Avertissement (%)" msgstr "Avertissement (%)"
@@ -1350,6 +1593,12 @@ msgstr "Configuration YAML"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "Configuration YAML" msgstr "Configuration YAML"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Oui"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Vos paramètres utilisateur ont été mis à jour." msgstr "Vos paramètres utilisateur ont été mis à jour."

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: hr\n" "Language: hr\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-20 21:37\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Croatian\n" "Language-Team: Croatian\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# dan} other {# dani}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# sat} other {# sati}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuta} few {# minuta} many {# minuta} other {# minute}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} od {1} redaka izabrano." msgstr "{0} od {1} redaka izabrano."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} dan} other {{countString} dani}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} sat} other {{countString} sati}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuta} few {{countString} minuta} many {{countString} minuta} other {{countString} minute}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 sat" msgstr "1 sat"
@@ -93,6 +90,10 @@ msgstr "Aktivan"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Aktivna upozorenja" msgstr "Aktivna upozorenja"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Aktivno stanje"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Dodaj <0>Sistem</0>" msgstr "Dodaj <0>Sistem</0>"
@@ -113,11 +114,19 @@ msgstr "Dodaj URL"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Podesite opcije prikaza za grafikone." msgstr "Podesite opcije prikaza za grafikone."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Prilagodite širinu glavnog rasporeda"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "Nakon"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agent" msgstr "Agent"
@@ -203,6 +212,18 @@ msgstr "Propusnost"
msgid "Battery" msgid "Battery"
msgstr "Baterija" msgstr "Baterija"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Postalo aktivno"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Postalo neaktivno"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Prije"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel podržava OpenID Connect i mnoge druge OAuth2 davatalje autentifikacije." msgstr "Beszel podržava OpenID Connect i mnoge druge OAuth2 davatalje autentifikacije."
@@ -220,6 +241,10 @@ msgstr "Binarni"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bitovi (Kbps, Mbps, Gbps)" msgstr "Bitovi (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Stanje pokretanja"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Bajtovi (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Predmemorija / Međuspremnici" msgstr "Predmemorija / Međuspremnici"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Može se ponovno učitati"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Može se pokrenuti"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Može se zaustaviti"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Otkaži" msgstr "Otkaži"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr ""
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Kapacitet" msgstr "Kapacitet"
@@ -309,6 +350,10 @@ msgstr "Konfigurirajte način primanja obavijesti upozorenja."
msgid "Confirm password" msgid "Confirm password"
msgstr "Potvrdite lozinku" msgstr "Potvrdite lozinku"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Sukobi"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "Veza je pala" msgstr "Veza je pala"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "Kopiraj YAML" msgstr "Kopiraj YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "Procesor" msgstr "Procesor"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU jezgre"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "CPU vrhunac"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "CPU vrijeme"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Raspodjela CPU vremena"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Iskorištenost procesora" msgstr "Iskorištenost procesora"
@@ -427,6 +490,10 @@ msgstr "Izbriši"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Izbriši otisak" msgstr "Izbriši otisak"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Opis"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Detalj" msgstr "Detalj"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "Docker Mrežni I/O" msgstr "Docker Mrežni I/O"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentacija" msgstr "Dokumentacija"
@@ -535,6 +603,7 @@ msgstr "Unesite Vašu jednokratnu lozinku."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Greška" msgstr "Greška"
@@ -545,10 +614,18 @@ msgstr "Greška"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Premašuje {0}{1} u posljednjih {2, plural, one {# minuta} other {# minute}}" msgstr "Premašuje {0}{1} u posljednjih {2, plural, one {# minuta} other {# minute}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "Glavni PID izvršavanja"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "Postojeći sistemi koji nisu definirani u <0>config.yml</0> će biti izbrisani. Molimo Vas napravite redovite sigurnosne kopije." msgstr "Postojeći sistemi koji nisu definirani u <0>config.yml</0> će biti izbrisani. Molimo Vas napravite redovite sigurnosne kopije."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Izašlo aktivno"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Izvezi" msgstr "Izvezi"
@@ -565,6 +642,10 @@ msgstr "Izvoz trenutne sistemske konfiguracije."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Farenhajt (°F)" msgstr "Farenhajt (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Neuspješno"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Neuspjeli atributi:" msgstr "Neuspjeli atributi:"
@@ -586,10 +667,16 @@ msgstr "Neuspješno slanje testne notifikacije"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Ažuriranje upozorenja nije uspjelo" msgstr "Ažuriranje upozorenja nije uspjelo"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Neuspjelo: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtriraj..." msgstr "Filtriraj..."
@@ -600,7 +687,7 @@ msgstr "Otisak prsta"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Firmware" msgid "Firmware"
msgstr "Firmware" msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -635,6 +722,10 @@ msgstr "GPU motori"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Energetska potrošnja grafičkog procesora" msgstr "Energetska potrošnja grafičkog procesora"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "Iskorištenost GPU-a"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Mreža" msgstr "Mreža"
@@ -684,6 +775,19 @@ msgstr "Jezik"
msgid "Layout" msgid "Layout"
msgstr "Izgled" msgstr "Izgled"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Širina rasporeda"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Životni ciklus"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "ograničenje"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Prosječno Opterećenje" msgstr "Prosječno Opterećenje"
@@ -705,6 +809,14 @@ msgstr "Prosječno Opterećenje 5m"
msgid "Load Avg" msgid "Load Avg"
msgstr "Prosječno opterećenje" msgstr "Prosječno opterećenje"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Stanje učitavanja"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Učitavanje..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Odjava" msgstr "Odjava"
@@ -728,6 +840,10 @@ msgstr "Logovi"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Tražite gdje stvoriti upozorenja? Kliknite ikonu zvona <0/> u tablici sustava." msgstr "Tražite gdje stvoriti upozorenja? Kliknite ikonu zvona <0/> u tablici sustava."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "Glavni PID"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Upravljajte postavkama prikaza i obavijesti." msgstr "Upravljajte postavkama prikaza i obavijesti."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "Maksimalno 1 minuta" msgstr "Maksimalno 1 minuta"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Memorija" msgstr "Memorija"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Ograničenje memorije"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Vrhunac memorije"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -758,11 +885,13 @@ msgstr "Upotreba memorije Docker spremnika"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Model" msgid "Model"
msgstr "Model" msgstr ""
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Ime" msgstr "Ime"
@@ -787,7 +916,14 @@ msgstr "Mrežni promet javnih sučelja"
msgid "Network unit" msgid "Network unit"
msgstr "Mjerna jedinica za mrežu" msgstr "Mjerna jedinica za mrežu"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "Ne"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "Nema rezultata." msgstr "Nema rezultata."
@@ -796,6 +932,7 @@ msgstr "Nema rezultata."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Nema rezultata." msgstr "Nema rezultata."
@@ -836,6 +973,10 @@ msgstr "Otvori menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Ili nastavi sa" msgstr "Ili nastavi sa"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Ostalo"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Prebrišite postojeća upozorenja" msgstr "Prebrišite postojeća upozorenja"
@@ -884,6 +1025,15 @@ msgstr "Pauzirano"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Pauzirano ({pausedSystemsLength})" msgstr "Pauzirano ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Prosječna iskorištenost po jezgri"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Postotak vremena provedenog u svakom stanju"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Molimo <0>konfigurirajte SMTP server</0> kako biste osigurali isporuku upozorenja." msgstr "Molimo <0>konfigurirajte SMTP server</0> kako biste osigurali isporuku upozorenja."
@@ -935,6 +1085,10 @@ msgstr "Precizno iskorištenje u zabilježenom vremenu"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Preferirani jezik" msgstr "Preferirani jezik"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Proces pokrenut"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "Primljeno"
msgid "Refresh" msgid "Refresh"
msgstr "Osvježi" msgstr "Osvježi"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Odnosi"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Zatraži jednokratnu lozinku" msgstr "Zatraži jednokratnu lozinku"
@@ -963,6 +1121,14 @@ msgstr "Zatraži jednokratnu lozinku"
msgid "Request OTP" msgid "Request OTP"
msgstr "Zatraži OTP" msgstr "Zatraži OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Zahtijeva"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Zahtijeva"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Resetiraj Lozinku" msgstr "Resetiraj Lozinku"
@@ -973,10 +1139,19 @@ msgstr "Resetiraj Lozinku"
msgid "Resolved" msgid "Resolved"
msgstr "Razrješeno" msgstr "Razrješeno"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Ponovna pokretanja"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Nastavi" msgstr "Nastavi"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Promijeni token" msgstr "Promijeni token"
@@ -985,6 +1160,10 @@ msgstr "Promijeni token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Redovi po stranici" msgstr "Redovi po stranici"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Metrike izvršavanja"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Detalji" msgstr "S.M.A.R.T. Detalji"
@@ -1026,6 +1205,14 @@ msgstr "Poslano"
msgid "Serial Number" msgid "Serial Number"
msgstr "Serijski broj" msgstr "Serijski broj"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Detalji usluge"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Usluge"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Postavite pragove postotka za boje mjerača." msgstr "Postavite pragove postotka za boje mjerača."
@@ -1055,16 +1242,22 @@ msgstr "Sortiraj po"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "Stanje" msgstr "Stanje"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Podstanje"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Swap prostor uzet od strane sistema" msgstr "Swap prostor uzet od strane sistema"
@@ -1085,6 +1278,10 @@ msgstr "Sistem"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Prosječno opterećenje sustava kroz vrijeme" msgstr "Prosječno opterećenje sustava kroz vrijeme"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr ""
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Sistemi" msgstr "Sistemi"
@@ -1097,6 +1294,10 @@ msgstr "Sistemima se može upravljati u <0>config.yml</0> datoteci unutar data d
msgid "Table" msgid "Table"
msgstr "Tablica" msgstr "Tablica"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Zadaci"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "Tokeni dopuštaju agentima prijavu i registraciju. Otisci su stabilni id
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "Tokeni se uz otiske koriste za autentifikaciju WebSocket veza prema središnjoj kontroli." msgstr "Tokeni se uz otiske koriste za autentifikaciju WebSocket veza prema središnjoj kontroli."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Ukupno"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Ukupni podaci primljeni za svako sučelje" msgstr "Ukupni podaci primljeni za svako sučelje"
@@ -1188,6 +1394,19 @@ msgstr "Ukupni podaci primljeni za svako sučelje"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Ukupni podaci poslani za svako sučelje" msgstr "Ukupni podaci poslani za svako sučelje"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Ukupno: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Pokrenuto od"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Okidači"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Pokreće se kada prosječna opterećenost sustava unutar 1 minute prijeđe prag" msgstr "Pokreće se kada prosječna opterećenost sustava unutar 1 minute prijeđe prag"
@@ -1212,6 +1431,10 @@ msgstr "Pokreće se kada kombinacija gore/dolje premaši prag"
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Pokreće se kada iskorištenost procesora premaši prag" msgstr "Pokreće se kada iskorištenost procesora premaši prag"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "Pokreće se kada iskorištenost GPU-a premaši prag"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Pokreće se kada iskorištenost memorije premaši prag" msgstr "Pokreće se kada iskorištenost memorije premaši prag"
@@ -1228,6 +1451,10 @@ msgstr "Pokreće se kada iskorištenost bilo kojeg diska premaši prag"
msgid "Type" msgid "Type"
msgstr "Vrsta" msgstr "Vrsta"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "Datoteka jedinice"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Sveopći token"
msgid "Unknown" msgid "Unknown"
msgstr "Nepoznata" msgstr "Nepoznata"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Neograničeno"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Sustav je podignut ({upSystemsLength})" msgstr "Sustav je podignut ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Ažurirano" msgstr "Ažurirano"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Ažurirano svakih 10 minuta."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Otpremi" msgstr "Otpremi"
@@ -1269,6 +1506,7 @@ msgstr "Vrijeme rada"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Iskorištenost" msgstr "Iskorištenost"
@@ -1294,6 +1532,7 @@ msgstr "Vrijednost"
msgid "View" msgid "View"
msgstr "Prikaz" msgstr "Prikaz"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Prikaži više" msgstr "Prikaži više"
@@ -1314,6 +1553,10 @@ msgstr "Čeka se na više podataka prije prikaza"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Želite li nam pomoći da naše prijevode učinimo još boljim? Posjetite <0>Crowdin</0> za više detalja." msgstr "Želite li nam pomoći da naše prijevode učinimo još boljim? Posjetite <0>Crowdin</0> za više detalja."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Želi"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Upozorenje (%)" msgstr "Upozorenje (%)"
@@ -1350,6 +1593,12 @@ msgstr "YAML konfiguracija"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML Konfiguracija" msgstr "YAML Konfiguracija"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Da"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Vaše korisničke postavke su ažurirane." msgstr "Vaše korisničke postavke su ažurirane."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: hu\n" "Language: hu\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-20 21:37\n" "PO-Revision-Date: 2025-10-28 22:59\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Hungarian\n" "Language-Team: Hungarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# nap} other {# nap}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# óra} other {# óra}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# perc} few {# perc} many {# perc} other {# perc}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} a(z) {1} sorból kiválasztva." msgstr "{0} a(z) {1} sorból kiválasztva."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} nap} other {{countString} nap}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} óra} other {{countString} óra}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} perc} few {{countString} perc} many {{countString} perc} other {{countString} perc}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 óra" msgstr "1 óra"
@@ -93,6 +90,10 @@ msgstr "Aktív"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Aktív riasztások" msgstr "Aktív riasztások"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Aktív állapot"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Hozzáadás <0>System</0>" msgstr "Hozzáadás <0>System</0>"
@@ -113,11 +114,19 @@ msgstr "URL hozzáadása"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Állítsa be a diagram megjelenítését." msgstr "Állítsa be a diagram megjelenítését."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "A fő elrendezés szélességének beállítása"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Adminisztráció" msgstr "Adminisztráció"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "Utána"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Ügynök" msgstr "Ügynök"
@@ -203,6 +212,18 @@ msgstr "Sávszélesség"
msgid "Battery" msgid "Battery"
msgstr "Akkumulátor" msgstr "Akkumulátor"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Aktívvá vált"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Inaktívvá vált"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Előtte"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "A Beszel támogatja az OpenID Connect-et és számos OAuth2 hitelesítési szolgáltatót." msgstr "A Beszel támogatja az OpenID Connect-et és számos OAuth2 hitelesítési szolgáltatót."
@@ -220,6 +241,10 @@ msgstr "Bináris"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bitek (Kbps, Mbps, Gbps)" msgstr "Bitek (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Indítási állapot"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Byte-ok (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Gyorsítótár / Pufferelések" msgstr "Gyorsítótár / Pufferelések"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Újratölthető"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Indítható"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Leállítható"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Mégsem" msgstr "Mégsem"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "Képességek"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Kapacitás" msgstr "Kapacitás"
@@ -309,6 +350,10 @@ msgstr "Konfiguráld, hogyan kapod az értesítéseket."
msgid "Confirm password" msgid "Confirm password"
msgstr "Jelszó megerősítése" msgstr "Jelszó megerősítése"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Konfliktusok"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "Kapcsolat megszakadt" msgstr "Kapcsolat megszakadt"
@@ -358,23 +403,41 @@ msgstr "Szöveg másolása"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>." msgid "Copy the installation command for the agent below, or register agents automatically with a <0>universal token</0>."
msgstr "" msgstr "Másold az alábbi ügynök telepítési parancsát, vagy regisztráld az ügynököket automatikusan egy <0>univerzális tokennel</0>."
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>." msgid "Copy the<0>docker-compose.yml</0> content for the agent below, or register agents automatically with a <1>universal token</1>."
msgstr "" msgstr "Másold az alábbi ügynök <0>docker-compose.yml</0> tartalmát, vagy regisztráld az ügynököket automatikusan egy <1>univerzális tokennel</1>."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Copy YAML" msgid "Copy YAML"
msgstr "YAML másolása" msgstr "YAML másolása"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU magok"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "CPU csúcs"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "CPU idő"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU idő felbontása"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU használat" msgstr "CPU használat"
@@ -427,6 +490,10 @@ msgstr "Törlés"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Ujjlenyomat törlése" msgstr "Ujjlenyomat törlése"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Leírás"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Részlet" msgstr "Részlet"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "Docker hálózat I/O" msgstr "Docker hálózat I/O"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Dokumentáció" msgstr "Dokumentáció"
@@ -535,6 +603,7 @@ msgstr "Adja meg az egyszeri jelszavát."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Hiba" msgstr "Hiba"
@@ -545,10 +614,18 @@ msgstr "Hiba"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Túllépi a {0}{1} értéket az elmúlt {2, plural, one {# percben} other {# percben}}" msgstr "Túllépi a {0}{1} értéket az elmúlt {2, plural, one {# percben} other {# percben}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr ""
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "A <0>config.yml</0> fájlban nem definiált meglévő rendszerek törlésre kerülnek. Kérjük, készítsen rendszeres biztonsági mentéseket." msgstr "A <0>config.yml</0> fájlban nem definiált meglévő rendszerek törlésre kerülnek. Kérjük, készítsen rendszeres biztonsági mentéseket."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Aktívként kilépett"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Exportálás" msgstr "Exportálás"
@@ -565,6 +642,10 @@ msgstr "Exportálja a jelenlegi rendszerkonfigurációt."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Sikertelen"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Sikertelen attribútumok:" msgstr "Sikertelen attribútumok:"
@@ -586,10 +667,16 @@ msgstr "Teszt értesítés elküldése sikertelen"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Nem sikerült frissíteni a riasztást" msgstr "Nem sikerült frissíteni a riasztást"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Sikertelen: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Szűrő..." msgstr "Szűrő..."
@@ -600,7 +687,7 @@ msgstr "Ujjlenyomat"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Firmware" msgid "Firmware"
msgstr "Firmware" msgstr ""
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}" msgid "For <0>{min}</0> {min, plural, one {minute} other {minutes}}"
@@ -635,6 +722,10 @@ msgstr "GPU-k"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPU áramfelvétele" msgstr "GPU áramfelvétele"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "GPU használat"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Rács" msgstr "Rács"
@@ -684,6 +775,19 @@ msgstr "Nyelv"
msgid "Layout" msgid "Layout"
msgstr "Elrendezés" msgstr "Elrendezés"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Elrendezés szélessége"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Életciklus"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "korlát"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Terhelési átlag" msgstr "Terhelési átlag"
@@ -705,6 +809,14 @@ msgstr "Terhelési átlag 5p"
msgid "Load Avg" msgid "Load Avg"
msgstr "Terhelési átlag" msgstr "Terhelési átlag"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Betöltési állapot"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Betöltés..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Kijelentkezés" msgstr "Kijelentkezés"
@@ -728,6 +840,10 @@ msgstr "Naplók"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Inkább azt keresi, hogy hol hozhat létre riasztásokat? Kattintson a csengő <0/> ikonokra a rendszerek táblázatában." msgstr "Inkább azt keresi, hogy hol hozhat létre riasztásokat? Kattintson a csengő <0/> ikonokra a rendszerek táblázatában."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr ""
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "A megjelenítési és értesítési beállítások kezelése." msgstr "A megjelenítési és értesítési beállítások kezelése."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "Maximum 1 perc" msgstr "Maximum 1 perc"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "RAM" msgstr "RAM"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Memória korlát"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Memória csúcs"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "Modell"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Név" msgstr "Név"
@@ -787,7 +916,14 @@ msgstr "Nyilvános interfészek hálózati forgalma"
msgid "Network unit" msgid "Network unit"
msgstr "Sávszélesség mértékegysége" msgstr "Sávszélesség mértékegysége"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "Nem"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "Nincs találat." msgstr "Nincs találat."
@@ -796,6 +932,7 @@ msgstr "Nincs találat."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Nincs találat." msgstr "Nincs találat."
@@ -836,6 +973,10 @@ msgstr "Menü megnyitása"
msgid "Or continue with" msgid "Or continue with"
msgstr "Vagy folytasd ezzel" msgstr "Vagy folytasd ezzel"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Egyéb"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Felülírja a meglévő riasztásokat" msgstr "Felülírja a meglévő riasztásokat"
@@ -884,6 +1025,15 @@ msgstr "Szüneteltetve"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "Szüneteltetve ({pausedSystemsLength})" msgstr "Szüneteltetve ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Átlagos kihasználtság magonként"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Az idő százalékos aránya minden állapotban"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Kérjük, <0>konfigurálj egy SMTP szervert</0> az értesítések kézbesítésének biztosítása érdekében." msgstr "Kérjük, <0>konfigurálj egy SMTP szervert</0> az értesítések kézbesítésének biztosítása érdekében."
@@ -935,6 +1085,10 @@ msgstr "Pontos kihasználás a rögzített időpontban"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Preferált nyelv" msgstr "Preferált nyelv"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Folyamat elindítva"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "Fogadott"
msgid "Refresh" msgid "Refresh"
msgstr "Frissítés" msgstr "Frissítés"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Kapcsolatok"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Egyszeri jelszó kérése" msgstr "Egyszeri jelszó kérése"
@@ -963,6 +1121,14 @@ msgstr "Egyszeri jelszó kérése"
msgid "Request OTP" msgid "Request OTP"
msgstr "OTP kérése" msgstr "OTP kérése"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Szükséges"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Igényel"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Jelszó visszaállítása" msgstr "Jelszó visszaállítása"
@@ -973,10 +1139,19 @@ msgstr "Jelszó visszaállítása"
msgid "Resolved" msgid "Resolved"
msgstr "Megoldva" msgstr "Megoldva"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Újraindítások"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Folytatás" msgstr "Folytatás"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Gyökér"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Tokenváltás" msgstr "Tokenváltás"
@@ -985,6 +1160,10 @@ msgstr "Tokenváltás"
msgid "Rows per page" msgid "Rows per page"
msgstr "Sorok száma oldalanként" msgstr "Sorok száma oldalanként"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Futásidejű metrikák"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T. Részletek" msgstr "S.M.A.R.T. Részletek"
@@ -1026,6 +1205,14 @@ msgstr "Elküldve"
msgid "Serial Number" msgid "Serial Number"
msgstr "Sorozatszám" msgstr "Sorozatszám"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Szolgáltatás részletei"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Szolgáltatások"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Százalékos küszöbértékek beállítása a mérőszínekhez." msgstr "Százalékos küszöbértékek beállítása a mérőszínekhez."
@@ -1055,16 +1242,22 @@ msgstr "Rendezés"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "Állapot" msgstr "Állapot"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Állapot" msgstr "Állapot"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Részállapot"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Rendszer által használt swap terület" msgstr "Rendszer által használt swap terület"
@@ -1085,6 +1278,10 @@ msgstr "Rendszer"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Rendszer terhelési átlaga" msgstr "Rendszer terhelési átlaga"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Systemd szolgáltatások"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Rendszer" msgstr "Rendszer"
@@ -1097,6 +1294,10 @@ msgstr "A rendszereket egy <0>config.yml</0> fájlban lehet kezelni az adatköny
msgid "Table" msgid "Table"
msgstr "Tábla" msgstr "Tábla"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Feladatok"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1174,11 +1375,16 @@ msgstr "Tokenek & Ujjlenyomatok"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection." msgid "Tokens allow agents to connect and register. Fingerprints are stable identifiers unique to each system, set on first connection."
msgstr "" msgstr "A tokenek lehetővé teszik az ügynökök csatlakozását és regisztrációját. A fingeravtrykkok stabil azonosítók, amelyek minden rendszerre egyediek, és az első kapcsolódáskor kerülnek beállításra."
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "" msgstr "A tokeneket és fingeravtrykkokat a hubhoz való WebSocket kapcsolatok hitelesítésére használják."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Összesen"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
@@ -1188,6 +1394,19 @@ msgstr "Összes fogadott adat minden interfészenként"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Összes elküldött adat minden interfészenként" msgstr "Összes elküldött adat minden interfészenként"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Összesen: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Kiváltva"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Kiváltók"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Riaszt, ha az 1 perces terhelési átlag túllép egy küszöbértéket" msgstr "Riaszt, ha az 1 perces terhelési átlag túllép egy küszöbértéket"
@@ -1212,6 +1431,10 @@ msgstr "Bekapcsol, ha bármelyik érzékelő túllép egy küszöbértéket"
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Bekapcsol, ha a CPU érzékelő túllép egy küszöbértéket" msgstr "Bekapcsol, ha a CPU érzékelő túllép egy küszöbértéket"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "Riaszt, ha a GPU használat túllép egy küszöbértéket"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Bekapcsol, ha a Ram érzékelő túllép egy küszöbértéket" msgstr "Bekapcsol, ha a Ram érzékelő túllép egy küszöbértéket"
@@ -1228,6 +1451,10 @@ msgstr "Bekapcsol, ha a lemez érzékelő túllép egy küszöbértéket"
msgid "Type" msgid "Type"
msgstr "Típus" msgstr "Típus"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "Egység fájl"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Univerzális token"
msgid "Unknown" msgid "Unknown"
msgstr "Ismeretlen" msgstr "Ismeretlen"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Korlátlan"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Online ({upSystemsLength})" msgstr "Online ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Frissítve" msgstr "Frissítve"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "10 percenként frissítve."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Feltöltés" msgstr "Feltöltés"
@@ -1269,6 +1506,7 @@ msgstr "Üzemidő"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Használat" msgstr "Használat"
@@ -1294,6 +1532,7 @@ msgstr "Érték"
msgid "View" msgid "View"
msgstr "Nézet" msgstr "Nézet"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Továbbiak megjelenítése" msgstr "Továbbiak megjelenítése"
@@ -1314,6 +1553,10 @@ msgstr "Elegendő rekordra várva a megjelenítéshez"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Szeretne segíteni nekünk abban, hogy fordításaink még jobbak legyenek? További részletekért nézze meg a <0>Crowdin</0> honlapot." msgstr "Szeretne segíteni nekünk abban, hogy fordításaink még jobbak legyenek? További részletekért nézze meg a <0>Crowdin</0> honlapot."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Igényel"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Figyelmeztetés (%)" msgstr "Figyelmeztetés (%)"
@@ -1328,7 +1571,7 @@ msgstr "Webhook / Push értesítések"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart." msgid "When enabled, this token allows agents to self-register without prior system creation. Expires after one hour or on hub restart."
msgstr "" msgstr "Ha engedélyezve van, ez a token lehetővé teszi az ügynökök önregisztrációját előzetes rendszerlétrehozás nélkül. Egy óra után vagy a hub újraindításakor lejár."
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
@@ -1350,6 +1593,12 @@ msgstr "YAML konfiguráció"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML konfiguráció" msgstr "YAML konfiguráció"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Igen"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "A felhasználói beállítások frissítésre kerültek." msgstr "A felhasználói beállítások frissítésre kerültek."

File diff suppressed because it is too large Load Diff

View File

@@ -778,7 +778,6 @@ msgstr "Net traffík docker kerfa"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
#: src/components/routes/system/network-sheet.tsx
msgid "Network traffic of public interfaces" msgid "Network traffic of public interfaces"
msgstr "" msgstr ""
@@ -921,11 +920,6 @@ msgstr "Vinsamlegast skráðu þig inn á aðganginn þinn"
msgid "Port" msgid "Port"
msgstr "" msgstr ""
#. Power On Time
#: src/components/routes/system/smart-table.tsx
msgid "Power On"
msgstr ""
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Precise utilization at the recorded time" msgid "Precise utilization at the recorded time"
@@ -950,11 +944,6 @@ msgstr "Lesa"
msgid "Received" msgid "Received"
msgstr "Móttekið" msgstr "Móttekið"
#: src/components/containers-table/containers-table.tsx
#: src/components/containers-table/containers-table.tsx
msgid "Refresh"
msgstr ""
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "" msgstr ""
@@ -1022,10 +1011,6 @@ msgstr ""
msgid "Sent" msgid "Sent"
msgstr "" msgstr ""
#: src/components/routes/system/smart-table.tsx
msgid "Serial Number"
msgstr ""
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Stilltu prósentuþröskuld fyrir mælaliti." msgstr "Stilltu prósentuþröskuld fyrir mælaliti."
@@ -1253,10 +1238,6 @@ msgstr ""
msgid "Up ({upSystemsLength})" msgid "Up ({upSystemsLength})"
msgstr "" msgstr ""
#: src/components/containers-table/containers-table-columns.tsx
msgid "Updated"
msgstr ""
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "" msgstr ""
@@ -1353,3 +1334,4 @@ msgstr ""
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Notenda stillingar vistaðar." msgstr "Notenda stillingar vistaðar."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: it\n" "Language: it\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-20 21:37\n" "PO-Revision-Date: 2025-10-30 21:53\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Italian\n" "Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# giorno} other {# giorni}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# ora} other {# ore}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# minuto} other {# minuti}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{0} di {1} righe selezionate." msgstr "{0} di {1} righe selezionate."
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} giorno} other {{countString} giorni}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} ora} other {{countString} ore}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} minuto} other {{countString} minuti}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1 ora" msgstr "1 ora"
@@ -93,6 +90,10 @@ msgstr "Attivo"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "Avvisi Attivi" msgstr "Avvisi Attivi"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "Stato attivo"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "Aggiungi <0>Sistema</0>" msgstr "Aggiungi <0>Sistema</0>"
@@ -113,11 +114,19 @@ msgstr "Aggiungi URL"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "Regola le opzioni di visualizzazione per i grafici." msgstr "Regola le opzioni di visualizzazione per i grafici."
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "Regola la larghezza del layout principale"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "Amministratore" msgstr "Amministratore"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "Dopo"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "Agente" msgstr "Agente"
@@ -203,6 +212,18 @@ msgstr "Larghezza di banda"
msgid "Battery" msgid "Battery"
msgstr "Batteria" msgstr "Batteria"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "Diventato attivo"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "Diventato inattivo"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "Prima"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "Beszel supporta OpenID Connect e molti provider di autenticazione OAuth2." msgstr "Beszel supporta OpenID Connect e molti provider di autenticazione OAuth2."
@@ -220,6 +241,10 @@ msgstr "Binario"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "Bit (Kbps, Mbps, Gbps)" msgstr "Bit (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "Stato di avvio"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "Byte (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "Cache / Buffer" msgstr "Cache / Buffer"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "Può ricaricare"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "Può avviare"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "Può fermare"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "Annulla" msgstr "Annulla"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "Funzionalità"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "Capacità" msgstr "Capacità"
@@ -309,6 +350,10 @@ msgstr "Configura come ricevere le notifiche di avviso."
msgid "Confirm password" msgid "Confirm password"
msgstr "Conferma password" msgstr "Conferma password"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "Conflitti"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "La connessione è interrotta" msgstr "La connessione è interrotta"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "Copia YAML" msgstr "Copia YAML"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "CPU" msgstr "CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "Core CPU"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "Picco CPU"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "Tempo CPU"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "Suddivisione tempo CPU"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "Utilizzo CPU" msgstr "Utilizzo CPU"
@@ -427,6 +490,10 @@ msgstr "Elimina"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "Elimina impronta digitale" msgstr "Elimina impronta digitale"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "Descrizione"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "Dettagli" msgstr "Dettagli"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "I/O di Rete Docker" msgstr "I/O di Rete Docker"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "Documentazione" msgstr "Documentazione"
@@ -535,6 +603,7 @@ msgstr "Inserisci la tua password monouso."
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "Errore" msgstr "Errore"
@@ -545,10 +614,18 @@ msgstr "Errore"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "Supera {0}{1} negli ultimi {2, plural, one {# minuto} other {# minuti}}" msgstr "Supera {0}{1} negli ultimi {2, plural, one {# minuto} other {# minuti}}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "PID principale exec"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "I sistemi esistenti non definiti in <0>config.yml</0> verranno eliminati. Si prega di effettuare backup regolari." msgstr "I sistemi esistenti non definiti in <0>config.yml</0> verranno eliminati. Si prega di effettuare backup regolari."
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "Uscito attivo"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "Esporta" msgstr "Esporta"
@@ -565,6 +642,10 @@ msgstr "Esporta la configurazione attuale dei tuoi sistemi."
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "Fahrenheit (°F)" msgstr "Fahrenheit (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "Fallito"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "Attributi falliti:" msgstr "Attributi falliti:"
@@ -586,10 +667,16 @@ msgstr "Invio della notifica di test fallito"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "Aggiornamento dell'avviso fallito" msgstr "Aggiornamento dell'avviso fallito"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "Fallito: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "Filtra..." msgstr "Filtra..."
@@ -635,6 +722,10 @@ msgstr "Motori GPU"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "Consumo della GPU" msgstr "Consumo della GPU"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "Utilizzo GPU"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "Griglia" msgstr "Griglia"
@@ -684,6 +775,19 @@ msgstr "Lingua"
msgid "Layout" msgid "Layout"
msgstr "Aspetto" msgstr "Aspetto"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "Larghezza del layout"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "Ciclo di vita"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "limite"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "Carico medio" msgstr "Carico medio"
@@ -705,6 +809,14 @@ msgstr "Caricamento medio 5m"
msgid "Load Avg" msgid "Load Avg"
msgstr "Carico Medio" msgstr "Carico Medio"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "Stato di caricamento"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "Caricamento..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "Disconnetti" msgstr "Disconnetti"
@@ -728,6 +840,10 @@ msgstr "Log"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "Cerchi invece dove creare avvisi? Clicca sulle icone della campana <0/> nella tabella dei sistemi." msgstr "Cerchi invece dove creare avvisi? Clicca sulle icone della campana <0/> nella tabella dei sistemi."
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "PID principale"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "Gestisci le preferenze di visualizzazione e notifica." msgstr "Gestisci le preferenze di visualizzazione e notifica."
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "Max 1 min" msgstr "Max 1 min"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "Memoria" msgstr "Memoria"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "Limite memoria"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "Picco memoria"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "Modello"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "Nome" msgstr "Nome"
@@ -787,7 +916,14 @@ msgstr "Traffico di rete delle interfacce pubbliche"
msgid "Network unit" msgid "Network unit"
msgstr "Unità rete" msgstr "Unità rete"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "No"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "Nessun risultato trovato." msgstr "Nessun risultato trovato."
@@ -796,6 +932,7 @@ msgstr "Nessun risultato trovato."
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "Nessun risultato." msgstr "Nessun risultato."
@@ -836,6 +973,10 @@ msgstr "Apri menu"
msgid "Or continue with" msgid "Or continue with"
msgstr "Oppure continua con" msgstr "Oppure continua con"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "Altro"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "Sovrascrivi avvisi esistenti" msgstr "Sovrascrivi avvisi esistenti"
@@ -884,6 +1025,15 @@ msgstr "In pausa"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "In pausa ({pausedSystemsLength})" msgstr "In pausa ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "Utilizzo medio per core"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "Percentuale di tempo trascorso in ogni stato"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "Si prega di <0>configurare un server SMTP</0> per garantire la consegna degli avvisi." msgstr "Si prega di <0>configurare un server SMTP</0> per garantire la consegna degli avvisi."
@@ -935,6 +1085,10 @@ msgstr "Utilizzo preciso al momento registrato"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "Lingua Preferita" msgstr "Lingua Preferita"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "Processo avviato"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "Ricevuto"
msgid "Refresh" msgid "Refresh"
msgstr "Aggiorna" msgstr "Aggiorna"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "Relazioni"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "Richiedi una password monouso" msgstr "Richiedi una password monouso"
@@ -963,6 +1121,14 @@ msgstr "Richiedi una password monouso"
msgid "Request OTP" msgid "Request OTP"
msgstr "Richiedi OTP" msgstr "Richiedi OTP"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "Richiesto da"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "Richiede"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "Reimposta Password" msgstr "Reimposta Password"
@@ -973,10 +1139,19 @@ msgstr "Reimposta Password"
msgid "Resolved" msgid "Resolved"
msgstr "Risolto" msgstr "Risolto"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "Riavvii"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "Riprendi" msgstr "Riprendi"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr "Root"
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "Ruota token" msgstr "Ruota token"
@@ -985,6 +1160,10 @@ msgstr "Ruota token"
msgid "Rows per page" msgid "Rows per page"
msgstr "Righe per pagina" msgstr "Righe per pagina"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "Metriche di runtime"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "Dettagli S.M.A.R.T." msgstr "Dettagli S.M.A.R.T."
@@ -1026,6 +1205,14 @@ msgstr "Inviato"
msgid "Serial Number" msgid "Serial Number"
msgstr "Numero di serie" msgstr "Numero di serie"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "Dettagli servizio"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "Servizi"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "Imposta le soglie percentuali per i colori dei contatori." msgstr "Imposta le soglie percentuali per i colori dei contatori."
@@ -1055,16 +1242,22 @@ msgstr "Ordina per"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "Stato" msgstr "Stato"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "Stato" msgstr "Stato"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "Sotto-stato"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "Spazio di swap utilizzato dal sistema" msgstr "Spazio di swap utilizzato dal sistema"
@@ -1085,6 +1278,10 @@ msgstr "Sistema"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "Medie di carico del sistema nel tempo" msgstr "Medie di carico del sistema nel tempo"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Servizi Systemd"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "Sistemi" msgstr "Sistemi"
@@ -1097,6 +1294,10 @@ msgstr "I sistemi possono essere gestiti in un file <0>config.yml</0> all'intern
msgid "Table" msgid "Table"
msgstr "Tabella" msgstr "Tabella"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "Attività"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "I token consentono agli agenti di connettersi e registrarsi. Le impronte
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub." msgstr "I token e le impronte digitali vengono utilizzati per autenticare le connessioni WebSocket all'hub."
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "Totale"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "Dati totali ricevuti per ogni interfaccia" msgstr "Dati totali ricevuti per ogni interfaccia"
@@ -1188,6 +1394,19 @@ msgstr "Dati totali ricevuti per ogni interfaccia"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "Dati totali inviati per ogni interfaccia" msgstr "Dati totali inviati per ogni interfaccia"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "Totale: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "Attivato da"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "Trigger"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "Si attiva quando la media di carico di 1 minuto supera una soglia" msgstr "Si attiva quando la media di carico di 1 minuto supera una soglia"
@@ -1212,6 +1431,10 @@ msgstr "Attiva quando il combinato up/down supera una soglia"
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "Attiva quando l'utilizzo della CPU supera una soglia" msgstr "Attiva quando l'utilizzo della CPU supera una soglia"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "Si attiva quando l'utilizzo della GPU supera una soglia"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "Attiva quando l'utilizzo della memoria supera una soglia" msgstr "Attiva quando l'utilizzo della memoria supera una soglia"
@@ -1228,6 +1451,10 @@ msgstr "Attiva quando l'utilizzo di un disco supera una soglia"
msgid "Type" msgid "Type"
msgstr "Tipo" msgstr "Tipo"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "File unit"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "Token universale"
msgid "Unknown" msgid "Unknown"
msgstr "Sconosciuta" msgstr "Sconosciuta"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "Illimitato"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "Attivo ({upSystemsLength})" msgstr "Attivo ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "Aggiornato" msgstr "Aggiornato"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "Aggiornato ogni 10 minuti."
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "Carica" msgstr "Carica"
@@ -1269,6 +1506,7 @@ msgstr "Tempo di attività"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "Utilizzo" msgstr "Utilizzo"
@@ -1294,6 +1532,7 @@ msgstr "Valore"
msgid "View" msgid "View"
msgstr "Vista" msgstr "Vista"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "Visualizza altro" msgstr "Visualizza altro"
@@ -1314,6 +1553,10 @@ msgstr "In attesa di abbastanza record da visualizzare"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "Vuoi aiutarci a migliorare ulteriormente le nostre traduzioni? Dai un'occhiata a <0>Crowdin</0> per maggiori dettagli." msgstr "Vuoi aiutarci a migliorare ulteriormente le nostre traduzioni? Dai un'occhiata a <0>Crowdin</0> per maggiori dettagli."
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "Desidera"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "Avviso (%)" msgstr "Avviso (%)"
@@ -1350,6 +1593,12 @@ msgstr "Configurazione YAML"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "Configurazione YAML" msgstr "Configurazione YAML"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "Sì"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "Le impostazioni utente sono state aggiornate." msgstr "Le impostazioni utente sono state aggiornate."

View File

@@ -8,7 +8,7 @@ msgstr ""
"Language: ja\n" "Language: ja\n"
"Project-Id-Version: beszel\n" "Project-Id-Version: beszel\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-10-20 21:37\n" "PO-Revision-Date: 2025-10-28 23:00\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: Japanese\n" "Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0;\n" "Plural-Forms: nplurals=1; plural=0;\n"
@@ -18,27 +18,24 @@ msgstr ""
"X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n" "X-Crowdin-File: /main/internal/site/src/locales/en/en.po\n"
"X-Crowdin-File-ID: 32\n" "X-Crowdin-File-ID: 32\n"
#. placeholder {0}: Math.trunc(system.info?.u / 86400)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# day} other {# days}}"
msgstr "{0, plural, one {# 日} other {# 日}}"
#. placeholder {0}: Math.trunc(system.info.u / 3600)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# hour} other {# hours}}"
msgstr "{0, plural, one {# 時間} other {# 時間}}"
#. placeholder {0}: Math.trunc(system.info.u / 60)
#: src/components/routes/system.tsx
msgid "{0, plural, one {# minute} few {# minutes} many {# minutes} other {# minutes}}"
msgstr "{0, plural, one {# 分} few {# 分} many {# 分} other {# 分}}"
#. placeholder {0}: table.getFilteredSelectedRowModel().rows.length #. placeholder {0}: table.getFilteredSelectedRowModel().rows.length
#. placeholder {1}: table.getFilteredRowModel().rows.length #. placeholder {1}: table.getFilteredRowModel().rows.length
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "{0} of {1} row(s) selected." msgid "{0} of {1} row(s) selected."
msgstr "{1}行のうち{0}行が選択されました。" msgstr "{1}行のうち{0}行が選択されました。"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} day} other {{countString} days}}"
msgstr "{count, plural, one {{countString} 日} other {{countString} 日}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} hour} other {{countString} hours}}"
msgstr "{count, plural, one {{countString} 時間} other {{countString} 時間}}"
#: src/lib/utils.ts
msgid "{count, plural, one {{countString} minute} few {{countString} minutes} many {{countString} minutes} other {{countString} minutes}}"
msgstr "{count, plural, one {{countString} 分} few {{countString} 分} many {{countString} 分} other {{countString} 分}}"
#: src/lib/utils.ts #: src/lib/utils.ts
msgid "1 hour" msgid "1 hour"
msgstr "1時間" msgstr "1時間"
@@ -93,6 +90,10 @@ msgstr "アクティブ"
msgid "Active Alerts" msgid "Active Alerts"
msgstr "アクティブなアラート" msgstr "アクティブなアラート"
#: src/components/systemd-table/systemd-table.tsx
msgid "Active state"
msgstr "アクティブ状態"
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Add <0>System</0>" msgid "Add <0>System</0>"
msgstr "<0>システム</0>を追加" msgstr "<0>システム</0>を追加"
@@ -113,11 +114,19 @@ msgstr "URLを追加"
msgid "Adjust display options for charts." msgid "Adjust display options for charts."
msgstr "チャートの表示オプションを調整します。" msgstr "チャートの表示オプションを調整します。"
#: src/components/routes/settings/general.tsx
msgid "Adjust the width of the main layout"
msgstr "メインレイアウトの幅を調整"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
msgid "Admin" msgid "Admin"
msgstr "管理者" msgstr "管理者"
#: src/components/systemd-table/systemd-table.tsx
msgid "After"
msgstr "後"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Agent" msgid "Agent"
msgstr "エージェント" msgstr "エージェント"
@@ -203,6 +212,18 @@ msgstr "帯域幅"
msgid "Battery" msgid "Battery"
msgstr "バッテリー" msgstr "バッテリー"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became active"
msgstr "アクティブになった"
#: src/components/systemd-table/systemd-table.tsx
msgid "Became inactive"
msgstr "非アクティブになった"
#: src/components/systemd-table/systemd-table.tsx
msgid "Before"
msgstr "前"
#: src/components/login/auth-form.tsx #: src/components/login/auth-form.tsx
msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers." msgid "Beszel supports OpenID Connect and many OAuth2 authentication providers."
msgstr "BeszelはOpenID Connectと多くのOAuth2認証プロバイダーをサポートしています。" msgstr "BeszelはOpenID Connectと多くのOAuth2認証プロバイダーをサポートしています。"
@@ -220,6 +241,10 @@ msgstr "バイナリ"
msgid "Bits (Kbps, Mbps, Gbps)" msgid "Bits (Kbps, Mbps, Gbps)"
msgstr "ビット (Kbps, Mbps, Gbps)" msgstr "ビット (Kbps, Mbps, Gbps)"
#: src/components/systemd-table/systemd-table.tsx
msgid "Boot state"
msgstr "ブート状態"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Bytes (KB/s, MB/s, GB/s)" msgid "Bytes (KB/s, MB/s, GB/s)"
@@ -229,11 +254,27 @@ msgstr "バイト (KB/s, MB/s, GB/s)"
msgid "Cache / Buffers" msgid "Cache / Buffers"
msgstr "キャッシュ / バッファ" msgstr "キャッシュ / バッファ"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can reload"
msgstr "リロード可能"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can start"
msgstr "開始可能"
#: src/components/systemd-table/systemd-table.tsx
msgid "Can stop"
msgstr "停止可能"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Cancel" msgid "Cancel"
msgstr "キャンセル" msgstr "キャンセル"
#: src/components/systemd-table/systemd-table.tsx
msgid "Capabilities"
msgstr "機能"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Capacity" msgid "Capacity"
msgstr "容量" msgstr "容量"
@@ -309,6 +350,10 @@ msgstr "アラート通知の受信方法を設定します。"
msgid "Confirm password" msgid "Confirm password"
msgstr "パスワードを確認" msgstr "パスワードを確認"
#: src/components/systemd-table/systemd-table.tsx
msgid "Conflicts"
msgstr "競合"
#: src/components/active-alerts.tsx #: src/components/active-alerts.tsx
msgid "Connection is down" msgid "Connection is down"
msgstr "接続が切断されました" msgstr "接続が切断されました"
@@ -369,12 +414,30 @@ msgid "Copy YAML"
msgstr "YAMLをコピー" msgstr "YAMLをコピー"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "CPU" msgid "CPU"
msgstr "" msgstr ""
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Cores"
msgstr "CPU コア"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "CPU Peak"
msgstr "CPUピーク"
#: src/components/systemd-table/systemd-table.tsx
msgid "CPU time"
msgstr "CPU時間"
#: src/components/routes/system/cpu-sheet.tsx
msgid "CPU Time Breakdown"
msgstr "CPU 時間の内訳"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "CPU Usage" msgid "CPU Usage"
msgstr "CPU使用率" msgstr "CPU使用率"
@@ -427,6 +490,10 @@ msgstr "削除"
msgid "Delete fingerprint" msgid "Delete fingerprint"
msgstr "フィンガープリントを削除" msgstr "フィンガープリントを削除"
#: src/components/systemd-table/systemd-table.tsx
msgid "Description"
msgstr "説明"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
msgid "Detail" msgid "Detail"
msgstr "詳細" msgstr "詳細"
@@ -475,6 +542,7 @@ msgid "Docker Network I/O"
msgstr "DockerネットワークI/O" msgstr "DockerネットワークI/O"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Documentation" msgid "Documentation"
msgstr "ドキュメント" msgstr "ドキュメント"
@@ -535,6 +603,7 @@ msgstr "ワンタイムパスワードを入力してください。"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Error" msgid "Error"
msgstr "エラー" msgstr "エラー"
@@ -545,10 +614,18 @@ msgstr "エラー"
msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}" msgid "Exceeds {0}{1} in last {2, plural, one {# minute} other {# minutes}}"
msgstr "過去{2, plural, one {# 分} other {# 分}}で{0}{1}を超えています" msgstr "過去{2, plural, one {# 分} other {# 分}}で{0}{1}を超えています"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exec main PID"
msgstr "実行メインPID"
#: src/components/routes/settings/config-yaml.tsx #: src/components/routes/settings/config-yaml.tsx
msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups." msgid "Existing systems not defined in <0>config.yml</0> will be deleted. Please make regular backups."
msgstr "<0>config.yml</0>に定義されていない既存のシステムは削除されます。定期的にバックアップを作成してください。" msgstr "<0>config.yml</0>に定義されていない既存のシステムは削除されます。定期的にバックアップを作成してください。"
#: src/components/systemd-table/systemd-table.tsx
msgid "Exited active"
msgstr "アクティブ状態で終了"
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
msgid "Export" msgid "Export"
msgstr "エクスポート" msgstr "エクスポート"
@@ -565,6 +642,10 @@ msgstr "現在のシステム設定をエクスポートします。"
msgid "Fahrenheit (°F)" msgid "Fahrenheit (°F)"
msgstr "華氏 (°F)" msgstr "華氏 (°F)"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Failed"
msgstr "失敗"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "Failed Attributes:" msgid "Failed Attributes:"
msgstr "失敗した属性:" msgstr "失敗した属性:"
@@ -586,10 +667,16 @@ msgstr "テスト通知の送信に失敗しました"
msgid "Failed to update alert" msgid "Failed to update alert"
msgstr "アラートの更新に失敗しました" msgstr "アラートの更新に失敗しました"
#. placeholder {0}: statusTotals[ServiceStatus.Failed]
#: src/components/systemd-table/systemd-table.tsx
msgid "Failed: {0}"
msgstr "失敗: {0}"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Filter..." msgid "Filter..."
msgstr "フィルター..." msgstr "フィルター..."
@@ -635,6 +722,10 @@ msgstr "GPUエンジン"
msgid "GPU Power Draw" msgid "GPU Power Draw"
msgstr "GPUの消費電力" msgstr "GPUの消費電力"
#: src/lib/alerts.ts
msgid "GPU Usage"
msgstr "GPU使用率"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Grid" msgid "Grid"
msgstr "グリッド" msgstr "グリッド"
@@ -684,6 +775,19 @@ msgstr "言語"
msgid "Layout" msgid "Layout"
msgstr "レイアウト" msgstr "レイアウト"
#: src/components/routes/settings/general.tsx
msgid "Layout width"
msgstr "レイアウト幅"
#: src/components/systemd-table/systemd-table.tsx
msgid "Lifecycle"
msgstr "ライフサイクル"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "limit"
msgstr "制限"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Load Average" msgid "Load Average"
msgstr "負荷平均" msgstr "負荷平均"
@@ -705,6 +809,14 @@ msgstr "負荷平均 (5分)"
msgid "Load Avg" msgid "Load Avg"
msgstr "負荷平均" msgstr "負荷平均"
#: src/components/systemd-table/systemd-table.tsx
msgid "Load state"
msgstr "ロード状態"
#: src/components/systemd-table/systemd-table.tsx
msgid "Loading..."
msgstr "読み込み中..."
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Log Out" msgid "Log Out"
msgstr "ログアウト" msgstr "ログアウト"
@@ -728,6 +840,10 @@ msgstr "ログ"
msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table." msgid "Looking instead for where to create alerts? Click the bell <0/> icons in the systems table."
msgstr "アラートを作成する場所を探していますか?システムテーブルのベル<0/>アイコンをクリックしてください。" msgstr "アラートを作成する場所を探していますか?システムテーブルのベル<0/>アイコンをクリックしてください。"
#: src/components/systemd-table/systemd-table.tsx
msgid "Main PID"
msgstr "メインPID"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Manage display and notification preferences." msgid "Manage display and notification preferences."
msgstr "表示と通知の設定を管理します。" msgstr "表示と通知の設定を管理します。"
@@ -743,10 +859,21 @@ msgid "Max 1 min"
msgstr "最大1分" msgstr "最大1分"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Memory" msgid "Memory"
msgstr "メモリ" msgstr "メモリ"
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory limit"
msgstr "メモリ制限"
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Memory Peak"
msgstr "メモリピーク"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Memory Usage" msgid "Memory Usage"
@@ -763,6 +890,8 @@ msgstr "モデル"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Name" msgid "Name"
msgstr "名前" msgstr "名前"
@@ -787,7 +916,14 @@ msgstr "パブリックインターフェースのネットワークトラフィ
msgid "Network unit" msgid "Network unit"
msgstr "ネットワーク単位" msgstr "ネットワーク単位"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No"
msgstr "いいえ"
#: src/components/command-palette.tsx #: src/components/command-palette.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results found." msgid "No results found."
msgstr "結果が見つかりませんでした。" msgstr "結果が見つかりませんでした。"
@@ -796,6 +932,7 @@ msgstr "結果が見つかりませんでした。"
#: src/components/containers-table/containers-table.tsx #: src/components/containers-table/containers-table.tsx
#: src/components/routes/settings/alerts-history-data-table.tsx #: src/components/routes/settings/alerts-history-data-table.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "No results." msgid "No results."
msgstr "結果がありません。" msgstr "結果がありません。"
@@ -836,6 +973,10 @@ msgstr "メニューを開く"
msgid "Or continue with" msgid "Or continue with"
msgstr "または、以下の方法でログイン" msgstr "または、以下の方法でログイン"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Other"
msgstr "その他"
#: src/components/alerts/alerts-sheet.tsx #: src/components/alerts/alerts-sheet.tsx
msgid "Overwrite existing alerts" msgid "Overwrite existing alerts"
msgstr "既存のアラートを上書き" msgstr "既存のアラートを上書き"
@@ -884,6 +1025,15 @@ msgstr "一時停止中"
msgid "Paused ({pausedSystemsLength})" msgid "Paused ({pausedSystemsLength})"
msgstr "一時停止 ({pausedSystemsLength})" msgstr "一時停止 ({pausedSystemsLength})"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Per-core average utilization"
msgstr "コアごとの平均使用率"
#: src/components/routes/system/cpu-sheet.tsx
msgid "Percentage of time spent in each state"
msgstr "各状態で費やした時間の割合"
#: src/components/routes/settings/notifications.tsx #: src/components/routes/settings/notifications.tsx
msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered." msgid "Please <0>configure an SMTP server</0> to ensure alerts are delivered."
msgstr "アラートが配信されるように<0>SMTPサーバーを設定</0>してください。" msgstr "アラートが配信されるように<0>SMTPサーバーを設定</0>してください。"
@@ -935,6 +1085,10 @@ msgstr "記録された時点での正確な利用"
msgid "Preferred Language" msgid "Preferred Language"
msgstr "優先言語" msgstr "優先言語"
#: src/components/systemd-table/systemd-table.tsx
msgid "Process started"
msgstr "プロセス開始"
#. Use 'Key' if your language requires many more characters #. Use 'Key' if your language requires many more characters
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Public Key" msgid "Public Key"
@@ -955,6 +1109,10 @@ msgstr "受信"
msgid "Refresh" msgid "Refresh"
msgstr "更新" msgstr "更新"
#: src/components/systemd-table/systemd-table.tsx
msgid "Relationships"
msgstr "関係"
#: src/components/login/login.tsx #: src/components/login/login.tsx
msgid "Request a one-time password" msgid "Request a one-time password"
msgstr "ワンタイムパスワードをリクエスト" msgstr "ワンタイムパスワードをリクエスト"
@@ -963,6 +1121,14 @@ msgstr "ワンタイムパスワードをリクエスト"
msgid "Request OTP" msgid "Request OTP"
msgstr "OTP をリクエスト" msgstr "OTP をリクエスト"
#: src/components/systemd-table/systemd-table.tsx
msgid "Required by"
msgstr "必要とされる"
#: src/components/systemd-table/systemd-table.tsx
msgid "Requires"
msgstr "必要とする"
#: src/components/login/forgot-pass-form.tsx #: src/components/login/forgot-pass-form.tsx
msgid "Reset Password" msgid "Reset Password"
msgstr "パスワードをリセット" msgstr "パスワードをリセット"
@@ -973,10 +1139,19 @@ msgstr "パスワードをリセット"
msgid "Resolved" msgid "Resolved"
msgstr "解決済み" msgstr "解決済み"
#: src/components/systemd-table/systemd-table.tsx
msgid "Restarts"
msgstr "再起動"
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
msgid "Resume" msgid "Resume"
msgstr "再開" msgstr "再開"
#: src/components/systems-table/systems-table-columns.tsx
msgctxt "Root disk label"
msgid "Root"
msgstr ""
#: src/components/routes/settings/tokens-fingerprints.tsx #: src/components/routes/settings/tokens-fingerprints.tsx
msgid "Rotate token" msgid "Rotate token"
msgstr "トークンをローテート" msgstr "トークンをローテート"
@@ -985,6 +1160,10 @@ msgstr "トークンをローテート"
msgid "Rows per page" msgid "Rows per page"
msgstr "ページあたりの行数" msgstr "ページあたりの行数"
#: src/components/systemd-table/systemd-table.tsx
msgid "Runtime Metrics"
msgstr "ランタイムメトリクス"
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
msgid "S.M.A.R.T. Details" msgid "S.M.A.R.T. Details"
msgstr "S.M.A.R.T.詳細" msgstr "S.M.A.R.T.詳細"
@@ -1026,6 +1205,14 @@ msgstr "送信"
msgid "Serial Number" msgid "Serial Number"
msgstr "シリアル番号" msgstr "シリアル番号"
#: src/components/systemd-table/systemd-table.tsx
msgid "Service Details"
msgstr "サービス詳細"
#: src/components/systems-table/systems-table-columns.tsx
msgid "Services"
msgstr "サービス"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Set percentage thresholds for meter colors." msgid "Set percentage thresholds for meter colors."
msgstr "メーターの色を変更するしきい値(パーセンテージ)を設定します。" msgstr "メーターの色を変更するしきい値(パーセンテージ)を設定します。"
@@ -1055,16 +1242,22 @@ msgstr "並び替え基準"
#. Context: alert state (active or resolved) #. Context: alert state (active or resolved)
#: src/components/alerts-history-columns.tsx #: src/components/alerts-history-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "State" msgid "State"
msgstr "状態" msgstr "状態"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Status" msgid "Status"
msgstr "ステータス" msgstr "ステータス"
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Sub State"
msgstr "サブ状態"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Swap space used by the system" msgid "Swap space used by the system"
msgstr "システムが使用するスワップ領域" msgstr "システムが使用するスワップ領域"
@@ -1085,6 +1278,10 @@ msgstr "システム"
msgid "System load averages over time" msgid "System load averages over time"
msgstr "システムの負荷平均の推移" msgstr "システムの負荷平均の推移"
#: src/components/systemd-table/systemd-table.tsx
msgid "Systemd Services"
msgstr "Systemdサービス"
#: src/components/navbar.tsx #: src/components/navbar.tsx
msgid "Systems" msgid "Systems"
msgstr "システム" msgstr "システム"
@@ -1097,6 +1294,10 @@ msgstr "システムはデータディレクトリ内の<0>config.yml</0>ファ
msgid "Table" msgid "Table"
msgstr "テーブル" msgstr "テーブル"
#: src/components/systemd-table/systemd-table.tsx
msgid "Tasks"
msgstr "タスク"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/routes/system/smart-table.tsx #: src/components/routes/system/smart-table.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1180,6 +1381,11 @@ msgstr "トークンはエージェントの接続と登録を可能にします
msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub." msgid "Tokens and fingerprints are used to authenticate WebSocket connections to the hub."
msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。" msgstr "トークンとフィンガープリントは、ハブへのWebSocket接続の認証に使用されます。"
#: src/components/ui/chart.tsx
#: src/components/ui/chart.tsx
msgid "Total"
msgstr "総数"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Total data received for each interface" msgid "Total data received for each interface"
msgstr "各インターフェースの総受信データ量" msgstr "各インターフェースの総受信データ量"
@@ -1188,6 +1394,19 @@ msgstr "各インターフェースの総受信データ量"
msgid "Total data sent for each interface" msgid "Total data sent for each interface"
msgstr "各インターフェースの総送信データ量" msgstr "各インターフェースの総送信データ量"
#. placeholder {0}: data.length
#: src/components/systemd-table/systemd-table.tsx
msgid "Total: {0}"
msgstr "合計: {0}"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggered by"
msgstr "トリガー元"
#: src/components/systemd-table/systemd-table.tsx
msgid "Triggers"
msgstr "トリガー"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when 1 minute load average exceeds a threshold" msgid "Triggers when 1 minute load average exceeds a threshold"
msgstr "1分間の負荷平均がしきい値を超えたときにトリガーされます" msgstr "1分間の負荷平均がしきい値を超えたときにトリガーされます"
@@ -1212,6 +1431,10 @@ msgstr "上り/下りの合計がしきい値を超えたときにトリガー
msgid "Triggers when CPU usage exceeds a threshold" msgid "Triggers when CPU usage exceeds a threshold"
msgstr "CPU使用率がしきい値を超えたときにトリガーされます" msgstr "CPU使用率がしきい値を超えたときにトリガーされます"
#: src/lib/alerts.ts
msgid "Triggers when GPU usage exceeds a threshold"
msgstr "GPU使用率がしきい値を超えたときにトリガーされます"
#: src/lib/alerts.ts #: src/lib/alerts.ts
msgid "Triggers when memory usage exceeds a threshold" msgid "Triggers when memory usage exceeds a threshold"
msgstr "メモリ使用率がしきい値を超えたときにトリガーされます" msgstr "メモリ使用率がしきい値を超えたときにトリガーされます"
@@ -1228,6 +1451,10 @@ msgstr "ディスクの使用量がしきい値を超えたときにトリガー
msgid "Type" msgid "Type"
msgstr "タイプ" msgstr "タイプ"
#: src/components/systemd-table/systemd-table.tsx
msgid "Unit file"
msgstr "ユニットファイル"
#. Temperature / network units #. Temperature / network units
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Unit preferences" msgid "Unit preferences"
@@ -1243,6 +1470,11 @@ msgstr "ユニバーサルトークン"
msgid "Unknown" msgid "Unknown"
msgstr "不明" msgstr "不明"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Unlimited"
msgstr "無制限"
#. Context: System is up #. Context: System is up
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/systems-table/systems-table-columns.tsx #: src/components/systems-table/systems-table-columns.tsx
@@ -1254,9 +1486,14 @@ msgid "Up ({upSystemsLength})"
msgstr "正常 ({upSystemsLength})" msgstr "正常 ({upSystemsLength})"
#: src/components/containers-table/containers-table-columns.tsx #: src/components/containers-table/containers-table-columns.tsx
#: src/components/systemd-table/systemd-table-columns.tsx
msgid "Updated" msgid "Updated"
msgstr "更新済み" msgstr "更新済み"
#: src/components/systemd-table/systemd-table.tsx
msgid "Updated every 10 minutes."
msgstr "10分ごとに更新されます。"
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "Upload" msgid "Upload"
msgstr "アップロード" msgstr "アップロード"
@@ -1269,6 +1506,7 @@ msgstr "稼働時間"
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
#: src/components/routes/system/cpu-sheet.tsx
msgid "Usage" msgid "Usage"
msgstr "使用量" msgstr "使用量"
@@ -1294,6 +1532,7 @@ msgstr "値"
msgid "View" msgid "View"
msgstr "表示" msgstr "表示"
#: src/components/routes/system/cpu-sheet.tsx
#: src/components/routes/system/network-sheet.tsx #: src/components/routes/system/network-sheet.tsx
msgid "View more" msgid "View more"
msgstr "もっと見る" msgstr "もっと見る"
@@ -1314,6 +1553,10 @@ msgstr "表示するのに十分なレコードを待っています"
msgid "Want to help improve our translations? Check <0>Crowdin</0> for details." msgid "Want to help improve our translations? Check <0>Crowdin</0> for details."
msgstr "翻訳をさらに良くするためにご協力をお願いします。詳細については<0>Crowdin</0>をご覧ください。" msgstr "翻訳をさらに良くするためにご協力をお願いします。詳細については<0>Crowdin</0>をご覧ください。"
#: src/components/systemd-table/systemd-table.tsx
msgid "Wants"
msgstr "要求"
#: src/components/routes/settings/general.tsx #: src/components/routes/settings/general.tsx
msgid "Warning (%)" msgid "Warning (%)"
msgstr "警告 (%)" msgstr "警告 (%)"
@@ -1350,6 +1593,12 @@ msgstr "YAML設定"
msgid "YAML Configuration" msgid "YAML Configuration"
msgstr "YAML設定" msgstr "YAML設定"
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
#: src/components/systemd-table/systemd-table.tsx
msgid "Yes"
msgstr "はい"
#: src/components/routes/settings/layout.tsx #: src/components/routes/settings/layout.tsx
msgid "Your user settings have been updated." msgid "Your user settings have been updated."
msgstr "ユーザー設定が更新されました。" msgstr "ユーザー設定が更新されました。"

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